diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index e2525d0a1..c2c75173b 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -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
diff --git a/cmd/derper/depaware.txt b/cmd/derper/depaware.txt
index b0a84d134..22e910179 100644
--- a/cmd/derper/depaware.txt
+++ b/cmd/derper/depaware.txt
@@ -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+
diff --git a/cmd/tailscale/cli/status.go b/cmd/tailscale/cli/status.go
index 863bed15b..e4dccc247 100644
--- a/cmd/tailscale/cli/status.go
+++ b/cmd/tailscale/cli/status.go
@@ -23,7 +23,7 @@
"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()
diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt
index 268dd1b3e..c7b03ec9a 100644
--- a/cmd/tailscale/depaware.txt
+++ b/cmd/tailscale/depaware.txt
@@ -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+
diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt
index cb5399640..218e48dac 100644
--- a/cmd/tailscaled/depaware.txt
+++ b/cmd/tailscaled/depaware.txt
@@ -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+
diff --git a/doctor/ethtool/ethtool_linux.go b/doctor/ethtool/ethtool_linux.go
index 07a7dd9cc..b8cc08002 100644
--- a/doctor/ethtool/ethtool_linux.go
+++ b/doctor/ethtool/ethtool_linux.go
@@ -8,7 +8,7 @@
"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 {
diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go
index e4ed287fc..8fa684a56 100644
--- a/ipn/ipnlocal/local.go
+++ b/ipn/ipnlocal/local.go
@@ -59,7 +59,6 @@
"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 @@ func (b *LocalBackend) setFilter(f *filter.Filter) {
// 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
diff --git a/ipn/ipnlocal/local_test.go b/ipn/ipnlocal/local_test.go
index 741eeb6ac..4a966b79f 100644
--- a/ipn/ipnlocal/local_test.go
+++ b/ipn/ipnlocal/local_test.go
@@ -32,8 +32,8 @@
"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 @@ type interfacePrefix struct {
}
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 @@ type interfacePrefix struct {
}
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 @@ type interfacePrefix struct {
tests := []struct {
name string
goos string
- il interfaces.List
+ il netmon.InterfaceList
wantInt []netip.Prefix
wantExt []netip.Prefix
}{
diff --git a/ipn/ipnlocal/peerapi.go b/ipn/ipnlocal/peerapi.go
index 9a2657034..e4d0d8ac6 100644
--- a/ipn/ipnlocal/peerapi.go
+++ b/ipn/ipnlocal/peerapi.go
@@ -34,7 +34,6 @@
"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, "
Interfaces
")
- if dr, err := interfaces.DefaultRoute(); err == nil {
+ if dr, err := netmon.DefaultRoute(); err == nil {
fmt.Fprintf(w, "Default route is %q(%d)
\n", html.EscapeString(dr.InterfaceName), dr.InterfaceIndex)
} else {
fmt.Fprintf(w, "Could not get the default route: %s
\n", html.EscapeString(err.Error()))
}
- if hasCGNATInterface, err := interfaces.HasCGNATInterface(); hasCGNATInterface {
+ if hasCGNATInterface, err := netmon.HasCGNATInterface(); hasCGNATInterface {
fmt.Fprintln(w, "There is another interface using the CGNAT range.
")
} else if err != nil {
fmt.Fprintf(w, "Could not check for CGNAT interfaces: %s
\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, "%v | ", v)
}
fmt.Fprint(w, "\n")
- i.ForeachInterface(func(iface interfaces.Interface, ipps []netip.Prefix) {
+ i.ForeachInterface(func(iface netmon.Interface, ipps []netip.Prefix) {
fmt.Fprint(w, "")
for _, v := range []any{iface.Index, iface.Name, iface.MTU, iface.Flags, ipps} {
fmt.Fprintf(w, "%s | ", 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, "%s | ", html.EscapeString(extras))
} else if err != nil {
fmt.Fprintf(w, "%s | ", html.EscapeString(err.Error()))
diff --git a/net/interfaces/interfaces.go b/net/interfaces/interfaces.go
deleted file mode 100644
index 0682374a0..000000000
--- a/net/interfaces/interfaces.go
+++ /dev/null
@@ -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
-}
diff --git a/net/netcheck/netcheck.go b/net/netcheck/netcheck.go
index a987df6f3..efcc1a5de 100644
--- a/net/netcheck/netcheck.go
+++ b/net/netcheck/netcheck.go
@@ -388,7 +388,7 @@ func sortRegions(dm *tailcfg.DERPMap, last *Report) (prev []*tailcfg.DERPRegion)
// 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)
}
diff --git a/net/interfaces/defaultroute_bsd.go b/net/netmon/defaultroute_bsd.go
similarity index 96%
rename from net/interfaces/defaultroute_bsd.go
rename to net/netmon/defaultroute_bsd.go
index 6efd53f82..b66a84636 100644
--- a/net/interfaces/defaultroute_bsd.go
+++ b/net/netmon/defaultroute_bsd.go
@@ -7,7 +7,7 @@
//go:build !ios && (darwin || freebsd)
-package interfaces
+package netmon
import "net"
diff --git a/net/interfaces/defaultroute_ios.go b/net/netmon/defaultroute_ios.go
similarity index 99%
rename from net/interfaces/defaultroute_ios.go
rename to net/netmon/defaultroute_ios.go
index f8011253b..63d5e49ce 100644
--- a/net/interfaces/defaultroute_ios.go
+++ b/net/netmon/defaultroute_ios.go
@@ -3,7 +3,7 @@
//go:build ios
-package interfaces
+package netmon
import (
"log"
diff --git a/net/interfaces/interfaces_android.go b/net/netmon/interfaces_android.go
similarity index 99%
rename from net/interfaces/interfaces_android.go
rename to net/netmon/interfaces_android.go
index 21a205f5a..a96423eb6 100644
--- a/net/interfaces/interfaces_android.go
+++ b/net/netmon/interfaces_android.go
@@ -1,7 +1,7 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
-package interfaces
+package netmon
import (
"bytes"
diff --git a/net/interfaces/interfaces_bsd.go b/net/netmon/interfaces_bsd.go
similarity index 99%
rename from net/interfaces/interfaces_bsd.go
rename to net/netmon/interfaces_bsd.go
index 76d265681..86bc5615d 100644
--- a/net/interfaces/interfaces_bsd.go
+++ b/net/netmon/interfaces_bsd.go
@@ -6,7 +6,7 @@
//go:build darwin || freebsd
-package interfaces
+package netmon
import (
"errors"
diff --git a/net/interfaces/interfaces_darwin.go b/net/netmon/interfaces_darwin.go
similarity index 99%
rename from net/interfaces/interfaces_darwin.go
rename to net/netmon/interfaces_darwin.go
index 6c68844fe..b175f980a 100644
--- a/net/interfaces/interfaces_darwin.go
+++ b/net/netmon/interfaces_darwin.go
@@ -1,7 +1,7 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
-package interfaces
+package netmon
import (
"fmt"
diff --git a/net/interfaces/interfaces_darwin_test.go b/net/netmon/interfaces_darwin_test.go
similarity index 99%
rename from net/interfaces/interfaces_darwin_test.go
rename to net/netmon/interfaces_darwin_test.go
index bcc508e18..d34040d60 100644
--- a/net/interfaces/interfaces_darwin_test.go
+++ b/net/netmon/interfaces_darwin_test.go
@@ -1,7 +1,7 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
-package interfaces
+package netmon
import (
"errors"
diff --git a/net/interfaces/interfaces_default_route_test.go b/net/netmon/interfaces_default_route_test.go
similarity index 95%
rename from net/interfaces/interfaces_default_route_test.go
rename to net/netmon/interfaces_default_route_test.go
index 4f81ed167..e231eea9a 100644
--- a/net/interfaces/interfaces_default_route_test.go
+++ b/net/netmon/interfaces_default_route_test.go
@@ -3,7 +3,7 @@
//go:build linux || (darwin && !ts_macext)
-package interfaces
+package netmon
import (
"testing"
diff --git a/net/interfaces/interfaces_defaultrouteif_todo.go b/net/netmon/interfaces_defaultrouteif_todo.go
similarity index 93%
rename from net/interfaces/interfaces_defaultrouteif_todo.go
rename to net/netmon/interfaces_defaultrouteif_todo.go
index f8c636e9d..df0820fa9 100644
--- a/net/interfaces/interfaces_defaultrouteif_todo.go
+++ b/net/netmon/interfaces_defaultrouteif_todo.go
@@ -3,7 +3,7 @@
//go:build !linux && !windows && !darwin && !freebsd && !android
-package interfaces
+package netmon
import "errors"
diff --git a/net/interfaces/interfaces_freebsd.go b/net/netmon/interfaces_freebsd.go
similarity index 96%
rename from net/interfaces/interfaces_freebsd.go
rename to net/netmon/interfaces_freebsd.go
index 085db0d96..654eb5316 100644
--- a/net/interfaces/interfaces_freebsd.go
+++ b/net/netmon/interfaces_freebsd.go
@@ -5,7 +5,7 @@
//go:build freebsd
-package interfaces
+package netmon
import (
"syscall"
diff --git a/net/interfaces/interfaces_linux.go b/net/netmon/interfaces_linux.go
similarity index 99%
rename from net/interfaces/interfaces_linux.go
rename to net/netmon/interfaces_linux.go
index cb164b2f8..ef7dcaaca 100644
--- a/net/interfaces/interfaces_linux.go
+++ b/net/netmon/interfaces_linux.go
@@ -3,7 +3,7 @@
//go:build !android
-package interfaces
+package netmon
import (
"bufio"
diff --git a/net/interfaces/interfaces_linux_test.go b/net/netmon/interfaces_linux_test.go
similarity index 99%
rename from net/interfaces/interfaces_linux_test.go
rename to net/netmon/interfaces_linux_test.go
index 98f9853f5..4f740ac28 100644
--- a/net/interfaces/interfaces_linux_test.go
+++ b/net/netmon/interfaces_linux_test.go
@@ -1,7 +1,7 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
-package interfaces
+package netmon
import (
"errors"
diff --git a/net/interfaces/interfaces_test.go b/net/netmon/interfaces_test.go
similarity index 99%
rename from net/interfaces/interfaces_test.go
rename to net/netmon/interfaces_test.go
index 08bfc2b68..edd4f6d6e 100644
--- a/net/interfaces/interfaces_test.go
+++ b/net/netmon/interfaces_test.go
@@ -1,7 +1,7 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
-package interfaces
+package netmon
import (
"encoding/json"
diff --git a/net/interfaces/interfaces_windows.go b/net/netmon/interfaces_windows.go
similarity index 99%
rename from net/interfaces/interfaces_windows.go
rename to net/netmon/interfaces_windows.go
index eaf759234..00b686e59 100644
--- a/net/interfaces/interfaces_windows.go
+++ b/net/netmon/interfaces_windows.go
@@ -1,7 +1,7 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
-package interfaces
+package netmon
import (
"log"
diff --git a/net/interfaces/interfaces_windows_test.go b/net/netmon/interfaces_windows_test.go
similarity index 93%
rename from net/interfaces/interfaces_windows_test.go
rename to net/netmon/interfaces_windows_test.go
index b02a58d03..91db7bcc5 100644
--- a/net/interfaces/interfaces_windows_test.go
+++ b/net/netmon/interfaces_windows_test.go
@@ -1,7 +1,7 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
-package interfaces
+package netmon
import "testing"
diff --git a/net/netmon/netmon.go b/net/netmon/netmon.go
index 0b35bc301..47b540d6a 100644
--- a/net/netmon/netmon.go
+++ b/net/netmon/netmon.go
@@ -14,7 +14,6 @@
"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
}
diff --git a/net/netmon/netmon_test.go b/net/netmon/netmon_test.go
index 9c08b0df1..ce55d1946 100644
--- a/net/netmon/netmon_test.go
+++ b/net/netmon/netmon_test.go
@@ -11,7 +11,6 @@
"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
diff --git a/net/netmon/state.go b/net/netmon/state.go
index 33d4a3fb3..55aef478b 100644
--- a/net/netmon/state.go
+++ b/net/netmon/state.go
@@ -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
+}
diff --git a/net/netns/netns_darwin.go b/net/netns/netns_darwin.go
index b9395f734..5a096d845 100644
--- a/net/netns/netns_darwin.go
+++ b/net/netns/netns_darwin.go
@@ -18,7 +18,6 @@
"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
diff --git a/net/netns/netns_darwin_test.go b/net/netns/netns_darwin_test.go
index 17d1f9945..2030c169e 100644
--- a/net/netns/netns_darwin_test.go
+++ b/net/netns/netns_darwin_test.go
@@ -6,7 +6,7 @@
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)
}
diff --git a/net/netns/netns_linux.go b/net/netns/netns_linux.go
index bac14e9d7..aaf6dab4a 100644
--- a/net/netns/netns_linux.go
+++ b/net/netns/netns_linux.go
@@ -14,7 +14,6 @@
"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.
diff --git a/net/netns/netns_windows.go b/net/netns/netns_windows.go
index 8aa1da18d..fb842eeac 100644
--- a/net/netns/netns_windows.go
+++ b/net/netns/netns_windows.go
@@ -11,7 +11,6 @@
"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
}
diff --git a/net/portmapper/portmapper.go b/net/portmapper/portmapper.go
index 8c42b17fe..cc0542c17 100644
--- a/net/portmapper/portmapper.go
+++ b/net/portmapper/portmapper.go
@@ -21,7 +21,6 @@
"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,
}
diff --git a/net/routetable/routetable_bsd.go b/net/routetable/routetable_bsd.go
index ff3e75ec2..1de1a2734 100644
--- a/net/routetable/routetable_bsd.go
+++ b/net/routetable/routetable_bsd.go
@@ -17,7 +17,7 @@
"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
}
diff --git a/net/routetable/routetable_bsd_test.go b/net/routetable/routetable_bsd_test.go
index fc134617f..29493d59b 100644
--- a/net/routetable/routetable_bsd_test.go
+++ b/net/routetable/routetable_bsd_test.go
@@ -15,11 +15,11 @@
"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",
diff --git a/net/routetable/routetable_linux.go b/net/routetable/routetable_linux.go
index d13e7d8be..88dc8535a 100644
--- a/net/routetable/routetable_linux.go
+++ b/net/routetable/routetable_linux.go
@@ -13,8 +13,8 @@
"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
}
diff --git a/net/sockstats/sockstats_tsgo.go b/net/sockstats/sockstats_tsgo.go
index d94f279ad..2d1ccd5a3 100644
--- a/net/sockstats/sockstats_tsgo.go
+++ b/net/sockstats/sockstats_tsgo.go
@@ -15,7 +15,6 @@
"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{}
diff --git a/tstest/integration/tailscaled_deps_test_darwin.go b/tstest/integration/tailscaled_deps_test_darwin.go
index 4ab429ff6..6d366d46e 100644
--- a/tstest/integration/tailscaled_deps_test_darwin.go
+++ b/tstest/integration/tailscaled_deps_test_darwin.go
@@ -27,7 +27,6 @@
_ "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"
diff --git a/tstest/integration/tailscaled_deps_test_freebsd.go b/tstest/integration/tailscaled_deps_test_freebsd.go
index 4ab429ff6..6d366d46e 100644
--- a/tstest/integration/tailscaled_deps_test_freebsd.go
+++ b/tstest/integration/tailscaled_deps_test_freebsd.go
@@ -27,7 +27,6 @@
_ "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"
diff --git a/tstest/integration/tailscaled_deps_test_linux.go b/tstest/integration/tailscaled_deps_test_linux.go
index 4ab429ff6..6d366d46e 100644
--- a/tstest/integration/tailscaled_deps_test_linux.go
+++ b/tstest/integration/tailscaled_deps_test_linux.go
@@ -27,7 +27,6 @@
_ "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"
diff --git a/tstest/integration/tailscaled_deps_test_openbsd.go b/tstest/integration/tailscaled_deps_test_openbsd.go
index 4ab429ff6..6d366d46e 100644
--- a/tstest/integration/tailscaled_deps_test_openbsd.go
+++ b/tstest/integration/tailscaled_deps_test_openbsd.go
@@ -27,7 +27,6 @@
_ "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"
diff --git a/tstest/integration/tailscaled_deps_test_windows.go b/tstest/integration/tailscaled_deps_test_windows.go
index 7607f75d2..015bf8284 100644
--- a/tstest/integration/tailscaled_deps_test_windows.go
+++ b/tstest/integration/tailscaled_deps_test_windows.go
@@ -35,7 +35,6 @@
_ "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"
diff --git a/tstest/integration/vms/derive_bindhost_test.go b/tstest/integration/vms/derive_bindhost_test.go
index b80e29030..728f60c01 100644
--- a/tstest/integration/vms/derive_bindhost_test.go
+++ b/tstest/integration/vms/derive_bindhost_test.go
@@ -8,19 +8,19 @@
"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
}
diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go
index c5693e28a..f30682f13 100644
--- a/wgengine/magicsock/magicsock.go
+++ b/wgengine/magicsock/magicsock.go
@@ -33,7 +33,6 @@
"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
}
diff --git a/wgengine/router/ifconfig_windows.go b/wgengine/router/ifconfig_windows.go
index 7901ba211..b9b1873b8 100644
--- a/wgengine/router/ifconfig_windows.go
+++ b/wgengine/router/ifconfig_windows.go
@@ -20,7 +20,7 @@
"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
}