Update dependencies

This commit is contained in:
bluepython508
2024-11-01 17:33:34 +00:00
parent 033ac0b400
commit 5cdfab398d
3596 changed files with 1033483 additions and 259 deletions

26
vendor/tailscale.com/net/netmon/defaultroute_bsd.go generated vendored Normal file
View File

@@ -0,0 +1,26 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Common code for FreeBSD. This might also work on other
// BSD systems (e.g. OpenBSD) but has not been tested.
// Not used on iOS or macOS. See defaultroute_darwin.go.
//go:build freebsd
package netmon
import "net"
func defaultRoute() (d DefaultRouteDetails, err error) {
idx, err := DefaultRouteInterfaceIndex()
if err != nil {
return d, err
}
iface, err := net.InterfaceByIndex(idx)
if err != nil {
return d, err
}
d.InterfaceName = iface.Name
d.InterfaceIndex = idx
return d, nil
}

96
vendor/tailscale.com/net/netmon/defaultroute_darwin.go generated vendored Normal file
View File

@@ -0,0 +1,96 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build darwin || ios
package netmon
import (
"log"
"net"
"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_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 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
// 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.
// Start by getting all available interfaces.
interfaces, err := netInterfaces()
if err != nil {
log.Printf("defaultroute_darwin: 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_darwin: %s is down", name)
return nil
}
addrs, _ := ifc.Addrs()
if len(addrs) == 0 {
log.Printf("defaultroute_darwin: %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
}
}
// Fallback to the BSD logic
idx, err := DefaultRouteInterfaceIndex()
if err != nil {
return d, err
}
iface, err := net.InterfaceByIndex(idx)
if err != nil {
return d, err
}
d.InterfaceName = iface.Name
d.InterfaceIndex = idx
return d, nil
}

183
vendor/tailscale.com/net/netmon/interfaces_android.go generated vendored Normal file
View File

@@ -0,0 +1,183 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package netmon
import (
"bytes"
"errors"
"log"
"net/netip"
"os/exec"
"sync/atomic"
"go4.org/mem"
"golang.org/x/sys/unix"
"tailscale.com/net/netaddr"
"tailscale.com/syncs"
"tailscale.com/util/lineread"
)
var (
lastKnownDefaultRouteIfName syncs.AtomicValue[string]
)
var procNetRoutePath = "/proc/net/route"
// maxProcNetRouteRead is the max number of lines to read from
// /proc/net/route looking for a default route.
const maxProcNetRouteRead = 1000
func init() {
likelyHomeRouterIP = likelyHomeRouterIPAndroid
}
var procNetRouteErr atomic.Bool
// errStopReading is a sentinel error value used internally by
// lineread.File callers to stop reading. It doesn't escape to
// callers/users.
var errStopReading = errors.New("stop reading")
/*
Parse 10.0.0.1 out of:
$ cat /proc/net/route
Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
ens18 00000000 0100000A 0003 0 0 0 00000000 0 0 0
ens18 0000000A 00000000 0001 0 0 0 0000FFFF 0 0 0
*/
func likelyHomeRouterIPAndroid() (ret netip.Addr, myIP netip.Addr, ok bool) {
if procNetRouteErr.Load() {
// If we failed to read /proc/net/route previously, don't keep trying.
return likelyHomeRouterIPHelper()
}
lineNum := 0
var f []mem.RO
err := lineread.File(procNetRoutePath, func(line []byte) error {
lineNum++
if lineNum == 1 {
// Skip header line.
return nil
}
if lineNum > maxProcNetRouteRead {
return errStopReading
}
f = mem.AppendFields(f[:0], mem.B(line))
if len(f) < 4 {
return nil
}
gwHex, flagsHex := f[2], f[3]
flags, err := mem.ParseUint(flagsHex, 16, 16)
if err != nil {
return nil // ignore error, skip line and keep going
}
if flags&(unix.RTF_UP|unix.RTF_GATEWAY) != unix.RTF_UP|unix.RTF_GATEWAY {
return nil
}
ipu32, err := mem.ParseUint(gwHex, 16, 32)
if err != nil {
return nil // ignore error, skip line and keep going
}
ip := netaddr.IPv4(byte(ipu32), byte(ipu32>>8), byte(ipu32>>16), byte(ipu32>>24))
if ip.IsPrivate() {
ret = ip
return errStopReading
}
return nil
})
if errors.Is(err, errStopReading) {
err = nil
}
if err != nil {
procNetRouteErr.Store(true)
return likelyHomeRouterIP()
}
if ret.IsValid() {
// Try to get the local IP of the interface associated with
// this route to short-circuit finding the IP associated with
// this gateway. This isn't fatal if it fails.
if len(f) > 0 && !disableLikelyHomeRouterIPSelf() {
ForeachInterface(func(ni Interface, pfxs []netip.Prefix) {
// Ensure this is the same interface
if !f[0].EqualString(ni.Name) {
return
}
// Find the first IPv4 address and use it.
for _, pfx := range pfxs {
if addr := pfx.Addr(); addr.Is4() {
myIP = addr
break
}
}
})
}
return ret, myIP, true
}
if lineNum >= maxProcNetRouteRead {
// If we went over our line limit without finding an answer, assume
// we're a big fancy Linux router (or at least not a home system)
// and set the error bit so we stop trying this in the future (and wasting CPU).
// See https://github.com/tailscale/tailscale/issues/7621.
//
// Remember that "likelyHomeRouterIP" exists purely to find the port
// mapping service (UPnP, PMP, PCP) often present on a home router. If we hit
// the route (line) limit without finding an answer, we're unlikely to ever
// find one in the future.
procNetRouteErr.Store(true)
}
return netip.Addr{}, netip.Addr{}, false
}
// Android apps don't have permission to read /proc/net/route, at
// least on Google devices and the Android emulator.
func likelyHomeRouterIPHelper() (ret netip.Addr, _ netip.Addr, ok bool) {
cmd := exec.Command("/system/bin/ip", "route", "show", "table", "0")
out, err := cmd.StdoutPipe()
if err != nil {
return
}
if err := cmd.Start(); err != nil {
log.Printf("interfaces: running /system/bin/ip: %v", err)
return
}
// Search for line like "default via 10.0.2.2 dev radio0 table 1016 proto static mtu 1500 "
lineread.Reader(out, func(line []byte) error {
const pfx = "default via "
if !mem.HasPrefix(mem.B(line), mem.S(pfx)) {
return nil
}
line = line[len(pfx):]
sp := bytes.IndexByte(line, ' ')
if sp == -1 {
return nil
}
ipb := line[:sp]
if ip, err := netip.ParseAddr(string(ipb)); err == nil && ip.Is4() {
ret = ip
log.Printf("interfaces: found Android default route %v", ip)
}
return nil
})
cmd.Process.Kill()
cmd.Wait()
return ret, netip.Addr{}, ret.IsValid()
}
// UpdateLastKnownDefaultRouteInterface is called by libtailscale in the Android app when
// the connectivity manager detects a network path transition. If ifName is "", network has been lost.
// After updating the interface, Android calls Monitor.InjectEvent(), triggering a link change.
func UpdateLastKnownDefaultRouteInterface(ifName string) {
if old := lastKnownDefaultRouteIfName.Swap(ifName); old != ifName {
log.Printf("defaultroute: update from Android, ifName = %s (was %s)", ifName, old)
}
}
func defaultRoute() (d DefaultRouteDetails, err error) {
if ifName := lastKnownDefaultRouteIfName.Load(); ifName != "" {
d.InterfaceName = ifName
}
return d, nil
}

153
vendor/tailscale.com/net/netmon/interfaces_bsd.go generated vendored Normal file
View File

