net/{interfaces,netmon}, all: merge net/interfaces package into net/netmon

In prep for most of the package funcs in net/interfaces to become
methods in a long-lived netmon.Monitor that can cache things.  (Many
of the funcs are very heavy to call regularly, whereas the long-lived
netmon.Monitor can subscribe to things from the OS and remember
answers to questions it's asked regularly later)

Updates tailscale/corp#10910
Updates tailscale/corp#18960
Updates #7967
Updates #3299

Change-Id: Ie4e8dedb70136af2d611b990b865a822cd1797e5
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
pull/8101/merge
Brad Fitzpatrick 3 weeks ago committed by Brad Fitzpatrick
parent 6b95219e3a
commit b9adbe2002

@ -356,7 +356,7 @@ jobs:
# some Android breakages early.
# TODO(bradfitz): better; see https://github.com/tailscale/tailscale/issues/4482
- name: build some
run: ./tool/go install ./net/netns ./ipn/ipnlocal ./wgengine/magicsock/ ./wgengine/ ./wgengine/router/ ./wgengine/netstack ./util/dnsname/ ./ipn/ ./net/interfaces ./wgengine/router/ ./tailcfg/ ./types/logger/ ./net/dns ./hostinfo ./version
run: ./tool/go install ./net/netns ./ipn/ipnlocal ./wgengine/magicsock/ ./wgengine/ ./wgengine/router/ ./wgengine/netstack ./util/dnsname/ ./ipn/ ./net/netmon ./wgengine/router/ ./tailcfg/ ./types/logger/ ./net/dns ./hostinfo ./version
env:
GOOS: android
GOARCH: arm64

@ -20,7 +20,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
github.com/google/uuid from tailscale.com/util/fastuuid
github.com/hdevalence/ed25519consensus from tailscale.com/tka
L github.com/josharian/native from github.com/mdlayher/netlink+
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces+
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/netmon
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
L 💣 github.com/mdlayher/netlink from github.com/google/nftables+
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
@ -47,7 +47,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
github.com/x448/float16 from github.com/fxamacker/cbor/v2
💣 go4.org/mem from tailscale.com/client/tailscale+
go4.org/netipx from tailscale.com/net/tsaddr+
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/netmon+
google.golang.org/protobuf/encoding/protodelim from github.com/prometheus/common/expfmt
google.golang.org/protobuf/encoding/prototext from github.com/prometheus/common/expfmt+
google.golang.org/protobuf/encoding/protowire from google.golang.org/protobuf/encoding/protodelim+
@ -90,17 +90,16 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
tailscale.com/drive from tailscale.com/client/tailscale+
tailscale.com/envknob from tailscale.com/client/tailscale+
tailscale.com/health from tailscale.com/net/tlsdial+
tailscale.com/hostinfo from tailscale.com/net/interfaces+
tailscale.com/hostinfo from tailscale.com/net/netmon+
tailscale.com/ipn from tailscale.com/client/tailscale
tailscale.com/ipn/ipnstate from tailscale.com/client/tailscale+
tailscale.com/metrics from tailscale.com/cmd/derper+
tailscale.com/net/dnscache from tailscale.com/derp/derphttp
tailscale.com/net/flowtrack from tailscale.com/net/packet+
💣 tailscale.com/net/interfaces from tailscale.com/net/netmon+
tailscale.com/net/ktimeout from tailscale.com/cmd/derper
tailscale.com/net/netaddr from tailscale.com/ipn+
tailscale.com/net/netknob from tailscale.com/net/netns
tailscale.com/net/netmon from tailscale.com/derp/derphttp+
💣 tailscale.com/net/netmon from tailscale.com/derp/derphttp+
tailscale.com/net/netns from tailscale.com/derp/derphttp
tailscale.com/net/netutil from tailscale.com/client/tailscale
tailscale.com/net/packet from tailscale.com/wgengine/filter
@ -117,7 +116,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
tailscale.com/syncs from tailscale.com/cmd/derper+
tailscale.com/tailcfg from tailscale.com/client/tailscale+
tailscale.com/tka from tailscale.com/client/tailscale+
W tailscale.com/tsconst from tailscale.com/net/interfaces
W tailscale.com/tsconst from tailscale.com/net/netmon
tailscale.com/tstime from tailscale.com/derp+
tailscale.com/tstime/mono from tailscale.com/tstime/rate
tailscale.com/tstime/rate from tailscale.com/derp+
@ -149,7 +148,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
tailscale.com/util/httpm from tailscale.com/client/tailscale
tailscale.com/util/lineread from tailscale.com/hostinfo+
L tailscale.com/util/linuxfw from tailscale.com/net/netns
tailscale.com/util/mak from tailscale.com/net/interfaces+
tailscale.com/util/mak from tailscale.com/health+
tailscale.com/util/multierr from tailscale.com/health+
tailscale.com/util/nocasemaps from tailscale.com/types/ipproto
tailscale.com/util/set from tailscale.com/derp+

@ -23,7 +23,7 @@ import (
"golang.org/x/net/idna"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
"tailscale.com/net/interfaces"
"tailscale.com/net/netmon"
"tailscale.com/util/dnsname"
)
@ -102,7 +102,7 @@ func runStatus(ctx context.Context, args []string) error {
if err != nil {
return err
}
statusURL := interfaces.HTTPOfListener(ln)
statusURL := netmon.HTTPOfListener(ln)
printf("Serving Tailscale status at %v ...\n", statusURL)
go func() {
<-ctx.Done()

@ -23,7 +23,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
github.com/gorilla/securecookie from github.com/gorilla/csrf
github.com/hdevalence/ed25519consensus from tailscale.com/clientupdate/distsign+
L github.com/josharian/native from github.com/mdlayher/netlink+
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces+
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/netmon
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
github.com/kballard/go-shellquote from tailscale.com/cmd/tailscale/cli
💣 github.com/mattn/go-colorable from tailscale.com/cmd/tailscale/cli
@ -60,7 +60,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
github.com/x448/float16 from github.com/fxamacker/cbor/v2
💣 go4.org/mem from tailscale.com/client/tailscale+
go4.org/netipx from tailscale.com/net/tsaddr+
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/netmon+
k8s.io/client-go/util/homedir from tailscale.com/cmd/tailscale/cli
nhooyr.io/websocket from tailscale.com/control/controlhttp+
nhooyr.io/websocket/internal/errd from nhooyr.io/websocket
@ -99,12 +99,11 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/net/dnscache from tailscale.com/control/controlhttp+
tailscale.com/net/dnsfallback from tailscale.com/control/controlhttp
tailscale.com/net/flowtrack from tailscale.com/net/packet+
💣 tailscale.com/net/interfaces from tailscale.com/cmd/tailscale/cli+
tailscale.com/net/netaddr from tailscale.com/ipn+
tailscale.com/net/netcheck from tailscale.com/cmd/tailscale/cli
tailscale.com/net/neterror from tailscale.com/net/netcheck+
tailscale.com/net/netknob from tailscale.com/net/netns
tailscale.com/net/netmon from tailscale.com/cmd/tailscale/cli+
💣 tailscale.com/net/netmon from tailscale.com/cmd/tailscale/cli+
tailscale.com/net/netns from tailscale.com/derp/derphttp+
tailscale.com/net/netutil from tailscale.com/client/tailscale+
tailscale.com/net/packet from tailscale.com/wgengine/capture+
@ -123,7 +122,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/tailcfg from tailscale.com/client/tailscale+
tailscale.com/tempfork/spf13/cobra from tailscale.com/cmd/tailscale/cli/ffcomplete+
tailscale.com/tka from tailscale.com/client/tailscale+
W tailscale.com/tsconst from tailscale.com/net/interfaces
W tailscale.com/tsconst from tailscale.com/net/netmon
tailscale.com/tstime from tailscale.com/control/controlhttp+
tailscale.com/tstime/mono from tailscale.com/tstime/rate
tailscale.com/tstime/rate from tailscale.com/cmd/tailscale/cli+

@ -119,7 +119,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
github.com/jellydator/ttlcache/v3 from tailscale.com/drive/driveimpl/compositedav
L github.com/jmespath/go-jmespath from github.com/aws/aws-sdk-go-v2/service/ssm
L github.com/josharian/native from github.com/mdlayher/netlink+
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces+
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/netmon
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
github.com/klauspost/compress from github.com/klauspost/compress/zstd
github.com/klauspost/compress/fse from github.com/klauspost/compress/huff0
@ -295,13 +295,12 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/net/dnscache from tailscale.com/control/controlclient+
tailscale.com/net/dnsfallback from tailscale.com/cmd/tailscaled+
tailscale.com/net/flowtrack from tailscale.com/net/packet+
💣 tailscale.com/net/interfaces from tailscale.com/doctor/ethtool+
tailscale.com/net/netaddr from tailscale.com/ipn+
tailscale.com/net/netcheck from tailscale.com/wgengine/magicsock+
tailscale.com/net/neterror from tailscale.com/net/dns/resolver+
tailscale.com/net/netkernelconf from tailscale.com/ipn/ipnlocal
tailscale.com/net/netknob from tailscale.com/logpolicy+
tailscale.com/net/netmon from tailscale.com/cmd/tailscaled+
💣 tailscale.com/net/netmon from tailscale.com/cmd/tailscaled+
tailscale.com/net/netns from tailscale.com/cmd/tailscaled+
W 💣 tailscale.com/net/netstat from tailscale.com/portlist
tailscale.com/net/netutil from tailscale.com/client/tailscale+
@ -333,7 +332,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
LD tailscale.com/tempfork/gliderlabs/ssh from tailscale.com/ssh/tailssh
tailscale.com/tempfork/heap from tailscale.com/wgengine/magicsock
tailscale.com/tka from tailscale.com/client/tailscale+
W tailscale.com/tsconst from tailscale.com/net/interfaces
W tailscale.com/tsconst from tailscale.com/net/netmon
tailscale.com/tsd from tailscale.com/cmd/tailscaled+
tailscale.com/tstime from tailscale.com/control/controlclient+
tailscale.com/tstime/mono from tailscale.com/net/tstun+

@ -8,7 +8,7 @@ import (
"sort"
"github.com/safchain/ethtool"
"tailscale.com/net/interfaces"
"tailscale.com/net/netmon"
"tailscale.com/types/logger"
"tailscale.com/util/set"
)
@ -21,7 +21,7 @@ func ethtoolImpl(logf logger.Logf) error {
}
defer et.Close()
interfaces.ForeachInterface(func(iface interfaces.Interface, _ []netip.Prefix) {
netmon.ForeachInterface(func(iface netmon.Interface, _ []netip.Prefix) {
ilogf := logger.WithPrefix(logf, iface.Name+": ")
features, err := et.Features(iface.Name)
if err == nil {

@ -59,7 +59,6 @@ import (
"tailscale.com/net/dns"
"tailscale.com/net/dnscache"
"tailscale.com/net/dnsfallback"
"tailscale.com/net/interfaces"
"tailscale.com/net/netcheck"
"tailscale.com/net/netkernelconf"
"tailscale.com/net/netmon"
@ -2029,18 +2028,18 @@ var removeFromDefaultRoute = []netip.Prefix{
// Given that "internal" routes don't leave the device, we choose to
// trust them more, allowing access to them when an Exit Node is enabled.
func internalAndExternalInterfaces() (internal, external []netip.Prefix, err error) {
il, err := interfaces.GetList()
il, err := netmon.GetInterfaceList()
if err != nil {
return nil, nil, err
}
return internalAndExternalInterfacesFrom(il, runtime.GOOS)
}
func internalAndExternalInterfacesFrom(il interfaces.List, goos string) (internal, external []netip.Prefix, err error) {
func internalAndExternalInterfacesFrom(il netmon.InterfaceList, goos string) (internal, external []netip.Prefix, err error) {
// We use an IPSetBuilder here to canonicalize the prefixes
// and to remove any duplicate entries.
var internalBuilder, externalBuilder netipx.IPSetBuilder
if err := il.ForeachInterfaceAddress(func(iface interfaces.Interface, pfx netip.Prefix) {
if err := il.ForeachInterfaceAddress(func(iface netmon.Interface, pfx netip.Prefix) {
if tsaddr.IsTailscaleIP(pfx.Addr()) {
return
}
@ -2084,7 +2083,7 @@ func internalAndExternalInterfacesFrom(il interfaces.List, goos string) (interna
func interfaceRoutes() (ips *netipx.IPSet, hostIPs []netip.Addr, err error) {
var b netipx.IPSetBuilder
if err := interfaces.ForeachInterfaceAddress(func(_ interfaces.Interface, pfx netip.Prefix) {
if err := netmon.ForeachInterfaceAddress(func(_ netmon.Interface, pfx netip.Prefix) {
if tsaddr.IsTailscaleIP(pfx.Addr()) {
return
}
@ -3647,7 +3646,7 @@ func shouldUseOneCGNATRoute(logf logger.Logf, controlKnobs *controlknobs.Knobs,
// use fine-grained routes if another interfaces is also using the CGNAT
// IP range.
if versionOS == "macOS" {
hasCGNATInterface, err := interfaces.HasCGNATInterface()
hasCGNATInterface, err := netmon.HasCGNATInterface()
if err != nil {
logf("shouldUseOneCGNATRoute: Could not determine if any interfaces use CGNAT: %v", err)
return false

@ -32,8 +32,8 @@ import (
"tailscale.com/drive/driveimpl"
"tailscale.com/ipn"
"tailscale.com/ipn/store/mem"
"tailscale.com/net/interfaces"
"tailscale.com/net/netcheck"
"tailscale.com/net/netmon"
"tailscale.com/net/tsaddr"
"tailscale.com/tailcfg"
"tailscale.com/tsd"
@ -603,7 +603,7 @@ func TestFileTargets(t *testing.T) {
func TestInternalAndExternalInterfaces(t *testing.T) {
type interfacePrefix struct {
i interfaces.Interface
i netmon.Interface
pfx netip.Prefix
}
@ -613,7 +613,7 @@ func TestInternalAndExternalInterfaces(t *testing.T) {
}
return pfxs
}
iList := func(ips ...interfacePrefix) (il interfaces.List) {
iList := func(ips ...interfacePrefix) (il netmon.InterfaceList) {
for _, ip := range ips {
il = append(il, ip.i)
}
@ -621,7 +621,7 @@ func TestInternalAndExternalInterfaces(t *testing.T) {
}
newInterface := func(name, pfx string, wsl2, loopback bool) interfacePrefix {
ippfx := netip.MustParsePrefix(pfx)
ip := interfaces.Interface{
ip := netmon.Interface{
Interface: &net.Interface{},
AltAddrs: []net.Addr{
netipx.PrefixIPNet(ippfx),
@ -645,7 +645,7 @@ func TestInternalAndExternalInterfaces(t *testing.T) {
tests := []struct {
name string
goos string
il interfaces.List
il netmon.InterfaceList
wantInt []netip.Prefix
wantExt []netip.Prefix
}{

@ -34,7 +34,6 @@ import (
"tailscale.com/health"
"tailscale.com/hostinfo"
"tailscale.com/ipn"
"tailscale.com/net/interfaces"
"tailscale.com/net/netaddr"
"tailscale.com/net/netmon"
"tailscale.com/net/netutil"
@ -445,19 +444,19 @@ func (h *peerAPIHandler) handleServeInterfaces(w http.ResponseWriter, r *http.Re
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintln(w, "<h1>Interfaces</h1>")
if dr, err := interfaces.DefaultRoute(); err == nil {
if dr, err := netmon.DefaultRoute(); err == nil {
fmt.Fprintf(w, "<h3>Default route is %q(%d)</h3>\n", html.EscapeString(dr.InterfaceName), dr.InterfaceIndex)
} else {
fmt.Fprintf(w, "<h3>Could not get the default route: %s</h3>\n", html.EscapeString(err.Error()))
}
if hasCGNATInterface, err := interfaces.HasCGNATInterface(); hasCGNATInterface {
if hasCGNATInterface, err := netmon.HasCGNATInterface(); hasCGNATInterface {
fmt.Fprintln(w, "<p>There is another interface using the CGNAT range.</p>")
} else if err != nil {
fmt.Fprintf(w, "<p>Could not check for CGNAT interfaces: %s</p>\n", html.EscapeString(err.Error()))
}
i, err := interfaces.GetList()
i, err := netmon.GetInterfaceList()
if err != nil {
fmt.Fprintf(w, "Could not get interfaces: %s\n", html.EscapeString(err.Error()))
return
@ -469,12 +468,12 @@ func (h *peerAPIHandler) handleServeInterfaces(w http.ResponseWriter, r *http.Re
fmt.Fprintf(w, "<th>%v</th> ", v)
}
fmt.Fprint(w, "</tr>\n")
i.ForeachInterface(func(iface interfaces.Interface, ipps []netip.Prefix) {
i.ForeachInterface(func(iface netmon.Interface, ipps []netip.Prefix) {
fmt.Fprint(w, "<tr>")
for _, v := range []any{iface.Index, iface.Name, iface.MTU, iface.Flags, ipps} {
fmt.Fprintf(w, "<td>%s</td> ", html.EscapeString(fmt.Sprintf("%v", v)))
}
if extras, err := interfaces.InterfaceDebugExtras(iface.Index); err == nil && extras != "" {
if extras, err := netmon.InterfaceDebugExtras(iface.Index); err == nil && extras != "" {
fmt.Fprintf(w, "<td>%s</td> ", html.EscapeString(extras))
} else if err != nil {
fmt.Fprintf(w, "<td>%s</td> ", html.EscapeString(err.Error()))

@ -1,774 +0,0 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package interfaces contains helpers for looking up system network interfaces.
package interfaces
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 := GetList()
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 List) 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 := GetList()
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 List) 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
}
// List is a list of interfaces on the machine.
type List []Interface
// GetList returns the list of interfaces on the machine.
func GetList() (List, 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
}

@ -388,7 +388,7 @@ const numIncrementalRegions = 3
// makeProbePlan generates the probe plan for a DERPMap, given the most
// recent report and whether IPv6 is configured on an interface.
func makeProbePlan(dm *tailcfg.DERPMap, ifState *State, last *Report) (plan probePlan) {
func makeProbePlan(dm *tailcfg.DERPMap, ifState *netmon.State, last *Report) (plan probePlan) {
if last == nil || len(last.RegionLatency) == 0 {
return makeProbePlanInitial(dm, ifState)
}

@ -7,7 +7,7 @@
//go:build !ios && (darwin || freebsd)
package interfaces
package netmon
import "net"

@ -3,7 +3,7 @@
//go:build ios
package interfaces
package netmon
import (
"log"

@ -1,7 +1,7 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package interfaces
package netmon
import (
"bytes"

@ -6,7 +6,7 @@
//go:build darwin || freebsd
package interfaces
package netmon
import (
"errors"

@ -1,7 +1,7 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package interfaces
package netmon
import (
"fmt"

@ -1,7 +1,7 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package interfaces
package netmon
import (
"errors"

@ -3,7 +3,7 @@
//go:build linux || (darwin && !ts_macext)
package interfaces
package netmon
import (
"testing"

@ -3,7 +3,7 @@
//go:build !linux && !windows && !darwin && !freebsd && !android
package interfaces
package netmon
import "errors"

@ -5,7 +5,7 @@
//go:build freebsd
package interfaces
package netmon
import (
"syscall"

@ -3,7 +3,7 @@
//go:build !android
package interfaces
package netmon
import (
"bufio"

@ -1,7 +1,7 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package interfaces
package netmon
import (
"errors"

@ -1,7 +1,7 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package interfaces
package netmon
import (
"encoding/json"

@ -1,7 +1,7 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package interfaces
package netmon
import (
"log"

@ -1,7 +1,7 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package interfaces
package netmon
import "testing"

@ -14,7 +14,6 @@ import (
"sync"
"time"
"tailscale.com/net/interfaces"
"tailscale.com/types/logger"
"tailscale.com/util/clientmetric"
"tailscale.com/util/set"
@ -162,7 +161,7 @@ func (m *Monitor) InterfaceState() *State {
}
func (m *Monitor) interfaceStateUncached() (*State, error) {
return interfaces.GetState()
return GetState()
}
// SetTailscaleInterfaceName sets the name of the Tailscale interface. For
@ -189,7 +188,7 @@ func (m *Monitor) GatewayAndSelfIP() (gw, myIP netip.Addr, ok bool) {
if m.gwValid {
return m.gw, m.gwSelfIP, true
}
gw, myIP, ok = interfaces.LikelyHomeRouterIP()
gw, myIP, ok = LikelyHomeRouterIP()
changed := false
if ok {
changed = m.gw != gw || m.gwSelfIP != myIP
@ -376,7 +375,7 @@ func (m *Monitor) notifyRuleDeleted(rdm ipRuleDeletedMessage) {
// 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 interfaces.Interface, ips []netip.Prefix) bool {
func (m *Monitor) isInterestingInterface(i Interface, ips []netip.Prefix) bool {
if !m.om.IsInterestingInterface(i.Name) {
return false
}

@ -11,7 +11,6 @@ import (
"testing"
"time"
"tailscale.com/net/interfaces"
"tailscale.com/util/mak"
)
@ -116,8 +115,6 @@ func TestMonitorMode(t *testing.T) {
// tests (*State).IsMajorChangeFrom
func TestIsMajorChangeFrom(t *testing.T) {
type State = interfaces.State
type Interface = interfaces.Interface
tests := []struct {
name string
s1, s2 *State

@ -1,8 +1,774 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package interfaces contains helpers for looking up system network interfaces.
package netmon
import "tailscale.com/net/interfaces"
import (
"bytes"
"fmt"
"net"
"net/http"
"net/netip"
"runtime"
"slices"
"sort"
"strings"
type State = interfaces.State // temporary (2024-04-27) alias during multi-step removal of net/interfaces
"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
}

@ -18,7 +18,6 @@ import (
"golang.org/x/net/route"
"golang.org/x/sys/unix"
"tailscale.com/envknob"
"tailscale.com/net/interfaces"
"tailscale.com/net/netmon"
"tailscale.com/net/tsaddr"
"tailscale.com/types/logger"
@ -62,12 +61,12 @@ func getInterfaceIndex(logf logger.Logf, netMon *netmon.Monitor, address string)
// Helper so we can log errors.
defaultIdx := func() (int, error) {
if netMon == nil {
idx, err := interfaces.DefaultRouteInterfaceIndex()
idx, err := netmon.DefaultRouteInterfaceIndex()
if err != nil {
// It's somewhat common for there to be no default gateway route
// (e.g. on a phone with no connectivity), don't log those errors
// since they are expected.
if !errors.Is(err, interfaces.ErrNoGatewayIndexFound) {
if !errors.Is(err, netmon.ErrNoGatewayIndexFound) {
logf("[unexpected] netns: DefaultRouteInterfaceIndex: %v", err)
}
return -1, err

@ -6,7 +6,7 @@ package netns
import (
"testing"
"tailscale.com/net/interfaces"
"tailscale.com/net/netmon"
)
func TestGetInterfaceIndex(t *testing.T) {
@ -63,7 +63,7 @@ func TestGetInterfaceIndex(t *testing.T) {
t.Skip("no tailscale interface on this machine")
}
defaultIdx, err := interfaces.DefaultRouteInterfaceIndex()
defaultIdx, err := netmon.DefaultRouteInterfaceIndex()
if err != nil {
t.Fatal(err)
}

@ -14,7 +14,6 @@ import (
"golang.org/x/sys/unix"
"tailscale.com/envknob"
"tailscale.com/net/interfaces"
"tailscale.com/net/netmon"
"tailscale.com/types/logger"
"tailscale.com/util/linuxfw"
@ -119,7 +118,7 @@ func setBypassMark(fd uintptr) error {
}
func bindToDevice(fd uintptr) error {
ifc, err := interfaces.DefaultRouteInterface()
ifc, err := netmon.DefaultRouteInterface()
if err != nil {
// Make sure we bind to *some* interface,
// or we could get a routing loop.

@ -11,7 +11,6 @@ import (
"golang.org/x/sys/cpu"
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
"tailscale.com/net/interfaces"
"tailscale.com/net/netmon"
"tailscale.com/types/logger"
)
@ -52,7 +51,7 @@ func controlC(network, address string, c syscall.RawConn) error {
}
if canV4 {
iface, err := interfaces.GetWindowsDefault(windows.AF_INET)
iface, err := netmon.GetWindowsDefault(windows.AF_INET)
if err != nil {
return err
}
@ -62,7 +61,7 @@ func controlC(network, address string, c syscall.RawConn) error {
}
if canV6 {
iface, err := interfaces.GetWindowsDefault(windows.AF_INET6)
iface, err := netmon.GetWindowsDefault(windows.AF_INET6)
if err != nil {
return err
}

@ -21,7 +21,6 @@ import (
"go4.org/mem"
"tailscale.com/control/controlknobs"
"tailscale.com/net/interfaces"
"tailscale.com/net/netaddr"
"tailscale.com/net/neterror"
"tailscale.com/net/netmon"
@ -216,7 +215,7 @@ func NewClient(logf logger.Logf, netMon *netmon.Monitor, debug *DebugKnobs, cont
ret := &Client{
logf: logf,
netMon: netMon,
ipAndGateway: interfaces.LikelyHomeRouterIP, // TODO(bradfitz): move this to netMon
ipAndGateway: netmon.LikelyHomeRouterIP, // TODO(bradfitz): move this to method on netMon
onChange: onChange,
controlKnobs: controlKnobs,
}

@ -17,7 +17,7 @@ import (
"golang.org/x/net/route"
"golang.org/x/sys/unix"
"tailscale.com/net/interfaces"
"tailscale.com/net/netmon"
"tailscale.com/types/logger"
)
@ -80,7 +80,7 @@ func (r RouteEntryBSD) Format(f fmt.State, verb rune) {
// ipFromRMAddr returns a netip.Addr converted from one of the
// route.Inet{4,6}Addr types.
func ipFromRMAddr(ifs map[int]interfaces.Interface, addr any) netip.Addr {
func ipFromRMAddr(ifs map[int]netmon.Interface, addr any) netip.Addr {
switch v := addr.(type) {
case *route.Inet4Addr:
return netip.AddrFrom4(v.IP)
@ -102,7 +102,7 @@ func ipFromRMAddr(ifs map[int]interfaces.Interface, addr any) netip.Addr {
}
// populateGateway populates gateway fields on a RouteEntry/RouteEntryBSD.
func populateGateway(re *RouteEntry, reSys *RouteEntryBSD, ifs map[int]interfaces.Interface, addr any) {
func populateGateway(re *RouteEntry, reSys *RouteEntryBSD, ifs map[int]netmon.Interface, addr any) {
// If the address type has a valid IP, use that.
if ip := ipFromRMAddr(ifs, addr); ip.IsValid() {
re.Gateway = ip
@ -128,7 +128,7 @@ func populateGateway(re *RouteEntry, reSys *RouteEntryBSD, ifs map[int]interface
// populateDestination populates the 'Dst' field on a RouteEntry based on the
// RouteMessage's destination and netmask fields.
func populateDestination(re *RouteEntry, ifs map[int]interfaces.Interface, rm *route.RouteMessage) {
func populateDestination(re *RouteEntry, ifs map[int]netmon.Interface, rm *route.RouteMessage) {
dst := rm.Addrs[unix.RTAX_DST]
if dst == nil {
return
@ -194,7 +194,7 @@ func populateDestination(re *RouteEntry, ifs map[int]interfaces.Interface, rm *r
// routeEntryFromMsg returns a RouteEntry from a single route.Message
// returned by the operating system.
func routeEntryFromMsg(ifsByIdx map[int]interfaces.Interface, msg route.Message) (RouteEntry, bool) {
func routeEntryFromMsg(ifsByIdx map[int]netmon.Interface, msg route.Message) (RouteEntry, bool) {
rm, ok := msg.(*route.RouteMessage)
if !ok {
return RouteEntry{}, false
@ -260,12 +260,12 @@ func routeEntryFromMsg(ifsByIdx map[int]interfaces.Interface, msg route.Message)
func Get(max int) ([]RouteEntry, error) {
// Fetching the list of interfaces can race with fetching our route
// table, but we do it anyway since it's helpful for debugging.
ifs, err := interfaces.GetList()
ifs, err := netmon.GetInterfaceList()
if err != nil {
return nil, err
}
ifsByIdx := make(map[int]interfaces.Interface)
ifsByIdx := make(map[int]netmon.Interface)
for _, iif := range ifs {
ifsByIdx[iif.Index] = iif
}

@ -15,11 +15,11 @@ import (
"golang.org/x/net/route"
"golang.org/x/sys/unix"
"tailscale.com/net/interfaces"
"tailscale.com/net/netmon"
)
func TestRouteEntryFromMsg(t *testing.T) {
ifs := map[int]interfaces.Interface{
ifs := map[int]netmon.Interface{
1: {
Interface: &net.Interface{
Name: "iface0",

@ -13,8 +13,8 @@ import (
"github.com/tailscale/netlink"
"golang.org/x/sys/unix"
"tailscale.com/net/interfaces"
"tailscale.com/net/netaddr"
"tailscale.com/net/netmon"
"tailscale.com/types/logger"
)
@ -141,12 +141,12 @@ func (r RouteEntryLinux) ScopeName() string {
func Get(max int) ([]RouteEntry, error) {
// Fetching the list of interfaces can race with fetching our route
// table, but we do it anyway since it's helpful for debugging.
ifs, err := interfaces.GetList()
ifs, err := netmon.GetInterfaceList()
if err != nil {
return nil, err
}
ifsByIdx := make(map[int]interfaces.Interface)
ifsByIdx := make(map[int]netmon.Interface)
for _, iif := range ifs {
ifsByIdx[iif.Index] = iif
}

@ -15,7 +15,6 @@ import (
"syscall"
"time"
"tailscale.com/net/interfaces"
"tailscale.com/net/netmon"
"tailscale.com/types/logger"
"tailscale.com/util/clientmetric"
@ -89,7 +88,7 @@ func withSockStats(ctx context.Context, label Label, logf logger.Logf) context.C
// had a chance to populate knownInterfaces). In that case, we'll have
// to get the list of interfaces ourselves.
if len(sockStats.knownInterfaces) == 0 {
if ifaces, err := interfaces.GetList(); err == nil {
if ifaces, err := netmon.GetInterfaceList(); err == nil {
for _, iface := range ifaces {
counters.rxBytesByInterface[iface.Index] = &atomic.Uint64{}
counters.txBytesByInterface[iface.Index] = &atomic.Uint64{}

@ -27,7 +27,6 @@ import (
_ "tailscale.com/logtail"
_ "tailscale.com/net/dns"
_ "tailscale.com/net/dnsfallback"
_ "tailscale.com/net/interfaces"
_ "tailscale.com/net/netmon"
_ "tailscale.com/net/netns"
_ "tailscale.com/net/proxymux"

@ -27,7 +27,6 @@ import (
_ "tailscale.com/logtail"
_ "tailscale.com/net/dns"
_ "tailscale.com/net/dnsfallback"
_ "tailscale.com/net/interfaces"
_ "tailscale.com/net/netmon"
_ "tailscale.com/net/netns"
_ "tailscale.com/net/proxymux"

@ -27,7 +27,6 @@ import (
_ "tailscale.com/logtail"
_ "tailscale.com/net/dns"
_ "tailscale.com/net/dnsfallback"
_ "tailscale.com/net/interfaces"
_ "tailscale.com/net/netmon"
_ "tailscale.com/net/netns"
_ "tailscale.com/net/proxymux"

@ -27,7 +27,6 @@ import (
_ "tailscale.com/logtail"
_ "tailscale.com/net/dns"
_ "tailscale.com/net/dnsfallback"
_ "tailscale.com/net/interfaces"
_ "tailscale.com/net/netmon"
_ "tailscale.com/net/netns"
_ "tailscale.com/net/proxymux"

@ -35,7 +35,6 @@ import (
_ "tailscale.com/logtail/backoff"
_ "tailscale.com/net/dns"
_ "tailscale.com/net/dnsfallback"
_ "tailscale.com/net/interfaces"
_ "tailscale.com/net/netmon"
_ "tailscale.com/net/netns"
_ "tailscale.com/net/proxymux"

@ -8,19 +8,19 @@ import (
"runtime"
"testing"
"tailscale.com/net/interfaces"
"tailscale.com/net/netmon"
)
func deriveBindhost(t *testing.T) string {
t.Helper()
ifName, err := interfaces.DefaultRouteInterface()
ifName, err := netmon.DefaultRouteInterface()
if err != nil {
t.Fatal(err)
}
var ret string
err = interfaces.ForeachInterfaceAddress(func(i interfaces.Interface, prefix netip.Prefix) {
err = netmon.ForeachInterfaceAddress(func(i netmon.Interface, prefix netip.Prefix) {
if ret != "" || i.Name != ifName {
return
}

@ -33,7 +33,6 @@ import (
"tailscale.com/hostinfo"
"tailscale.com/ipn/ipnstate"
"tailscale.com/net/connstats"
"tailscale.com/net/interfaces"
"tailscale.com/net/netcheck"
"tailscale.com/net/neterror"
"tailscale.com/net/netmon"
@ -936,7 +935,7 @@ func (c *Conn) determineEndpoints(ctx context.Context) ([]tailcfg.Endpoint, erro
eps = c.endpointTracker.update(time.Now(), eps)
if localAddr := c.pconn4.LocalAddr(); localAddr.IP.IsUnspecified() {
ips, loopback, err := interfaces.LocalAddresses()
ips, loopback, err := netmon.LocalAddresses()
if err != nil {
return nil, err
}

@ -20,7 +20,7 @@ import (
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
"tailscale.com/health"
"tailscale.com/net/interfaces"
"tailscale.com/net/netmon"
"tailscale.com/net/tsaddr"
"tailscale.com/net/tstun"
"tailscale.com/util/multierr"
@ -110,7 +110,7 @@ func monitorDefaultRoutes(tun *tun.NativeTun) (*winipcfg.RouteChangeCallback, er
}
func getDefaultRouteMTU() (uint32, error) {
mtus, err := interfaces.NonTailscaleMTUs()
mtus, err := netmon.NonTailscaleMTUs()
if err != nil {
return 0, err
}

Loading…
Cancel
Save