mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 11:05:45 +00:00
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>
This commit is contained in:
parent
6b95219e3a
commit
b9adbe2002
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -356,7 +356,7 @@ jobs:
|
|||||||
# some Android breakages early.
|
# some Android breakages early.
|
||||||
# TODO(bradfitz): better; see https://github.com/tailscale/tailscale/issues/4482
|
# TODO(bradfitz): better; see https://github.com/tailscale/tailscale/issues/4482
|
||||||
- name: build some
|
- 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:
|
env:
|
||||||
GOOS: android
|
GOOS: android
|
||||||
GOARCH: arm64
|
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/google/uuid from tailscale.com/util/fastuuid
|
||||||
github.com/hdevalence/ed25519consensus from tailscale.com/tka
|
github.com/hdevalence/ed25519consensus from tailscale.com/tka
|
||||||
L github.com/josharian/native from github.com/mdlayher/netlink+
|
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/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
||||||
L 💣 github.com/mdlayher/netlink from github.com/google/nftables+
|
L 💣 github.com/mdlayher/netlink from github.com/google/nftables+
|
||||||
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
|
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
|
github.com/x448/float16 from github.com/fxamacker/cbor/v2
|
||||||
💣 go4.org/mem from tailscale.com/client/tailscale+
|
💣 go4.org/mem from tailscale.com/client/tailscale+
|
||||||
go4.org/netipx from tailscale.com/net/tsaddr+
|
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/protodelim from github.com/prometheus/common/expfmt
|
||||||
google.golang.org/protobuf/encoding/prototext 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+
|
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/drive from tailscale.com/client/tailscale+
|
||||||
tailscale.com/envknob from tailscale.com/client/tailscale+
|
tailscale.com/envknob from tailscale.com/client/tailscale+
|
||||||
tailscale.com/health from tailscale.com/net/tlsdial+
|
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 from tailscale.com/client/tailscale
|
||||||
tailscale.com/ipn/ipnstate from tailscale.com/client/tailscale+
|
tailscale.com/ipn/ipnstate from tailscale.com/client/tailscale+
|
||||||
tailscale.com/metrics from tailscale.com/cmd/derper+
|
tailscale.com/metrics from tailscale.com/cmd/derper+
|
||||||
tailscale.com/net/dnscache from tailscale.com/derp/derphttp
|
tailscale.com/net/dnscache from tailscale.com/derp/derphttp
|
||||||
tailscale.com/net/flowtrack from tailscale.com/net/packet+
|
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/ktimeout from tailscale.com/cmd/derper
|
||||||
tailscale.com/net/netaddr from tailscale.com/ipn+
|
tailscale.com/net/netaddr from tailscale.com/ipn+
|
||||||
tailscale.com/net/netknob from tailscale.com/net/netns
|
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/netns from tailscale.com/derp/derphttp
|
||||||
tailscale.com/net/netutil from tailscale.com/client/tailscale
|
tailscale.com/net/netutil from tailscale.com/client/tailscale
|
||||||
tailscale.com/net/packet from tailscale.com/wgengine/filter
|
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/syncs from tailscale.com/cmd/derper+
|
||||||
tailscale.com/tailcfg from tailscale.com/client/tailscale+
|
tailscale.com/tailcfg from tailscale.com/client/tailscale+
|
||||||
tailscale.com/tka 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 from tailscale.com/derp+
|
||||||
tailscale.com/tstime/mono from tailscale.com/tstime/rate
|
tailscale.com/tstime/mono from tailscale.com/tstime/rate
|
||||||
tailscale.com/tstime/rate from tailscale.com/derp+
|
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/httpm from tailscale.com/client/tailscale
|
||||||
tailscale.com/util/lineread from tailscale.com/hostinfo+
|
tailscale.com/util/lineread from tailscale.com/hostinfo+
|
||||||
L tailscale.com/util/linuxfw from tailscale.com/net/netns
|
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/multierr from tailscale.com/health+
|
||||||
tailscale.com/util/nocasemaps from tailscale.com/types/ipproto
|
tailscale.com/util/nocasemaps from tailscale.com/types/ipproto
|
||||||
tailscale.com/util/set from tailscale.com/derp+
|
tailscale.com/util/set from tailscale.com/derp+
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
"golang.org/x/net/idna"
|
"golang.org/x/net/idna"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
"tailscale.com/net/interfaces"
|
"tailscale.com/net/netmon"
|
||||||
"tailscale.com/util/dnsname"
|
"tailscale.com/util/dnsname"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -102,7 +102,7 @@ func runStatus(ctx context.Context, args []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
statusURL := interfaces.HTTPOfListener(ln)
|
statusURL := netmon.HTTPOfListener(ln)
|
||||||
printf("Serving Tailscale status at %v ...\n", statusURL)
|
printf("Serving Tailscale status at %v ...\n", statusURL)
|
||||||
go func() {
|
go func() {
|
||||||
<-ctx.Done()
|
<-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/gorilla/securecookie from github.com/gorilla/csrf
|
||||||
github.com/hdevalence/ed25519consensus from tailscale.com/clientupdate/distsign+
|
github.com/hdevalence/ed25519consensus from tailscale.com/clientupdate/distsign+
|
||||||
L github.com/josharian/native from github.com/mdlayher/netlink+
|
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/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
||||||
github.com/kballard/go-shellquote from tailscale.com/cmd/tailscale/cli
|
github.com/kballard/go-shellquote from tailscale.com/cmd/tailscale/cli
|
||||||
💣 github.com/mattn/go-colorable 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
|
github.com/x448/float16 from github.com/fxamacker/cbor/v2
|
||||||
💣 go4.org/mem from tailscale.com/client/tailscale+
|
💣 go4.org/mem from tailscale.com/client/tailscale+
|
||||||
go4.org/netipx from tailscale.com/net/tsaddr+
|
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
|
k8s.io/client-go/util/homedir from tailscale.com/cmd/tailscale/cli
|
||||||
nhooyr.io/websocket from tailscale.com/control/controlhttp+
|
nhooyr.io/websocket from tailscale.com/control/controlhttp+
|
||||||
nhooyr.io/websocket/internal/errd from nhooyr.io/websocket
|
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/dnscache from tailscale.com/control/controlhttp+
|
||||||
tailscale.com/net/dnsfallback 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/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/netaddr from tailscale.com/ipn+
|
||||||
tailscale.com/net/netcheck from tailscale.com/cmd/tailscale/cli
|
tailscale.com/net/netcheck from tailscale.com/cmd/tailscale/cli
|
||||||
tailscale.com/net/neterror from tailscale.com/net/netcheck+
|
tailscale.com/net/neterror from tailscale.com/net/netcheck+
|
||||||
tailscale.com/net/netknob from tailscale.com/net/netns
|
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/netns from tailscale.com/derp/derphttp+
|
||||||
tailscale.com/net/netutil from tailscale.com/client/tailscale+
|
tailscale.com/net/netutil from tailscale.com/client/tailscale+
|
||||||
tailscale.com/net/packet from tailscale.com/wgengine/capture+
|
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/tailcfg from tailscale.com/client/tailscale+
|
||||||
tailscale.com/tempfork/spf13/cobra from tailscale.com/cmd/tailscale/cli/ffcomplete+
|
tailscale.com/tempfork/spf13/cobra from tailscale.com/cmd/tailscale/cli/ffcomplete+
|
||||||
tailscale.com/tka 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/control/controlhttp+
|
tailscale.com/tstime from tailscale.com/control/controlhttp+
|
||||||
tailscale.com/tstime/mono from tailscale.com/tstime/rate
|
tailscale.com/tstime/mono from tailscale.com/tstime/rate
|
||||||
tailscale.com/tstime/rate from tailscale.com/cmd/tailscale/cli+
|
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
|
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/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/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/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
||||||
github.com/klauspost/compress from github.com/klauspost/compress/zstd
|
github.com/klauspost/compress from github.com/klauspost/compress/zstd
|
||||||
github.com/klauspost/compress/fse from github.com/klauspost/compress/huff0
|
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/dnscache from tailscale.com/control/controlclient+
|
||||||
tailscale.com/net/dnsfallback from tailscale.com/cmd/tailscaled+
|
tailscale.com/net/dnsfallback from tailscale.com/cmd/tailscaled+
|
||||||
tailscale.com/net/flowtrack from tailscale.com/net/packet+
|
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/netaddr from tailscale.com/ipn+
|
||||||
tailscale.com/net/netcheck from tailscale.com/wgengine/magicsock+
|
tailscale.com/net/netcheck from tailscale.com/wgengine/magicsock+
|
||||||
tailscale.com/net/neterror from tailscale.com/net/dns/resolver+
|
tailscale.com/net/neterror from tailscale.com/net/dns/resolver+
|
||||||
tailscale.com/net/netkernelconf from tailscale.com/ipn/ipnlocal
|
tailscale.com/net/netkernelconf from tailscale.com/ipn/ipnlocal
|
||||||
tailscale.com/net/netknob from tailscale.com/logpolicy+
|
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+
|
tailscale.com/net/netns from tailscale.com/cmd/tailscaled+
|
||||||
W 💣 tailscale.com/net/netstat from tailscale.com/portlist
|
W 💣 tailscale.com/net/netstat from tailscale.com/portlist
|
||||||
tailscale.com/net/netutil from tailscale.com/client/tailscale+
|
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
|
LD tailscale.com/tempfork/gliderlabs/ssh from tailscale.com/ssh/tailssh
|
||||||
tailscale.com/tempfork/heap from tailscale.com/wgengine/magicsock
|
tailscale.com/tempfork/heap from tailscale.com/wgengine/magicsock
|
||||||
tailscale.com/tka 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/tsd from tailscale.com/cmd/tailscaled+
|
tailscale.com/tsd from tailscale.com/cmd/tailscaled+
|
||||||
tailscale.com/tstime from tailscale.com/control/controlclient+
|
tailscale.com/tstime from tailscale.com/control/controlclient+
|
||||||
tailscale.com/tstime/mono from tailscale.com/net/tstun+
|
tailscale.com/tstime/mono from tailscale.com/net/tstun+
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/safchain/ethtool"
|
"github.com/safchain/ethtool"
|
||||||
"tailscale.com/net/interfaces"
|
"tailscale.com/net/netmon"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/util/set"
|
"tailscale.com/util/set"
|
||||||
)
|
)
|
||||||
@ -21,7 +21,7 @@ func ethtoolImpl(logf logger.Logf) error {
|
|||||||
}
|
}
|
||||||
defer et.Close()
|
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+": ")
|
ilogf := logger.WithPrefix(logf, iface.Name+": ")
|
||||||
features, err := et.Features(iface.Name)
|
features, err := et.Features(iface.Name)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -59,7 +59,6 @@
|
|||||||
"tailscale.com/net/dns"
|
"tailscale.com/net/dns"
|
||||||
"tailscale.com/net/dnscache"
|
"tailscale.com/net/dnscache"
|
||||||
"tailscale.com/net/dnsfallback"
|
"tailscale.com/net/dnsfallback"
|
||||||
"tailscale.com/net/interfaces"
|
|
||||||
"tailscale.com/net/netcheck"
|
"tailscale.com/net/netcheck"
|
||||||
"tailscale.com/net/netkernelconf"
|
"tailscale.com/net/netkernelconf"
|
||||||
"tailscale.com/net/netmon"
|
"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
|
// 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.
|
// trust them more, allowing access to them when an Exit Node is enabled.
|
||||||
func internalAndExternalInterfaces() (internal, external []netip.Prefix, err error) {
|
func internalAndExternalInterfaces() (internal, external []netip.Prefix, err error) {
|
||||||
il, err := interfaces.GetList()
|
il, err := netmon.GetInterfaceList()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
return internalAndExternalInterfacesFrom(il, runtime.GOOS)
|
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
|
// We use an IPSetBuilder here to canonicalize the prefixes
|
||||||
// and to remove any duplicate entries.
|
// and to remove any duplicate entries.
|
||||||
var internalBuilder, externalBuilder netipx.IPSetBuilder
|
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()) {
|
if tsaddr.IsTailscaleIP(pfx.Addr()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -2084,7 +2083,7 @@ func internalAndExternalInterfacesFrom(il interfaces.List, goos string) (interna
|
|||||||
|
|
||||||
func interfaceRoutes() (ips *netipx.IPSet, hostIPs []netip.Addr, err error) {
|
func interfaceRoutes() (ips *netipx.IPSet, hostIPs []netip.Addr, err error) {
|
||||||
var b netipx.IPSetBuilder
|
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()) {
|
if tsaddr.IsTailscaleIP(pfx.Addr()) {
|
||||||
return
|
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
|
// use fine-grained routes if another interfaces is also using the CGNAT
|
||||||
// IP range.
|
// IP range.
|
||||||
if versionOS == "macOS" {
|
if versionOS == "macOS" {
|
||||||
hasCGNATInterface, err := interfaces.HasCGNATInterface()
|
hasCGNATInterface, err := netmon.HasCGNATInterface()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logf("shouldUseOneCGNATRoute: Could not determine if any interfaces use CGNAT: %v", err)
|
logf("shouldUseOneCGNATRoute: Could not determine if any interfaces use CGNAT: %v", err)
|
||||||
return false
|
return false
|
||||||
|
@ -32,8 +32,8 @@
|
|||||||
"tailscale.com/drive/driveimpl"
|
"tailscale.com/drive/driveimpl"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/ipn/store/mem"
|
"tailscale.com/ipn/store/mem"
|
||||||
"tailscale.com/net/interfaces"
|
|
||||||
"tailscale.com/net/netcheck"
|
"tailscale.com/net/netcheck"
|
||||||
|
"tailscale.com/net/netmon"
|
||||||
"tailscale.com/net/tsaddr"
|
"tailscale.com/net/tsaddr"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/tsd"
|
"tailscale.com/tsd"
|
||||||
@ -603,7 +603,7 @@ func TestFileTargets(t *testing.T) {
|
|||||||
|
|
||||||
func TestInternalAndExternalInterfaces(t *testing.T) {
|
func TestInternalAndExternalInterfaces(t *testing.T) {
|
||||||
type interfacePrefix struct {
|
type interfacePrefix struct {
|
||||||
i interfaces.Interface
|
i netmon.Interface
|
||||||
pfx netip.Prefix
|
pfx netip.Prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -613,7 +613,7 @@ type interfacePrefix struct {
|
|||||||
}
|
}
|
||||||
return pfxs
|
return pfxs
|
||||||
}
|
}
|
||||||
iList := func(ips ...interfacePrefix) (il interfaces.List) {
|
iList := func(ips ...interfacePrefix) (il netmon.InterfaceList) {
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
il = append(il, ip.i)
|
il = append(il, ip.i)
|
||||||
}
|
}
|
||||||
@ -621,7 +621,7 @@ type interfacePrefix struct {
|
|||||||
}
|
}
|
||||||
newInterface := func(name, pfx string, wsl2, loopback bool) interfacePrefix {
|
newInterface := func(name, pfx string, wsl2, loopback bool) interfacePrefix {
|
||||||
ippfx := netip.MustParsePrefix(pfx)
|
ippfx := netip.MustParsePrefix(pfx)
|
||||||
ip := interfaces.Interface{
|
ip := netmon.Interface{
|
||||||
Interface: &net.Interface{},
|
Interface: &net.Interface{},
|
||||||
AltAddrs: []net.Addr{
|
AltAddrs: []net.Addr{
|
||||||
netipx.PrefixIPNet(ippfx),
|
netipx.PrefixIPNet(ippfx),
|
||||||
@ -645,7 +645,7 @@ type interfacePrefix struct {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
goos string
|
goos string
|
||||||
il interfaces.List
|
il netmon.InterfaceList
|
||||||
wantInt []netip.Prefix
|
wantInt []netip.Prefix
|
||||||
wantExt []netip.Prefix
|
wantExt []netip.Prefix
|
||||||
}{
|
}{
|
||||||
|
@ -34,7 +34,6 @@
|
|||||||
"tailscale.com/health"
|
"tailscale.com/health"
|
||||||
"tailscale.com/hostinfo"
|
"tailscale.com/hostinfo"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/net/interfaces"
|
|
||||||
"tailscale.com/net/netaddr"
|
"tailscale.com/net/netaddr"
|
||||||
"tailscale.com/net/netmon"
|
"tailscale.com/net/netmon"
|
||||||
"tailscale.com/net/netutil"
|
"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")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
fmt.Fprintln(w, "<h1>Interfaces</h1>")
|
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)
|
fmt.Fprintf(w, "<h3>Default route is %q(%d)</h3>\n", html.EscapeString(dr.InterfaceName), dr.InterfaceIndex)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(w, "<h3>Could not get the default route: %s</h3>\n", html.EscapeString(err.Error()))
|
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>")
|
fmt.Fprintln(w, "<p>There is another interface using the CGNAT range.</p>")
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
fmt.Fprintf(w, "<p>Could not check for CGNAT interfaces: %s</p>\n", html.EscapeString(err.Error()))
|
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 {
|
if err != nil {
|
||||||
fmt.Fprintf(w, "Could not get interfaces: %s\n", html.EscapeString(err.Error()))
|
fmt.Fprintf(w, "Could not get interfaces: %s\n", html.EscapeString(err.Error()))
|
||||||
return
|
return
|
||||||
@ -469,12 +468,12 @@ func (h *peerAPIHandler) handleServeInterfaces(w http.ResponseWriter, r *http.Re
|
|||||||
fmt.Fprintf(w, "<th>%v</th> ", v)
|
fmt.Fprintf(w, "<th>%v</th> ", v)
|
||||||
}
|
}
|
||||||
fmt.Fprint(w, "</tr>\n")
|
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>")
|
fmt.Fprint(w, "<tr>")
|
||||||
for _, v := range []any{iface.Index, iface.Name, iface.MTU, iface.Flags, ipps} {
|
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)))
|
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))
|
fmt.Fprintf(w, "<td>%s</td> ", html.EscapeString(extras))
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
fmt.Fprintf(w, "<td>%s</td> ", html.EscapeString(err.Error()))
|
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 @@ func sortRegions(dm *tailcfg.DERPMap, last *Report) (prev []*tailcfg.DERPRegion)
|
|||||||
|
|
||||||
// makeProbePlan generates the probe plan for a DERPMap, given the most
|
// makeProbePlan generates the probe plan for a DERPMap, given the most
|
||||||
// recent report and whether IPv6 is configured on an interface.
|
// 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 {
|
if last == nil || len(last.RegionLatency) == 0 {
|
||||||
return makeProbePlanInitial(dm, ifState)
|
return makeProbePlanInitial(dm, ifState)
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
//go:build !ios && (darwin || freebsd)
|
//go:build !ios && (darwin || freebsd)
|
||||||
|
|
||||||
package interfaces
|
package netmon
|
||||||
|
|
||||||
import "net"
|
import "net"
|
||||||
|
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
//go:build ios
|
//go:build ios
|
||||||
|
|
||||||
package interfaces
|
package netmon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
package interfaces
|
package netmon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
//go:build darwin || freebsd
|
//go:build darwin || freebsd
|
||||||
|
|
||||||
package interfaces
|
package netmon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
package interfaces
|
package netmon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
package interfaces
|
package netmon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
//go:build linux || (darwin && !ts_macext)
|
//go:build linux || (darwin && !ts_macext)
|
||||||
|
|
||||||
package interfaces
|
package netmon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
//go:build !linux && !windows && !darwin && !freebsd && !android
|
//go:build !linux && !windows && !darwin && !freebsd && !android
|
||||||
|
|
||||||
package interfaces
|
package netmon
|
||||||
|
|
||||||
import "errors"
|
import "errors"
|
||||||
|
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
//go:build freebsd
|
//go:build freebsd
|
||||||
|
|
||||||
package interfaces
|
package netmon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"syscall"
|
"syscall"
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
//go:build !android
|
//go:build !android
|
||||||
|
|
||||||
package interfaces
|
package netmon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
package interfaces
|
package netmon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
package interfaces
|
package netmon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
package interfaces
|
package netmon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
package interfaces
|
package netmon
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
@ -14,7 +14,6 @@
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"tailscale.com/net/interfaces"
|
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/util/clientmetric"
|
"tailscale.com/util/clientmetric"
|
||||||
"tailscale.com/util/set"
|
"tailscale.com/util/set"
|
||||||
@ -162,7 +161,7 @@ func (m *Monitor) InterfaceState() *State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Monitor) interfaceStateUncached() (*State, error) {
|
func (m *Monitor) interfaceStateUncached() (*State, error) {
|
||||||
return interfaces.GetState()
|
return GetState()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTailscaleInterfaceName sets the name of the Tailscale interface. For
|
// 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 {
|
if m.gwValid {
|
||||||
return m.gw, m.gwSelfIP, true
|
return m.gw, m.gwSelfIP, true
|
||||||
}
|
}
|
||||||
gw, myIP, ok = interfaces.LikelyHomeRouterIP()
|
gw, myIP, ok = LikelyHomeRouterIP()
|
||||||
changed := false
|
changed := false
|
||||||
if ok {
|
if ok {
|
||||||
changed = m.gw != gw || m.gwSelfIP != myIP
|
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
|
// isInterestingInterface reports whether the provided interface should be
|
||||||
// considered when checking for network state changes.
|
// considered when checking for network state changes.
|
||||||
// The ips parameter should be the IPs of the provided interface.
|
// 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) {
|
if !m.om.IsInterestingInterface(i.Name) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"tailscale.com/net/interfaces"
|
|
||||||
"tailscale.com/util/mak"
|
"tailscale.com/util/mak"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -116,8 +115,6 @@ func TestMonitorMode(t *testing.T) {
|
|||||||
|
|
||||||
// tests (*State).IsMajorChangeFrom
|
// tests (*State).IsMajorChangeFrom
|
||||||
func TestIsMajorChangeFrom(t *testing.T) {
|
func TestIsMajorChangeFrom(t *testing.T) {
|
||||||
type State = interfaces.State
|
|
||||||
type Interface = interfaces.Interface
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
s1, s2 *State
|
s1, s2 *State
|
||||||
|
@ -1,8 +1,774 @@
|
|||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
// Package interfaces contains helpers for looking up system network interfaces.
|
||||||
package netmon
|
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 @@
|
|||||||
"golang.org/x/net/route"
|
"golang.org/x/net/route"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
"tailscale.com/envknob"
|
"tailscale.com/envknob"
|
||||||
"tailscale.com/net/interfaces"
|
|
||||||
"tailscale.com/net/netmon"
|
"tailscale.com/net/netmon"
|
||||||
"tailscale.com/net/tsaddr"
|
"tailscale.com/net/tsaddr"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
@ -62,12 +61,12 @@ func getInterfaceIndex(logf logger.Logf, netMon *netmon.Monitor, address string)
|
|||||||
// Helper so we can log errors.
|
// Helper so we can log errors.
|
||||||
defaultIdx := func() (int, error) {
|
defaultIdx := func() (int, error) {
|
||||||
if netMon == nil {
|
if netMon == nil {
|
||||||
idx, err := interfaces.DefaultRouteInterfaceIndex()
|
idx, err := netmon.DefaultRouteInterfaceIndex()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// It's somewhat common for there to be no default gateway route
|
// 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
|
// (e.g. on a phone with no connectivity), don't log those errors
|
||||||
// since they are expected.
|
// since they are expected.
|
||||||
if !errors.Is(err, interfaces.ErrNoGatewayIndexFound) {
|
if !errors.Is(err, netmon.ErrNoGatewayIndexFound) {
|
||||||
logf("[unexpected] netns: DefaultRouteInterfaceIndex: %v", err)
|
logf("[unexpected] netns: DefaultRouteInterfaceIndex: %v", err)
|
||||||
}
|
}
|
||||||
return -1, err
|
return -1, err
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"tailscale.com/net/interfaces"
|
"tailscale.com/net/netmon"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetInterfaceIndex(t *testing.T) {
|
func TestGetInterfaceIndex(t *testing.T) {
|
||||||
@ -63,7 +63,7 @@ func TestGetInterfaceIndex(t *testing.T) {
|
|||||||
t.Skip("no tailscale interface on this machine")
|
t.Skip("no tailscale interface on this machine")
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultIdx, err := interfaces.DefaultRouteInterfaceIndex()
|
defaultIdx, err := netmon.DefaultRouteInterfaceIndex()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
"tailscale.com/envknob"
|
"tailscale.com/envknob"
|
||||||
"tailscale.com/net/interfaces"
|
|
||||||
"tailscale.com/net/netmon"
|
"tailscale.com/net/netmon"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/util/linuxfw"
|
"tailscale.com/util/linuxfw"
|
||||||
@ -119,7 +118,7 @@ func setBypassMark(fd uintptr) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func bindToDevice(fd uintptr) error {
|
func bindToDevice(fd uintptr) error {
|
||||||
ifc, err := interfaces.DefaultRouteInterface()
|
ifc, err := netmon.DefaultRouteInterface()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Make sure we bind to *some* interface,
|
// Make sure we bind to *some* interface,
|
||||||
// or we could get a routing loop.
|
// or we could get a routing loop.
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
"golang.org/x/sys/cpu"
|
"golang.org/x/sys/cpu"
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
||||||
"tailscale.com/net/interfaces"
|
|
||||||
"tailscale.com/net/netmon"
|
"tailscale.com/net/netmon"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
)
|
)
|
||||||
@ -52,7 +51,7 @@ func controlC(network, address string, c syscall.RawConn) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if canV4 {
|
if canV4 {
|
||||||
iface, err := interfaces.GetWindowsDefault(windows.AF_INET)
|
iface, err := netmon.GetWindowsDefault(windows.AF_INET)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -62,7 +61,7 @@ func controlC(network, address string, c syscall.RawConn) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if canV6 {
|
if canV6 {
|
||||||
iface, err := interfaces.GetWindowsDefault(windows.AF_INET6)
|
iface, err := netmon.GetWindowsDefault(windows.AF_INET6)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@
|
|||||||
|
|
||||||
"go4.org/mem"
|
"go4.org/mem"
|
||||||
"tailscale.com/control/controlknobs"
|
"tailscale.com/control/controlknobs"
|
||||||
"tailscale.com/net/interfaces"
|
|
||||||
"tailscale.com/net/netaddr"
|
"tailscale.com/net/netaddr"
|
||||||
"tailscale.com/net/neterror"
|
"tailscale.com/net/neterror"
|
||||||
"tailscale.com/net/netmon"
|
"tailscale.com/net/netmon"
|
||||||
@ -216,7 +215,7 @@ func NewClient(logf logger.Logf, netMon *netmon.Monitor, debug *DebugKnobs, cont
|
|||||||
ret := &Client{
|
ret := &Client{
|
||||||
logf: logf,
|
logf: logf,
|
||||||
netMon: netMon,
|
netMon: netMon,
|
||||||
ipAndGateway: interfaces.LikelyHomeRouterIP, // TODO(bradfitz): move this to netMon
|
ipAndGateway: netmon.LikelyHomeRouterIP, // TODO(bradfitz): move this to method on netMon
|
||||||
onChange: onChange,
|
onChange: onChange,
|
||||||
controlKnobs: controlKnobs,
|
controlKnobs: controlKnobs,
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
"golang.org/x/net/route"
|
"golang.org/x/net/route"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
"tailscale.com/net/interfaces"
|
"tailscale.com/net/netmon"
|
||||||
"tailscale.com/types/logger"
|
"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
|
// ipFromRMAddr returns a netip.Addr converted from one of the
|
||||||
// route.Inet{4,6}Addr types.
|
// 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) {
|
switch v := addr.(type) {
|
||||||
case *route.Inet4Addr:
|
case *route.Inet4Addr:
|
||||||
return netip.AddrFrom4(v.IP)
|
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.
|
// 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 the address type has a valid IP, use that.
|
||||||
if ip := ipFromRMAddr(ifs, addr); ip.IsValid() {
|
if ip := ipFromRMAddr(ifs, addr); ip.IsValid() {
|
||||||
re.Gateway = ip
|
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
|
// populateDestination populates the 'Dst' field on a RouteEntry based on the
|
||||||
// RouteMessage's destination and netmask fields.
|
// 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]
|
dst := rm.Addrs[unix.RTAX_DST]
|
||||||
if dst == nil {
|
if dst == nil {
|
||||||
return
|
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
|
// routeEntryFromMsg returns a RouteEntry from a single route.Message
|
||||||
// returned by the operating system.
|
// 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)
|
rm, ok := msg.(*route.RouteMessage)
|
||||||
if !ok {
|
if !ok {
|
||||||
return RouteEntry{}, false
|
return RouteEntry{}, false
|
||||||
@ -260,12 +260,12 @@ func routeEntryFromMsg(ifsByIdx map[int]interfaces.Interface, msg route.Message)
|
|||||||
func Get(max int) ([]RouteEntry, error) {
|
func Get(max int) ([]RouteEntry, error) {
|
||||||
// Fetching the list of interfaces can race with fetching our route
|
// Fetching the list of interfaces can race with fetching our route
|
||||||
// table, but we do it anyway since it's helpful for debugging.
|
// table, but we do it anyway since it's helpful for debugging.
|
||||||
ifs, err := interfaces.GetList()
|
ifs, err := netmon.GetInterfaceList()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ifsByIdx := make(map[int]interfaces.Interface)
|
ifsByIdx := make(map[int]netmon.Interface)
|
||||||
for _, iif := range ifs {
|
for _, iif := range ifs {
|
||||||
ifsByIdx[iif.Index] = iif
|
ifsByIdx[iif.Index] = iif
|
||||||
}
|
}
|
||||||
|
@ -15,11 +15,11 @@
|
|||||||
|
|
||||||
"golang.org/x/net/route"
|
"golang.org/x/net/route"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
"tailscale.com/net/interfaces"
|
"tailscale.com/net/netmon"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRouteEntryFromMsg(t *testing.T) {
|
func TestRouteEntryFromMsg(t *testing.T) {
|
||||||
ifs := map[int]interfaces.Interface{
|
ifs := map[int]netmon.Interface{
|
||||||
1: {
|
1: {
|
||||||
Interface: &net.Interface{
|
Interface: &net.Interface{
|
||||||
Name: "iface0",
|
Name: "iface0",
|
||||||
|
@ -13,8 +13,8 @@
|
|||||||
|
|
||||||
"github.com/tailscale/netlink"
|
"github.com/tailscale/netlink"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
"tailscale.com/net/interfaces"
|
|
||||||
"tailscale.com/net/netaddr"
|
"tailscale.com/net/netaddr"
|
||||||
|
"tailscale.com/net/netmon"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -141,12 +141,12 @@ func (r RouteEntryLinux) ScopeName() string {
|
|||||||
func Get(max int) ([]RouteEntry, error) {
|
func Get(max int) ([]RouteEntry, error) {
|
||||||
// Fetching the list of interfaces can race with fetching our route
|
// Fetching the list of interfaces can race with fetching our route
|
||||||
// table, but we do it anyway since it's helpful for debugging.
|
// table, but we do it anyway since it's helpful for debugging.
|
||||||
ifs, err := interfaces.GetList()
|
ifs, err := netmon.GetInterfaceList()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ifsByIdx := make(map[int]interfaces.Interface)
|
ifsByIdx := make(map[int]netmon.Interface)
|
||||||
for _, iif := range ifs {
|
for _, iif := range ifs {
|
||||||
ifsByIdx[iif.Index] = iif
|
ifsByIdx[iif.Index] = iif
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"tailscale.com/net/interfaces"
|
|
||||||
"tailscale.com/net/netmon"
|
"tailscale.com/net/netmon"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/util/clientmetric"
|
"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
|
// had a chance to populate knownInterfaces). In that case, we'll have
|
||||||
// to get the list of interfaces ourselves.
|
// to get the list of interfaces ourselves.
|
||||||
if len(sockStats.knownInterfaces) == 0 {
|
if len(sockStats.knownInterfaces) == 0 {
|
||||||
if ifaces, err := interfaces.GetList(); err == nil {
|
if ifaces, err := netmon.GetInterfaceList(); err == nil {
|
||||||
for _, iface := range ifaces {
|
for _, iface := range ifaces {
|
||||||
counters.rxBytesByInterface[iface.Index] = &atomic.Uint64{}
|
counters.rxBytesByInterface[iface.Index] = &atomic.Uint64{}
|
||||||
counters.txBytesByInterface[iface.Index] = &atomic.Uint64{}
|
counters.txBytesByInterface[iface.Index] = &atomic.Uint64{}
|
||||||
|
@ -27,7 +27,6 @@
|
|||||||
_ "tailscale.com/logtail"
|
_ "tailscale.com/logtail"
|
||||||
_ "tailscale.com/net/dns"
|
_ "tailscale.com/net/dns"
|
||||||
_ "tailscale.com/net/dnsfallback"
|
_ "tailscale.com/net/dnsfallback"
|
||||||
_ "tailscale.com/net/interfaces"
|
|
||||||
_ "tailscale.com/net/netmon"
|
_ "tailscale.com/net/netmon"
|
||||||
_ "tailscale.com/net/netns"
|
_ "tailscale.com/net/netns"
|
||||||
_ "tailscale.com/net/proxymux"
|
_ "tailscale.com/net/proxymux"
|
||||||
|
@ -27,7 +27,6 @@
|
|||||||
_ "tailscale.com/logtail"
|
_ "tailscale.com/logtail"
|
||||||
_ "tailscale.com/net/dns"
|
_ "tailscale.com/net/dns"
|
||||||
_ "tailscale.com/net/dnsfallback"
|
_ "tailscale.com/net/dnsfallback"
|
||||||
_ "tailscale.com/net/interfaces"
|
|
||||||
_ "tailscale.com/net/netmon"
|
_ "tailscale.com/net/netmon"
|
||||||
_ "tailscale.com/net/netns"
|
_ "tailscale.com/net/netns"
|
||||||
_ "tailscale.com/net/proxymux"
|
_ "tailscale.com/net/proxymux"
|
||||||
|
@ -27,7 +27,6 @@
|
|||||||
_ "tailscale.com/logtail"
|
_ "tailscale.com/logtail"
|
||||||
_ "tailscale.com/net/dns"
|
_ "tailscale.com/net/dns"
|
||||||
_ "tailscale.com/net/dnsfallback"
|
_ "tailscale.com/net/dnsfallback"
|
||||||
_ "tailscale.com/net/interfaces"
|
|
||||||
_ "tailscale.com/net/netmon"
|
_ "tailscale.com/net/netmon"
|
||||||
_ "tailscale.com/net/netns"
|
_ "tailscale.com/net/netns"
|
||||||
_ "tailscale.com/net/proxymux"
|
_ "tailscale.com/net/proxymux"
|
||||||
|
@ -27,7 +27,6 @@
|
|||||||
_ "tailscale.com/logtail"
|
_ "tailscale.com/logtail"
|
||||||
_ "tailscale.com/net/dns"
|
_ "tailscale.com/net/dns"
|
||||||
_ "tailscale.com/net/dnsfallback"
|
_ "tailscale.com/net/dnsfallback"
|
||||||
_ "tailscale.com/net/interfaces"
|
|
||||||
_ "tailscale.com/net/netmon"
|
_ "tailscale.com/net/netmon"
|
||||||
_ "tailscale.com/net/netns"
|
_ "tailscale.com/net/netns"
|
||||||
_ "tailscale.com/net/proxymux"
|
_ "tailscale.com/net/proxymux"
|
||||||
|
@ -35,7 +35,6 @@
|
|||||||
_ "tailscale.com/logtail/backoff"
|
_ "tailscale.com/logtail/backoff"
|
||||||
_ "tailscale.com/net/dns"
|
_ "tailscale.com/net/dns"
|
||||||
_ "tailscale.com/net/dnsfallback"
|
_ "tailscale.com/net/dnsfallback"
|
||||||
_ "tailscale.com/net/interfaces"
|
|
||||||
_ "tailscale.com/net/netmon"
|
_ "tailscale.com/net/netmon"
|
||||||
_ "tailscale.com/net/netns"
|
_ "tailscale.com/net/netns"
|
||||||
_ "tailscale.com/net/proxymux"
|
_ "tailscale.com/net/proxymux"
|
||||||
|
@ -8,19 +8,19 @@
|
|||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"tailscale.com/net/interfaces"
|
"tailscale.com/net/netmon"
|
||||||
)
|
)
|
||||||
|
|
||||||
func deriveBindhost(t *testing.T) string {
|
func deriveBindhost(t *testing.T) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
ifName, err := interfaces.DefaultRouteInterface()
|
ifName, err := netmon.DefaultRouteInterface()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ret string
|
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 {
|
if ret != "" || i.Name != ifName {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,6 @@
|
|||||||
"tailscale.com/hostinfo"
|
"tailscale.com/hostinfo"
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
"tailscale.com/net/connstats"
|
"tailscale.com/net/connstats"
|
||||||
"tailscale.com/net/interfaces"
|
|
||||||
"tailscale.com/net/netcheck"
|
"tailscale.com/net/netcheck"
|
||||||
"tailscale.com/net/neterror"
|
"tailscale.com/net/neterror"
|
||||||
"tailscale.com/net/netmon"
|
"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)
|
eps = c.endpointTracker.update(time.Now(), eps)
|
||||||
|
|
||||||
if localAddr := c.pconn4.LocalAddr(); localAddr.IP.IsUnspecified() {
|
if localAddr := c.pconn4.LocalAddr(); localAddr.IP.IsUnspecified() {
|
||||||
ips, loopback, err := interfaces.LocalAddresses()
|
ips, loopback, err := netmon.LocalAddresses()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
||||||
"tailscale.com/health"
|
"tailscale.com/health"
|
||||||
"tailscale.com/net/interfaces"
|
"tailscale.com/net/netmon"
|
||||||
"tailscale.com/net/tsaddr"
|
"tailscale.com/net/tsaddr"
|
||||||
"tailscale.com/net/tstun"
|
"tailscale.com/net/tstun"
|
||||||
"tailscale.com/util/multierr"
|
"tailscale.com/util/multierr"
|
||||||
@ -110,7 +110,7 @@ func monitorDefaultRoutes(tun *tun.NativeTun) (*winipcfg.RouteChangeCallback, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getDefaultRouteMTU() (uint32, error) {
|
func getDefaultRouteMTU() (uint32, error) {
|
||||||
mtus, err := interfaces.NonTailscaleMTUs()
|
mtus, err := netmon.NonTailscaleMTUs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user