@@ -0,0 +1,153 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Common code for FreeBSD and Darwin. This might also work on other
// BSD systems (e.g. OpenBSD) but has not been tested.
//go:build darwin || freebsd
package netmon
import (
"errors"
"fmt"
"log"
"net/netip"
"syscall"
"golang.org/x/net/route"
"golang.org/x/sys/unix"
"tailscale.com/net/netaddr"
)
// ErrNoGatewayIndexFound is returned by DefaultRouteInterfaceIndex when no
// default route is found.
var ErrNoGatewayIndexFound = errors.New("no gateway index found")
// DefaultRouteInterfaceIndex returns the index of the network interface that
// owns the default route. It returns the first IPv4 or IPv6 default route it
// finds (it does not prefer one or the other).
func DefaultRouteInterfaceIndex() (int, error) {
// $ netstat -nr
// Routing tables
// Internet:
// Destination Gateway Flags Netif Expire
// default 10.0.0.1 UGSc en0 <-- want this one
// default 10.0.0.1 UGScI en1
// From man netstat:
// U RTF_UP Route usable
// G RTF_GATEWAY Destination requires forwarding by intermediary
// S RTF_STATIC Manually added
// c RTF_PRCLONING Protocol-specified generate new routes on use
// I RTF_IFSCOPE Route is associated with an interface scope
rib, err := fetchRoutingTable()
if err != nil {
return 0, fmt.Errorf("route.FetchRIB: %w", err)
}
msgs, err := parseRoutingTable(rib)
if err != nil {
return 0, fmt.Errorf("route.ParseRIB: %w", err)
}
for _, m := range msgs {
rm, ok := m.(*route.RouteMessage)
if !ok {
continue
}
if isDefaultGateway(rm) {
if delegatedIndex, err := getDelegatedInterface(rm.Index); err == nil && delegatedIndex != 0 {
return delegatedIndex, nil
} else if err != nil {
log.Printf("interfaces_bsd: could not get delegated interface: %v", err)
}
return rm.Index, nil
}
}
return 0, ErrNoGatewayIndexFound
}
func init() {
likelyHomeRouterIP = likelyHomeRouterIPBSDFetchRIB
}
func likelyHomeRouterIPBSDFetchRIB() (ret, myIP netip.Addr, ok bool) {
rib, err := fetchRoutingTable()
if err != nil {
log.Printf("routerIP/FetchRIB: %v", err)
return ret, myIP, false
}
msgs, err := parseRoutingTable(rib)
if err != nil {
log.Printf("routerIP/ParseRIB: %v", err)
return ret, myIP, false
}
for _, m := range msgs {
rm, ok := m.(*route.RouteMessage)
if !ok {
continue
}
if !isDefaultGateway(rm) {
continue
}
gw, ok := rm.Addrs[unix.RTAX_GATEWAY].(*route.Inet4Addr)
if !ok {
continue
}
// If the route entry has an interface address associated with
// it, then parse and return that. This is optional.
if len(rm.Addrs) >= unix.RTAX_IFA {
if addr, ok := rm.Addrs[unix.RTAX_IFA].(*route.Inet4Addr); ok {
myIP = netaddr.IPv4(addr.IP[0], addr.IP[1], addr.IP[2], addr.IP[3])
}
}
return netaddr.IPv4(gw.IP[0], gw.IP[1], gw.IP[2], gw.IP[3]), myIP, true
}
return ret, myIP, false
}
var v4default = [4]byte{0, 0, 0, 0}
var v6default = [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
func isDefaultGateway(rm *route.RouteMessage) bool {
if rm.Flags&unix.RTF_GATEWAY == 0 {
return false
}
// Defined locally because FreeBSD does not have unix.RTF_IFSCOPE.
const RTF_IFSCOPE = 0x1000000
if rm.Flags&RTF_IFSCOPE != 0 {
return false
}
// Addrs is [RTAX_DST, RTAX_GATEWAY, RTAX_NETMASK, ...]
if len(rm.Addrs) <= unix.RTAX_NETMASK {
return false
}
dst := rm.Addrs[unix.RTAX_DST]
netmask := rm.Addrs[unix.RTAX_NETMASK]
if dst == nil || netmask == nil {
return false
}
if dst.Family() == syscall.AF_INET && netmask.Family() == syscall.AF_INET {
dstAddr, dstOk := dst.(*route.Inet4Addr)
nmAddr, nmOk := netmask.(*route.Inet4Addr)
if dstOk && nmOk && dstAddr.IP == v4default && nmAddr.IP == v4default {
return true
}
}
if dst.Family() == syscall.AF_INET6 && netmask.Family() == syscall.AF_INET6 {
dstAddr, dstOk := dst.(*route.Inet6Addr)
nmAddr, nmOk := netmask.(*route.Inet6Addr)
if dstOk && nmOk && dstAddr.IP == v6default && nmAddr.IP == v6default {
return true
}
}
return false
}

111
vendor/tailscale.com/net/netmon/interfaces_darwin.go generated vendored Normal file
View File

@@ -0,0 +1,111 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package netmon
import (
"fmt"
"net"
"strings"
"sync"
"syscall"
"unsafe"
"golang.org/x/net/route"
"golang.org/x/sys/unix"
"tailscale.com/util/mak"
)
// fetchRoutingTable calls route.FetchRIB, fetching NET_RT_DUMP2.
func fetchRoutingTable() (rib []byte, err error) {
return route.FetchRIB(syscall.AF_UNSPEC, syscall.NET_RT_DUMP2, 0)
}
func parseRoutingTable(rib []byte) ([]route.Message, error) {
return route.ParseRIB(syscall.NET_RT_IFLIST2, rib)
}
var ifNames struct {
sync.Mutex
m map[int]string // ifindex => name
}
func init() {
interfaceDebugExtras = interfaceDebugExtrasDarwin
}
// getDelegatedInterface returns the interface index of the underlying interface
// for the given interface index. 0 is returned if the interface does not
// delegate.
func getDelegatedInterface(ifIndex int) (int, error) {
ifNames.Lock()
defer ifNames.Unlock()
// To get the delegated interface, we do what ifconfig does and use the
// SIOCGIFDELEGATE ioctl. It operates in term of a ifreq struct, which
// has to be populated with a interface name. To avoid having to do a
// interface index -> name lookup every time, we cache interface names
// (since indexes and names are stable after boot).
ifName, ok := ifNames.m[ifIndex]
if !ok {
iface, err := net.InterfaceByIndex(ifIndex)
if err != nil {
return 0, err
}
ifName = iface.Name
mak.Set(&ifNames.m, ifIndex, ifName)
}
// Only tunnels (like Tailscale itself) have a delegated interface, avoid
// the ioctl if we can.
if !strings.HasPrefix(ifName, "utun") {
return 0, nil
}
// We don't cache the result of the ioctl, since the delegated interface can
// change, e.g. if the user changes the preferred service order in the
// network preference pane.
fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
if err != nil {
return 0, err
}
defer unix.Close(fd)
// Match the ifreq struct/union from the bsd/net/if.h header in the Darwin
// open source release.
var ifr struct {
ifr_name [unix.IFNAMSIZ]byte
ifr_delegated uint32
}
copy(ifr.ifr_name[:], ifName)
// SIOCGIFDELEGATE is not in the Go x/sys package or in the public macOS
// <sys/sockio.h> headers. However, it is in the Darwin/xnu open source
// release (and is used by ifconfig, see
// https://github.com/apple-oss-distributions/network_cmds/blob/6ccdc225ad5aa0d23ea5e7d374956245d2462427/ifconfig.tproj/ifconfig.c#L2183-L2187).
// We generate its value by evaluating the `_IOWR('i', 157, struct ifreq)`
// macro, which is how it's defined in
// https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/sys/sockio.h#L264
const SIOCGIFDELEGATE = 0xc020699d
_, _, errno := syscall.Syscall(
syscall.SYS_IOCTL,
uintptr(fd),
uintptr(SIOCGIFDELEGATE),
uintptr(unsafe.Pointer(&ifr)))
if errno != 0 {
return 0, errno
}
return int(ifr.ifr_delegated), nil
}
func interfaceDebugExtrasDarwin(ifIndex int) (string, error) {
delegated, err := getDelegatedInterface(ifIndex)
if err != nil {
return "", err
}
if delegated == 0 {
return "", nil
}
return fmt.Sprintf("delegated=%d", delegated), nil
}

View File

@@ -0,0 +1,14 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !linux && !windows && !darwin && !freebsd && !android
package netmon
import "errors"
var errTODO = errors.New("TODO")
func defaultRoute() (DefaultRouteDetails, error) {
return DefaultRouteDetails{}, errTODO
}

28
vendor/tailscale.com/net/netmon/interfaces_freebsd.go generated vendored Normal file
View File

@@ -0,0 +1,28 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// This might work on other BSDs, but only tested on FreeBSD.
//go:build freebsd
package netmon
import (
"syscall"
"golang.org/x/net/route"
"golang.org/x/sys/unix"
)
// fetchRoutingTable calls route.FetchRIB, fetching NET_RT_DUMP.
func fetchRoutingTable() (rib []byte, err error) {
return route.FetchRIB(syscall.AF_UNSPEC, unix.NET_RT_DUMP, 0)
}
func parseRoutingTable(rib []byte) ([]route.Message, error) {
return route.ParseRIB(syscall.NET_RT_IFLIST, rib)
}
func getDelegatedInterface(ifIndex int) (int, error) {
return 0, nil
}

262
vendor/tailscale.com/net/netmon/interfaces_linux.go generated vendored Normal file
View File

@@ -0,0 +1,262 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !android
package netmon
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"log"
"net"
"net/netip"
"os"
"strings"
"sync/atomic"
"github.com/jsimonetti/rtnetlink"
"github.com/mdlayher/netlink"
"go4.org/mem"
"golang.org/x/sys/unix"
"tailscale.com/net/netaddr"
"tailscale.com/util/lineread"
)
func init() {
likelyHomeRouterIP = likelyHomeRouterIPLinux
}
var procNetRouteErr atomic.Bool
// errStopReading is a sentinel error value used internally by
// lineread.File callers to stop reading. It doesn't escape to
// callers/users.
var errStopReading = errors.New("stop reading")
/*
Parse 10.0.0.1 out of:
$ cat /proc/net/route
Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
ens18 00000000 0100000A 0003 0 0 0 00000000 0 0 0
ens18 0000000A 00000000 0001 0 0 0 0000FFFF 0 0 0
*/
func likelyHomeRouterIPLinux() (ret netip.Addr, myIP netip.Addr, ok bool) {
if procNetRouteErr.Load() {
// If we failed to read /proc/net/route previously, don't keep trying.
return ret, myIP, false
}
lineNum := 0
var f []mem.RO
err := lineread.File(procNetRoutePath, func(line []byte) error {
lineNum++
if lineNum == 1 {
// Skip header line.
return nil
}
if lineNum > maxProcNetRouteRead {
return errStopReading
}
f = mem.AppendFields(f[:0], mem.B(line))
if len(f) < 4 {
return nil
}
gwHex, flagsHex := f[2], f[3]
flags, err := mem.ParseUint(flagsHex, 16, 16)
if err != nil {
return nil // ignore error, skip line and keep going
}
if flags&(unix.RTF_UP|unix.RTF_GATEWAY) != unix.RTF_UP|unix.RTF_GATEWAY {
return nil
}
ipu32, err := mem.ParseUint(gwHex, 16, 32)
if err != nil {
return nil // ignore error, skip line and keep going
}
ip := netaddr.IPv4(byte(ipu32), byte(ipu32>>8), byte(ipu32>>16), byte(ipu32>>24))
if ip.IsPrivate() {
ret = ip
return errStopReading
}
return nil
})
if errors.Is(err, errStopReading) {
err = nil
}
if err != nil {
procNetRouteErr.Store(true)
log.Printf("interfaces: failed to read /proc/net/route: %v", err)
}
if ret.IsValid() {
// Try to get the local IP of the interface associated with
// this route to short-circuit finding the IP associated with
// this gateway. This isn't fatal if it fails.
if len(f) > 0 && !disableLikelyHomeRouterIPSelf() {
ForeachInterface(func(ni Interface, pfxs []netip.Prefix) {
// Ensure this is the same interface
if !f[0].EqualString(ni.Name) {
return
}
// Find the first IPv4 address and use it.
for _, pfx := range pfxs {
if addr := pfx.Addr(); addr.Is4() {
myIP = addr
break
}
}
})
}
return ret, myIP, true
}
if lineNum >= maxProcNetRouteRead {
// If we went over our line limit without finding an answer, assume
// we're a big fancy Linux router (or at least not a home system)
// and set the error bit so we stop trying this in the future (and wasting CPU).
// See https://github.com/tailscale/tailscale/issues/7621.
//
// Remember that "likelyHomeRouterIP" exists purely to find the port
// mapping service (UPnP, PMP, PCP) often present on a home router. If we hit
// the route (line) limit without finding an answer, we're unlikely to ever
// find one in the future.
procNetRouteErr.Store(true)
}
return netip.Addr{}, netip.Addr{}, false
}
func defaultRoute() (d DefaultRouteDetails, err error) {
v, err := defaultRouteInterfaceProcNet()
if err == nil {
d.InterfaceName = v
return d, nil
}
// Issue 4038: the default route (such as on Unifi UDM Pro)
// might be in a non-default table, so it won't show up in
// /proc/net/route. Use netlink to find the default route.
//
// TODO(bradfitz): this allocates a fair bit. We should track
// this in net/interfaces/monitor instead and have
// interfaces.GetState take a netmon.Monitor or similar so the
// routing table can be cached and the monitor's existing
// subscription to route changes can update the cached state,
// rather than querying the whole thing every time like
// defaultRouteFromNetlink does.
//
// Then we should just always try to use the cached route
// table from netlink every time, and only use /proc/net/route
// as a fallback for weird environments where netlink might be
// banned but /proc/net/route is emulated (e.g. stuff like
// Cloud Run?).
return defaultRouteFromNetlink()
}
func defaultRouteFromNetlink() (d DefaultRouteDetails, err error) {
c, err := rtnetlink.Dial(&netlink.Config{Strict: true})
if err != nil {
return d, fmt.Errorf("defaultRouteFromNetlink: Dial: %w", err)
}
defer c.Close()
rms, err := c.Route.List()
if err != nil {
return d, fmt.Errorf("defaultRouteFromNetlink: List: %w", err)
}
for _, rm := range rms {
if rm.Attributes.Gateway == nil {
// A default route has a gateway. If it doesn't, skip it.
continue
}
if rm.Attributes.Dst != nil {
// A default route has a nil destination to mean anything
// so ignore any route for a specific destination.
// TODO(bradfitz): better heuristic?
// empirically this seems like enough.
continue
}
// TODO(bradfitz): care about address family, if
// callers ever start caring about v4-vs-v6 default
// route differences.
idx := int(rm.Attributes.OutIface)
if idx == 0 {
continue
}
if iface, err := net.InterfaceByIndex(idx); err == nil {
d.InterfaceName = iface.Name
d.InterfaceIndex = idx
return d, nil
}
}
return d, errNoDefaultRoute
}
var zeroRouteBytes = []byte("00000000")
var procNetRoutePath = "/proc/net/route"
// maxProcNetRouteRead is the max number of lines to read from
// /proc/net/route looking for a default route.
const maxProcNetRouteRead = 1000
var errNoDefaultRoute = errors.New("no default route found")
func defaultRouteInterfaceProcNetInternal(bufsize int) (string, error) {
f, err := os.Open(procNetRoutePath)
if err != nil {
return "", err
}
defer f.Close()
br := bufio.NewReaderSize(f, bufsize)
lineNum := 0
for {
lineNum++
line, err := br.ReadSlice('\n')
if err == io.EOF || lineNum > maxProcNetRouteRead {
return "", errNoDefaultRoute
}
if err != nil {
return "", err
}
if !bytes.Contains(line, zeroRouteBytes) {
continue
}
fields := strings.Fields(string(line))
ifc := fields[0]
ip := fields[1]
netmask := fields[7]
if strings.HasPrefix(ifc, "tailscale") ||
strings.HasPrefix(ifc, "wg") {
continue
}
if ip == "00000000" && netmask == "00000000" {
// default route
return ifc, nil // interface name
}
}
}
// returns string interface name and an error.
// io.EOF: full route table processed, no default route found.
// other io error: something went wrong reading the route file.
func defaultRouteInterfaceProcNet() (string, error) {
rc, err := defaultRouteInterfaceProcNetInternal(128)
if rc == "" && (errors.Is(err, io.EOF) || err == nil) {
// https://github.com/google/gvisor/issues/5732
// On a regular Linux kernel you can read the first 128 bytes of /proc/net/route,
// then come back later to read the next 128 bytes and so on.
//
// In Google Cloud Run, where /proc/net/route comes from gVisor, you have to
// read it all at once. If you read only the first few bytes then the second
// read returns 0 bytes no matter how much originally appeared to be in the file.
//
// At the time of this writing (Mar 2021) Google Cloud Run has eth0 and eth1
// with a 384 byte /proc/net/route. We allocate a large buffer to ensure we'll
// read it all in one call.
return defaultRouteInterfaceProcNetInternal(4096)
}
return rc, err
}

278
vendor/tailscale.com/net/netmon/interfaces_windows.go generated vendored Normal file
View File

