From fa1303d6327ba81cfa44a9b99aab542d13d0d688 Mon Sep 17 00:00:00 2001 From: Jonathan Nobels Date: Wed, 1 May 2024 09:20:09 -0400 Subject: [PATCH] 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 --- net/netmon/defaultroute_bsd.go | 6 +-- ...ultroute_ios.go => defaultroute_darwin.go} | 52 ++++++++----------- 2 files changed, 24 insertions(+), 34 deletions(-) rename net/netmon/{defaultroute_ios.go => defaultroute_darwin.go} (59%) diff --git a/net/netmon/defaultroute_bsd.go b/net/netmon/defaultroute_bsd.go index b66a84636..9195ae073 100644 --- a/net/netmon/defaultroute_bsd.go +++ b/net/netmon/defaultroute_bsd.go @@ -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 diff --git a/net/netmon/defaultroute_ios.go b/net/netmon/defaultroute_darwin.go similarity index 59% rename from net/netmon/defaultroute_ios.go rename to net/netmon/defaultroute_darwin.go index 63d5e49ce..9da52e311 100644 --- a/net/netmon/defaultroute_ios.go +++ b/net/netmon/defaultroute_darwin.go @@ -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 }