From 7e771b0c9eb11cafe9fd91e36f000bfd3e68c8dd Mon Sep 17 00:00:00 2001 From: Jonathan Nobels Date: Thu, 8 Jan 2026 11:23:44 -0500 Subject: [PATCH] Renaming reachability->probe Correctly handle interface probes for 0.0.0.0 and :: Made all hooks sync.atomic so we're not blowing up the tests. --- net/netns/netns_darwin.go | 45 +++++++--- net/netns/netns_linux.go | 10 +++ net/netns/netns_probe.go | 170 ++++++++++++++++++++++++-------------- net/netns/netns_test.go | 117 +++++++++----------------- 4 files changed, 193 insertions(+), 149 deletions(-) diff --git a/net/netns/netns_darwin.go b/net/netns/netns_darwin.go index 91956930e..d5e9a4245 100644 --- a/net/netns/netns_darwin.go +++ b/net/netns/netns_darwin.go @@ -22,6 +22,9 @@ import ( "tailscale.com/version" ) +const VERBOSE_LOGS = true +const CHECK_PROBE_RESULTS_WITH_RIB = true + func control(logf logger.Logf, netMon *netmon.Monitor) func(network, address string, c syscall.RawConn) error { return func(network, address string, c syscall.RawConn) error { return controlLogf(logf, netMon, network, address, c) @@ -47,23 +50,42 @@ func controlLogf(logf logger.Logf, netMon *netmon.Monitor, network, address stri return fmt.Errorf("netns: control: SplitHostPort %q: %w", address, err) } + hpn := NewProbeTarget(network, host, port) + logf("netns: probing for interface to reach %s/%s:%s", network, hpn.Host, hpn.Port) + opts := probeOpts{ - logf: logf, - hpn: HostPortNetwork{Network: network, Host: host, Port: port}, - filterf: filterInvalidIntefaces, - race: true, - cache: cache(), + logf: logf, + pt: hpn, + filterf: nil, + race: true, + cache: cache(), + debugLogs: VERBOSE_LOGS, } // No netmon and no routing table. iface, err := findInterfaceThatCanReach(opts) - if err != nil || iface == nil { + if CHECK_PROBE_RESULTS_WITH_RIB { + ribIdx, berr := getInterfaceIndex(logf, netMon, address) + probeIdx := 0 + if iface != nil { + probeIdx = iface.Index + } + if berr == nil && iface != nil && iface.Index != ribIdx { + logf("netns: [unexpected] probe chose ifindex %d but routing table chose ifindex %d", probeIdx, ribIdx) + } + if berr != nil && iface != nil { + logf("netns: [unexpected] probe chose ifindex %d but routing table lookup failed: %v", probeIdx, berr) + } + } + + if err != nil { + logf("netns: probe found no interface to reach %s/%s", network, address) return err } - bindFn := getBindFn(network, address) logf("netns: post-probe binding to interface %q (index %d) for %s/%s", iface.Name, iface.Index, network, address) + bindFn := bindFnByAddrType(network, address) return bindFn(c, uint32(iface.Index)) } @@ -73,8 +95,11 @@ func controlLogf(logf logger.Logf, netMon *netmon.Monitor, network, address stri } // Bind using the legacy RIB / netmon method. - idx, _ := getInterfaceIndex(logf, netMon, address) - bindFn := getBindFn(network, address) + idx, err := getInterfaceIndex(logf, netMon, address) + if err != nil { + return err + } + bindFn := bindFnByAddrType(network, address) return bindFn(c, uint32(idx)) } @@ -99,7 +124,7 @@ func SetListenConfigInterfaceIndex(lc *net.ListenConfig, ifIndex int) error { return errors.New("ListenConfig.Control already set") } lc.Control = func(network, address string, c syscall.RawConn) error { - bindFn := getBindFn(network, address) + bindFn := bindFnByAddrType(network, address) return bindFn(c, uint32(ifIndex)) } return nil diff --git a/net/netns/netns_linux.go b/net/netns/netns_linux.go index 609f524b5..48700cd17 100644 --- a/net/netns/netns_linux.go +++ b/net/netns/netns_linux.go @@ -6,6 +6,7 @@ package netns import ( + "errors" "fmt" "net" "os" @@ -131,3 +132,12 @@ func bindToDevice(fd uintptr) error { } return nil } + +func bindSocket6(c syscall.RawConn, idx uint32) error { + return errors.New("bindSocket6 not implemented on Linux. Use BindToDevice or SocketMark instead") +} + +func bindSocket4(c syscall.RawConn, idx uint32) error { + return errors.New("bindSocket4 not implemented on Linux. Use BindToDevice or SocketMark instead") + +} diff --git a/net/netns/netns_probe.go b/net/netns/netns_probe.go index 67247bcbe..51060daaf 100644 --- a/net/netns/netns_probe.go +++ b/net/netns/netns_probe.go @@ -21,6 +21,7 @@ import ( "net" "net/netip" "strings" + "sync/atomic" "syscall" "time" @@ -66,9 +67,9 @@ func tailscaleInterface() (*net.Interface, error) { return nil, nil } -// inetReachability describes an interface and whether it was able to reach +// probeResult describes an interface and whether it was able to reach // the provided address. -type inetReachability struct { +type probeResult struct { iface net.Interface // TODO (barnstar): These are invariant. reachable should be true if err==nil. reachable bool @@ -77,35 +78,49 @@ type inetReachability struct { // Tuple of the destination host, port, and network. // ie: "tcp4", "example.com", "80" -type HostPortNetwork struct { +type ProbeTarget struct { Host string Port string Network string } -func (hpn HostPortNetwork) String() string { +func NewProbeTarget(network, host, port string) ProbeTarget { + // Probe scans require the explicit zero host not an empty string + if host == "" { + isV6 := strings.Contains(network, "6") + if !isV6 { + host = "0.0.0.0" + } else { + host = "::" + } + } + + return ProbeTarget{ + Host: host, + Port: port, + Network: network, + } +} + +func (hpn ProbeTarget) String() string { return fmt.Sprintf("%s/%s:%s", hpn.Network, hpn.Host, hpn.Port) } type probeOpts struct { - logf logger.Logf - hpn HostPortNetwork - race bool // if true, we'll pick the first interface that responds. sortf is ignored. - filterf interfaceFilter // optional pre-filter for interfaces - cache *routeCache // must be non-nil + logf logger.Logf + pt ProbeTarget + race bool // if true, we'll pick the first interface that responds. sortf is ignored. + filterf interfaceFilter // optional pre-filter for interfaces + cache *routeCache // must be non-nil + debugLogs bool // if true, log verbose output } -type DefaultIfaceHintFn func() int - -var defaultIfaceHintFn DefaultIfaceHintFn - -// Platforms may set defaultIFQueryFn to a function that returns the platforms's high -// level view of the default interface index. -func SetDefaultIFQueryFn(fn DefaultIfaceHintFn) { - defaultIfaceHintFn = fn +func (p *probeOpts) logDebug(format string, args ...any) { + if p.debugLogs && p.logf != nil { + p.logf(format, args...) + } } -// uint type bindFn func(c syscall.RawConn, ifidx uint32) error // Returns the proper bind function for the given network and address. @@ -118,59 +133,77 @@ func bindFnByAddrType(network, address string) bindFn { return bindSocket4 } -type bindFunctionHook func(network, address string) bindFn +// For testing +var interfacesHookFn func() ([]net.Interface, error) + +type probeHookFn func(iface *net.Interface, pt ProbeTarget) error -var getBindFn bindFunctionHook = bindFnByAddrType +var ( + interfacesHook atomic.Value // of interfacesHookFn + probeHook atomic.Value // of probeHookFn +) -var interfacesHookFn func() ([]net.Interface, error) +func init() { + // Set default hooks + probeHook.Store(probe) + interfacesHook.Store(net.Interfaces) +} -var interfacesHook = net.Interfaces +func ifaceListDesc(ifaces []net.Interface) string { + names := "" + for i, iface := range ifaces { + if i > 0 { + names += ", " + } + names += iface.Name + } + + return names +} // ProbeInterfacesReachability probes all non-loopback, up interfaces // concurrently to determine which can reach the given address. It returns // a slice with one entry per probed interface in the same order as // net.Interfaces() filtered by the probe criteria. -func probeInterfacesReachability(opts probeOpts) ([]inetReachability, error) { - ifaces, err := interfacesHook() +func probeInterfacesReachability(opts probeOpts) ([]probeResult, error) { + ifaces, err := interfacesHook.Load().(func() ([]net.Interface, error))() if err != nil { - opts.logf("netns: ProbeInterfacesReachability: net.Interfaces: %v", err) + opts.logf("netns: probe failed to find net.Interfaces: %v", err) return nil, err } - results := make(chan inetReachability, len(ifaces)) + results := make(chan probeResult, len(ifaces)) tsiface, _ := tailscaleInterface() var candidates []net.Interface + for _, iface := range ifaces { // Individual platforms can exclude potential intefaces based on platorm-specific logic. // For example, on Darwin, we skip "utun" interfaces. if opts.filterf != nil && !opts.filterf(iface) { continue } - // Only consider up, non-loopback interfaces. if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 || iface.Flags&net.FlagRunning == 0 { continue } - // Skip the Tailscale interface. if tsiface != nil && iface.Index == tsiface.Index { continue } - // require an IPv4 or IPv6 global unicast address if !ifaceHasV4OrGlobalV6(&iface) { continue } - candidates = append(candidates, iface) } if len(candidates) == 0 { - opts.logf("netns: ProbeInterfacesReachability: no candidate interfaces found") + opts.logDebug("netns: ProbeInterfacesReachability: no candidate interfaces found") return nil, errors.New("no candidate interfaces") } + opts.logDebug("netns: using candidate interfaces: %s", ifaceListDesc(candidates)) // Close this channel to abort ongoing probes if we're racing and are only interested // in the first result. @@ -184,13 +217,15 @@ func probeInterfacesReachability(opts probeOpts) ([]inetReachability, error) { default: } - // Per-probe timeout. - err := reachabilityHook(&iface, opts.hpn) - results <- inetReachability{iface: iface, reachable: err == nil, err: err} + opts.logDebug("netns: probing %s for reachability to %s via %s", iface.Name, opts.pt.Host, opts.pt.Network) + probeFn := probeHook.Load().(func(*net.Interface, ProbeTarget) error) + + err := probeFn(&iface, opts.pt) + results <- probeResult{iface: iface, reachable: err == nil, err: err} }() } - out := make([]inetReachability, 0, len(candidates)) + out := make([]probeResult, 0, len(candidates)) timeout := time.After(600 * time.Millisecond) received := 0 @@ -202,7 +237,7 @@ func probeInterfacesReachability(opts probeOpts) ([]inetReachability, error) { // can't get the conn up and running later but signal early if we're racing. if opts.race && r.reachable { close(done) - return []inetReachability{r}, nil + return []probeResult{r}, nil } // .. otherwise, collect all results including the unreachable ones. out = append(out, r) @@ -215,12 +250,15 @@ func probeInterfacesReachability(opts probeOpts) ([]inetReachability, error) { return out, nil } -// For testing -type reachabilityHookFn func(iface *net.Interface, hpn HostPortNetwork) error - -var reachabilityHook reachabilityHookFn = reachabilityCheck +func probe(iface *net.Interface, pt ProbeTarget) error { + // For unspecified hosts, we need to listen rather than dial. + if pt.Host == "0.0.0.0" || pt.Host == "::" { + return probeBindListen(iface, pt) + } + return probeBindDial(iface, pt) +} -func reachabilityCheck(iface *net.Interface, hpn HostPortNetwork) error { +func probeBindDial(iface *net.Interface, pt ProbeTarget) error { // Per-probe timeout. dialCtx, dialCancel := context.WithTimeout(context.Background(), 300*time.Millisecond) defer dialCancel() @@ -228,19 +266,37 @@ func reachabilityCheck(iface *net.Interface, hpn HostPortNetwork) error { d := net.Dialer{ Control: func(network, address string, c syscall.RawConn) error { // (barnstar) TODO: The bind step here is still platform specific - bindFn := getBindFn(network, address) + bindFn := bindFnByAddrType(network, address) return bindFn(c, uint32(iface.Index)) }, } - dst := net.JoinHostPort(hpn.Host, hpn.Port) - conn, err := d.DialContext(dialCtx, hpn.Network, dst) + dst := net.JoinHostPort(pt.Host, pt.Port) + conn, err := d.DialContext(dialCtx, pt.Network, dst) if err == nil { defer conn.Close() } return err } +func probeBindListen(iface *net.Interface, pt ProbeTarget) error { + lc := net.ListenConfig{ + Control: func(network, address string, c syscall.RawConn) error { + bindFn := bindFnByAddrType(network, address) + return bindFn(c, uint32(iface.Index)) + }, + } + + dst := net.JoinHostPort(pt.Host, pt.Port) + // Bind to this interface on any available port + listener, err := lc.ListenPacket(context.Background(), pt.Network, dst) + if err != nil { + return err + } + listener.Close() + return nil +} + // Pre-filter for interfaces. Platform-specific code can provide a filter // to exclude certain interfaces from consideration. For example, on Darwin, // we exclude "utun" interfaces and various other types which will never provie @@ -259,6 +315,7 @@ func filterInPlace[T any](s []T, keep func(T) bool) []T { } var errUnspecifiedHost = errors.New("unspecified host") +var errNoAvailableInterface = errors.New("no available interface") func parseAddress(address string) (addr netip.Addr, err error) { host, _, err := net.SplitHostPort(address) @@ -289,12 +346,12 @@ func parseAddress(address string) (addr netip.Addr, err error) { // nil is returned if no interface can reach the destination. func findInterfaceThatCanReach(opts probeOpts) (iface *net.Interface, err error) { // Try to parse the host as an IP address for cache lookup - addr, err := parseAddress(opts.hpn.Host) - if err == nil && addr.IsValid() { + addr, err := parseAddress(opts.pt.Host) + if opts.cache != nil && err == nil && addr.IsValid() { // Check cache first if cached := opts.cache.lookupCachedRoute(addr); cached != nil { hits, misses, total := opts.cache.stats() - opts.logf("netns: cachHit for %v cache stats: hits=%d misses=%d total=%d", addr, hits, misses, total) + opts.logDebug("netns: cachHit for %v cache stats: hits=%d misses=%d total=%d", addr, hits, misses, total) return cached, nil } } @@ -305,10 +362,10 @@ func findInterfaceThatCanReach(opts probeOpts) (iface *net.Interface, err error) return nil, err } - res = filterInPlace(res, func(r inetReachability) bool { return r.reachable }) + res = filterInPlace(res, func(r probeResult) bool { return r.reachable }) if len(res) == 0 { - opts.logf("netns: could not find interface on network %v to reach %q:%q on %q: %v", opts.hpn.Network, opts.hpn.Host, opts.hpn.Port, opts.hpn.Network, err) - return nil, nil + opts.logf("netns: could not find interface on network %v to reach %s:%s on %s: %v", opts.pt.Network, opts.pt.Host, opts.pt.Port, opts.pt.Network, err) + return nil, errNoAvailableInterface } candidatesNames := make([]string, 0, len(res)) @@ -317,19 +374,8 @@ func findInterfaceThatCanReach(opts probeOpts) (iface *net.Interface, err error) } iface = &res[0].iface - if defaultIfaceHintFn != nil { - defIdx := defaultIfaceHintFn() - for _, r := range res { - if r.iface.Index == defIdx { - opts.logf("netns: using default iface hint") - iface = &r.iface - break - } - } - } - // Cache the result if we have a valid IP address - if addr.IsValid() { + if opts.cache != nil && addr.IsValid() { opts.cache.setCachedRoute(addr, iface) } diff --git a/net/netns/netns_test.go b/net/netns/netns_test.go index b6272e07f..121f37995 100644 --- a/net/netns/netns_test.go +++ b/net/netns/netns_test.go @@ -298,11 +298,11 @@ func TestGlobalRouteCache(t *testing.T) { } func hookInterfaces(t *testing.T, ifaces []net.Interface) { - interfacesHook = func() ([]net.Interface, error) { + interfacesHook.Store(func() ([]net.Interface, error) { return ifaces, nil - } + }) t.Cleanup(func() { - interfacesHook = net.Interfaces + interfacesHook.Store(net.Interfaces) }) } @@ -336,10 +336,10 @@ var ( ) func TestFindInterfaceThatCanReach(t *testing.T) { - origReachabilityHook := reachabilityHook + origProbeHook := probeHook.Load().(func(*net.Interface, ProbeTarget) error) t.Cleanup(func() { ifaceHasV4AndGlobalV6Hook = nil - reachabilityHook = origReachabilityHook + probeHook.Store(origProbeHook) }) ifaceHasV4AndGlobalV6Hook = func(iface *net.Interface) bool { @@ -355,14 +355,14 @@ func TestFindInterfaceThatCanReach(t *testing.T) { cache.setCachedRoute(addr, &interfaceWlan0) // Hook should never be called when cache hits - reachabilityHook = func(iface *net.Interface, hpn HostPortNetwork) error { + probeHook.Store(func(iface *net.Interface, hpn ProbeTarget) error { t.Error("reachabilityHookFn should not be called when cache hits") return nil - } + }) opts := probeOpts{ logf: t.Logf, - hpn: HostPortNetwork{Host: "8.8.8.8", Port: "53", Network: "udp"}, + pt: ProbeTarget{Host: "8.8.8.8", Port: "53", Network: "udp"}, cache: cache, } @@ -385,13 +385,13 @@ func TestFindInterfaceThatCanReach(t *testing.T) { hookDefaultInterfaces(t) // All interfaces succeed - reachabilityHook = func(iface *net.Interface, hpn HostPortNetwork) error { + probeHook.Store(func(iface *net.Interface, hpn ProbeTarget) error { return nil - } + }) opts := probeOpts{ logf: t.Logf, - hpn: HostPortNetwork{Host: "1.1.1.1", Port: "53", Network: "udp"}, + pt: ProbeTarget{Host: "1.1.1.1", Port: "53", Network: "udp"}, cache: cache, } @@ -419,13 +419,13 @@ func TestFindInterfaceThatCanReach(t *testing.T) { hookDefaultInterfaces(t) // All interfaces fail - reachabilityHook = func(iface *net.Interface, hpn HostPortNetwork) error { + probeHook.Store(func(iface *net.Interface, hpn ProbeTarget) error { return errors.New("unreachable") - } + }) opts := probeOpts{ logf: t.Logf, - hpn: HostPortNetwork{Host: "192.0.2.1", Port: "53", Network: "udp"}, + pt: ProbeTarget{Host: "192.0.2.1", Port: "53", Network: "udp"}, cache: cache, } @@ -451,15 +451,15 @@ func TestFindInterfaceThatCanReach(t *testing.T) { prefix2 := netip.MustParsePrefix("10.0.1.0/24") cache.setCachedRoutePrefix(prefix2, &interfaceWlan0) - reachabilityHook = func(iface *net.Interface, hpn HostPortNetwork) error { + probeHook.Store(func(iface *net.Interface, hpn ProbeTarget) error { t.Error("should use cache, not probe") return nil - } + }) // Test 10.0.1.5 -> should match more specific /24 opts1 := probeOpts{ logf: t.Logf, - hpn: HostPortNetwork{Host: "10.0.1.5", Port: "53", Network: "udp"}, + pt: ProbeTarget{Host: "10.0.1.5", Port: "53", Network: "udp"}, cache: cache, } @@ -471,7 +471,7 @@ func TestFindInterfaceThatCanReach(t *testing.T) { // Test 10.0.2.5 -> should match broader /8 opts2 := probeOpts{ logf: t.Logf, - hpn: HostPortNetwork{Host: "10.0.2.5", Port: "53", Network: "udp"}, + pt: ProbeTarget{Host: "10.0.2.5", Port: "53", Network: "udp"}, cache: cache, } @@ -492,7 +492,7 @@ func TestFindInterfaceThatCanReach(t *testing.T) { wlan0Done := make(chan struct{}) eth1Done := make(chan struct{}) - reachabilityHook = func(iface *net.Interface, hpn HostPortNetwork) error { + probeHook.Store(func(iface *net.Interface, hpn ProbeTarget) error { switch iface.Index { case interfaceEth0.Index: // eth0 - returns immediately return nil @@ -504,7 +504,7 @@ func TestFindInterfaceThatCanReach(t *testing.T) { return nil } return errors.New("unknown interface") - } + }) defer func() { close(wlan0Done) close(eth1Done) @@ -512,7 +512,7 @@ func TestFindInterfaceThatCanReach(t *testing.T) { opts := probeOpts{ logf: t.Logf, - hpn: HostPortNetwork{Host: "8.8.8.8", Port: "53", Network: "udp"}, + pt: ProbeTarget{Host: "8.8.8.8", Port: "53", Network: "udp"}, race: true, cache: cache, } @@ -535,14 +535,14 @@ func TestFindInterfaceThatCanReach(t *testing.T) { hookDefaultInterfaces(t) probeCount := atomic.Int32{} - reachabilityHook = func(iface *net.Interface, hpn HostPortNetwork) error { + probeHook.Store(func(iface *net.Interface, hpn ProbeTarget) error { probeCount.Add(1) return nil - } + }) opts := probeOpts{ logf: t.Logf, - hpn: HostPortNetwork{Host: "8.8.8.8", Port: "53", Network: "udp"}, + pt: ProbeTarget{Host: "8.8.8.8", Port: "53", Network: "udp"}, cache: cache, filterf: func(iface net.Interface) bool { // Exclude wlan0 and eth1 @@ -569,14 +569,14 @@ func TestFindInterfaceThatCanReach(t *testing.T) { cache := NewRouteCache() hookDefaultInterfaces(t) - reachabilityHook = func(iface *net.Interface, hpn HostPortNetwork) error { + probeHook.Store(func(iface *net.Interface, hpn ProbeTarget) error { return nil - } + }) // Use a hostname that can't be parsed as an IP opts := probeOpts{ logf: t.Logf, - hpn: HostPortNetwork{Host: "example.com", Port: "443", Network: "tcp"}, + pt: ProbeTarget{Host: "example.com", Port: "443", Network: "tcp"}, cache: cache, } @@ -596,43 +596,6 @@ func TestFindInterfaceThatCanReach(t *testing.T) { } }) - t.Run("default interface hint is respected", func(t *testing.T) { - cache := NewRouteCache() - hookDefaultInterfaces(t) - - // All interfaces are reachable - reachabilityHook = func(iface *net.Interface, hpn HostPortNetwork) error { - return nil - } - - // Set hint to prefer iface2 (index 2) - origHintFn := defaultIfaceHintFn - defer func() { defaultIfaceHintFn = origHintFn }() - - defaultIfaceHintFn = func() int { - return 2 // iface2 / wlan0 - } - - opts := probeOpts{ - logf: t.Logf, - hpn: HostPortNetwork{Host: "1.1.1.1", Port: "53", Network: "udp"}, - cache: cache, - } - - result, err := findInterfaceThatCanReach(opts) - if err != nil { - t.Fatalf("findInterfaceThatCanReach failed: %v", err) - } - - if result == nil { - t.Fatal("expected non-nil result") - } - - if result.Index != 2 { - t.Errorf("expected default hint interface (index 2), got index %d (%s)", result.Index, result.Name) - } - }) - t.Run("IPv6 address uses IPv6 cache table", func(t *testing.T) { cache := NewRouteCache() hookDefaultInterfaces(t) @@ -641,14 +604,14 @@ func TestFindInterfaceThatCanReach(t *testing.T) { addr6 := netip.MustParseAddr("2001:4860:4860::8888") cache.setCachedRoute(addr6, &interfaceEth1) - reachabilityHook = func(iface *net.Interface, hpn HostPortNetwork) error { + probeHook.Store(func(iface *net.Interface, hpn ProbeTarget) error { t.Error("should use cache for IPv6") return nil - } + }) opts := probeOpts{ logf: t.Logf, - hpn: HostPortNetwork{Host: "2001:4860:4860::8888", Port: "53", Network: "udp6"}, + pt: ProbeTarget{Host: "2001:4860:4860::8888", Port: "53", Network: "udp6"}, cache: cache, } @@ -672,15 +635,15 @@ func TestFindInterfaceThatCanReach(t *testing.T) { cache.setCachedRoute(addr4, &interfaceEth0) cache.setCachedRoute(addr6, &interfaceWlan0) - reachabilityHook = func(iface *net.Interface, hpn HostPortNetwork) error { + probeHook.Store(func(iface *net.Interface, hpn ProbeTarget) error { t.Error("should use cache") return nil - } + }) // Test IPv4 opts4 := probeOpts{ logf: t.Logf, - hpn: HostPortNetwork{Host: "8.8.8.8", Port: "53", Network: "udp"}, + pt: ProbeTarget{Host: "8.8.8.8", Port: "53", Network: "udp"}, cache: cache, } result4, _ := findInterfaceThatCanReach(opts4) @@ -691,7 +654,7 @@ func TestFindInterfaceThatCanReach(t *testing.T) { // Test IPv6 opts6 := probeOpts{ logf: t.Logf, - hpn: HostPortNetwork{Host: "2001:4860:4860::8888", Port: "53", Network: "udp6"}, + pt: ProbeTarget{Host: "2001:4860:4860::8888", Port: "53", Network: "udp6"}, cache: cache, } result6, _ := findInterfaceThatCanReach(opts6) @@ -704,13 +667,13 @@ func TestFindInterfaceThatCanReach(t *testing.T) { cache := NewRouteCache() hookDefaultInterfaces(t) - reachabilityHook = func(iface *net.Interface, hpn HostPortNetwork) error { + probeHook.Store(func(iface *net.Interface, hpn ProbeTarget) error { return nil - } + }) opts := probeOpts{ logf: t.Logf, - hpn: HostPortNetwork{Host: "", Port: "53", Network: "udp"}, + pt: ProbeTarget{Host: "", Port: "53", Network: "udp"}, cache: cache, } @@ -730,10 +693,10 @@ func TestFindInterfaceThatCanReach(t *testing.T) { prefix := netip.MustParsePrefix("192.168.0.0/16") cache.setCachedRoutePrefix(prefix, &interfaceEth0) - reachabilityHook = func(iface *net.Interface, hpn HostPortNetwork) error { + probeHook.Store(func(iface *net.Interface, hpn ProbeTarget) error { t.Error("should use cached subnet") return nil - } + }) // Test various IPs in the subnet testIPs := []string{ @@ -745,7 +708,7 @@ func TestFindInterfaceThatCanReach(t *testing.T) { for _, ip := range testIPs { opts := probeOpts{ logf: t.Logf, - hpn: HostPortNetwork{Host: ip, Port: "53", Network: "udp"}, + pt: ProbeTarget{Host: ip, Port: "53", Network: "udp"}, cache: cache, }