@@ -0,0 +1,278 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package netmon
import (
"log"
"net/netip"
"net/url"
"strings"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
"tailscale.com/tsconst"
)
const (
fallbackInterfaceMetric = uint32(0) // Used if we cannot get the actual interface metric
)
func init() {
likelyHomeRouterIP = likelyHomeRouterIPWindows
getPAC = getPACWindows
}
func likelyHomeRouterIPWindows() (ret netip.Addr, _ netip.Addr, ok bool) {
rs, err := winipcfg.GetIPForwardTable2(windows.AF_INET)
if err != nil {
log.Printf("routerIP/GetIPForwardTable2 error: %v", err)
return
}
var ifaceMetricCache map[winipcfg.LUID]uint32
getIfaceMetric := func(luid winipcfg.LUID) (metric uint32) {
if ifaceMetricCache == nil {
ifaceMetricCache = make(map[winipcfg.LUID]uint32)
} else if m, ok := ifaceMetricCache[luid]; ok {
return m
}
if iface, err := luid.IPInterface(windows.AF_INET); err == nil {
metric = iface.Metric
} else {
log.Printf("routerIP/luid.IPInterface error: %v", err)
metric = fallbackInterfaceMetric
}
ifaceMetricCache[luid] = metric
return
}
v4unspec := netip.IPv4Unspecified()
var best *winipcfg.MibIPforwardRow2 // best (lowest metric) found so far, or nil
for i := range rs {
r := &rs[i]
if r.Loopback || r.DestinationPrefix.PrefixLength != 0 || r.DestinationPrefix.Prefix().Addr().Unmap() != v4unspec {
// Not a default route, so skip
continue
}
ip := r.NextHop.Addr().Unmap()
if !ip.IsValid() {
// Not a valid gateway, so skip (won't happen though)
continue
}
if best == nil {
best = r
ret = ip
continue
}
// We can get here only if there are multiple default gateways defined (rare case),
// in which case we need to calculate the effective metric.
// Effective metric is sum of interface metric and route metric offset
if ifaceMetricCache == nil {
// If we're here it means that previous route still isn't updated, so update it
best.Metric += getIfaceMetric(best.InterfaceLUID)
}
r.Metric += getIfaceMetric(r.InterfaceLUID)
if best.Metric > r.Metric || best.Metric == r.Metric && ret.Compare(ip) > 0 {
// Pick the route with lower metric, or lower IP if metrics are equal
best = r
ret = ip
}
}
if ret.IsValid() && !ret.IsPrivate() {
// Default route has a non-private gateway
return netip.Addr{}, netip.Addr{}, false
}
return ret, netip.Addr{}, ret.IsValid()
}
// NonTailscaleMTUs returns a map of interface LUID to interface MTU,
// for all interfaces except Tailscale tunnels.
func NonTailscaleMTUs() (map[winipcfg.LUID]uint32, error) {
mtus := map[winipcfg.LUID]uint32{}
ifs, err := NonTailscaleInterfaces()
for luid, iface := range ifs {
mtus[luid] = iface.MTU
}
return mtus, err
}
func notTailscaleInterface(iface *winipcfg.IPAdapterAddresses) bool {
// TODO(bradfitz): do this without the Description method's
// utf16-to-string allocation. But at least we only do it for
// the virtual interfaces, for which there won't be many.
if iface.IfType != winipcfg.IfTypePropVirtual {
return true
}
desc := iface.Description()
return !(strings.Contains(desc, tsconst.WintunInterfaceDesc) ||
strings.Contains(desc, tsconst.WintunInterfaceDesc0_14))
}
// NonTailscaleInterfaces returns a map of interface LUID to interface
// for all interfaces except Tailscale tunnels.
func NonTailscaleInterfaces() (map[winipcfg.LUID]*winipcfg.IPAdapterAddresses, error) {
return getInterfaces(windows.AF_UNSPEC, winipcfg.GAAFlagIncludeAllInterfaces, notTailscaleInterface)
}
// getInterfaces returns a map of interfaces keyed by their LUID for
// all interfaces matching the provided match predicate.
//
// The family (AF_UNSPEC, AF_INET, or AF_INET6) and flags are passed
// to winipcfg.GetAdaptersAddresses.
func getInterfaces(family winipcfg.AddressFamily, flags winipcfg.GAAFlags, match func(*winipcfg.IPAdapterAddresses) bool) (map[winipcfg.LUID]*winipcfg.IPAdapterAddresses, error) {
ifs, err := winipcfg.GetAdaptersAddresses(family, flags)
if err != nil {
return nil, err
}
ret := map[winipcfg.LUID]*winipcfg.IPAdapterAddresses{}
for _, iface := range ifs {
if match(iface) {
ret[iface.LUID] = iface
}
}
return ret, nil
}
// GetWindowsDefault returns the interface that has the non-Tailscale
// default route for the given address family.
//
// It returns (nil, nil) if no interface is found.
//
// The family must be one of AF_INET or AF_INET6.
func GetWindowsDefault(family winipcfg.AddressFamily) (*winipcfg.IPAdapterAddresses, error) {
ifs, err := getInterfaces(family, winipcfg.GAAFlagIncludeAllInterfaces, func(iface *winipcfg.IPAdapterAddresses) bool {
switch iface.IfType {
case winipcfg.IfTypeSoftwareLoopback:
return false
}
switch family {
case windows.AF_INET:
if iface.Flags&winipcfg.IPAAFlagIpv4Enabled == 0 {
return false
}
case windows.AF_INET6:
if iface.Flags&winipcfg.IPAAFlagIpv6Enabled == 0 {
return false
}
}
return iface.OperStatus == winipcfg.IfOperStatusUp && notTailscaleInterface(iface)
})
if err != nil {
return nil, err
}
routes, err := winipcfg.GetIPForwardTable2(family)
if err != nil {
return nil, err
}
bestMetric := ^uint32(0)
var bestIface *winipcfg.IPAdapterAddresses
for _, route := range routes {
if route.DestinationPrefix.PrefixLength != 0 {
// Not a default route.
continue
}
iface := ifs[route.InterfaceLUID]
if iface == nil {
continue
}
// Microsoft docs say:
//
// "The actual route metric used to compute the route
// preferences for IPv4 is the summation of the route
// metric offset specified in the Metric member of the
// MIB_IPFORWARD_ROW2 structure and the interface
// metric specified in this member for IPv4"
metric := route.Metric
switch family {
case windows.AF_INET:
metric += iface.Ipv4Metric
case windows.AF_INET6:
metric += iface.Ipv6Metric
}
if metric < bestMetric {
bestMetric = metric
bestIface = iface
}
}
return bestIface, nil
}
func defaultRoute() (d DefaultRouteDetails, err error) {
// We always return the IPv4 default route.
// TODO(bradfitz): adjust API if/when anything cares. They could in theory differ, though,
// in which case we might send traffic to the wrong interface.
iface, err := GetWindowsDefault(windows.AF_INET)
if err != nil {
return d, err
}
if iface != nil {
d.InterfaceName = iface.FriendlyName()
d.InterfaceDesc = iface.Description()
d.InterfaceIndex = int(iface.IfIndex)
}
return d, nil
}
var (
winHTTP = windows.NewLazySystemDLL("winhttp.dll")
detectAutoProxyConfigURL = winHTTP.NewProc("WinHttpDetectAutoProxyConfigUrl")
kernel32 = windows.NewLazySystemDLL("kernel32.dll")
globalFree = kernel32.NewProc("GlobalFree")
)
const (
winHTTP_AUTO_DETECT_TYPE_DHCP = 0x00000001
winHTTP_AUTO_DETECT_TYPE_DNS_A = 0x00000002
)
func getPACWindows() string {
var res *uint16
r, _, e := detectAutoProxyConfigURL.Call(
winHTTP_AUTO_DETECT_TYPE_DHCP|winHTTP_AUTO_DETECT_TYPE_DNS_A,
uintptr(unsafe.Pointer(&res)),
)
if r == 1 {
if res == nil {
log.Printf("getPACWindows: unexpected success with nil result")
return ""
}
defer globalFree.Call(uintptr(unsafe.Pointer(res)))
s := windows.UTF16PtrToString(res)
s = strings.TrimSpace(s)
if s == "" {
return "" // Issue 2357: invalid URL "\n" from winhttp; ignoring
}
if _, err := url.Parse(s); err != nil {
log.Printf("getPACWindows: invalid URL %q from winhttp; ignoring", s)
return ""
}
return s
}
const (
ERROR_WINHTTP_AUTODETECTION_FAILED = 12180
)
if e == syscall.Errno(ERROR_WINHTTP_AUTODETECTION_FAILED) {
// Common case on networks without advertised PAC.
return ""
}
log.Printf("getPACWindows: %T=%v", e, e) // syscall.Errno=0x....
return ""
}

626
vendor/tailscale.com/net/netmon/netmon.go generated vendored Normal file
View File

