diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 6a8ab8f1c..cc56040ba 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -3840,6 +3840,8 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) { // See the netns package for documentation on what this capability does. netns.SetBindToInterfaceByRoute(hasCapability(nm, tailcfg.CapabilityBindToInterfaceByRoute)) + interfaces.SetDisableAlternateDefaultRouteInterface(hasCapability(nm, tailcfg.CapabilityDebugDisableAlternateDefaultRouteInterface)) + netns.SetDisableBindConnToInterface(hasCapability(nm, tailcfg.CapabilityDebugDisableBindConnToInterface)) b.setTCPPortsInterceptedFromNetmapAndPrefsLocked(b.pm.CurrentPrefs()) if nm == nil { diff --git a/net/interfaces/interfaces.go b/net/interfaces/interfaces.go index c9d92dfcb..40cb1e122 100644 --- a/net/interfaces/interfaces.go +++ b/net/interfaces/interfaces.go @@ -13,6 +13,7 @@ import ( "runtime" "sort" "strings" + "sync/atomic" "tailscale.com/hostinfo" "tailscale.com/net/netaddr" @@ -756,3 +757,14 @@ func HasCGNATInterface() (bool, error) { } return hasCGNATInterface, nil } + +var disableAlternateDefaultRouteInterface atomic.Bool + +// SetDisableAlternateDefaultRouteInterface disables the optional external/ +// alternate mechanism for getting the default route network interface. +// +// Currently, this only changes the behaviour on BSD-like sytems (FreeBSD and +// Darwin). +func SetDisableAlternateDefaultRouteInterface(v bool) { + disableAlternateDefaultRouteInterface.Store(v) +} diff --git a/net/interfaces/interfaces_bsd.go b/net/interfaces/interfaces_bsd.go index 1a68e675d..610871d7c 100644 --- a/net/interfaces/interfaces_bsd.go +++ b/net/interfaces/interfaces_bsd.go @@ -40,9 +40,15 @@ func defaultRoute() (d DefaultRouteDetails, err error) { // owns the default route. It returns the first IPv4 or IPv6 default route it // finds (it does not prefer one or the other). func DefaultRouteInterfaceIndex() (int, error) { + disabledAlternateDefaultRouteInterface := false if f := defaultRouteInterfaceIndexFunc.Load(); f != nil { if ifIndex := f(); ifIndex != 0 { - return ifIndex, nil + if !disableAlternateDefaultRouteInterface.Load() { + return ifIndex, nil + } else { + disabledAlternateDefaultRouteInterface = true + log.Printf("interfaces_bsd: alternate default route interface function disabled, would have returned interface %d", ifIndex) + } } // Fallthrough if we can't use the alternate implementation. } @@ -75,6 +81,9 @@ func DefaultRouteInterfaceIndex() (int, error) { continue } if isDefaultGateway(rm) { + if disabledAlternateDefaultRouteInterface { + log.Printf("interfaces_bsd: alternate default route interface function disabled, default implementation returned %d", rm.Index) + } return rm.Index, nil } } diff --git a/net/netns/netns.go b/net/netns/netns.go index 1502aad18..86aafcac2 100644 --- a/net/netns/netns.go +++ b/net/netns/netns.go @@ -42,6 +42,16 @@ func SetBindToInterfaceByRoute(v bool) { bindToInterfaceByRoute.Store(v) } +var disableBindConnToInterface atomic.Bool + +// SetDisableBindConnToInterface disables the (normal) behavior of binding +// connections to the default network interface. +// +// Currently, this only has an effect on Darwin. +func SetDisableBindConnToInterface(v bool) { + disableBindConnToInterface.Store(v) +} + // Listener returns a new net.Listener with its Control hook func // initialized as necessary to run in logical network namespace that // doesn't route back into Tailscale. diff --git a/net/netns/netns_darwin.go b/net/netns/netns_darwin.go index 22d7be434..50d7fcaad 100644 --- a/net/netns/netns_darwin.go +++ b/net/netns/netns_darwin.go @@ -42,6 +42,11 @@ func controlLogf(logf logger.Logf, network, address string, c syscall.RawConn) e return nil } + if disableBindConnToInterface.Load() { + logf("netns_darwin: binding connection to interfaces disabled") + return nil + } + idx, err := getInterfaceIndex(logf, address) if err != nil { // callee logged diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 95dc79404..5f47a8502 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -1765,6 +1765,17 @@ const ( // details on the behaviour of this capability. CapabilityBindToInterfaceByRoute = "https://tailscale.com/cap/bind-to-interface-by-route" + // CapabilityDebugDisableAlternateDefaultRouteInterface changes how Darwin + // nodes get the default interface. There is an optional hook (used by the + // macOS and iOS clients) to override the default interface, this capability + // disables that and uses the default behavior (of parsing the routing + // table). + CapabilityDebugDisableAlternateDefaultRouteInterface = "https://tailscale.com/cap/debug-disable-alternate-default-route-interface" + + // CapabilityDebugDisableBindConnToInterface disables the automatic binding + // of connections to the default network interface on Darwin nodes. + CapabilityDebugDisableBindConnToInterface = "https://tailscale.com/cap/debug-disable-bind-conn-to-interface" + // CapabilityTailnetLockAlpha indicates the node is in the tailnet lock alpha, // and initialization of tailnet lock may proceed. //