mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-03-13 10:26:06 +00:00
Capitalize the first letter of all log messages throughout the codebase to improve readability and consistency in logging output. Key improvements: - All log messages now start with capital letters - Consistent formatting across all logging statements - Improved readability for debugging and monitoring - Enhanced user experience with better formatted messages Files updated: - CLI commands and service management - Internal client information discovery - Network operations and configuration - DNS resolver and proxy operations - Platform-specific implementations This completes the final phase of the logging improvement project, ensuring all log messages follow consistent capitalization standards for better readability and professional appearance.
237 lines
6.0 KiB
Go
237 lines
6.0 KiB
Go
//go:build darwin
|
|
|
|
package ctrld
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"os/exec"
|
|
"regexp"
|
|
"slices"
|
|
"strings"
|
|
"time"
|
|
|
|
"tailscale.com/net/netmon"
|
|
)
|
|
|
|
func dnsFns() []dnsFn {
|
|
return []dnsFn{dnsFromResolvConf, getDNSFromScutil, getAllDHCPNameservers}
|
|
}
|
|
|
|
func getDNSFromScutil(ctx context.Context) []string {
|
|
logger := LoggerFromCtx(ctx)
|
|
|
|
const (
|
|
maxRetries = 10
|
|
retryInterval = 100 * time.Millisecond
|
|
)
|
|
|
|
regularIPs, loopbackIPs, _ := netmon.LocalAddresses()
|
|
|
|
var nameservers []string
|
|
for attempt := 0; attempt < maxRetries; attempt++ {
|
|
if attempt > 0 {
|
|
time.Sleep(retryInterval)
|
|
}
|
|
|
|
cmd := exec.Command("scutil", "--dns")
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
Log(context.Background(), logger.Error(), "Failed to execute scutil --dns (attempt %d/%d): %v", attempt+1, maxRetries, err)
|
|
continue
|
|
}
|
|
|
|
var localDNS []string
|
|
seen := make(map[string]bool)
|
|
|
|
scanner := bufio.NewScanner(bytes.NewReader(output))
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if strings.HasPrefix(line, "nameserver[") {
|
|
parts := strings.Split(line, ":")
|
|
if len(parts) == 2 {
|
|
ns := strings.TrimSpace(parts[1])
|
|
if ip := net.ParseIP(ns); ip != nil {
|
|
// skip loopback IPs
|
|
isLocal := false
|
|
for _, v := range slices.Concat(regularIPs, loopbackIPs) {
|
|
ipStr := v.String()
|
|
if ip.String() == ipStr {
|
|
isLocal = true
|
|
break
|
|
}
|
|
}
|
|
if !isLocal && !seen[ip.String()] {
|
|
seen[ip.String()] = true
|
|
localDNS = append(localDNS, ip.String())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
Log(context.Background(), logger.Error(), "Error scanning scutil output (attempt %d/%d): %v", attempt+1, maxRetries, err)
|
|
continue
|
|
}
|
|
|
|
// If we successfully read the output and found nameservers, return them
|
|
if len(localDNS) > 0 {
|
|
return localDNS
|
|
}
|
|
}
|
|
|
|
return nameservers
|
|
}
|
|
|
|
func getDHCPNameservers(iface string) ([]string, error) {
|
|
// Run the ipconfig command for the given interface.
|
|
cmd := exec.Command("ipconfig", "getpacket", iface)
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error running ipconfig: %v", err)
|
|
}
|
|
|
|
// Look for a line like:
|
|
// domain_name_servers = 192.168.1.1 8.8.8.8;
|
|
re := regexp.MustCompile(`domain_name_servers\s*=\s*(.*);`)
|
|
matches := re.FindStringSubmatch(string(output))
|
|
if len(matches) < 2 {
|
|
return nil, fmt.Errorf("no DHCP nameservers found")
|
|
}
|
|
|
|
// Split the nameservers by whitespace.
|
|
nameservers := strings.Fields(matches[1])
|
|
return nameservers, nil
|
|
}
|
|
|
|
func getAllDHCPNameservers(ctx context.Context) []string {
|
|
logger := LoggerFromCtx(ctx)
|
|
|
|
interfaces, err := net.Interfaces()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
regularIPs, loopbackIPs, _ := netmon.LocalAddresses()
|
|
|
|
var allNameservers []string
|
|
seen := make(map[string]bool)
|
|
|
|
for _, iface := range interfaces {
|
|
// Skip interfaces that are:
|
|
// - down
|
|
// - loopback
|
|
// - not physical (virtual)
|
|
// - point-to-point (like VPN interfaces)
|
|
// - without MAC address (non-physical)
|
|
if iface.Flags&net.FlagUp == 0 ||
|
|
iface.Flags&net.FlagLoopback != 0 ||
|
|
iface.Flags&net.FlagPointToPoint != 0 ||
|
|
(iface.Flags&net.FlagBroadcast == 0 &&
|
|
iface.Flags&net.FlagMulticast == 0) ||
|
|
len(iface.HardwareAddr) == 0 ||
|
|
strings.HasPrefix(iface.Name, "utun") ||
|
|
strings.HasPrefix(iface.Name, "llw") ||
|
|
strings.HasPrefix(iface.Name, "awdl") {
|
|
continue
|
|
}
|
|
|
|
// Verify it's a valid MAC address (should be 6 bytes for IEEE 802 MAC-48)
|
|
if len(iface.HardwareAddr) != 6 {
|
|
continue
|
|
}
|
|
|
|
nameservers, err := getDHCPNameservers(iface.Name)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
// Add unique nameservers to the result, skipping local IPs
|
|
for _, ns := range nameservers {
|
|
if ip := net.ParseIP(ns); ip != nil {
|
|
// skip loopback and local IPs
|
|
isLocal := false
|
|
for _, v := range slices.Concat(regularIPs, loopbackIPs) {
|
|
if ip.String() == v.String() {
|
|
isLocal = true
|
|
break
|
|
}
|
|
}
|
|
if !isLocal && !seen[ns] {
|
|
seen[ns] = true
|
|
allNameservers = append(allNameservers, ns)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// if we have static DNS servers saved for the current default route, we should add them to the list
|
|
drIfaceName, err := netmon.DefaultRouteInterface()
|
|
Log(context.Background(), logger.Debug(), "Checking for static DNS servers for default route interface: %s", drIfaceName)
|
|
if err != nil {
|
|
Log(context.Background(), logger.Debug(),
|
|
"Failed to get default route interface: %v", err)
|
|
} else {
|
|
drIface, err := net.InterfaceByName(drIfaceName)
|
|
if err != nil {
|
|
Log(context.Background(), logger.Debug(),
|
|
"Failed to get interface by name %s: %v", drIfaceName, err)
|
|
} else if drIface != nil {
|
|
if _, err := patchNetIfaceName(drIface); err != nil {
|
|
Log(context.Background(), logger.Debug(),
|
|
"Failed to patch interface name %s: %v", drIfaceName, err)
|
|
}
|
|
staticNs, file := SavedStaticNameserversAndPath(drIface)
|
|
Log(context.Background(), logger.Debug(),
|
|
"static dns servers from %s: %v", file, staticNs)
|
|
if len(staticNs) > 0 {
|
|
Log(context.Background(), logger.Debug(),
|
|
"Adding static DNS servers from %s: %v", drIface.Name, staticNs)
|
|
allNameservers = append(allNameservers, staticNs...)
|
|
}
|
|
}
|
|
}
|
|
|
|
return allNameservers
|
|
}
|
|
|
|
func patchNetIfaceName(iface *net.Interface) (bool, error) {
|
|
b, err := exec.Command("networksetup", "-listnetworkserviceorder").Output()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
patched := false
|
|
if name := networkServiceName(iface.Name, bytes.NewReader(b)); name != "" {
|
|
patched = true
|
|
iface.Name = name
|
|
}
|
|
return patched, nil
|
|
}
|
|
|
|
func networkServiceName(ifaceName string, r io.Reader) string {
|
|
scanner := bufio.NewScanner(r)
|
|
prevLine := ""
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
if strings.Contains(line, "*") {
|
|
// Network services is disabled.
|
|
continue
|
|
}
|
|
if !strings.Contains(line, "Device: "+ifaceName) {
|
|
prevLine = line
|
|
continue
|
|
}
|
|
parts := strings.SplitN(prevLine, " ", 2)
|
|
if len(parts) == 2 {
|
|
return strings.TrimSpace(parts[1])
|
|
}
|
|
}
|
|
return ""
|
|
}
|