@@ -0,0 +1,626 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package monitor provides facilities for monitoring network
// interface and route changes. It primarily exists to know when
// portable devices move between different networks.
package netmon
import (
"encoding/json"
"errors"
"net/netip"
"runtime"
"sync"
"time"
"tailscale.com/types/logger"
"tailscale.com/util/clientmetric"
"tailscale.com/util/set"
)
// pollWallTimeInterval is how often we check the time to check
// for big jumps in wall (non-monotonic) time as a backup mechanism
// to get notified of a sleeping device waking back up.
// Usually there are also minor network change events on wake that let
// us check the wall time sooner than this.
const pollWallTimeInterval = 15 * time.Second
// message represents a message returned from an osMon.
type message interface {
// Ignore is whether we should ignore this message.
ignore() bool
}
// osMon is the interface that each operating system-specific
// implementation of the link monitor must implement.
type osMon interface {
Close() error
// Receive returns a new network interface change message. It
// should block until there's either something to return, or
// until the osMon is closed. After a Close, the returned
// error is ignored.
Receive() (message, error)
// IsInterestingInterface reports whether the provided interface should
// be considered for network change events.
IsInterestingInterface(iface string) bool
}
// Monitor represents a monitoring instance.
type Monitor struct {
logf logger.Logf
om osMon // nil means not supported on this platform
change chan bool // send false to wake poller, true to also force ChangeDeltas be sent
stop chan struct{} // closed on Stop
static bool // static Monitor that doesn't actually monitor
// Things that must be set early, before use,
// and not change at runtime.
tsIfName string // tailscale interface name, if known/set ("tailscale0", "utun3", ...)
mu sync.Mutex // guards all following fields
cbs set.HandleSet[ChangeFunc]
ruleDelCB set.HandleSet[RuleDeleteCallback]
ifState *State
gwValid bool // whether gw and gwSelfIP are valid
gw netip.Addr // our gateway's IP
gwSelfIP netip.Addr // our own IP address (that corresponds to gw)
started bool
closed bool
goroutines sync.WaitGroup
wallTimer *time.Timer // nil until Started; re-armed AfterFunc per tick
lastWall time.Time
timeJumped bool // whether we need to send a changed=true after a big time jump
}
// ChangeFunc is a callback function registered with Monitor that's called when the
// network changed.
type ChangeFunc func(*ChangeDelta)
// ChangeDelta describes the difference between two network states.
type ChangeDelta struct {
// Monitor is the network monitor that sent this delta.
Monitor *Monitor
// Old is the old interface state, if known.
// It's nil if the old state is unknown.
// Do not mutate it.
Old *State
// New is the new network state.
// It is always non-nil.
// Do not mutate it.
New *State
// Major is our legacy boolean of whether the network changed in some major
// way.
//
// Deprecated: do not remove. As of 2023-08-23 we're in a renewed effort to
// remove it and ask specific qustions of ChangeDelta instead. Look at Old
// and New (or add methods to ChangeDelta) instead of using Major.
Major bool
// TimeJumped is whether there was a big jump in wall time since the last
// time we checked. This is a hint that a mobile sleeping device might have
// come out of sleep.
TimeJumped bool
// TODO(bradfitz): add some lazy cached fields here as needed with methods
// on *ChangeDelta to let callers ask specific questions
}
// New instantiates and starts a monitoring instance.
// The returned monitor is inactive until it's started by the Start method.
// Use RegisterChangeCallback to get notified of network changes.
func New(logf logger.Logf) (*Monitor, error) {
logf = logger.WithPrefix(logf, "monitor: ")
m := &Monitor{
logf: logf,
change: make(chan bool, 1),
stop: make(chan struct{}),
lastWall: wallTime(),
}
st, err := m.interfaceStateUncached()
if err != nil {
return nil, err
}
m.ifState = st
m.om, err = newOSMon(logf, m)
if err != nil {
return nil, err
}
if m.om == nil {
return nil, errors.New("newOSMon returned nil, nil")
}
return m, nil
}
// NewStatic returns a Monitor that's a one-time snapshot of the network state
// but doesn't actually monitor for changes. It should only be used in tests
// and situations like cleanups or short-lived CLI programs.
func NewStatic() *Monitor {
m := &Monitor{static: true}
if st, err := m.interfaceStateUncached(); err == nil {
m.ifState = st
}
return m
}
// InterfaceState returns the latest snapshot of the machine's network
// interfaces.
//
// The returned value is owned by Mon; it must not be modified.
func (m *Monitor) InterfaceState() *State {
m.mu.Lock()
defer m.mu.Unlock()
return m.ifState
}
func (m *Monitor) interfaceStateUncached() (*State, error) {
return GetState()
}
// SetTailscaleInterfaceName sets the name of the Tailscale interface. For
// example, "tailscale0", "tun0", "utun3", etc.
//
// This must be called only early in tailscaled startup before the monitor is
// used.
func (m *Monitor) SetTailscaleInterfaceName(ifName string) {
m.tsIfName = ifName
}
// GatewayAndSelfIP returns the current network's default gateway, and
// the machine's default IP for that gateway.
//
// It's the same as interfaces.LikelyHomeRouterIP, but it caches the
// result until the monitor detects a network change.
func (m *Monitor) GatewayAndSelfIP() (gw, myIP netip.Addr, ok bool) {
if m.static {
return
}
m.mu.Lock()
defer m.mu.Unlock()
if m.gwValid {
return m.gw, m.gwSelfIP, true
}
gw, myIP, ok = LikelyHomeRouterIP()
changed := false
if ok {
changed = m.gw != gw || m.gwSelfIP != myIP
m.gw, m.gwSelfIP = gw, myIP
m.gwValid = true
}
if changed {
m.logf("gateway and self IP changed: gw=%v self=%v", m.gw, m.gwSelfIP)
}
return gw, myIP, ok
}
// RegisterChangeCallback adds callback to the set of parties to be
// notified (in their own goroutine) when the network state changes.
// To remove this callback, call unregister (or close the monitor).
func (m *Monitor) RegisterChangeCallback(callback ChangeFunc) (unregister func()) {
if m.static {
return func() {}
}
m.mu.Lock()
defer m.mu.Unlock()
handle := m.cbs.Add(callback)
return func() {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.cbs, handle)
}
}
// RuleDeleteCallback is a callback when a Linux IP policy routing
// rule is deleted. The table is the table number (52, 253, 354) and
// priority is the priority order number (for Tailscale rules
// currently: 5210, 5230, 5250, 5270)
type RuleDeleteCallback func(table uint8, priority uint32)
// RegisterRuleDeleteCallback adds callback to the set of parties to be
// notified (in their own goroutine) when a Linux ip rule is deleted.
// To remove this callback, call unregister (or close the monitor).
func (m *Monitor) RegisterRuleDeleteCallback(callback RuleDeleteCallback) (unregister func()) {
if m.static {
return func() {}
}
m.mu.Lock()
defer m.mu.Unlock()
handle := m.ruleDelCB.Add(callback)
return func() {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.ruleDelCB, handle)
}
}
// Start starts the monitor.
// A monitor can only be started & closed once.
func (m *Monitor) Start() {
if m.static {
return
}
m.mu.Lock()
defer m.mu.Unlock()
if m.started || m.closed {
return
}
m.started = true
if shouldMonitorTimeJump {
m.wallTimer = time.AfterFunc(pollWallTimeInterval, m.pollWallTime)
}
if m.om == nil {
return
}
m.goroutines.Add(2)
go m.pump()
go m.debounce()
}
// Close closes the monitor.
func (m *Monitor) Close() error {
if m.static {
return nil
}
m.mu.Lock()
if m.closed {
m.mu.Unlock()
return nil
}
m.closed = true
close(m.stop)
if m.wallTimer != nil {
m.wallTimer.Stop()
}
var err error
if m.om != nil {
err = m.om.Close()
}
started := m.started
m.mu.Unlock()
if started {
m.goroutines.Wait()
}
return err
}
// InjectEvent forces the monitor to pretend there was a network
// change and re-check the state of the network. Any registered
// ChangeFunc callbacks will be called within the event coalescing
// period (under a fraction of a second).
func (m *Monitor) InjectEvent() {
if m.static {
return
}
select {
case m.change <- true:
default:
// Another change signal is already
// buffered. Debounce will wake up soon
// enough.
}
}
// Poll forces the monitor to pretend there was a network
// change and re-check the state of the network.
//
// This is like InjectEvent but only fires ChangeFunc callbacks
// if the network state differed at all.
func (m *Monitor) Poll() {
if m.static {
return
}
select {
case m.change <- false:
default:
}
}
func (m *Monitor) stopped() bool {
select {
case <-m.stop:
return true
default:
return false
}
}
// pump continuously retrieves messages from the connection, notifying
// the change channel of changes, and stopping when a stop is issued.
func (m *Monitor) pump() {
defer m.goroutines.Done()
for !m.stopped() {
msg, err := m.om.Receive()
if err != nil {
if m.stopped() {
return
}
// Keep retrying while we're not closed.
m.logf("error from link monitor: %v", err)
time.Sleep(time.Second)
continue
}
if rdm, ok := msg.(ipRuleDeletedMessage); ok {
m.notifyRuleDeleted(rdm)
continue
}
if msg.ignore() {
continue
}
m.Poll()
}
}
func (m *Monitor) notifyRuleDeleted(rdm ipRuleDeletedMessage) {
m.mu.Lock()
defer m.mu.Unlock()
for _, cb := range m.ruleDelCB {
go cb(rdm.table, rdm.priority)
}
}
// isInterestingInterface reports whether the provided interface should be
// considered when checking for network state changes.
// The ips parameter should be the IPs of the provided interface.
func (m *Monitor) isInterestingInterface(i Interface, ips []netip.Prefix) bool {
if !m.om.IsInterestingInterface(i.Name) {
return false
}
return true
}
// debounce calls the callback function with a delay between events
// and exits when a stop is issued.
func (m *Monitor) debounce() {
defer m.goroutines.Done()
for {
var forceCallbacks bool
select {
case <-m.stop:
return
case forceCallbacks = <-m.change:
}
if newState, err := m.interfaceStateUncached(); err != nil {
m.logf("interfaces.State: %v", err)
} else {
m.handlePotentialChange(newState, forceCallbacks)
}
select {
case <-m.stop:
return
case <-time.After(250 * time.Millisecond):
}
}
}
var (
metricChangeEq = clientmetric.NewCounter("netmon_link_change_eq")
metricChange = clientmetric.NewCounter("netmon_link_change")
metricChangeTimeJump = clientmetric.NewCounter("netmon_link_change_timejump")
metricChangeMajor = clientmetric.NewCounter("netmon_link_change_major")
)
// handlePotentialChange considers whether newState is different enough to wake
// up callers and updates the monitor's state if so.
//
// If forceCallbacks is true, they're always notified.
func (m *Monitor) handlePotentialChange(newState *State, forceCallbacks bool) {
m.mu.Lock()
defer m.mu.Unlock()
oldState := m.ifState
timeJumped := shouldMonitorTimeJump && m.checkWallTimeAdvanceLocked()
if !timeJumped && !forceCallbacks && oldState.Equal(newState) {
// Exactly equal. Nothing to do.
metricChangeEq.Add(1)
return
}
delta := &ChangeDelta{
Monitor: m,
Old: oldState,
New: newState,
TimeJumped: timeJumped,
}
delta.Major = m.IsMajorChangeFrom(oldState, newState)
if delta.Major {
m.gwValid = false
m.ifState = newState
if s1, s2 := oldState.String(), delta.New.String(); s1 == s2 {
m.logf("[unexpected] network state changed, but stringification didn't: %v", s1)
m.logf("[unexpected] old: %s", jsonSummary(oldState))
m.logf("[unexpected] new: %s", jsonSummary(newState))
}
}
// See if we have a queued or new time jump signal.
if timeJumped {
m.resetTimeJumpedLocked()
if !delta.Major {
// Only log if it wasn't an interesting change.
m.logf("time jumped (probably wake from sleep); synthesizing major change event")
delta.Major = true
}
}
metricChange.Add(1)
if delta.Major {
metricChangeMajor.Add(1)
}
if delta.TimeJumped {
metricChangeTimeJump.Add(1)
}
for _, cb := range m.cbs {
go cb(delta)
}
}
// IsMajorChangeFrom reports whether the transition from s1 to s2 is
// a "major" change, where major roughly means it's worth tearing down
// a bunch of connections and rebinding.
//
// TODO(bradiftz): tigten this definition.
func (m *Monitor) IsMajorChangeFrom(s1, s2 *State) bool {
if s1 == nil && s2 == nil {
return false
}
if s1 == nil || s2 == nil {
return true
}
if s1.HaveV6 != s2.HaveV6 ||
s1.HaveV4 != s2.HaveV4 ||
s1.IsExpensive != s2.IsExpensive ||
s1.DefaultRouteInterface != s2.DefaultRouteInterface ||
s1.HTTPProxy != s2.HTTPProxy ||
s1.PAC != s2.PAC {
return true
}
for iname, i := range s1.Interface {
if iname == m.tsIfName {
// Ignore changes in the Tailscale interface itself.
continue
}
ips := s1.InterfaceIPs[iname]
if !m.isInterestingInterface(i, ips) {
continue
}
i2, ok := s2.Interface[iname]
if !ok {
return true
}
ips2, ok := s2.InterfaceIPs[iname]
if !ok {
return true
}
if !i.Equal(i2) || !prefixesMajorEqual(ips, ips2) {
return true
}
}
// Iterate over s2 in case there is a field in s2 that doesn't exist in s1
for iname, i := range s2.Interface {
if iname == m.tsIfName {
// Ignore changes in the Tailscale interface itself.
continue
}
ips := s2.InterfaceIPs[iname]
if !m.isInterestingInterface(i, ips) {
continue
}
i1, ok := s1.Interface[iname]
if !ok {
return true
}
ips1, ok := s1.InterfaceIPs[iname]
if !ok {
return true
}
if !i.Equal(i1) || !prefixesMajorEqual(ips, ips1) {
return true
}
}
return false
}
// prefixesMajorEqual reports whether a and b are equal after ignoring
// boring things like link-local, loopback, and multicast addresses.
func prefixesMajorEqual(a, b []netip.Prefix) bool {
// trim returns a subslice of p with link local unicast,
// loopback, and multicast prefixes removed from the front.
trim := func(p []netip.Prefix) []netip.Prefix {
for len(p) > 0 {
a := p[0].Addr()
if a.IsLinkLocalUnicast() || a.IsLoopback() || a.IsMulticast() {
p = p[1:]
continue
}
break
}
return p
}
for {
a = trim(a)
b = trim(b)
if len(a) == 0 || len(b) == 0 {
return len(a) == 0 && len(b) == 0
}
if a[0] != b[0] {
return false
}
a, b = a[1:], b[1:]
}
}
func jsonSummary(x any) any {
j, err := json.Marshal(x)
if err != nil {
return err
}
return j
}
func wallTime() time.Time {
// From time package's docs: "The canonical way to strip a
// monotonic clock reading is to use t = t.Round(0)."
return time.Now().Round(0)
}
func (m *Monitor) pollWallTime() {
m.mu.Lock()
defer m.mu.Unlock()
if m.closed {
return
}
if m.checkWallTimeAdvanceLocked() {
m.InjectEvent()
}
m.wallTimer.Reset(pollWallTimeInterval)
}
// shouldMonitorTimeJump is whether we keep a regular periodic timer running in
// the background watching for jumps in wall time.
//
// We don't do this on mobile platforms for battery reasons, and because these
// platforms don't really sleep in the same way.
const shouldMonitorTimeJump = runtime.GOOS != "android" && runtime.GOOS != "ios"
// checkWallTimeAdvanceLocked reports whether wall time jumped more than 150% of
// pollWallTimeInterval, indicating we probably just came out of sleep. Once a
// time jump is detected it must be reset by calling resetTimeJumpedLocked.
func (m *Monitor) checkWallTimeAdvanceLocked() bool {
if !shouldMonitorTimeJump {
panic("unreachable") // if callers are correct
}
now := wallTime()
if now.Sub(m.lastWall) > pollWallTimeInterval*3/2 {
m.timeJumped = true // it is reset by debounce.
}
m.lastWall = now
return m.timeJumped
}
// resetTimeJumpedLocked consumes the signal set by checkWallTimeAdvanceLocked.
func (m *Monitor) resetTimeJumpedLocked() {
m.timeJumped = false
}
type ipRuleDeletedMessage struct {
table uint8
priority uint32
}
func (ipRuleDeletedMessage) ignore() bool { return true }

225
vendor/tailscale.com/net/netmon/netmon_darwin.go generated vendored Normal file
View File

