diff --git a/cmd/tsshd/tsshd.go b/cmd/tsshd/tsshd.go index d405472a2..a247fb55b 100644 --- a/cmd/tsshd/tsshd.go +++ b/cmd/tsshd/tsshd.go @@ -32,7 +32,7 @@ import ( "github.com/gliderlabs/ssh" "github.com/kr/pty" gossh "golang.org/x/crypto/ssh" - "tailscale.com/interfaces" + "tailscale.com/net/interfaces" ) var ( diff --git a/interfaces/interfaces.go b/net/interfaces/interfaces.go similarity index 84% rename from interfaces/interfaces.go rename to net/interfaces/interfaces.go index 45a6708fc..0d37144d6 100644 --- a/interfaces/interfaces.go +++ b/net/interfaces/interfaces.go @@ -137,6 +137,36 @@ func LocalAddresses() (regular, loopback []string, err error) { return regular, loopback, nil } +// Interface is a wrapper around Go's net.Interface with some extra methods. +type Interface struct { + *net.Interface +} + +func (i Interface) IsLoopback() bool { return isLoopback(i.Interface) } +func (i Interface) IsUp() bool { return isUp(i.Interface) } + +// ForeachInterfaceAddress calls fn for each interface's address on the machine. +func ForeachInterfaceAddress(fn func(Interface, net.IP)) error { + ifaces, err := net.Interfaces() + if err != nil { + return err + } + for i := range ifaces { + iface := &ifaces[i] + addrs, err := iface.Addrs() + if err != nil { + return err + } + for _, a := range addrs { + switch v := a.(type) { + case *net.IPNet: + fn(Interface{iface}, v.IP) + } + } + } + return nil +} + var cgNAT = func() *net.IPNet { _, ipNet, err := net.ParseCIDR("100.64.0.0/10") if err != nil { diff --git a/interfaces/interfaces_test.go b/net/interfaces/interfaces_test.go similarity index 100% rename from interfaces/interfaces_test.go rename to net/interfaces/interfaces_test.go diff --git a/netcheck/netcheck.go b/netcheck/netcheck.go index ae57a91be..9099af0ae 100644 --- a/netcheck/netcheck.go +++ b/netcheck/netcheck.go @@ -18,8 +18,8 @@ import ( "golang.org/x/sync/errgroup" "tailscale.com/derp/derpmap" - "tailscale.com/interfaces" "tailscale.com/net/dnscache" + "tailscale.com/net/interfaces" "tailscale.com/stun" "tailscale.com/stunner" "tailscale.com/types/logger" diff --git a/tsweb/tsweb.go b/tsweb/tsweb.go index dfdd0da3a..5c7a06b23 100644 --- a/tsweb/tsweb.go +++ b/tsweb/tsweb.go @@ -20,8 +20,8 @@ import ( "strings" "time" - "tailscale.com/interfaces" "tailscale.com/metrics" + "tailscale.com/net/interfaces" ) // DevMode controls whether extra output in shown, for when the binary is being run in dev mode. diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 3f8d888a4..d98d86ff9 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -31,8 +31,8 @@ import ( "tailscale.com/derp" "tailscale.com/derp/derphttp" "tailscale.com/derp/derpmap" - "tailscale.com/interfaces" "tailscale.com/net/dnscache" + "tailscale.com/net/interfaces" "tailscale.com/netcheck" "tailscale.com/stun" "tailscale.com/tailcfg" diff --git a/wgengine/monitor/monitor.go b/wgengine/monitor/monitor.go index 1de09d1e3..2abbcd244 100644 --- a/wgengine/monitor/monitor.go +++ b/wgengine/monitor/monitor.go @@ -7,9 +7,14 @@ package monitor import ( + "fmt" + "net" + "runtime" + "strings" "sync" "time" + "tailscale.com/net/interfaces" "tailscale.com/types/logger" ) @@ -99,6 +104,7 @@ func (m *Mon) Close() error { // the change channel of changes, and stopping when a stop is issued. func (m *Mon) pump() { defer m.goroutines.Done() + last := interfaceSummary() for { _, err := m.om.Receive() if err != nil { @@ -113,9 +119,17 @@ func (m *Mon) pump() { continue } + cur := interfaceSummary() + if cur == last { + continue + } + m.logf("wgengine/monitor: now %v (was %v)", cur, last) + last = cur + select { case m.change <- struct{}{}: - default: + case <-m.stop: + return } } } @@ -140,3 +154,17 @@ func (m *Mon) debounce() { } } } + +func interfaceSummary() string { + var sb strings.Builder + _ = interfaces.ForeachInterfaceAddress(func(ni interfaces.Interface, addr net.IP) { + if runtime.GOOS == "linux" && strings.HasPrefix(ni.Name, "tailscale") { + // Skip tailscale0, etc on Linux. + return + } + if ni.IsUp() { + fmt.Fprintf(&sb, "%s=%s ", ni.Name, addr) + } + }) + return strings.TrimSpace(sb.String()) +}