Update
This commit is contained in:
108
vendor/tailscale.com/net/netmon/defaultroute_darwin.go
generated
vendored
108
vendor/tailscale.com/net/netmon/defaultroute_darwin.go
generated
vendored
@@ -6,6 +6,8 @@
|
||||
package netmon
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
@@ -16,14 +18,26 @@ var (
|
||||
lastKnownDefaultRouteIfName syncs.AtomicValue[string]
|
||||
)
|
||||
|
||||
// UpdateLastKnownDefaultRouteInterface is called by ipn-go-bridge in the iOS app when
|
||||
// UpdateLastKnownDefaultRouteInterface is called by ipn-go-bridge from apple network extensions 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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,45 +54,12 @@ func defaultRoute() (d DefaultRouteDetails, err error) {
|
||||
//
|
||||
// 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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// Fallback to the BSD logic
|
||||
@@ -94,3 +75,48 @@ func defaultRoute() (d DefaultRouteDetails, err error) {
|
||||
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) {
|
||||
|
||||
// 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.
|
||||
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 {
|
||||
d.InterfaceName = ifc.Name
|
||||
d.InterfaceIndex = ifc.Index
|
||||
return d, nil
|
||||
}
|
||||
}
|
||||
err = errors.New("no os provided default route interface found")
|
||||
return d, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
return nil, errors.New("no interfaces found")
|
||||
}
|
||||
|
||||
103
vendor/tailscale.com/net/netmon/interfaces.go
generated
vendored
Normal file
103
vendor/tailscale.com/net/netmon/interfaces.go
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package netmon
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"tailscale.com/syncs"
|
||||
)
|
||||
|
||||
type ifProps struct {
|
||||
mu syncs.Mutex
|
||||
name string // interface name, if known/set
|
||||
index int // interface index, if known/set
|
||||
}
|
||||
|
||||
// tsIfProps tracks the properties (name and index) of the tailscale interface.
|
||||
// There is only one tailscale interface per tailscaled instance.
|
||||
var tsIfProps ifProps
|
||||
|
||||
func (p *ifProps) tsIfName() string {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
return p.name
|
||||
}
|
||||
|
||||
func (p *ifProps) tsIfIndex() int {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
return p.index
|
||||
}
|
||||
|
||||
func (p *ifProps) set(ifName string, ifIndex int) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
p.name = ifName
|
||||
p.index = ifIndex
|
||||
}
|
||||
|
||||
// TODO (barnstar): This doesn't need the Monitor receiver anymore but we're
|
||||
// keeping it for API compatibility to avoid a breaking change. This can be
|
||||
// removed when the various clients have switched to SetTailscaleInterfaceProps
|
||||
func (m *Monitor) SetTailscaleInterfaceName(ifName string) {
|
||||
SetTailscaleInterfaceProps(ifName, 0)
|
||||
}
|
||||
|
||||
// SetTailscaleInterfaceProps sets the name of the Tailscale interface and
|
||||
// its index for use by various listeners/dialers. If the index is zero,
|
||||
// an attempt will be made to look it up by name. This makes no attempt
|
||||
// to validate that the interface exists at the time of calling.
|
||||
//
|
||||
// If this method is called, it is the responsibility of the caller to
|
||||
// update the interface name and index if they change.
|
||||
//
|
||||
// This should be called as early as possible during tailscaled startup.
|
||||
func SetTailscaleInterfaceProps(ifName string, ifIndex int) {
|
||||
if ifIndex != 0 {
|
||||
tsIfProps.set(ifName, ifIndex)
|
||||
return
|
||||
}
|
||||
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, iface := range ifaces {
|
||||
if iface.Name == ifName {
|
||||
ifIndex = iface.Index
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
tsIfProps.set(ifName, ifIndex)
|
||||
}
|
||||
|
||||
// TailscaleInterfaceName returns the name of the Tailscale interface.
|
||||
// For example, "tailscale0", "tun0", "utun3", etc or an error if unset.
|
||||
//
|
||||
// Callers must handle errors, as the Tailscale interface
|
||||
// name may not be set in some environments.
|
||||
func TailscaleInterfaceName() (string, error) {
|
||||
name := tsIfProps.tsIfName()
|
||||
if name == "" {
|
||||
return "", errors.New("Tailscale interface name not set")
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// TailscaleInterfaceIndex returns the index of the Tailscale interface or
|
||||
// an error if unset.
|
||||
//
|
||||
// Callers must handle errors, as the Tailscale interface
|
||||
// index may not be set in some environments.
|
||||
func TailscaleInterfaceIndex() (int, error) {
|
||||
index := tsIfProps.tsIfIndex()
|
||||
if index == 0 {
|
||||
return 0, errors.New("Tailscale interface index not set")
|
||||
}
|
||||
return index, nil
|
||||
}
|
||||
4
vendor/tailscale.com/net/netmon/interfaces_darwin.go
generated
vendored
4
vendor/tailscale.com/net/netmon/interfaces_darwin.go
generated
vendored
@@ -7,12 +7,12 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/net/route"
|
||||
"golang.org/x/sys/unix"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/util/mak"
|
||||
)
|
||||
|
||||
@@ -26,7 +26,7 @@ func parseRoutingTable(rib []byte) ([]route.Message, error) {
|
||||
}
|
||||
|
||||
var ifNames struct {
|
||||
sync.Mutex
|
||||
syncs.Mutex
|
||||
m map[int]string // ifindex => name
|
||||
}
|
||||
|
||||
|
||||
4
vendor/tailscale.com/net/netmon/interfaces_linux.go
generated
vendored
4
vendor/tailscale.com/net/netmon/interfaces_linux.go
generated
vendored
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/mdlayher/netlink"
|
||||
"go4.org/mem"
|
||||
"golang.org/x/sys/unix"
|
||||
"tailscale.com/feature/buildfeatures"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/util/lineiter"
|
||||
)
|
||||
@@ -41,6 +42,9 @@ ens18 00000000 0100000A 0003 0 0 0 00000000
|
||||
ens18 0000000A 00000000 0001 0 0 0 0000FFFF 0 0 0
|
||||
*/
|
||||
func likelyHomeRouterIPLinux() (ret netip.Addr, myIP netip.Addr, ok bool) {
|
||||
if !buildfeatures.HasPortMapper {
|
||||
return
|
||||
}
|
||||
if procNetRouteErr.Load() {
|
||||
// If we failed to read /proc/net/route previously, don't keep trying.
|
||||
return ret, myIP, false
|
||||
|
||||
8
vendor/tailscale.com/net/netmon/interfaces_windows.go
generated
vendored
8
vendor/tailscale.com/net/netmon/interfaces_windows.go
generated
vendored
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
||||
"tailscale.com/feature/buildfeatures"
|
||||
"tailscale.com/tsconst"
|
||||
)
|
||||
|
||||
@@ -22,7 +23,9 @@ const (
|
||||
|
||||
func init() {
|
||||
likelyHomeRouterIP = likelyHomeRouterIPWindows
|
||||
getPAC = getPACWindows
|
||||
if buildfeatures.HasUseProxy {
|
||||
getPAC = getPACWindows
|
||||
}
|
||||
}
|
||||
|
||||
func likelyHomeRouterIPWindows() (ret netip.Addr, _ netip.Addr, ok bool) {
|
||||
@@ -244,6 +247,9 @@ const (
|
||||
)
|
||||
|
||||
func getPACWindows() string {
|
||||
if !buildfeatures.HasUseProxy {
|
||||
return ""
|
||||
}
|
||||
var res *uint16
|
||||
r, _, e := detectAutoProxyConfigURL.Call(
|
||||
winHTTP_AUTO_DETECT_TYPE_DHCP|winHTTP_AUTO_DETECT_TYPE_DNS_A,
|
||||
|
||||
51
vendor/tailscale.com/net/netmon/loghelper.go
generated
vendored
51
vendor/tailscale.com/net/netmon/loghelper.go
generated
vendored
@@ -4,39 +4,46 @@
|
||||
package netmon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/eventbus"
|
||||
)
|
||||
|
||||
const cooldownSeconds = 300
|
||||
|
||||
// LinkChangeLogLimiter returns a new [logger.Logf] that logs each unique
|
||||
// format string to the underlying logger only once per major LinkChange event.
|
||||
// format string to the underlying logger only once per major LinkChange event
|
||||
// with a cooldownSeconds second cooldown.
|
||||
//
|
||||
// The returned function should be called when the logger is no longer needed,
|
||||
// to release resources from the Monitor.
|
||||
func LinkChangeLogLimiter(logf logger.Logf, nm *Monitor) (_ logger.Logf, unregister func()) {
|
||||
var formatSeen sync.Map // map[string]bool
|
||||
unregister = nm.RegisterChangeCallback(func(cd *ChangeDelta) {
|
||||
// If we're in a major change or a time jump, clear the seen map.
|
||||
if cd.Major || cd.TimeJumped {
|
||||
formatSeen.Clear()
|
||||
// The logger stops tracking seen format strings when the provided context is
|
||||
// done.
|
||||
func LinkChangeLogLimiter(ctx context.Context, logf logger.Logf, nm *Monitor) logger.Logf {
|
||||
var formatLastSeen sync.Map // map[string]int64
|
||||
|
||||
sub := eventbus.SubscribeFunc(nm.b, func(cd *ChangeDelta) {
|
||||
// Any link changes that are flagged as likely require a rebind are
|
||||
// interesting enough that we should log them.
|
||||
if cd.RebindLikelyRequired {
|
||||
formatLastSeen.Clear()
|
||||
}
|
||||
})
|
||||
|
||||
context.AfterFunc(ctx, sub.Close)
|
||||
return func(format string, args ...any) {
|
||||
// We only store 'true' in the map, so if it's present then it
|
||||
// means we've already logged this format string.
|
||||
_, loaded := formatSeen.LoadOrStore(format, true)
|
||||
if loaded {
|
||||
// TODO(andrew-d): we may still want to log this
|
||||
// message every N minutes (1x/hour?) even if it's been
|
||||
// seen, so that debugging doesn't require searching
|
||||
// back in the logs for an unbounded amount of time.
|
||||
//
|
||||
// See: https://github.com/tailscale/tailscale/issues/13145
|
||||
return
|
||||
// get the current timestamp
|
||||
now := time.Now().Unix()
|
||||
lastSeen, ok := formatLastSeen.Load(format)
|
||||
if ok {
|
||||
// if we've seen this format string within the last cooldownSeconds, skip logging
|
||||
if now-lastSeen.(int64) < cooldownSeconds {
|
||||
return
|
||||
}
|
||||
}
|
||||
// update the last seen timestamp for this format string
|
||||
formatLastSeen.Store(format, now)
|
||||
|
||||
logf(format, args...)
|
||||
}, unregister
|
||||
}
|
||||
}
|
||||
|
||||
474
vendor/tailscale.com/net/netmon/netmon.go
generated
vendored
474
vendor/tailscale.com/net/netmon/netmon.go
generated
vendored
@@ -7,15 +7,20 @@
|
||||
package netmon
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"tailscale.com/feature/buildfeatures"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/eventbus"
|
||||
"tailscale.com/util/set"
|
||||
)
|
||||
|
||||
@@ -42,27 +47,28 @@ type osMon interface {
|
||||
// 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
|
||||
}
|
||||
|
||||
// IsInterestingInterface is the function used to determine whether
|
||||
// a given interface name is interesting enough to pay attention to
|
||||
// for network change monitoring purposes.
|
||||
//
|
||||
// If nil, all interfaces are considered interesting.
|
||||
var IsInterestingInterface func(Interface, []netip.Prefix) bool
|
||||
|
||||
// Monitor represents a monitoring instance.
|
||||
type Monitor struct {
|
||||
logf logger.Logf
|
||||
logf logger.Logf
|
||||
b *eventbus.Client
|
||||
changed *eventbus.Publisher[ChangeDelta]
|
||||
|
||||
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
|
||||
mu syncs.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
|
||||
@@ -80,55 +86,246 @@ type Monitor struct {
|
||||
type ChangeFunc func(*ChangeDelta)
|
||||
|
||||
// ChangeDelta describes the difference between two network states.
|
||||
//
|
||||
// Use NewChangeDelta to construct a delta and compute the cached fields.
|
||||
type ChangeDelta struct {
|
||||
// Monitor is the network monitor that sent this delta.
|
||||
Monitor *Monitor
|
||||
|
||||
// Old is the old interface state, if known.
|
||||
// old is the old interface state, if known.
|
||||
// It's nil if the old state is unknown.
|
||||
// Do not mutate it.
|
||||
Old *State
|
||||
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
|
||||
new *State
|
||||
|
||||
// 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
|
||||
// time we checked. This is a hint that a 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
|
||||
DefaultRouteInterface string
|
||||
|
||||
// Computed Fields
|
||||
|
||||
DefaultInterfaceChanged bool // whether default route interface changed
|
||||
IsLessExpensive bool // whether new state's default interface is less expensive than old.
|
||||
HasPACOrProxyConfigChanged bool // whether PAC/HTTP proxy config changed
|
||||
InterfaceIPsChanged bool // whether any interface IPs changed in a meaningful way
|
||||
AvailableProtocolsChanged bool // whether we have seen a change in available IPv4/IPv6
|
||||
DefaultInterfaceMaybeViable bool // whether the default interface is potentially viable (has usable IPs, is up and is not the tunnel itself)
|
||||
IsInitialState bool // whether this is the initial state (old == nil, new != nil)
|
||||
|
||||
// RebindLikelyRequired combines the various fields above to report whether this change likely requires us
|
||||
// to rebind sockets. This is a very conservative estimate and covers a number ofcases where a rebind
|
||||
// may not be strictly necessary. Consumers of the ChangeDelta should consider checking the individual fields
|
||||
// above or the state of their sockets.
|
||||
RebindLikelyRequired bool
|
||||
}
|
||||
|
||||
// CurrentState returns the current (new) state after the change.
|
||||
func (cd *ChangeDelta) CurrentState() *State {
|
||||
return cd.new
|
||||
}
|
||||
|
||||
// NewChangeDelta builds a ChangeDelta and eagerly computes the cached fields.
|
||||
// forceViability, if true, forces DefaultInterfaceMaybeViable to be true regardless of the
|
||||
// actual state of the default interface. This is useful in testing.
|
||||
func NewChangeDelta(old, new *State, timeJumped bool, forceViability bool) (*ChangeDelta, error) {
|
||||
cd := ChangeDelta{
|
||||
old: old,
|
||||
new: new,
|
||||
TimeJumped: timeJumped,
|
||||
}
|
||||
|
||||
if cd.new == nil {
|
||||
log.Printf("[unexpected] NewChangeDelta called with nil new state")
|
||||
return nil, errors.New("new state cannot be nil")
|
||||
} else if cd.old == nil && cd.new != nil {
|
||||
cd.DefaultInterfaceChanged = cd.new.DefaultRouteInterface != ""
|
||||
cd.IsLessExpensive = false
|
||||
cd.HasPACOrProxyConfigChanged = true
|
||||
cd.InterfaceIPsChanged = true
|
||||
cd.IsInitialState = true
|
||||
} else {
|
||||
cd.AvailableProtocolsChanged = (cd.old.HaveV4 != cd.new.HaveV4) || (cd.old.HaveV6 != cd.new.HaveV6)
|
||||
cd.DefaultInterfaceChanged = cd.old.DefaultRouteInterface != cd.new.DefaultRouteInterface
|
||||
cd.IsLessExpensive = cd.old.IsExpensive && !cd.new.IsExpensive
|
||||
cd.HasPACOrProxyConfigChanged = (cd.old.PAC != cd.new.PAC) || (cd.old.HTTPProxy != cd.new.HTTPProxy)
|
||||
cd.InterfaceIPsChanged = cd.isInterestingInterfaceChange()
|
||||
}
|
||||
|
||||
cd.DefaultRouteInterface = new.DefaultRouteInterface
|
||||
defIf := new.Interface[cd.DefaultRouteInterface]
|
||||
|
||||
tsIfName, err := TailscaleInterfaceName()
|
||||
|
||||
// The default interface is not viable if it is down or it is the Tailscale interface itself.
|
||||
if !forceViability && (!defIf.IsUp() || (err == nil && cd.DefaultRouteInterface == tsIfName)) {
|
||||
cd.DefaultInterfaceMaybeViable = false
|
||||
} else {
|
||||
cd.DefaultInterfaceMaybeViable = true
|
||||
}
|
||||
|
||||
// Compute rebind requirement. The default interface needs to be viable and
|
||||
// one of the other conditions needs to be true.
|
||||
cd.RebindLikelyRequired = (cd.old == nil ||
|
||||
cd.TimeJumped ||
|
||||
cd.DefaultInterfaceChanged ||
|
||||
cd.InterfaceIPsChanged ||
|
||||
cd.IsLessExpensive ||
|
||||
cd.HasPACOrProxyConfigChanged ||
|
||||
cd.AvailableProtocolsChanged) &&
|
||||
cd.DefaultInterfaceMaybeViable
|
||||
|
||||
return &cd, nil
|
||||
}
|
||||
|
||||
// StateDesc returns a description of the old and new states for logging.
|
||||
func (cd *ChangeDelta) StateDesc() string {
|
||||
return fmt.Sprintf("old: %v new: %v", cd.old, cd.new)
|
||||
}
|
||||
|
||||
// InterfaceIPDisappeared reports whether the given IP address exists on any interface
|
||||
// in the old state, but not in the new state.
|
||||
func (cd *ChangeDelta) InterfaceIPDisappeared(ip netip.Addr) bool {
|
||||
if cd.old == nil {
|
||||
return false
|
||||
}
|
||||
if cd.new == nil && cd.old.HasIP(ip) {
|
||||
return true
|
||||
}
|
||||
return cd.new.HasIP(ip) && !cd.old.HasIP(ip)
|
||||
}
|
||||
|
||||
// AnyInterfaceUp reports whether any interfaces are up in the new state.
|
||||
func (cd *ChangeDelta) AnyInterfaceUp() bool {
|
||||
if cd.new == nil {
|
||||
return false
|
||||
}
|
||||
for _, ifi := range cd.new.Interface {
|
||||
if ifi.IsUp() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isInterestingInterfaceChange reports whether any interfaces have changed in a meaningful way.
|
||||
// This excludes interfaces that are not interesting per IsInterestingInterface and
|
||||
// filters out changes to interface IPs that that are uninteresting (e.g. link-local addresses).
|
||||
func (cd *ChangeDelta) isInterestingInterfaceChange() bool {
|
||||
// If there is no old state, everything is considered changed.
|
||||
if cd.old == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// Compare interfaces in both directions. Old to new and new to old.
|
||||
tsIfName, ifNameErr := TailscaleInterfaceName()
|
||||
|
||||
for iname, oldInterface := range cd.old.Interface {
|
||||
if ifNameErr == nil && iname == tsIfName {
|
||||
// Ignore changes in the Tailscale interface itself
|
||||
continue
|
||||
}
|
||||
oldIps := filterRoutableIPs(cd.old.InterfaceIPs[iname])
|
||||
if IsInterestingInterface != nil && !IsInterestingInterface(oldInterface, oldIps) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Old interfaces with no routable addresses are not interesting
|
||||
if len(oldIps) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// The old interface doesn't exist in the new interface set and it has
|
||||
// a global unicast IP. That's considered a change from the perspective
|
||||
// of anything that may have been bound to it. If it didn't have a global
|
||||
// unicast IP, it's not interesting.
|
||||
newInterface, ok := cd.new.Interface[iname]
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
newIps, ok := cd.new.InterfaceIPs[iname]
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
newIps = filterRoutableIPs(newIps)
|
||||
|
||||
if !oldInterface.Equal(newInterface) || !prefixesEqual(oldIps, newIps) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for iname, newInterface := range cd.new.Interface {
|
||||
if ifNameErr == nil && iname == tsIfName {
|
||||
// Ignore changes in the Tailscale interface itself
|
||||
continue
|
||||
}
|
||||
newIps := filterRoutableIPs(cd.new.InterfaceIPs[iname])
|
||||
if IsInterestingInterface != nil && !IsInterestingInterface(newInterface, newIps) {
|
||||
continue
|
||||
}
|
||||
|
||||
// New interfaces with no routable addresses are not interesting
|
||||
if len(newIps) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
oldInterface, ok := cd.old.Interface[iname]
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
oldIps, ok := cd.old.InterfaceIPs[iname]
|
||||
if !ok {
|
||||
// Redundant but we can't dig up the "old" IPs for this interface.
|
||||
return true
|
||||
}
|
||||
oldIps = filterRoutableIPs(oldIps)
|
||||
|
||||
// The interface's IPs, Name, MTU, etc have changed. This is definitely interesting.
|
||||
if !newInterface.Equal(oldInterface) || !prefixesEqual(oldIps, newIps) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func filterRoutableIPs(addrs []netip.Prefix) []netip.Prefix {
|
||||
var filtered []netip.Prefix
|
||||
for _, pfx := range addrs {
|
||||
a := pfx.Addr()
|
||||
// Skip link-local multicast addresses.
|
||||
if a.IsLinkLocalMulticast() {
|
||||
continue
|
||||
}
|
||||
|
||||
if isUsableV4(a) || isUsableV6(a) {
|
||||
filtered = append(filtered, pfx)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
// 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) {
|
||||
func New(bus *eventbus.Bus, logf logger.Logf) (*Monitor, error) {
|
||||
logf = logger.WithPrefix(logf, "monitor: ")
|
||||
m := &Monitor{
|
||||
logf: logf,
|
||||
b: bus.Client("netmon"),
|
||||
change: make(chan bool, 1),
|
||||
stop: make(chan struct{}),
|
||||
lastWall: wallTime(),
|
||||
}
|
||||
m.changed = eventbus.Publish[ChangeDelta](m.b)
|
||||
st, err := m.interfaceStateUncached()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.ifState = st
|
||||
|
||||
m.om, err = newOSMon(logf, m)
|
||||
m.om, err = newOSMon(bus, logf, m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -161,16 +358,7 @@ func (m *Monitor) InterfaceState() *State {
|
||||
}
|
||||
|
||||
func (m *Monitor) interfaceStateUncached() (*State, error) {
|
||||
return getState(m.tsIfName)
|
||||
}
|
||||
|
||||
// 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
|
||||
return getState(tsIfProps.tsIfName())
|
||||
}
|
||||
|
||||
// GatewayAndSelfIP returns the current network's default gateway, and
|
||||
@@ -179,6 +367,9 @@ func (m *Monitor) SetTailscaleInterfaceName(ifName string) {
|
||||
// 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 !buildfeatures.HasPortMapper {
|
||||
return
|
||||
}
|
||||
if m.static {
|
||||
return
|
||||
}
|
||||
@@ -218,29 +409,6 @@ func (m *Monitor) RegisterChangeCallback(callback ChangeFunc) (unregister func()
|
||||
}
|
||||
}
|
||||
|
||||
// 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() {
|
||||
@@ -353,10 +521,6 @@ func (m *Monitor) pump() {
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
if rdm, ok := msg.(ipRuleDeletedMessage); ok {
|
||||
m.notifyRuleDeleted(rdm)
|
||||
continue
|
||||
}
|
||||
if msg.ignore() {
|
||||
continue
|
||||
}
|
||||
@@ -364,25 +528,6 @@ func (m *Monitor) pump() {
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -404,7 +549,10 @@ func (m *Monitor) debounce() {
|
||||
select {
|
||||
case <-m.stop:
|
||||
return
|
||||
case <-time.After(250 * time.Millisecond):
|
||||
// 1s is reasonable debounce time for network changes. Events such as undocking a laptop
|
||||
// or roaming onto wifi will often generate multiple events in quick succession as interfaces
|
||||
// flap. We want to avoid spamming consumers of these events.
|
||||
case <-time.After(1000 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -431,146 +579,51 @@ func (m *Monitor) handlePotentialChange(newState *State, forceCallbacks bool) {
|
||||
return
|
||||
}
|
||||
|
||||
delta := &ChangeDelta{
|
||||
Monitor: m,
|
||||
Old: oldState,
|
||||
New: newState,
|
||||
TimeJumped: timeJumped,
|
||||
delta, err := NewChangeDelta(oldState, newState, timeJumped, false)
|
||||
if err != nil {
|
||||
m.logf("[unexpected] error creating ChangeDelta: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
delta.Major = m.IsMajorChangeFrom(oldState, newState)
|
||||
if delta.Major {
|
||||
if delta.RebindLikelyRequired {
|
||||
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))
|
||||
}
|
||||
}
|
||||
m.ifState = 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 {
|
||||
if delta.RebindLikelyRequired {
|
||||
metricChangeMajor.Add(1)
|
||||
}
|
||||
if delta.TimeJumped {
|
||||
metricChangeTimeJump.Add(1)
|
||||
}
|
||||
m.changed.Publish(*delta)
|
||||
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 {
|
||||
// reports whether a and b contain the same set of prefixes regardless of order.
|
||||
func prefixesEqual(a, b []netip.Prefix) bool {
|
||||
if len(a) != len(b) {
|
||||
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:]
|
||||
}
|
||||
}
|
||||
aa := make([]netip.Prefix, len(a))
|
||||
bb := make([]netip.Prefix, len(b))
|
||||
copy(aa, a)
|
||||
copy(bb, b)
|
||||
|
||||
func jsonSummary(x any) any {
|
||||
j, err := json.Marshal(x)
|
||||
if err != nil {
|
||||
return err
|
||||
less := func(x, y netip.Prefix) int {
|
||||
return x.Addr().Compare(y.Addr())
|
||||
}
|
||||
return j
|
||||
|
||||
slices.SortFunc(aa, less)
|
||||
slices.SortFunc(bb, less)
|
||||
return slices.Equal(aa, bb)
|
||||
}
|
||||
|
||||
func wallTime() time.Time {
|
||||
@@ -596,7 +649,7 @@ func (m *Monitor) pollWallTime() {
|
||||
//
|
||||
// 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"
|
||||
const shouldMonitorTimeJump = runtime.GOOS != "android" && runtime.GOOS != "ios" && runtime.GOOS != "plan9"
|
||||
|
||||
// checkWallTimeAdvanceLocked reports whether wall time jumped more than 150% of
|
||||
// pollWallTimeInterval, indicating we probably just came out of sleep. Once a
|
||||
@@ -617,10 +670,3 @@ func (m *Monitor) checkWallTimeAdvanceLocked() bool {
|
||||
func (m *Monitor) resetTimeJumpedLocked() {
|
||||
m.timeJumped = false
|
||||
}
|
||||
|
||||
type ipRuleDeletedMessage struct {
|
||||
table uint8
|
||||
priority uint32
|
||||
}
|
||||
|
||||
func (ipRuleDeletedMessage) ignore() bool { return true }
|
||||
|
||||
24
vendor/tailscale.com/net/netmon/netmon_darwin.go
generated
vendored
24
vendor/tailscale.com/net/netmon/netmon_darwin.go
generated
vendored
@@ -13,8 +13,15 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/eventbus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
IsInterestingInterface = func(iface Interface, prefixes []netip.Prefix) bool {
|
||||
return isInterestingInterface(iface.Name)
|
||||
}
|
||||
}
|
||||
|
||||
const debugRouteMessages = false
|
||||
|
||||
// unspecifiedMessage is a minimal message implementation that should not
|
||||
@@ -24,7 +31,7 @@ type unspecifiedMessage struct{}
|
||||
|
||||
func (unspecifiedMessage) ignore() bool { return false }
|
||||
|
||||
func newOSMon(logf logger.Logf, _ *Monitor) (osMon, error) {
|
||||
func newOSMon(_ *eventbus.Bus, logf logger.Logf, _ *Monitor) (osMon, error) {
|
||||
fd, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -124,11 +131,10 @@ func addrType(addrs []route.Addr, rtaxType int) route.Addr {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *darwinRouteMon) IsInterestingInterface(iface string) bool {
|
||||
func 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":
|
||||
case "llw", "awdl", "ipsec", "gif", "XHC", "anpi", "lo", "utun":
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -136,7 +142,7 @@ func (m *darwinRouteMon) IsInterestingInterface(iface string) bool {
|
||||
|
||||
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) {
|
||||
if !isInterestingInterface(la.Name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -149,6 +155,14 @@ func (m *darwinRouteMon) skipRouteMessage(msg *route.RouteMessage) bool {
|
||||
// dst = fe80::b476:66ff:fe30:c8f6%15
|
||||
return true
|
||||
}
|
||||
|
||||
// We can skip route messages from uninteresting interfaces. We do this upstream
|
||||
// against the InterfaceMonitor, but skipping them here avoids unnecessary work.
|
||||
if la, ok := addrType(msg.Addrs, unix.RTAX_IFP).(*route.LinkAddr); ok {
|
||||
if !isInterestingInterface(la.Name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
5
vendor/tailscale.com/net/netmon/netmon_freebsd.go
generated
vendored
5
vendor/tailscale.com/net/netmon/netmon_freebsd.go
generated
vendored
@@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/eventbus"
|
||||
)
|
||||
|
||||
// unspecifiedMessage is a minimal message implementation that should not
|
||||
@@ -24,7 +25,7 @@ type devdConn struct {
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
func newOSMon(logf logger.Logf, m *Monitor) (osMon, error) {
|
||||
func newOSMon(_ *eventbus.Bus, 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)
|
||||
@@ -33,8 +34,6 @@ func newOSMon(logf logger.Logf, m *Monitor) (osMon, error) {
|
||||
return &devdConn{conn}, nil
|
||||
}
|
||||
|
||||
func (c *devdConn) IsInterestingInterface(iface string) bool { return true }
|
||||
|
||||
func (c *devdConn) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
47
vendor/tailscale.com/net/netmon/netmon_linux.go
generated
vendored
47
vendor/tailscale.com/net/netmon/netmon_linux.go
generated
vendored
@@ -16,6 +16,7 @@ import (
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/eventbus"
|
||||
)
|
||||
|
||||
var debugNetlinkMessages = envknob.RegisterBool("TS_DEBUG_NETLINK")
|
||||
@@ -27,15 +28,26 @@ type unspecifiedMessage struct{}
|
||||
|
||||
func (unspecifiedMessage) ignore() bool { return false }
|
||||
|
||||
// RuleDeleted reports that one of Tailscale's policy routing rules
|
||||
// was deleted.
|
||||
type RuleDeleted struct {
|
||||
// Table is the table number that the deleted rule referenced.
|
||||
Table uint8
|
||||
// Priority is the lookup priority of the deleted rule.
|
||||
Priority uint32
|
||||
}
|
||||
|
||||
// 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
|
||||
busClient *eventbus.Client
|
||||
rulesDeleted *eventbus.Publisher[RuleDeleted]
|
||||
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
|
||||
@@ -44,7 +56,7 @@ type nlConn struct {
|
||||
addrCache map[uint32]map[netip.Addr]bool
|
||||
}
|
||||
|
||||
func newOSMon(logf logger.Logf, m *Monitor) (osMon, error) {
|
||||
func newOSMon(bus *eventbus.Bus, 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
|
||||
@@ -59,12 +71,20 @@ func newOSMon(logf logger.Logf, m *Monitor) (osMon, error) {
|
||||
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
|
||||
client := bus.Client("netmon-iprules")
|
||||
return &nlConn{
|
||||
busClient: client,
|
||||
rulesDeleted: eventbus.Publish[RuleDeleted](client),
|
||||
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) Close() error {
|
||||
c.busClient.Close()
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
func (c *nlConn) Receive() (message, error) {
|
||||
if len(c.buffered) == 0 {
|
||||
@@ -219,14 +239,15 @@ func (c *nlConn) Receive() (message, error) {
|
||||
// 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,
|
||||
rd := RuleDeleted{
|
||||
Table: rmsg.Table,
|
||||
Priority: rmsg.Attributes.Priority,
|
||||
}
|
||||
c.rulesDeleted.Publish(rd)
|
||||
if debugNetlinkMessages() {
|
||||
c.logf("%+v", rdm)
|
||||
c.logf("%+v", rd)
|
||||
}
|
||||
return rdm, nil
|
||||
return ignoreMessage{}, 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
|
||||
|
||||
3
vendor/tailscale.com/net/netmon/netmon_polling.go
generated
vendored
3
vendor/tailscale.com/net/netmon/netmon_polling.go
generated
vendored
@@ -7,9 +7,10 @@ package netmon
|
||||
|
||||
import (
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/eventbus"
|
||||
)
|
||||
|
||||
func newOSMon(logf logger.Logf, m *Monitor) (osMon, error) {
|
||||
func newOSMon(_ *eventbus.Bus, logf logger.Logf, m *Monitor) (osMon, error) {
|
||||
return newPollingMon(logf, m)
|
||||
}
|
||||
|
||||
|
||||
5
vendor/tailscale.com/net/netmon/netmon_windows.go
generated
vendored
5
vendor/tailscale.com/net/netmon/netmon_windows.go
generated
vendored
@@ -13,6 +13,7 @@ import (
|
||||
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/eventbus"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -45,7 +46,7 @@ type winMon struct {
|
||||
noDeadlockTicker *time.Ticker
|
||||
}
|
||||
|
||||
func newOSMon(logf logger.Logf, pm *Monitor) (osMon, error) {
|
||||
func newOSMon(_ *eventbus.Bus, logf logger.Logf, pm *Monitor) (osMon, error) {
|
||||
m := &winMon{
|
||||
logf: logf,
|
||||
isActive: pm.isActive,
|
||||
@@ -73,8 +74,6 @@ func newOSMon(logf logger.Logf, pm *Monitor) (osMon, error) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *winMon) IsInterestingInterface(iface string) bool { return true }
|
||||
|
||||
func (m *winMon) Close() (ret error) {
|
||||
m.cancel()
|
||||
m.noDeadlockTicker.Stop()
|
||||
|
||||
4
vendor/tailscale.com/net/netmon/polling.go
generated
vendored
4
vendor/tailscale.com/net/netmon/polling.go
generated
vendored
@@ -35,10 +35,6 @@ type pollingMon struct {
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
func (pm *pollingMon) IsInterestingInterface(iface string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (pm *pollingMon) Close() error {
|
||||
pm.closeOnce.Do(func() {
|
||||
close(pm.stop)
|
||||
|
||||
71
vendor/tailscale.com/net/netmon/state.go
generated
vendored
71
vendor/tailscale.com/net/netmon/state.go
generated
vendored
@@ -15,10 +15,11 @@ import (
|
||||
"strings"
|
||||
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/feature"
|
||||
"tailscale.com/feature/buildfeatures"
|
||||
"tailscale.com/hostinfo"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/net/tshttpproxy"
|
||||
"tailscale.com/util/mak"
|
||||
)
|
||||
|
||||
@@ -148,12 +149,28 @@ type Interface struct {
|
||||
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) IsLoopback() bool {
|
||||
if i.Interface == nil {
|
||||
return false
|
||||
}
|
||||
return isLoopback(i.Interface)
|
||||
}
|
||||
|
||||
func (i Interface) IsUp() bool {
|
||||
if i.Interface == nil {
|
||||
return false
|
||||
}
|
||||
return isUp(i.Interface)
|
||||
}
|
||||
|
||||
func (i Interface) Addrs() ([]net.Addr, error) {
|
||||
if i.AltAddrs != nil {
|
||||
return i.AltAddrs, nil
|
||||
}
|
||||
if i.Interface == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return i.Interface.Addrs()
|
||||
}
|
||||
|
||||
@@ -182,6 +199,10 @@ func (ifaces InterfaceList) ForeachInterfaceAddress(fn func(Interface, netip.Pre
|
||||
if pfx, ok := netaddr.FromStdIPNet(v); ok {
|
||||
fn(iface, pfx)
|
||||
}
|
||||
case *net.IPAddr:
|
||||
if ip, ok := netip.AddrFromSlice(v.IP); ok {
|
||||
fn(iface, netip.PrefixFrom(ip, ip.BitLen()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -214,6 +235,10 @@ func (ifaces InterfaceList) ForeachInterface(fn func(Interface, []netip.Prefix))
|
||||
if pfx, ok := netaddr.FromStdIPNet(v); ok {
|
||||
pfxs = append(pfxs, pfx)
|
||||
}
|
||||
case *net.IPAddr:
|
||||
if ip, ok := netip.AddrFromSlice(v.IP); ok {
|
||||
pfxs = append(pfxs, netip.PrefixFrom(ip, ip.BitLen()))
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Slice(pfxs, func(i, j int) bool {
|
||||
@@ -445,15 +470,22 @@ func hasTailscaleIP(pfxs []netip.Prefix) bool {
|
||||
}
|
||||
|
||||
func isTailscaleInterface(name string, ips []netip.Prefix) bool {
|
||||
// Sandboxed macOS and Plan9 (and anything else that explicitly calls SetTailscaleInterfaceProps).
|
||||
tsIfName, err := TailscaleInterfaceName()
|
||||
if err == nil {
|
||||
// If we've been told the Tailscale interface name, use that.
|
||||
return name == tsIfName
|
||||
}
|
||||
|
||||
// The sandboxed app should (as of 1.92) set the tun interface name via SetTailscaleInterfaceProps
|
||||
// early in the startup process. The non-sandboxed app does not.
|
||||
// TODO (barnstar): If Wireguard created the tun device on darwin, it should know the name and it should
|
||||
// be explicitly set instead checking addresses here.
|
||||
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
|
||||
}
|
||||
|
||||
// Windows, Linux...
|
||||
return name == "Tailscale" || // as it is on Windows
|
||||
strings.HasPrefix(name, "tailscale") // TODO: use --tun flag value, etc; see TODO in method doc
|
||||
}
|
||||
@@ -476,9 +508,16 @@ func getState(optTSInterfaceName string) (*State, error) {
|
||||
ifUp := ni.IsUp()
|
||||
s.Interface[ni.Name] = ni
|
||||
s.InterfaceIPs[ni.Name] = append(s.InterfaceIPs[ni.Name], pfxs...)
|
||||
|
||||
// Skip uninteresting interfaces
|
||||
if IsInterestingInterface != nil && !IsInterestingInterface(ni, pfxs) {
|
||||
return
|
||||
}
|
||||
|
||||
if !ifUp || isTSInterfaceName || isTailscaleInterface(ni.Name, pfxs) {
|
||||
return
|
||||
}
|
||||
|
||||
for _, pfx := range pfxs {
|
||||
if pfx.Addr().IsLoopback() {
|
||||
continue
|
||||
@@ -501,13 +540,15 @@ func getState(optTSInterfaceName string) (*State, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if s.AnyInterfaceUp() {
|
||||
if buildfeatures.HasUseProxy && 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 proxyFromEnv, ok := feature.HookProxyFromEnvironment.GetOk(); ok {
|
||||
if u, err := proxyFromEnv(req); err == nil && u != nil {
|
||||
s.HTTPProxy = u.String()
|
||||
}
|
||||
}
|
||||
if getPAC != nil {
|
||||
s.PAC = getPAC()
|
||||
@@ -570,6 +611,9 @@ var disableLikelyHomeRouterIPSelf = envknob.RegisterBool("TS_DEBUG_DISABLE_LIKEL
|
||||
// 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 !buildfeatures.HasPortMapper {
|
||||
return
|
||||
}
|
||||
// If we don't have a way to get the home router IP, then we can't do
|
||||
// anything; just return.
|
||||
if likelyHomeRouterIP == nil {
|
||||
@@ -760,8 +804,7 @@ func (m *Monitor) HasCGNATInterface() (bool, error) {
|
||||
hasCGNATInterface := false
|
||||
cgnatRange := tsaddr.CGNATRange()
|
||||
err := ForeachInterface(func(i Interface, pfxs []netip.Prefix) {
|
||||
isTSInterfaceName := m.tsIfName != "" && i.Name == m.tsIfName
|
||||
if hasCGNATInterface || !i.IsUp() || isTSInterfaceName || isTailscaleInterface(i.Name, pfxs) {
|
||||
if hasCGNATInterface || !i.IsUp() || isTailscaleInterface(i.Name, pfxs) {
|
||||
return
|
||||
}
|
||||
for _, pfx := range pfxs {
|
||||
|
||||
Reference in New Issue
Block a user