@@ -0,0 +1,225 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package netmon
import (
"fmt"
"net/netip"
"strings"
"sync"
"golang.org/x/net/route"
"golang.org/x/sys/unix"
"tailscale.com/net/netaddr"
"tailscale.com/types/logger"
)
const debugRouteMessages = false
// unspecifiedMessage is a minimal message implementation that should not
// be ignored. In general, OS-specific implementations should use better
// types and avoid this if they can.
type unspecifiedMessage struct{}
func (unspecifiedMessage) ignore() bool { return false }
func newOSMon(logf logger.Logf, _ *Monitor) (osMon, error) {
fd, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0)
if err != nil {
return nil, err
}
return &darwinRouteMon{
logf: logf,
fd: fd,
}, nil
}
type darwinRouteMon struct {
logf logger.Logf
fd int // AF_ROUTE socket
buf [2 << 10]byte
closeOnce sync.Once
}
func (m *darwinRouteMon) Close() error {
var err error
m.closeOnce.Do(func() {
err = unix.Close(m.fd)
})
return err
}
func (m *darwinRouteMon) Receive() (message, error) {
for {
n, err := unix.Read(m.fd, m.buf[:])
if err != nil {
return nil, err
}
msgs, err := route.ParseRIB(route.RIBTypeRoute, m.buf[:n])
if err != nil {
if debugRouteMessages {
m.logf("read %d bytes (% 02x), failed to parse RIB: %v", n, m.buf[:n], err)
}
return unspecifiedMessage{}, nil
}
if len(msgs) == 0 {
if debugRouteMessages {
m.logf("read %d bytes with no messages (% 02x)", n, m.buf[:n])
}
continue
}
nSkip := 0
for _, msg := range msgs {
if m.skipMessage(msg) {
nSkip++
}
}
if debugRouteMessages {
m.logf("read %d bytes, %d messages (%d skipped)", n, len(msgs), nSkip)
if nSkip < len(msgs) {
m.logMessages(msgs)
}
}
if nSkip == len(msgs) {
continue
}
return unspecifiedMessage{}, nil
}
}
func (m *darwinRouteMon) skipMessage(msg route.Message) bool {
switch msg := msg.(type) {
case *route.InterfaceMulticastAddrMessage:
return true
case *route.InterfaceAddrMessage:
return m.skipInterfaceAddrMessage(msg)
case *route.RouteMessage:
return m.skipRouteMessage(msg)
}
return false
}
// addrType returns addrs[rtaxType], if that (the route address type) exists,
// else it returns nil.
//
// The RTAX_* constants at https://github.com/apple/darwin-xnu/blob/main/bsd/net/route.h
// for what each address index represents.
func addrType(addrs []route.Addr, rtaxType int) route.Addr {
if len(addrs) > rtaxType {
return addrs[rtaxType]
}
return nil
}
func (m *darwinRouteMon) IsInterestingInterface(iface string) bool {
baseName := strings.TrimRight(iface, "0123456789")
switch baseName {
// TODO(maisem): figure out what this list should actually be.
case "llw", "awdl", "ipsec":
return false
}
return true
}
func (m *darwinRouteMon) skipInterfaceAddrMessage(msg *route.InterfaceAddrMessage) bool {
if la, ok := addrType(msg.Addrs, unix.RTAX_IFP).(*route.LinkAddr); ok {
if !m.IsInterestingInterface(la.Name) {
return true
}
}
return false
}
func (m *darwinRouteMon) skipRouteMessage(msg *route.RouteMessage) bool {
if ip := ipOfAddr(addrType(msg.Addrs, unix.RTAX_DST)); ip.IsLinkLocalUnicast() {
// Skip those like:
// dst = fe80::b476:66ff:fe30:c8f6%15
return true
}
return false
}
func (m *darwinRouteMon) logMessages(msgs []route.Message) {
for i, msg := range msgs {
switch msg := msg.(type) {
default:
m.logf(" [%d] %T", i, msg)
case *route.InterfaceAddrMessage:
m.logf(" [%d] InterfaceAddrMessage: ver=%d, type=%v, flags=0x%x, idx=%v",
i, msg.Version, msg.Type, msg.Flags, msg.Index)
m.logAddrs(msg.Addrs)
case *route.InterfaceMulticastAddrMessage:
m.logf(" [%d] InterfaceMulticastAddrMessage: ver=%d, type=%v, flags=0x%x, idx=%v",
i, msg.Version, msg.Type, msg.Flags, msg.Index)
m.logAddrs(msg.Addrs)
case *route.RouteMessage:
m.logf(" [%d] RouteMessage: ver=%d, type=%v, flags=0x%x, idx=%v, id=%v, seq=%v, err=%v",
i, msg.Version, msg.Type, msg.Flags, msg.Index, msg.ID, msg.Seq, msg.Err)
m.logAddrs(msg.Addrs)
}
}
}
func (m *darwinRouteMon) logAddrs(addrs []route.Addr) {
for i, a := range addrs {
if a == nil {
continue
}
m.logf(" %v = %v", rtaxName(i), fmtAddr(a))
}
}
// ipOfAddr returns the route.Addr (possibly nil) as a netip.Addr
// (possibly zero).
func ipOfAddr(a route.Addr) netip.Addr {
switch a := a.(type) {
case *route.Inet4Addr:
return netaddr.IPv4(a.IP[0], a.IP[1], a.IP[2], a.IP[3])
case *route.Inet6Addr:
ip := netip.AddrFrom16(a.IP)
if a.ZoneID != 0 {
ip = ip.WithZone(fmt.Sprint(a.ZoneID)) // TODO: look up net.InterfaceByIndex? but it might be changing?
}
return ip
}
return netip.Addr{}
}
func fmtAddr(a route.Addr) any {
if a == nil {
return nil
}
if ip := ipOfAddr(a); ip.IsValid() {
return ip
}
switch a := a.(type) {
case *route.LinkAddr:
return fmt.Sprintf("[LinkAddr idx=%v name=%q addr=%x]", a.Index, a.Name, a.Addr)
default:
return fmt.Sprintf("%T: %+v", a, a)
}
}
// See https://github.com/apple/darwin-xnu/blob/main/bsd/net/route.h
func rtaxName(i int) string {
switch i {
case unix.RTAX_DST:
return "dst"
case unix.RTAX_GATEWAY:
return "gateway"
case unix.RTAX_NETMASK:
return "netmask"
case unix.RTAX_GENMASK:
return "genmask"
case unix.RTAX_IFP: // "interface name sockaddr present"
return "IFP"
case unix.RTAX_IFA: // "interface addr sockaddr present"
return "IFA"
case unix.RTAX_AUTHOR:
return "author"
case unix.RTAX_BRD:
return "BRD"
}
return fmt.Sprint(i)
}

56
vendor/tailscale.com/net/netmon/netmon_freebsd.go generated vendored Normal file
View File

@@ -0,0 +1,56 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package netmon
import (
"bufio"
"fmt"
"net"
"strings"
"tailscale.com/types/logger"
)
// unspecifiedMessage is a minimal message implementation that should not
// be ignored. In general, OS-specific implementations should use better
// types and avoid this if they can.
type unspecifiedMessage struct{}
func (unspecifiedMessage) ignore() bool { return false }
// devdConn implements osMon using devd(8).
type devdConn struct {
conn net.Conn
}
func newOSMon(logf logger.Logf, m *Monitor) (osMon, error) {
conn, err := net.Dial("unixpacket", "/var/run/devd.seqpacket.pipe")
if err != nil {
logf("devd dial error: %v, falling back to polling method", err)
return newPollingMon(logf, m)
}
return &devdConn{conn}, nil
}
func (c *devdConn) IsInterestingInterface(iface string) bool { return true }
func (c *devdConn) Close() error {
return c.conn.Close()
}
func (c *devdConn) Receive() (message, error) {
for {
msg, err := bufio.NewReader(c.conn).ReadString('\n')
if err != nil {
return nil, fmt.Errorf("reading devd socket: %v", err)
}
// Only return messages related to the network subsystem.
if !strings.Contains(msg, "system=IFNET") {
continue
}
// TODO: this is where the devd-specific message would
// get converted into a "standard" event message and returned.
return unspecifiedMessage{}, nil
}
}

290
vendor/tailscale.com/net/netmon/netmon_linux.go generated vendored Normal file
View File

