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>
pull/11953/head
Jonathan Nobels 1 month ago committed by GitHub
parent de85610be0
commit fa1303d632
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1,11 +1,11 @@
// Copyright (c) Tailscale Inc & AUTHORS // Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause // 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. // 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 package netmon

@ -1,12 +1,13 @@
// Copyright (c) Tailscale Inc & AUTHORS // Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
//go:build ios //go:build darwin || ios
package netmon package netmon
import ( import (
"log" "log"
"net"
"tailscale.com/syncs" "tailscale.com/syncs"
) )
@ -22,12 +23,12 @@ func UpdateLastKnownDefaultRouteInterface(ifName string) {
return return
} }
if old := lastKnownDefaultRouteIfName.Swap(ifName); old != ifName { 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) { 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. // 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 // 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 // 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 // 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. // 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 // If for any reason the Swift machinery didn't work and we don't get any updates, we will
// we also have some fallback logic: we try finding a hardcoded Wi-Fi interface called en0. // fallback to the BSD logic.
// 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.
// Start by getting all available interfaces. // Start by getting all available interfaces.
interfaces, err := netInterfaces() interfaces, err := netInterfaces()
if err != nil { 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 return d, ErrNoGatewayIndexFound
} }
@ -57,13 +55,13 @@ func defaultRoute() (d DefaultRouteDetails, err error) {
} }
if !ifc.IsUp() { if !ifc.IsUp() {
log.Printf("defaultroute_ios: %s is down", name) log.Printf("defaultroute_darwin: %s is down", name)
return nil return nil
} }
addrs, _ := ifc.Addrs() addrs, _ := ifc.Addrs()
if len(addrs) == 0 { 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 nil
} }
return &ifc return &ifc
@ -79,30 +77,22 @@ func defaultRoute() (d DefaultRouteDetails, err error) {
if ifc != nil { if ifc != nil {
d.InterfaceName = ifc.Name d.InterfaceName = ifc.Name
d.InterfaceIndex = ifc.Index d.InterfaceIndex = ifc.Index
log.Printf("defaultroute_darwin: using lastKnownDefaultRouteInterface %s %v", d.InterfaceName, d.InterfaceIndex)
return d, nil return d, nil
} }
} }
// Start of our fallback logic if Swift didn't give us an interface name, or gave us an invalid // Fallback to the BSD logic
// one. idx, err := DefaultRouteInterfaceIndex()
// We start by attempting to use the Wi-Fi interface, which on iPhone is always called en0. if err != nil {
enZeroIf := getInterfaceByName("en0") return d, err
if enZeroIf != nil {
log.Println("defaultroute_ios: using en0 (fallback)")
d.InterfaceName = enZeroIf.Name
d.InterfaceIndex = enZeroIf.Index
return d, nil
} }
iface, err := net.InterfaceByIndex(idx)
// Did it not work? Let's try with Cellular (pdp_ip0). if err != nil {
cellIf := getInterfaceByName("pdp_ip0") return d, err
if cellIf != nil {
log.Println("defaultroute_ios: using pdp_ip0 (fallback)")
d.InterfaceName = cellIf.Name
d.InterfaceIndex = cellIf.Index
return d, nil
} }
d.InterfaceName = iface.Name
log.Println("defaultroute_ios: no running interfaces available") d.InterfaceIndex = idx
return d, ErrNoGatewayIndexFound log.Printf("defaultroute_darwin: using table dervied default if %s %v", d.InterfaceName, d.InterfaceIndex)
return d, nil
} }
Loading…
Cancel
Save