Files
tailscale/net/netmon/defaultroute_darwin.go

123 lines
4.0 KiB
Go
Raw Normal View History

2024-01-04 09:40:18 -08:00
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build darwin || ios
2024-01-04 09:40:18 -08:00
package netmon
2024-01-04 09:40:18 -08:00
import (
"errors"
"fmt"
2024-01-04 09:40:18 -08:00
"log"
"net"
2024-01-04 09:40:18 -08:00
"tailscale.com/syncs"
)
var (
lastKnownDefaultRouteIfName syncs.AtomicValue[string]
)
// UpdateLastKnownDefaultRouteInterface is called by ipn-go-bridge from apple network extensions when
2024-01-04 09:40:18 -08:00
// our NWPathMonitor instance detects a network path transition.
func UpdateLastKnownDefaultRouteInterface(ifName string) {
if ifName == "" {
return
}
if old := lastKnownDefaultRouteIfName.Swap(ifName); old != ifName {
interfaces, err := netInterfaces()
if err != nil {
log.Printf("defaultroute_darwin: UpdateLastKnownDefaultRouteInterface could not get interfaces: %v", err)
return
}
netif, err := getInterfaceByName(ifName, interfaces)
if err != nil {
log.Printf("defaultroute_darwin: UpdateLastKnownDefaultRouteInterface could not find interface index for %s: %v", ifName, err)
return
}
log.Printf("defaultroute_darwin: updated last known default if from OS, ifName = %s index: %d (was %s)", ifName, netif.Index, old)
}
2024-01-04 09:40:18 -08:00
}
func defaultRoute() (d DefaultRouteDetails, err error) {
// We cannot rely on the delegated interface data on darwin. The NetworkExtension framework
2024-01-04 09:40:18 -08:00
// 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, we will
// fallback to the BSD logic.
osRoute, osRouteErr := OSDefaultRoute()
if osRouteErr == nil {
// If we got a valid interface from the OS, use it.
d.InterfaceName = osRoute.InterfaceName
d.InterfaceIndex = osRoute.InterfaceIndex
return d, nil
}
2024-01-04 09:40:18 -08:00
// Fallback to the BSD logic
idx, err := DefaultRouteInterfaceIndex()
2024-01-04 09:40:18 -08:00
if err != nil {
return d, err
2024-01-04 09:40:18 -08:00
}
iface, err := net.InterfaceByIndex(idx)
if err != nil {
return d, err
2024-01-04 09:40:18 -08:00
}
d.InterfaceName = iface.Name
d.InterfaceIndex = idx
return d, nil
}
// OSDefaultRoute returns the DefaultRouteDetails for the default interface as provided by the OS
// via UpdateLastKnownDefaultRouteInterface. If UpdateLastKnownDefaultRouteInterface has not been called,
// the interface name is not valid, or we cannot find its index, an error is returned.
func OSDefaultRoute() (d DefaultRouteDetails, err error) {
2024-01-04 09:40:18 -08:00
// 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 and is not the tunnel itself.
2024-01-04 09:40:18 -08:00
if swiftIfName := lastKnownDefaultRouteIfName.Load(); swiftIfName != "" {
// Start by getting all available interfaces.
interfaces, err := netInterfaces()
if err != nil {
log.Printf("defaultroute_darwin: could not get interfaces: %v", err)
return d, err
}
if ifc, err := getInterfaceByName(swiftIfName, interfaces); err == nil {
2024-01-04 09:40:18 -08:00
d.InterfaceName = ifc.Name
d.InterfaceIndex = ifc.Index
return d, nil
}
}
err = errors.New("no os provided default route interface found")
return d, err
}
2024-01-04 09:40:18 -08:00
func getInterfaceByName(name string, interfaces []Interface) (*Interface, error) {
for _, ifc := range interfaces {
if ifc.Name != name {
continue
}
if !ifc.IsUp() {
return nil, fmt.Errorf("defaultroute_darwin: %s is down", name)
}
addrs, _ := ifc.Addrs()
if len(addrs) == 0 {
return nil, fmt.Errorf("defaultroute_darwin: %s has no addresses", name)
}
return &ifc, nil
2024-01-04 09:40:18 -08:00
}
return nil, errors.New("no interfaces found")
2024-01-04 09:40:18 -08:00
}