@@ -0,0 +1,290 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !android
package netmon
import (
"net"
"net/netip"
"time"
"github.com/jsimonetti/rtnetlink"
"github.com/mdlayher/netlink"
"golang.org/x/sys/unix"
"tailscale.com/envknob"
"tailscale.com/net/tsaddr"
"tailscale.com/types/logger"
)
var debugNetlinkMessages = envknob.RegisterBool("TS_DEBUG_NETLINK")
// unspecifiedMessage is a minimal message implementation that should not
// be ignored. In general, OS-specific implementations should use better
// types and avoid this if they can.
type unspecifiedMessage struct{}
func (unspecifiedMessage) ignore() bool { return false }
// nlConn wraps a *netlink.Conn and returns a monitor.Message
// instead of a netlink.Message. Currently, messages are discarded,
// but down the line, when messages trigger different logic depending
// on the type of event, this provides the capability of handling
// each architecture-specific message in a generic fashion.
type nlConn struct {
logf logger.Logf
conn *netlink.Conn
buffered []netlink.Message
// addrCache maps interface indices to a set of addresses, and is
// used to suppress duplicate RTM_NEWADDR messages. It is populated
// by RTM_NEWADDR messages and de-populated by RTM_DELADDR. See
// issue #4282.
addrCache map[uint32]map[netip.Addr]bool
}
func newOSMon(logf logger.Logf, m *Monitor) (osMon, error) {
conn, err := netlink.Dial(unix.NETLINK_ROUTE, &netlink.Config{
// Routes get us most of the events of interest, but we need
// address as well to cover things like DHCP deciding to give
// us a new address upon renewal - routing wouldn't change,
// but all reachability would.
Groups: unix.RTMGRP_IPV4_IFADDR | unix.RTMGRP_IPV6_IFADDR |
unix.RTMGRP_IPV4_ROUTE | unix.RTMGRP_IPV6_ROUTE |
unix.RTMGRP_IPV4_RULE, // no IPV6_RULE in x/sys/unix
})
if err != nil {
// Google Cloud Run does not implement NETLINK_ROUTE RTMGRP support
logf("monitor_linux: AF_NETLINK RTMGRP failed, falling back to polling")
return newPollingMon(logf, m)
}
return &nlConn{logf: logf, conn: conn, addrCache: make(map[uint32]map[netip.Addr]bool)}, nil
}
func (c *nlConn) IsInterestingInterface(iface string) bool { return true }
func (c *nlConn) Close() error { return c.conn.Close() }
func (c *nlConn) Receive() (message, error) {
if len(c.buffered) == 0 {
var err error
c.buffered, err = c.conn.Receive()
if err != nil {
return nil, err
}
if len(c.buffered) == 0 {
// Unexpected. Not seen in wild, but sleep defensively.
time.Sleep(time.Second)
return ignoreMessage{}, nil
}
}
msg := c.buffered[0]
c.buffered = c.buffered[1:]
// See https://github.com/torvalds/linux/blob/master/include/uapi/linux/rtnetlink.h
// And https://man7.org/linux/man-pages/man7/rtnetlink.7.html
switch msg.Header.Type {
case unix.RTM_NEWADDR, unix.RTM_DELADDR:
var rmsg rtnetlink.AddressMessage
if err := rmsg.UnmarshalBinary(msg.Data); err != nil {
c.logf("failed to parse type %v: %v", msg.Header.Type, err)
return unspecifiedMessage{}, nil
}
nip := netaddrIP(rmsg.Attributes.Address)
if debugNetlinkMessages() {
typ := "RTM_NEWADDR"
if msg.Header.Type == unix.RTM_DELADDR {
typ = "RTM_DELADDR"
}
// label attributes are seemingly only populated for IPv4 addresses in the wild.
label := rmsg.Attributes.Label
if label == "" {
itf, err := net.InterfaceByIndex(int(rmsg.Index))
if err == nil {
label = itf.Name
}
}
c.logf("%s: %s(%d) %s / %s", typ, label, rmsg.Index, rmsg.Attributes.Address, rmsg.Attributes.Local)
}
addrs := c.addrCache[rmsg.Index]
// Ignore duplicate RTM_NEWADDR messages using c.addrCache to
// detect them. See nlConn.addrcache and issue #4282.
if msg.Header.Type == unix.RTM_NEWADDR {
if addrs == nil {
addrs = make(map[netip.Addr]bool)
c.addrCache[rmsg.Index] = addrs
}
if addrs[nip] {
if debugNetlinkMessages() {
c.logf("ignored duplicate RTM_NEWADDR for %s", nip)
}
return ignoreMessage{}, nil
}
addrs[nip] = true
} else { // msg.Header.Type == unix.RTM_DELADDR
if addrs != nil {
delete(addrs, nip)
}
if len(addrs) == 0 {
delete(c.addrCache, rmsg.Index)
}
}
nam := &newAddrMessage{
IfIndex: rmsg.Index,
Addr: nip,
Delete: msg.Header.Type == unix.RTM_DELADDR,
}
if debugNetlinkMessages() {
c.logf("%+v", nam)
}
return nam, nil
case unix.RTM_NEWROUTE, unix.RTM_DELROUTE:
typeStr := "RTM_NEWROUTE"
if msg.Header.Type == unix.RTM_DELROUTE {
typeStr = "RTM_DELROUTE"
}
var rmsg rtnetlink.RouteMessage
if err := rmsg.UnmarshalBinary(msg.Data); err != nil {
c.logf("%s: failed to parse: %v", typeStr, err)
return unspecifiedMessage{}, nil
}
src := netaddrIPPrefix(rmsg.Attributes.Src, rmsg.SrcLength)
dst := netaddrIPPrefix(rmsg.Attributes.Dst, rmsg.DstLength)
gw := netaddrIP(rmsg.Attributes.Gateway)
if msg.Header.Type == unix.RTM_NEWROUTE &&
(rmsg.Attributes.Table == 255 || rmsg.Attributes.Table == 254) &&
(dst.Addr().IsMulticast() || dst.Addr().IsLinkLocalUnicast()) {
if debugNetlinkMessages() {
c.logf("%s ignored", typeStr)
}
// Normal Linux route changes on new interface coming up; don't log or react.
return ignoreMessage{}, nil
}
if rmsg.Table == tsTable && dst.IsSingleIP() {
// Don't log. Spammy and normal to see a bunch of these on start-up,
// which we make ourselves.
} else if tsaddr.IsTailscaleIP(dst.Addr()) {
// Verbose only.
c.logf("%s: [v1] src=%v, dst=%v, gw=%v, outif=%v, table=%v", typeStr,
condNetAddrPrefix(src), condNetAddrPrefix(dst), condNetAddrIP(gw),
rmsg.Attributes.OutIface, rmsg.Attributes.Table)
} else {
c.logf("%s: src=%v, dst=%v, gw=%v, outif=%v, table=%v", typeStr,
condNetAddrPrefix(src), condNetAddrPrefix(dst), condNetAddrIP(gw),
rmsg.Attributes.OutIface, rmsg.Attributes.Table)
}
if msg.Header.Type == unix.RTM_DELROUTE {
// Just logging it for now.
// (Debugging https://github.com/tailscale/tailscale/issues/643)
return unspecifiedMessage{}, nil
}
nrm := &newRouteMessage{
Table: rmsg.Table,
Src: src,
Dst: dst,
Gateway: gw,
}
if debugNetlinkMessages() {
c.logf("%+v", nrm)
}
return nrm, nil
case unix.RTM_NEWRULE:
// Probably ourselves adding it.
return ignoreMessage{}, nil
case unix.RTM_DELRULE:
// For https://github.com/tailscale/tailscale/issues/1591 where
// systemd-networkd deletes our rules.
var rmsg rtnetlink.RouteMessage
err := rmsg.UnmarshalBinary(msg.Data)
if err != nil {
c.logf("ip rule deleted; failed to parse netlink message: %v", err)
} else {
c.logf("ip rule deleted: %+v", rmsg)
// On `ip -4 rule del pref 5210 table main`, logs:
// monitor: ip rule deleted: {Family:2 DstLength:0 SrcLength:0 Tos:0 Table:254 Protocol:0 Scope:0 Type:1 Flags:0 Attributes:{Dst:<nil> Src:<nil> Gateway:<nil> OutIface:0 Priority:5210 Table:254 Mark:4294967295 Expires:<nil> Metrics:<nil> Multipath:[]}}
}
rdm := ipRuleDeletedMessage{
table: rmsg.Table,
priority: rmsg.Attributes.Priority,
}
if debugNetlinkMessages() {
c.logf("%+v", rdm)
}
return rdm, nil
case unix.RTM_NEWLINK, unix.RTM_DELLINK:
// This is an unhandled message, but don't print an error.
// See https://github.com/tailscale/tailscale/issues/6806
return unspecifiedMessage{}, nil
default:
c.logf("unhandled netlink msg type %+v, %q", msg.Header, msg.Data)
return unspecifiedMessage{}, nil
}
}
func netaddrIP(std net.IP) netip.Addr {
ip, _ := netip.AddrFromSlice(std)
return ip.Unmap()
}
func netaddrIPPrefix(std net.IP, bits uint8) netip.Prefix {
ip, _ := netip.AddrFromSlice(std)
return netip.PrefixFrom(ip.Unmap(), int(bits))
}
func condNetAddrPrefix(ipp netip.Prefix) string {
if !ipp.Addr().IsValid() {
return ""
}
return ipp.String()
}
func condNetAddrIP(ip netip.Addr) string {
if !ip.IsValid() {
return ""
}
return ip.String()
}
// newRouteMessage is a message for a new route being added.
type newRouteMessage struct {
Src, Dst netip.Prefix
Gateway netip.Addr
Table uint8
}
const tsTable = 52
func (m *newRouteMessage) ignore() bool {
return m.Table == tsTable || tsaddr.IsTailscaleIP(m.Dst.Addr())
}
// newAddrMessage is a message for a new address being added.
type newAddrMessage struct {
Delete bool
Addr netip.Addr
IfIndex uint32 // interface index
}
func (m *newAddrMessage) ignore() bool {
return tsaddr.IsTailscaleIP(m.Addr)
}
type ignoreMessage struct{}
func (ignoreMessage) ignore() bool { return true }

21
vendor/tailscale.com/net/netmon/netmon_polling.go generated vendored Normal file
View File

@@ -0,0 +1,21 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build (!linux && !freebsd && !windows && !darwin) || android
package netmon
import (
"tailscale.com/types/logger"
)
func newOSMon(logf logger.Logf, m *Monitor) (osMon, error) {
return newPollingMon(logf, m)
}
// unspecifiedMessage is a minimal message implementation that should not
// be ignored. In general, OS-specific implementations should use better
// types and avoid this if they can.
type unspecifiedMessage struct{}
func (unspecifiedMessage) ignore() bool { return false }

190
vendor/tailscale.com/net/netmon/netmon_windows.go generated vendored Normal file
View File

@@ -0,0 +1,190 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package netmon
import (
"context"
"errors"
"strings"
"sync"
"time"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
"tailscale.com/net/tsaddr"
"tailscale.com/types/logger"
)
var (
errClosed = errors.New("closed")
)
type eventMessage struct {
eventType string
}
func (eventMessage) ignore() bool { return false }
type winMon struct {
logf logger.Logf
ctx context.Context
cancel context.CancelFunc
isActive func() bool
messagec chan eventMessage
addressChangeCallback *winipcfg.UnicastAddressChangeCallback
routeChangeCallback *winipcfg.RouteChangeCallback
mu sync.Mutex
lastLog time.Time // time we last logged about any windows change event
// noDeadlockTicker exists just to have something scheduled as
// far as the Go runtime is concerned. Otherwise "tailscaled
// debug --monitor" thinks it's deadlocked with nothing to do,
// as Go's runtime doesn't know about callbacks registered with
// Windows.
noDeadlockTicker *time.Ticker
}
func newOSMon(logf logger.Logf, pm *Monitor) (osMon, error) {
m := &winMon{
logf: logf,
isActive: pm.isActive,
messagec: make(chan eventMessage, 1),
noDeadlockTicker: time.NewTicker(5000 * time.Hour), // arbitrary
}
m.ctx, m.cancel = context.WithCancel(context.Background())
var err error
m.addressChangeCallback, err = winipcfg.RegisterUnicastAddressChangeCallback(m.unicastAddressChanged)
if err != nil {
m.logf("winipcfg.RegisterUnicastAddressChangeCallback error: %v", err)
m.cancel()
return nil, err
}
m.routeChangeCallback, err = winipcfg.RegisterRouteChangeCallback(m.routeChanged)
if err != nil {
m.addressChangeCallback.Unregister()
m.logf("winipcfg.RegisterRouteChangeCallback error: %v", err)
m.cancel()
return nil, err
}
return m, nil
}
func (m *winMon) IsInterestingInterface(iface string) bool { return true }
func (m *winMon) Close() (ret error) {
m.cancel()
m.noDeadlockTicker.Stop()
if m.addressChangeCallback != nil {
if err := m.addressChangeCallback.Unregister(); err != nil {
m.logf("addressChangeCallback.Unregister error: %v", err)
ret = err
} else {
m.addressChangeCallback = nil
}
}
if m.routeChangeCallback != nil {
if err := m.routeChangeCallback.Unregister(); err != nil {
m.logf("routeChangeCallback.Unregister error: %v", err)
ret = err
} else {
m.routeChangeCallback = nil
}
}
return
}
func (m *winMon) Receive() (message, error) {
if m.ctx.Err() != nil {
m.logf("Receive call on closed monitor")
return nil, errClosed
}
t0 := time.Now()
select {
case msg := <-m.messagec:
now := time.Now()
m.mu.Lock()
sinceLast := now.Sub(m.lastLog)
m.lastLog = now
m.mu.Unlock()
// If it's either been awhile since we last logged
// anything, or if this some route/addr that's not
// about a Tailscale IP ("ts" prefix), then log. This
// is mainly limited to suppress the flood about our own
// route updates after connecting to a large tailnet
// and all the IPv4 /32 routes.
if sinceLast > 5*time.Second || !strings.HasPrefix(msg.eventType, "ts") {
m.logf("got windows change event after %v: evt=%s", time.Since(t0).Round(time.Millisecond), msg.eventType)
}
return msg, nil
case <-m.ctx.Done():
return nil, errClosed
}
}
// unicastAddressChanged is the callback we register with Windows to call when unicast address changes.
func (m *winMon) unicastAddressChanged(_ winipcfg.MibNotificationType, row *winipcfg.MibUnicastIPAddressRow) {
if !m.isActive() {
// Avoid starting a goroutine that sends events to messagec,
// or sending messages to messagec directly, if the monitor
// hasn't started and Receive is not yet reading from messagec.
//
// Doing so can lead to goroutine leaks or deadlocks, especially
// if the monitor is never started.
return
}
what := "addr"
if ip := row.Address.Addr(); ip.IsValid() && tsaddr.IsTailscaleIP(ip.Unmap()) {
what = "tsaddr"
}
// start a goroutine to finish our work, to return to Windows out of this callback
go m.somethingChanged(what)
}
// routeChanged is the callback we register with Windows to call when route changes.
func (m *winMon) routeChanged(_ winipcfg.MibNotificationType, row *winipcfg.MibIPforwardRow2) {
if !m.isActive() {
// Avoid starting a goroutine that sends events to messagec,
// or sending messages to messagec directly, if the monitor
// hasn't started and Receive is not yet reading from messagec.
//
// Doing so can lead to goroutine leaks or deadlocks, especially
// if the monitor is never started.
return
}
what := "route"
ip := row.DestinationPrefix.Prefix().Addr().Unmap()
if ip.IsValid() && tsaddr.IsTailscaleIP(ip) {
what = "tsroute"
}
// start a goroutine to finish our work, to return to Windows out of this callback
go m.somethingChanged(what)
}
// somethingChanged gets called from OS callbacks whenever address or route changes.
func (m *winMon) somethingChanged(evt string) {
select {
case <-m.ctx.Done():
return
case m.messagec <- eventMessage{eventType: evt}:
return
}
}
// isActive reports whether this monitor has been started and not yet closed.
func (m *Monitor) isActive() bool {
m.mu.Lock()
defer m.mu.Unlock()
return m.started && !m.closed
}

86
vendor/tailscale.com/net/netmon/polling.go generated vendored Normal file
View File

