diff --git a/cmd/cli/dns_proxy.go b/cmd/cli/dns_proxy.go index 9bfa970..bdce33e 100644 --- a/cmd/cli/dns_proxy.go +++ b/cmd/cli/dns_proxy.go @@ -1435,7 +1435,7 @@ func (p *prog) monitorNetworkChanges(ctx context.Context) error { mon.RegisterChangeCallback(func(delta *netmon.ChangeDelta) { // Get map of valid interfaces - validIfaces := validInterfacesMap(ctrld.LoggerCtx(ctx, p.logger.Load())) + validIfaces := ctrld.ValidInterfaces(ctrld.LoggerCtx(ctx, p.logger.Load())) isMajorChange := mon.IsMajorChangeFrom(delta.Old, delta.New) diff --git a/cmd/cli/net_darwin.go b/cmd/cli/net_darwin.go index 7dac51d..7f756c4 100644 --- a/cmd/cli/net_darwin.go +++ b/cmd/cli/net_darwin.go @@ -3,7 +3,6 @@ package cli import ( "bufio" "bytes" - "context" "io" "net" "os/exec" @@ -50,28 +49,3 @@ func validInterface(iface *net.Interface, validIfacesMap map[string]struct{}) bo _, ok := validIfacesMap[iface.Name] return ok } - -// validInterfacesMap returns a set of all valid hardware ports. -func validInterfacesMap(ctx context.Context) map[string]struct{} { - b, err := exec.Command("networksetup", "-listallhardwareports").Output() - if err != nil { - return nil - } - return parseListAllHardwarePorts(bytes.NewReader(b)) -} - -// parseListAllHardwarePorts parses output of "networksetup -listallhardwareports" -// and returns map presents all hardware ports. -func parseListAllHardwarePorts(r io.Reader) map[string]struct{} { - m := make(map[string]struct{}) - scanner := bufio.NewScanner(r) - for scanner.Scan() { - line := scanner.Text() - after, ok := strings.CutPrefix(line, "Device: ") - if !ok { - continue - } - m[after] = struct{}{} - } - return m -} diff --git a/cmd/cli/net_linux.go b/cmd/cli/net_linux.go index a787e02..f5a07de 100644 --- a/cmd/cli/net_linux.go +++ b/cmd/cli/net_linux.go @@ -1,15 +1,7 @@ package cli import ( - "context" "net" - "net/netip" - "os" - "strings" - - "tailscale.com/net/netmon" - - "github.com/Control-D-Inc/ctrld" ) // patchNetIfaceName patches network interface names on Linux @@ -23,45 +15,3 @@ func validInterface(iface *net.Interface, validIfacesMap map[string]struct{}) bo _, ok := validIfacesMap[iface.Name] return ok } - -// validInterfacesMap returns a set containing non virtual interfaces. -// This filters out virtual interfaces to ensure DNS is only configured on physical interfaces -func validInterfacesMap(ctx context.Context) map[string]struct{} { - m := make(map[string]struct{}) - vis := virtualInterfaces(ctx) - netmon.ForeachInterface(func(i netmon.Interface, prefixes []netip.Prefix) { - if _, existed := vis[i.Name]; existed { - return - } - m[i.Name] = struct{}{} - }) - // Fallback to the default route interface if found nothing. - // This ensures we always have at least one interface to configure - if len(m) == 0 { - defaultRoute, err := netmon.DefaultRoute() - if err != nil { - return m - } - m[defaultRoute.InterfaceName] = struct{}{} - } - return m -} - -// virtualInterfaces returns a map of virtual interfaces on the current machine. -// This reads from /sys/devices/virtual/net to identify virtual network interfaces -// Virtual interfaces should not have DNS configured as they don't represent physical network connections -func virtualInterfaces(ctx context.Context) map[string]struct{} { - logger := ctrld.LoggerFromCtx(ctx) - s := make(map[string]struct{}) - entries, err := os.ReadDir("/sys/devices/virtual/net") - if err != nil { - logger.Error().Err(err).Msg("Failed to read /sys/devices/virtual/net") - return nil - } - for _, entry := range entries { - if entry.IsDir() { - s[strings.TrimSpace(entry.Name())] = struct{}{} - } - } - return s -} diff --git a/cmd/cli/net_others.go b/cmd/cli/net_others.go index 563bcad..4ab96de 100644 --- a/cmd/cli/net_others.go +++ b/cmd/cli/net_others.go @@ -3,10 +3,7 @@ package cli import ( - "context" "net" - - "tailscale.com/net/netmon" ) // patchNetIfaceName patches network interface names on non-Linux/Darwin platforms @@ -14,12 +11,3 @@ func patchNetIfaceName(iface *net.Interface) (bool, error) { return true, nil } // validInterface checks if an interface is valid on non-Linux/Darwin platforms func validInterface(iface *net.Interface, validIfacesMap map[string]struct{}) bool { return true } - -// validInterfacesMap returns a set containing only default route interfaces. -func validInterfacesMap(ctx context.Context) map[string]struct{} { - defaultRoute, err := netmon.DefaultRoute() - if err != nil { - return nil - } - return map[string]struct{}{defaultRoute.InterfaceName: {}} -} diff --git a/cmd/cli/net_windows.go b/cmd/cli/net_windows.go index 7b00a17..bdd6dcf 100644 --- a/cmd/cli/net_windows.go +++ b/cmd/cli/net_windows.go @@ -1,10 +1,7 @@ package cli import ( - "context" "net" - - "github.com/Control-D-Inc/ctrld" ) func patchNetIfaceName(iface *net.Interface) (bool, error) { @@ -17,12 +14,3 @@ func validInterface(iface *net.Interface, validIfacesMap map[string]struct{}) bo _, ok := validIfacesMap[iface.Name] return ok } - -// validInterfacesMap returns a set of all physical interfaces. -func validInterfacesMap(ctx context.Context) map[string]struct{} { - m := make(map[string]struct{}) - for ifaceName := range ctrld.ValidInterfaces(ctx) { - m[ifaceName] = struct{}{} - } - return m -} diff --git a/cmd/cli/prog.go b/cmd/cli/prog.go index 89fd8e3..069b883 100644 --- a/cmd/cli/prog.go +++ b/cmd/cli/prog.go @@ -1291,7 +1291,7 @@ func canBeLocalUpstream(addr string) bool { // the interface that matches excludeIfaceName. The context is used to clarify the // log message when error happens. func withEachPhysicalInterfaces(excludeIfaceName, contextStr string, f func(i *net.Interface) error) { - validIfacesMap := validInterfacesMap(ctrld.LoggerCtx(context.Background(), mainLog.Load())) + validIfacesMap := ctrld.ValidInterfaces(ctrld.LoggerCtx(context.Background(), mainLog.Load())) netmon.ForeachInterface(func(i netmon.Interface, prefixes []netip.Prefix) { // Skip loopback/virtual/down interface. if i.IsLoopback() || len(i.HardwareAddr) == 0 { diff --git a/nameservers_linux.go b/nameservers_linux.go index 8c93524..7a0406d 100644 --- a/nameservers_linux.go +++ b/nameservers_linux.go @@ -24,7 +24,7 @@ func dnsFns() []dnsFn { return []dnsFn{dnsFromResolvConf, dns4, dns6, dnsFromSystemdResolver} } -func dns4(_ context.Context) []string { +func dns4(ctx context.Context) []string { f, err := os.Open(v4RouteFile) if err != nil { return nil @@ -33,7 +33,7 @@ func dns4(_ context.Context) []string { var dns []string seen := make(map[string]bool) - vis := virtualInterfaces() + vis := virtualInterfaces(ctx) s := bufio.NewScanner(f) first := true for s.Scan() { @@ -46,7 +46,7 @@ func dns4(_ context.Context) []string { continue } // Skip virtual interfaces. - if vis.contains(string(bytes.TrimSpace(fields[0]))) { + if _, ok := vis[string(bytes.TrimSpace(fields[0]))]; ok { continue } gw := make([]byte, net.IPv4len) @@ -64,7 +64,7 @@ func dns4(_ context.Context) []string { return dns } -func dns6(_ context.Context) []string { +func dns6(ctx context.Context) []string { f, err := os.Open(v6RouteFile) if err != nil { return nil @@ -72,7 +72,7 @@ func dns6(_ context.Context) []string { defer f.Close() var dns []string - vis := virtualInterfaces() + vis := virtualInterfaces(ctx) s := bufio.NewScanner(f) for s.Scan() { fields := bytes.Fields(s.Bytes()) @@ -80,7 +80,7 @@ func dns6(_ context.Context) []string { continue } // Skip virtual interfaces. - if vis.contains(string(bytes.TrimSpace(fields[len(fields)-1]))) { + if _, ok := vis[string(bytes.TrimSpace(fields[len(fields)-1]))]; ok { continue } @@ -110,34 +110,29 @@ func dnsFromSystemdResolver(_ context.Context) []string { return ns } -type set map[string]struct{} - -func (s *set) add(e string) { - (*s)[e] = struct{}{} -} - -func (s *set) contains(e string) bool { - _, ok := (*s)[e] - return ok -} - -// virtualInterfaces returns a set of virtual interfaces on current machine. -func virtualInterfaces() set { - s := make(set) - entries, _ := os.ReadDir("/sys/devices/virtual/net") +// virtualInterfaces returns a map of virtual interfaces on the current machine. +// This reads from /sys/devices/virtual/net to identify virtual network interfaces +// Virtual interfaces should not have DNS configured as they don't represent physical network connections +func virtualInterfaces(ctx context.Context) map[string]struct{} { + logger := LoggerFromCtx(ctx) + s := make(map[string]struct{}) + entries, err := os.ReadDir("/sys/devices/virtual/net") + if err != nil { + logger.Error().Err(err).Msg("Failed to read /sys/devices/virtual/net") + return nil + } for _, entry := range entries { if entry.IsDir() { - s.add(strings.TrimSpace(entry.Name())) + s[strings.TrimSpace(entry.Name())] = struct{}{} } } return s } -// validInterfacesMap returns a set containing non virtual interfaces. -// TODO: deduplicated with cmd/cli/net_linux.go in v2. -func validInterfaces() set { +// ValidInterfaces returns a set containing non virtual interfaces. +func ValidInterfaces(ctx context.Context) map[string]struct{} { m := make(map[string]struct{}) - vis := virtualInterfaces() + vis := virtualInterfaces(ctx) netmon.ForeachInterface(func(i netmon.Interface, prefixes []netip.Prefix) { if _, existed := vis[i.Name]; existed { return diff --git a/nameservers_linux_test.go b/nameservers_linux_test.go index 23f1544..dddd377 100644 --- a/nameservers_linux_test.go +++ b/nameservers_linux_test.go @@ -1,10 +1,11 @@ package ctrld import ( + "context" "testing" ) func Test_virtualInterfaces(t *testing.T) { - vis := virtualInterfaces() + vis := virtualInterfaces(context.Background()) t.Log(vis) } diff --git a/nameservers_windows.go b/nameservers_windows.go index 547aac2..4ea0422 100644 --- a/nameservers_windows.go +++ b/nameservers_windows.go @@ -444,7 +444,3 @@ func ValidInterfaces(ctx context.Context) map[string]struct{} { } return m } - -func validInterfaces() map[string]struct{} { - return ValidInterfaces(context.Background()) -} diff --git a/net_darwin.go b/net_darwin.go index 5b01e9f..42c26a2 100644 --- a/net_darwin.go +++ b/net_darwin.go @@ -3,14 +3,14 @@ package ctrld import ( "bufio" "bytes" + "context" "io" "os/exec" "strings" ) -// validInterfaces returns a set of all valid hardware ports. -// TODO: deduplicated with cmd/cli/net_darwin.go in v2. -func validInterfaces() map[string]struct{} { +// ValidInterfaces returns a set of all valid hardware ports. +func ValidInterfaces(_ context.Context) map[string]struct{} { b, err := exec.Command("networksetup", "-listallhardwareports").Output() if err != nil { return nil diff --git a/net_others.go b/net_others.go index ae7ab8e..fef1e7d 100644 --- a/net_others.go +++ b/net_others.go @@ -2,11 +2,14 @@ package ctrld -import "tailscale.com/net/netmon" +import ( + "context" -// validInterfaces returns a set containing only default route interfaces. -// TODO: deuplicated with cmd/cli/net_others.go in v2. -func validInterfaces() map[string]struct{} { + "tailscale.com/net/netmon" +) + +// ValidInterfaces returns a set containing only default route interfaces. +func ValidInterfaces(_ context.Context) map[string]struct{} { defaultRoute, err := netmon.DefaultRoute() if err != nil { return nil diff --git a/resolver.go b/resolver.go index 425786d..878663d 100644 --- a/resolver.go +++ b/resolver.go @@ -711,7 +711,7 @@ func newResolverWithNameserver(nameservers []string) *osResolver { // Rfc1918Addresses returns the list of local physical interfaces private IP addresses func Rfc1918Addresses() []string { - vis := validInterfaces() + vis := ValidInterfaces(context.Background()) var res []string netmon.ForeachInterface(func(i netmon.Interface, prefixes []netip.Prefix) { // Skip virtual interfaces.