net/interfaces: redo how we get the default interface on macOS and iOS

With #6566 we added an external mechanism for getting the default
interface, and used it on macOS and iOS (see tailscale/corp#8201).
The goal was to be able to get the default physical interface even when
using an exit node (in which case the routing table would say that the
Tailscale utun* interface is the default).

However, the external mechanism turns out to be unreliable in some
cases, e.g. when multiple cellular interfaces are present/toggled (I
have occasionally gotten my phone into a state where it reports the pdp_ip1
interface as the default, even though it can't actually route traffic).

It was observed that `ifconfig -v` on macOS reports an "effective interface"
for the Tailscale utn* interface, which seems promising. By examining
the ifconfig source code, it turns out that this is done via a
SIOCGIFDELEGATE ioctl syscall. Though this is a private API, it appears
to have been around for a long time (e.g. it's in the 10.13 xnu release
at https://opensource.apple.com/source/xnu/xnu-4570.41.2/bsd/net/if_types.h.auto.html)
and thus is unlikely to go away.

We can thus use this ioctl if the routing table says that a utun*
interface is the default, and go back to the simpler mechanism that
we had before #6566.

Updates #7184
Updates #7188

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
This commit is contained in:
Mihai Parparita
2023-02-10 15:02:12 -08:00
committed by Mihai Parparita
parent 21fda7f670
commit fa932fefe7
6 changed files with 85 additions and 40 deletions

View File

@@ -19,7 +19,6 @@ import (
"golang.org/x/net/route"
"golang.org/x/sys/unix"
"tailscale.com/net/netaddr"
"tailscale.com/syncs"
)
func defaultRoute() (d DefaultRouteDetails, err error) {
@@ -40,19 +39,6 @@ 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 {
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.
}
// $ netstat -nr
// Routing tables
// Internet:
@@ -81,8 +67,10 @@ 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)
if delegatedIndex, err := getDelegatedInterface(rm.Index); err == nil && delegatedIndex != 0 {
return delegatedIndex, nil
} else if err != nil {
log.Printf("interfaces_bsd: could not get delegated interface: %v", err)
}
return rm.Index, nil
}
@@ -90,16 +78,6 @@ func DefaultRouteInterfaceIndex() (int, error) {
return 0, errors.New("no gateway index found")
}
var defaultRouteInterfaceIndexFunc syncs.AtomicValue[func() int]
// SetDefaultRouteInterfaceIndexFunc allows an alternate implementation of
// DefaultRouteInterfaceIndex to be provided. If none is set, or if f() returns a 0
// (indicating an unknown interface index), then the default implementation (that parses
// the routing table) will be used.
func SetDefaultRouteInterfaceIndexFunc(f func() int) {
defaultRouteInterfaceIndexFunc.Store(f)
}
func init() {
likelyHomeRouterIP = likelyHomeRouterIPBSDFetchRIB
}