@@ -0,0 +1,86 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !windows && !darwin
package netmon
import (
"bytes"
"errors"
"os"
"runtime"
"sync"
"time"
"tailscale.com/types/logger"
)
func newPollingMon(logf logger.Logf, m *Monitor) (osMon, error) {
return &pollingMon{
logf: logf,
m: m,
stop: make(chan struct{}),
}, nil
}
// pollingMon is a bad but portable implementation of the link monitor
// that works by polling the interface state every 10 seconds, in lieu
// of anything to subscribe to.
type pollingMon struct {
logf logger.Logf
m *Monitor
closeOnce sync.Once
stop chan struct{}
}
func (pm *pollingMon) IsInterestingInterface(iface string) bool {
return true
}
func (pm *pollingMon) Close() error {
pm.closeOnce.Do(func() {
close(pm.stop)
})
return nil
}
func (pm *pollingMon) isCloudRun() bool {
// https: //cloud.google.com/run/docs/reference/container-contract#env-vars
if os.Getenv("K_REVISION") == "" || os.Getenv("K_CONFIGURATION") == "" ||
os.Getenv("K_SERVICE") == "" || os.Getenv("PORT") == "" {
return false
}
vers, err := os.ReadFile("/proc/version")
if err != nil {
pm.logf("Failed to read /proc/version: %v", err)
return false
}
return string(bytes.TrimSpace(vers)) == "Linux version 4.4.0 #1 SMP Sun Jan 10 15:06:54 PST 2016"
}
func (pm *pollingMon) Receive() (message, error) {
d := 10 * time.Second
if runtime.GOOS == "android" {
// We'll have Android notify the link monitor to wake up earlier,
// so this can go very slowly there, to save battery.
// https://github.com/tailscale/tailscale/issues/1427
d = 10 * time.Minute
} else if pm.isCloudRun() {
// Cloud Run routes never change at runtime. the containers are killed within
// 15 minutes by default, set the interval long enough to be effectively infinite.
pm.logf("monitor polling: Cloud Run detected, reduce polling interval to 24h")
d = 24 * time.Hour
}
timer := time.NewTimer(d)
defer timer.Stop()
for {
select {
case <-timer.C:
return unspecifiedMessage{}, nil
case <-pm.stop:
return nil, errors.New("stopped")
}
}
}

773
vendor/tailscale.com/net/netmon/state.go generated vendored Normal file
View File

