| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108 |
- // Copyright (c) Tailscale Inc & AUTHORS
- // SPDX-License-Identifier: BSD-3-Clause
- //go:build ios
- package interfaces
- 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
- }
- lastKnownDefaultRouteIfName.Store(ifName)
- log.Printf("defaultroute_ios: update from Swift, ifName = %s", ifName)
- }
- 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.Println("defaultroute_ios: %s is down", name)
- return nil
- }
- addrs, _ := ifc.Addrs()
- if len(addrs) == 0 {
- log.Println("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 {
- log.Printf("defaultroute_ios: using %s (provided by Swift)", ifc.Name)
- 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
- }
|