net/netmon: swap to swift-derived defaultRoute on macos (#11936)

Updates tailscale/corp#18960

iOS uses Apple's NetworkMonitor to track the default interface and
there's no reason we shouldn't also use this on macOS, for the same
reasons noted in the comments for why this change was made on iOS.

This eliminates the need to load and parse the routing table when
querying the defaultRouter() in almost all cases.

A slight modification here (on both platforms) to fallback to the default
BSD logic in the unhappy-path rather than making assumptions that
may not hold.  If netmon is eventually parsing AF_ROUTE and able
to give a consistently correct answer for the  default interface index,
we can fall back to that and eliminate the Swift dependency.

Signed-off-by: Jonathan Nobels <jonathan@tailscale.com>
This commit is contained in:
Jonathan Nobels 2024-05-01 09:20:09 -04:00 committed by GitHub
parent de85610be0
commit fa1303d632
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 24 additions and 34 deletions

View File

@ -1,11 +1,11 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Common code for FreeBSD and Darwin. This might also work on other
// Common code for FreeBSD. This might also work on other
// BSD systems (e.g. OpenBSD) but has not been tested.
// Not used on iOS. See defaultroute_ios.go.
// Not used on iOS or macOS. See defaultroute_darwin.go.
//go:build !ios && (darwin || freebsd)
//go:build freebsd
package netmon

View File

@ -1,12 +1,13 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build ios
//go:build darwin || ios
package netmon
import (
"log"
"net"
"tailscale.com/syncs"
)
@ -22,12 +23,12 @@ func UpdateLastKnownDefaultRouteInterface(ifName string) {
return
}
if old := lastKnownDefaultRouteIfName.Swap(ifName); old != ifName {
log.Printf("defaultroute_ios: update from Swift, ifName = %s (was %s)", ifName, old)
log.Printf("defaultroute_darwin: update from Swift, ifName = %s (was %s)", ifName, old)
}
}
func defaultRoute() (d DefaultRouteDetails, err error) {
// We cannot rely on the delegated interface data on iOS. The NetworkExtension framework
// We cannot rely on the delegated interface data on darwin. The NetworkExtension framework
// seems to set the delegate interface only once, upon the *creation* of the VPN tunnel.
// If a network transition (e.g. from Wi-Fi to Cellular) happens while the tunnel is
// connected, it will be ignored and we will still try to set Wi-Fi as the default route
@ -37,16 +38,13 @@ func defaultRoute() (d DefaultRouteDetails, err error) {
// the interface name of the first currently satisfied network path. Our Swift code will
// call into `UpdateLastKnownDefaultRouteInterface`, so we can rely on that when it is set.
//
// If for any reason the Swift machinery didn't work and we don't get any updates, here
// we also have some fallback logic: we try finding a hardcoded Wi-Fi interface called en0.
// If en0 is down, we fall back to cellular (pdp_ip0) as a last resort. This doesn't handle
// all edge cases like USB-Ethernet adapters or multiple Ethernet interfaces, but is good
// enough to ensure connectivity isn't broken.
// If for any reason the Swift machinery didn't work and we don't get any updates, we will
// fallback to the BSD logic.
// Start by getting all available interfaces.
interfaces, err := netInterfaces()
if err != nil {
log.Printf("defaultroute_ios: could not get interfaces: %v", err)
log.Printf("defaultroute_darwin: could not get interfaces: %v", err)
return d, ErrNoGatewayIndexFound
}
@ -57,13 +55,13 @@ func defaultRoute() (d DefaultRouteDetails, err error) {
}
if !ifc.IsUp() {
log.Printf("defaultroute_ios: %s is down", name)
log.Printf("defaultroute_darwin: %s is down", name)
return nil
}
addrs, _ := ifc.Addrs()
if len(addrs) == 0 {
log.Printf("defaultroute_ios: %s has no addresses", name)
log.Printf("defaultroute_darwin: %s has no addresses", name)
return nil
}
return &ifc
@ -79,30 +77,22 @@ func defaultRoute() (d DefaultRouteDetails, err error) {
if ifc != nil {
d.InterfaceName = ifc.Name
d.InterfaceIndex = ifc.Index
log.Printf("defaultroute_darwin: using lastKnownDefaultRouteInterface %s %v", d.InterfaceName, d.InterfaceIndex)
return d, nil
}
}
// Start of our fallback logic if Swift didn't give us an interface name, or gave us an invalid
// one.
// We start by attempting to use the Wi-Fi interface, which on iPhone is always called en0.
enZeroIf := getInterfaceByName("en0")
if enZeroIf != nil {
log.Println("defaultroute_ios: using en0 (fallback)")
d.InterfaceName = enZeroIf.Name
d.InterfaceIndex = enZeroIf.Index
return d, nil
// Fallback to the BSD logic
idx, err := DefaultRouteInterfaceIndex()
if err != nil {
return d, err
}
// Did it not work? Let's try with Cellular (pdp_ip0).
cellIf := getInterfaceByName("pdp_ip0")
if cellIf != nil {
log.Println("defaultroute_ios: using pdp_ip0 (fallback)")
d.InterfaceName = cellIf.Name
d.InterfaceIndex = cellIf.Index
return d, nil
iface, err := net.InterfaceByIndex(idx)
if err != nil {
return d, err
}
log.Println("defaultroute_ios: no running interfaces available")
return d, ErrNoGatewayIndexFound
d.InterfaceName = iface.Name
d.InterfaceIndex = idx
log.Printf("defaultroute_darwin: using table dervied default if %s %v", d.InterfaceName, d.InterfaceIndex)
return d, nil
}