@@ -0,0 +1,773 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package netmon
import (
"bytes"
"fmt"
"net"
"net/http"
"net/netip"
"runtime"
"slices"
"sort"
"strings"
"tailscale.com/envknob"
"tailscale.com/hostinfo"
"tailscale.com/net/netaddr"
"tailscale.com/net/tsaddr"
"tailscale.com/net/tshttpproxy"
)
// LoginEndpointForProxyDetermination is the URL used for testing
// which HTTP proxy the system should use.
var LoginEndpointForProxyDetermination = "https://controlplane.tailscale.com/"
func isUp(nif *net.Interface) bool { return nif.Flags&net.FlagUp != 0 }
func isLoopback(nif *net.Interface) bool { return nif.Flags&net.FlagLoopback != 0 }
func isProblematicInterface(nif *net.Interface) bool {
name := nif.Name
// Don't try to send disco/etc packets over zerotier; they effectively
// DoS each other by doing traffic amplification, both of them
// preferring/trying to use each other for transport. See:
// https://github.com/tailscale/tailscale/issues/1208
if strings.HasPrefix(name, "zt") || (runtime.GOOS == "windows" && strings.Contains(name, "ZeroTier")) {
return true
}
return false
}
// LocalAddresses returns the machine's IP addresses, separated by
// whether they're loopback addresses. If there are no regular addresses
// it will return any IPv4 linklocal or IPv6 unique local addresses because we
// know of environments where these are used with NAT to provide connectivity.
func LocalAddresses() (regular, loopback []netip.Addr, err error) {
// TODO(crawshaw): don't serve interface addresses that we are routing
ifaces, err := netInterfaces()
if err != nil {
return nil, nil, err
}
var regular4, regular6, linklocal4, ula6 []netip.Addr
for _, iface := range ifaces {
stdIf := iface.Interface
if !isUp(stdIf) || isProblematicInterface(stdIf) {
// Skip down interfaces and ones that are
// problematic that we don't want to try to
// send Tailscale traffic over.
continue
}
ifcIsLoopback := isLoopback(stdIf)
addrs, err := iface.Addrs()
if err != nil {
return nil, nil, err
}
for _, a := range addrs {
switch v := a.(type) {
case *net.IPNet:
ip, ok := netip.AddrFromSlice(v.IP)
if !ok {
continue
}
ip = ip.Unmap()
// TODO(apenwarr): don't special case cgNAT.
// In the general wireguard case, it might
// very well be something we can route to
// directly, because both nodes are
// behind the same CGNAT router.
if tsaddr.IsTailscaleIP(ip) {
continue
}
if ip.IsLoopback() || ifcIsLoopback {
loopback = append(loopback, ip)
} else if ip.IsLinkLocalUnicast() {
if ip.Is4() {
linklocal4 = append(linklocal4, ip)
}
// We know of no cases where the IPv6 fe80:: addresses
// are used to provide WAN connectivity. It is also very
// common for users to have no IPv6 WAN connectivity,
// but their OS supports IPv6 so they have an fe80::
// address. We don't want to report all of those
// IPv6 LL to Control.
} else if ip.Is6() && ip.IsPrivate() {
// Google Cloud Run uses NAT with IPv6 Unique
// Local Addresses to provide IPv6 connectivity.
ula6 = append(ula6, ip)
} else {
if ip.Is4() {
regular4 = append(regular4, ip)
} else {
regular6 = append(regular6, ip)
}
}
}
}
}
if len(regular4) == 0 && len(regular6) == 0 {
// if we have no usable IP addresses then be willing to accept
// addresses we otherwise wouldn't, like:
// + 169.254.x.x (AWS Lambda and Azure App Services use NAT with these)
// + IPv6 ULA (Google Cloud Run uses these with address translation)
regular4 = linklocal4
regular6 = ula6
}
regular = append(regular4, regular6...)
sortIPs(regular)
sortIPs(loopback)
return regular, loopback, nil
}
func sortIPs(s []netip.Addr) {
sort.Slice(s, func(i, j int) bool { return s[i].Less(s[j]) })
}
// Interface is a wrapper around Go's net.Interface with some extra methods.
type Interface struct {
*net.Interface
AltAddrs []net.Addr // if non-nil, returned by Addrs
Desc string // extra description (used on Windows)
}
func (i Interface) IsLoopback() bool { return isLoopback(i.Interface) }
func (i Interface) IsUp() bool { return isUp(i.Interface) }
func (i Interface) Addrs() ([]net.Addr, error) {
if i.AltAddrs != nil {
return i.AltAddrs, nil
}
return i.Interface.Addrs()
}
// ForeachInterfaceAddress is a wrapper for GetList, then
// List.ForeachInterfaceAddress.
func ForeachInterfaceAddress(fn func(Interface, netip.Prefix)) error {
ifaces, err := GetInterfaceList()
if err != nil {
return err
}
return ifaces.ForeachInterfaceAddress(fn)
}
// ForeachInterfaceAddress calls fn for each interface in ifaces, with
// all its addresses. The IPPrefix's IP is the IP address assigned to
// the interface, and Bits are the subnet mask.
func (ifaces InterfaceList) ForeachInterfaceAddress(fn func(Interface, netip.Prefix)) error {
for _, iface := range ifaces {
addrs, err := iface.Addrs()
if err != nil {
return err
}
for _, a := range addrs {
switch v := a.(type) {
case *net.IPNet:
if pfx, ok := netaddr.FromStdIPNet(v); ok {
fn(iface, pfx)
}
}
}
}
return nil
}
// ForeachInterface is a wrapper for GetList, then
// List.ForeachInterface.
func ForeachInterface(fn func(Interface, []netip.Prefix)) error {
ifaces, err := GetInterfaceList()
if err != nil {
return err
}
return ifaces.ForeachInterface(fn)
}
// ForeachInterface calls fn for each interface in ifaces, with
// all its addresses. The IPPrefix's IP is the IP address assigned to
// the interface, and Bits are the subnet mask.
func (ifaces InterfaceList) ForeachInterface(fn func(Interface, []netip.Prefix)) error {
for _, iface := range ifaces {
addrs, err := iface.Addrs()
if err != nil {
return err
}
var pfxs []netip.Prefix
for _, a := range addrs {
switch v := a.(type) {
case *net.IPNet:
if pfx, ok := netaddr.FromStdIPNet(v); ok {
pfxs = append(pfxs, pfx)
}
}
}
sort.Slice(pfxs, func(i, j int) bool {
return pfxs[i].Addr().Less(pfxs[j].Addr())
})
fn(iface, pfxs)
}
return nil
}
// State is intended to store the state of the machine's network interfaces,
// routing table, and other network configuration.
// For now it's pretty basic.
type State struct {
// InterfaceIPs maps from an interface name to the IP addresses
// configured on that interface. Each address is represented as an
// IPPrefix, where the IP is the interface IP address and Bits is
// the subnet mask.
InterfaceIPs map[string][]netip.Prefix
Interface map[string]Interface
// HaveV6 is whether this machine has an IPv6 Global or Unique Local Address
// which might provide connectivity on a non-Tailscale interface that's up.
HaveV6 bool
// HaveV4 is whether the machine has some non-localhost,
// non-link-local IPv4 address on a non-Tailscale interface that's up.
HaveV4 bool
// IsExpensive is whether the current network interface is
// considered "expensive", which currently means LTE/etc
// instead of Wifi. This field is not populated by GetState.
IsExpensive bool
// DefaultRouteInterface is the interface name for the
// machine's default route.
//
// It is not yet populated on all OSes.
//
// When non-empty, its value is the map key into Interface and
// InterfaceIPs.
DefaultRouteInterface string
// HTTPProxy is the HTTP proxy to use, if any.
HTTPProxy string
// PAC is the URL to the Proxy Autoconfig URL, if applicable.
PAC string
}
func (s *State) String() string {
var sb strings.Builder
fmt.Fprintf(&sb, "interfaces.State{defaultRoute=%v ", s.DefaultRouteInterface)
if s.DefaultRouteInterface != "" {
if iface, ok := s.Interface[s.DefaultRouteInterface]; ok && iface.Desc != "" {
fmt.Fprintf(&sb, "(%s) ", iface.Desc)
}
}
sb.WriteString("ifs={")
var ifs []string
for k := range s.Interface {
if s.keepInterfaceInStringSummary(k) {
ifs = append(ifs, k)
}
}
sort.Slice(ifs, func(i, j int) bool {
upi, upj := s.Interface[ifs[i]].IsUp(), s.Interface[ifs[j]].IsUp()
if upi != upj {
// Up sorts before down.
return upi
}
return ifs[i] < ifs[j]
})
for i, ifName := range ifs {
if i > 0 {
sb.WriteString(" ")
}
iface := s.Interface[ifName]
if iface.Interface == nil {
fmt.Fprintf(&sb, "%s:nil", ifName)
continue
}
if !iface.IsUp() {
fmt.Fprintf(&sb, "%s:down", ifName)
continue
}
fmt.Fprintf(&sb, "%s:[", ifName)
needSpace := false
for _, pfx := range s.InterfaceIPs[ifName] {
a := pfx.Addr()
if a.IsMulticast() {
continue
}
fam := "4"
if a.Is6() {
fam = "6"
}
if needSpace {
sb.WriteString(" ")
}
needSpace = true
switch {
case a.IsLoopback():
fmt.Fprintf(&sb, "lo%s", fam)
case a.IsLinkLocalUnicast():
fmt.Fprintf(&sb, "llu%s", fam)
default:
fmt.Fprintf(&sb, "%s", pfx)
}
}
sb.WriteString("]")
}
sb.WriteString("}")
if s.IsExpensive {
sb.WriteString(" expensive")
}
if s.HTTPProxy != "" {
fmt.Fprintf(&sb, " httpproxy=%s", s.HTTPProxy)
}
if s.PAC != "" {
fmt.Fprintf(&sb, " pac=%s", s.PAC)
}
fmt.Fprintf(&sb, " v4=%v v6=%v}", s.HaveV4, s.HaveV6)
return sb.String()
}
// Equal reports whether s and s2 are exactly equal.
func (s *State) Equal(s2 *State) bool {
if s == nil && s2 == nil {
return true
}
if s == nil || s2 == nil {
return false
}
if s.HaveV6 != s2.HaveV6 ||
s.HaveV4 != s2.HaveV4 ||
s.IsExpensive != s2.IsExpensive ||
s.DefaultRouteInterface != s2.DefaultRouteInterface ||
s.HTTPProxy != s2.HTTPProxy ||
s.PAC != s2.PAC {
return false
}
// If s2 has more interfaces than s, it's not equal.
if len(s.Interface) != len(s2.Interface) || len(s.InterfaceIPs) != len(s2.InterfaceIPs) {
return false
}
// Now that we know that both states have the same number of
// interfaces, we can check each interface in s against s2. If it's not
// present or not exactly equal, then the states are not equal.
for iname, i := range s.Interface {
i2, ok := s2.Interface[iname]
if !ok {
return false
}
if !i.Equal(i2) {
return false
}
}
for iname, vv := range s.InterfaceIPs {
if !slices.Equal(vv, s2.InterfaceIPs[iname]) {
return false
}
}
return true
}
// HasIP reports whether any interface has the provided IP address.
func (s *State) HasIP(ip netip.Addr) bool {
if s == nil {
return false
}
for _, pv := range s.InterfaceIPs {
for _, p := range pv {
if p.Contains(ip) {
return true
}
}
}
return false
}
func (a Interface) Equal(b Interface) bool {
if (a.Interface == nil) != (b.Interface == nil) {
return false
}
if !(a.Desc == b.Desc && netAddrsEqual(a.AltAddrs, b.AltAddrs)) {
return false
}
if a.Interface != nil && !(a.Index == b.Index &&
a.MTU == b.MTU &&
a.Name == b.Name &&
a.Flags == b.Flags &&
bytes.Equal([]byte(a.HardwareAddr), []byte(b.HardwareAddr))) {
return false
}
return true
}
func (s *State) HasPAC() bool { return s != nil && s.PAC != "" }
// AnyInterfaceUp reports whether any interface seems like it has Internet access.
func (s *State) AnyInterfaceUp() bool {
if runtime.GOOS == "js" || runtime.GOOS == "tamago" {
return true
}
return s != nil && (s.HaveV4 || s.HaveV6)
}
func netAddrsEqual(a, b []net.Addr) bool {
if len(a) != len(b) {
return false
}
for i, av := range a {
if av.Network() != b[i].Network() || av.String() != b[i].String() {
return false
}
}
return true
}
func hasTailscaleIP(pfxs []netip.Prefix) bool {
for _, pfx := range pfxs {
if tsaddr.IsTailscaleIP(pfx.Addr()) {
return true
}
}
return false
}
func isTailscaleInterface(name string, ips []netip.Prefix) bool {
if runtime.GOOS == "darwin" && strings.HasPrefix(name, "utun") && hasTailscaleIP(ips) {
// On macOS in the sandboxed app (at least as of
// 2021-02-25), we often see two utun devices
// (e.g. utun4 and utun7) with the same IPv4 and IPv6
// addresses. Just remove all utun devices with
// Tailscale IPs until we know what's happening with
// macOS NetworkExtensions and utun devices.
return true
}
return name == "Tailscale" || // as it is on Windows
strings.HasPrefix(name, "tailscale") // TODO: use --tun flag value, etc; see TODO in method doc
}
// getPAC, if non-nil, returns the current PAC file URL.
var getPAC func() string
// GetState returns the state of all the current machine's network interfaces.
//
// It does not set the returned State.IsExpensive. The caller can populate that.
//
// Deprecated: use netmon.Monitor.InterfaceState instead.
func GetState() (*State, error) {
s := &State{
InterfaceIPs: make(map[string][]netip.Prefix),
Interface: make(map[string]Interface),
}
if err := ForeachInterface(func(ni Interface, pfxs []netip.Prefix) {
ifUp := ni.IsUp()
s.Interface[ni.Name] = ni
s.InterfaceIPs[ni.Name] = append(s.InterfaceIPs[ni.Name], pfxs...)
if !ifUp || isTailscaleInterface(ni.Name, pfxs) {
return
}
for _, pfx := range pfxs {
if pfx.Addr().IsLoopback() {
continue
}
s.HaveV6 = s.HaveV6 || isUsableV6(pfx.Addr())
s.HaveV4 = s.HaveV4 || isUsableV4(pfx.Addr())
}
}); err != nil {
return nil, err
}
dr, _ := DefaultRoute()
s.DefaultRouteInterface = dr.InterfaceName
// Populate description (for Windows, primarily) if present.
if desc := dr.InterfaceDesc; desc != "" {
if iface, ok := s.Interface[dr.InterfaceName]; ok {
iface.Desc = desc
s.Interface[dr.InterfaceName] = iface
}
}
if s.AnyInterfaceUp() {
req, err := http.NewRequest("GET", LoginEndpointForProxyDetermination, nil)
if err != nil {
return nil, err
}
if u, err := tshttpproxy.ProxyFromEnvironment(req); err == nil && u != nil {
s.HTTPProxy = u.String()
}
if getPAC != nil {
s.PAC = getPAC()
}
}
return s, nil
}
// HTTPOfListener returns the HTTP address to ln.
// If the listener is listening on the unspecified address, it
// it tries to find a reasonable interface address on the machine to use.
func HTTPOfListener(ln net.Listener) string {
ta, ok := ln.Addr().(*net.TCPAddr)
if !ok || !ta.IP.IsUnspecified() {
return fmt.Sprintf("http://%v/", ln.Addr())
}
var goodIP string
var privateIP string
ForeachInterfaceAddress(func(i Interface, pfx netip.Prefix) {
ip := pfx.Addr()
if ip.IsPrivate() {
if privateIP == "" {
privateIP = ip.String()
}
return
}
goodIP = ip.String()
})
if privateIP != "" {
goodIP = privateIP
}
if goodIP != "" {
return fmt.Sprintf("http://%v/", net.JoinHostPort(goodIP, fmt.Sprint(ta.Port)))
}
return fmt.Sprintf("http://localhost:%v/", fmt.Sprint(ta.Port))
}
// likelyHomeRouterIP, if present, is a platform-specific function that is used
// to determine the likely home router IP of the current system. The signature
// of this function is:
//
// func() (homeRouter, localAddr netip.Addr, ok bool)
//
// It should return a homeRouter IP and ok=true, or no homeRouter IP and
// ok=false. Optionally, an implementation can return the "self" IP address as
// well, which will be used instead of attempting to determine it by reading
// the system's interfaces.
var likelyHomeRouterIP func() (netip.Addr, netip.Addr, bool)
// For debugging the new behaviour where likelyHomeRouterIP can return the
// "self" IP; should remove after we're confidant this won't cause issues.
var disableLikelyHomeRouterIPSelf = envknob.RegisterBool("TS_DEBUG_DISABLE_LIKELY_HOME_ROUTER_IP_SELF")
// LikelyHomeRouterIP returns the likely IP of the residential router,
// which will always be an IPv4 private address, if found.
// In addition, it returns the IP address of the current machine on
// the LAN using that gateway.
// This is used as the destination for UPnP, NAT-PMP, PCP, etc queries.
func LikelyHomeRouterIP() (gateway, myIP netip.Addr, ok bool) {
// If we don't have a way to get the home router IP, then we can't do
// anything; just return.
if likelyHomeRouterIP == nil {
return
}
// Get the gateway next; if that fails, we can't continue.
gateway, myIP, ok = likelyHomeRouterIP()
if !ok {
return
}
// If the platform-specific implementation returned a valid myIP, then
// we can return it as-is without needing to iterate through all
// interface addresses.
if disableLikelyHomeRouterIPSelf() {
myIP = netip.Addr{}
}
if myIP.IsValid() {
return
}
// The platform-specific implementation didn't return a valid myIP;
// iterate through all interfaces and try to find the correct one.
ForeachInterfaceAddress(func(i Interface, pfx netip.Prefix) {
if !i.IsUp() {
// Skip interfaces that aren't up.
return
} else if myIP.IsValid() {
// We already have a valid self IP; skip this one.
return
}
ip := pfx.Addr()
if !ip.IsValid() || !ip.Is4() {
// Skip IPs that aren't valid or aren't IPv4, since we
// always return an IPv4 address.
return
}
// If this prefix ("interface") doesn't contain the gateway,
// then we skip it; this can happen if we have multiple valid
// interfaces and the interface with the route to the internet
// is ordered after another valid+running interface.
if !pfx.Contains(gateway) {
return
}
if gateway.IsPrivate() && ip.IsPrivate() {
myIP = ip
ok = true
return
}
})
return gateway, myIP, myIP.IsValid()
}
// isUsableV4 reports whether ip is a usable IPv4 address which could
// conceivably be used to get Internet connectivity. Globally routable and
// private IPv4 addresses are always Usable, and link local 169.254.x.x
// addresses are in some environments.
func isUsableV4(ip netip.Addr) bool {
if !ip.Is4() || ip.IsLoopback() {
return false
}
if ip.IsLinkLocalUnicast() {
switch hostinfo.GetEnvType() {
case hostinfo.AWSLambda:
return true
case hostinfo.AzureAppService:
return true
default:
return false
}
}
return true
}
// isUsableV6 reports whether ip is a usable IPv6 address which could
// conceivably be used to get Internet connectivity. Globally routable
// IPv6 addresses are always Usable, and Unique Local Addresses
// (fc00::/7) are in some environments used with address translation.
func isUsableV6(ip netip.Addr) bool {
return v6Global1.Contains(ip) ||
(ip.Is6() && ip.IsPrivate() && !tsaddr.TailscaleULARange().Contains(ip))
}
var (
v6Global1 = netip.MustParsePrefix("2000::/3")
)
// keepInterfaceInStringSummary reports whether the named interface should be included
// in the String method's summary string.
func (s *State) keepInterfaceInStringSummary(ifName string) bool {
iface, ok := s.Interface[ifName]
if !ok || iface.Interface == nil {
return false
}
if ifName == s.DefaultRouteInterface {
return true
}
up := iface.IsUp()
for _, p := range s.InterfaceIPs[ifName] {
a := p.Addr()
if a.IsLinkLocalUnicast() || a.IsLoopback() {
continue
}
if up || a.IsGlobalUnicast() || a.IsPrivate() {
return true
}
}
return false
}
var altNetInterfaces func() ([]Interface, error)
// RegisterInterfaceGetter sets the function that's used to query
// the system network interfaces.
func RegisterInterfaceGetter(getInterfaces func() ([]Interface, error)) {
altNetInterfaces = getInterfaces
}
// InterfaceList is a list of interfaces on the machine.
type InterfaceList []Interface
// GetInterfaceList returns the list of interfaces on the machine.
func GetInterfaceList() (InterfaceList, error) {
return netInterfaces()
}
// netInterfaces is a wrapper around the standard library's net.Interfaces
// that returns a []*Interface instead of a []net.Interface.
// It exists because Android SDK 30 no longer permits Go's net.Interfaces
// to work (Issue 2293); this wrapper lets us the Android app register
// an alternate implementation.
func netInterfaces() ([]Interface, error) {
if altNetInterfaces != nil {
return altNetInterfaces()
}
ifs, err := net.Interfaces()
if err != nil {
return nil, err
}
ret := make([]Interface, len(ifs))
for i := range ifs {
ret[i].Interface = &ifs[i]
}
return ret, nil
}
// DefaultRouteDetails are the details about a default route returned
// by DefaultRoute.
type DefaultRouteDetails struct {
// InterfaceName is the interface name. It must always be populated.
// It's like "eth0" (Linux), "Ethernet 2" (Windows), "en0" (macOS).
InterfaceName string
// InterfaceDesc is populated on Windows at least. It's a
// longer description, like "Red Hat VirtIO Ethernet Adapter".
InterfaceDesc string
// InterfaceIndex is like net.Interface.Index.
// Zero means not populated.
InterfaceIndex int
// TODO(bradfitz): break this out into v4-vs-v6 once that need arises.
}
// DefaultRouteInterface is like DefaultRoute but only returns the
// interface name.
func DefaultRouteInterface() (string, error) {
dr, err := DefaultRoute()
if err != nil {
return "", err
}
return dr.InterfaceName, nil
}
// DefaultRoute returns details of the network interface that owns
// the default route, not including any tailscale interfaces.
func DefaultRoute() (DefaultRouteDetails, error) {
return defaultRoute()
}
// HasCGNATInterface reports whether there are any non-Tailscale interfaces that
// use a CGNAT IP range.
func HasCGNATInterface() (bool, error) {
hasCGNATInterface := false
cgnatRange := tsaddr.CGNATRange()
err := ForeachInterface(func(i Interface, pfxs []netip.Prefix) {
if hasCGNATInterface || !i.IsUp() || isTailscaleInterface(i.Name, pfxs) {
return
}
for _, pfx := range pfxs {
if cgnatRange.Overlaps(pfx) {
hasCGNATInterface = true
break
}
}
})
if err != nil {
return false, err
}
return hasCGNATInterface, nil
}
var interfaceDebugExtras func(ifIndex int) (string, error)
// InterfaceDebugExtras returns extra debugging information about an interface
// if any (an empty string will be returned if there are no additional details).
// Formatting is platform-dependent and should not be parsed.
func InterfaceDebugExtras(ifIndex int) (string, error) {
if interfaceDebugExtras != nil {
return interfaceDebugExtras(ifIndex)
}
return "", nil
}