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.
jonathan/netns_probe
Jonathan Nobels 3 days ago
parent 5e37be0fb6
commit 7e771b0c9e

@ -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

@ -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")
}

@ -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)
}

@ -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,
}

Loading…
Cancel
Save