net/{interfaces,netmon}, all: merge net/interfaces package into net/netmon
In prep for most of the package funcs in net/interfaces to become methods in a long-lived netmon.Monitor that can cache things. (Many of the funcs are very heavy to call regularly, whereas the long-lived netmon.Monitor can subscribe to things from the OS and remember answers to questions it's asked regularly later) Updates tailscale/corp#10910 Updates tailscale/corp#18960 Updates #7967 Updates #3299 Change-Id: Ie4e8dedb70136af2d611b990b865a822cd1797e5 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
committed by
Brad Fitzpatrick
parent
6b95219e3a
commit
b9adbe2002
@@ -0,0 +1,108 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build ios
|
||||
|
||||
package netmon
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"tailscale.com/syncs"
|
||||
)
|
||||
|
||||
var (
|
||||
lastKnownDefaultRouteIfName syncs.AtomicValue[string]
|
||||
)
|
||||
|
||||
// UpdateLastKnownDefaultRouteInterface is called by ipn-go-bridge in the iOS app when
|
||||
// our NWPathMonitor instance detects a network path transition.
|
||||
func UpdateLastKnownDefaultRouteInterface(ifName string) {
|
||||
if ifName == "" {
|
||||
return
|
||||
}
|
||||
if old := lastKnownDefaultRouteIfName.Swap(ifName); old != ifName {
|
||||
log.Printf("defaultroute_ios: 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
|
||||
// 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
|
||||
// because the delegated interface is not updated by the NetworkExtension framework.
|
||||
//
|
||||
// We work around this on the Swift side with a NWPathMonitor instance that observes
|
||||
// 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.
|
||||
|
||||
// Start by getting all available interfaces.
|
||||
interfaces, err := netInterfaces()
|
||||
if err != nil {
|
||||
log.Printf("defaultroute_ios: could not get interfaces: %v", err)
|
||||
return d, ErrNoGatewayIndexFound
|
||||
}
|
||||
|
||||
getInterfaceByName := func(name string) *Interface {
|
||||
for _, ifc := range interfaces {
|
||||
if ifc.Name != name {
|
||||
continue
|
||||
}
|
||||
|
||||
if !ifc.IsUp() {
|
||||
log.Printf("defaultroute_ios: %s is down", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
addrs, _ := ifc.Addrs()
|
||||
if len(addrs) == 0 {
|
||||
log.Printf("defaultroute_ios: %s has no addresses", name)
|
||||
return nil
|
||||
}
|
||||
return &ifc
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Did Swift set lastKnownDefaultRouteInterface? If so, we should use it and don't bother
|
||||
// with anything else. However, for sanity, do check whether Swift gave us with an interface
|
||||
// that exists, is up, and has an address.
|
||||
if swiftIfName := lastKnownDefaultRouteIfName.Load(); swiftIfName != "" {
|
||||
ifc := getInterfaceByName(swiftIfName)
|
||||
if ifc != nil {
|
||||
d.InterfaceName = ifc.Name
|
||||
d.InterfaceIndex = ifc.Index
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
log.Println("defaultroute_ios: no running interfaces available")
|
||||
return d, ErrNoGatewayIndexFound
|
||||
}
|
||||
Reference in New Issue
Block a user