diff --git a/cmd/tailscale/netcheck.go b/cmd/tailscale/netcheck.go index 5d64495cc..c94ca1015 100644 --- a/cmd/tailscale/netcheck.go +++ b/cmd/tailscale/netcheck.go @@ -18,6 +18,7 @@ import ( "github.com/peterbourgon/ff/v2/ffcli" "tailscale.com/derp/derpmap" "tailscale.com/net/dnscache" + "tailscale.com/net/interfaces" "tailscale.com/net/netcheck" "tailscale.com/tailcfg" "tailscale.com/types/logger" @@ -50,6 +51,11 @@ func runNetcheck(ctx context.Context, args []string) error { if netcheckArgs.verbose { c.Logf = logger.WithPrefix(log.Printf, "netcheck: ") c.Verbose = true + if gw, ok := interfaces.LikelyHomeRouterIP(); ok { + c.Logf("likely home router: %v", gw) + } else { + c.Logf("no likely home router IP found") + } } else { c.Logf = logger.Discard } diff --git a/go.mod b/go.mod index 618fede82..b12a5c1f9 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/tailscale/wireguard-go v0.0.0-20200624060658-de1f1af1f35f github.com/tcnksm/go-httpstat v0.2.0 github.com/toqueteos/webbrowser v1.2.0 - go4.org/mem v0.0.0-20200601023850-d8ee1dfa5518 + go4.org/mem v0.0.0-20200706164138-185c595c3ecc golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6 golang.org/x/net v0.0.0-20200301022130-244492dfa37a golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d diff --git a/go.sum b/go.sum index fd0cb605c..9c26b5cdb 100644 --- a/go.sum +++ b/go.sum @@ -96,6 +96,8 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofm github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= go4.org/mem v0.0.0-20200601023850-d8ee1dfa5518 h1:AA3bSGklCgkrqIGnvL4894oa/2K9ltE0RejXh8CgyvA= go4.org/mem v0.0.0-20200601023850-d8ee1dfa5518/go.mod h1:NEYvpHWemiG/E5UWfaN5QAIGZeT1sa0Z2UNk6oeMb/k= +go4.org/mem v0.0.0-20200706164138-185c595c3ecc h1:paujszgN6SpsO/UsXC7xax3gQAKz/XQKCYZLQdU34Tw= +go4.org/mem v0.0.0-20200706164138-185c595c3ecc/go.mod h1:NEYvpHWemiG/E5UWfaN5QAIGZeT1sa0Z2UNk6oeMb/k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= diff --git a/net/interfaces/interfaces.go b/net/interfaces/interfaces.go index ae7a30acc..4626f5429 100644 --- a/net/interfaces/interfaces.go +++ b/net/interfaces/interfaces.go @@ -230,6 +230,18 @@ func HTTPOfListener(ln net.Listener) string { } +var likelyHomeRouterIP func() (netaddr.IP, bool) + +// LikelyHomeRouterIP returns the likely IP of the residential router, +// which will always be an IPv4 private address, if found. +// This is used as the destination for UPnP, NAT-PMP, PCP, etc queries. +func LikelyHomeRouterIP() (ip netaddr.IP, ok bool) { + if likelyHomeRouterIP != nil { + return likelyHomeRouterIP() + } + return ip, false +} + func isPrivateIP(ip netaddr.IP) bool { return private1.Contains(ip) || private2.Contains(ip) || private3.Contains(ip) } diff --git a/net/interfaces/interfaces_darwin.go b/net/interfaces/interfaces_darwin.go new file mode 100644 index 000000000..75bd1700c --- /dev/null +++ b/net/interfaces/interfaces_darwin.go @@ -0,0 +1,66 @@ +// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package interfaces + +import ( + "os/exec" + + "go4.org/mem" + "inet.af/netaddr" + "tailscale.com/util/lineread" +) + +func init() { + likelyHomeRouterIP = likelyHomeRouterIPDarwin +} + +/* +Parse out 10.0.0.1 from: + +$ netstat -r -n -f inet +Routing tables + +Internet: +Destination Gateway Flags Netif Expire +default 10.0.0.1 UGSc en0 +default link#14 UCSI utun2 +10/16 link#4 UCS en0 ! +10.0.0.1/32 link#4 UCS en0 ! +... + +*/ +func likelyHomeRouterIPDarwin() (ret netaddr.IP, ok bool) { + cmd := exec.Command("/usr/sbin/netstat", "-r", "-n", "-f", "inet") + stdout, err := cmd.StdoutPipe() + if err != nil { + return + } + if err := cmd.Start(); err != nil { + return + } + defer cmd.Wait() + + var f []mem.RO + lineread.Reader(stdout, func(lineb []byte) error { + line := mem.B(lineb) + if !mem.Contains(line, mem.S("default")) { + return nil + } + f = mem.AppendFields(f[:0], line) + if len(f) < 3 || !f[0].EqualString("default") { + return nil + } + ipm, flagsm := f[1], f[2] + if !mem.Contains(flagsm, mem.S("G")) { + return nil + } + ip, err := netaddr.ParseIP(string(mem.Append(nil, ipm))) + if err == nil && isPrivateIP(ip) { + ret = ip + } + return nil + }) + return ret, !ret.IsZero() +} diff --git a/net/interfaces/interfaces_linux.go b/net/interfaces/interfaces_linux.go new file mode 100644 index 000000000..85e1108be --- /dev/null +++ b/net/interfaces/interfaces_linux.go @@ -0,0 +1,59 @@ +// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package interfaces + +import ( + "go4.org/mem" + "inet.af/netaddr" + "tailscale.com/util/lineread" +) + +func init() { + likelyHomeRouterIP = likelyHomeRouterIPLinux +} + +/* +Parse 10.0.0.1 out of: + +$ cat /proc/net/route +Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT +ens18 00000000 0100000A 0003 0 0 0 00000000 0 0 0 +ens18 0000000A 00000000 0001 0 0 0 0000FFFF 0 0 0 +*/ +func likelyHomeRouterIPLinux() (ret netaddr.IP, ok bool) { + lineNum := 0 + var f []mem.RO + lineread.File("/proc/net/route", func(line []byte) error { + lineNum++ + if lineNum == 1 { + // Skip header line. + return nil + } + f = mem.AppendFields(f[:0], mem.B(line)) + if len(f) < 4 { + return nil + } + gwHex, flagsHex := f[2], f[3] + flags, err := mem.ParseUint(flagsHex, 16, 16) + if err != nil { + return nil // ignore error, skip line and keep going + } + const RTF_UP = 0x0001 + const RTF_GATEWAY = 0x0002 + if flags&(RTF_UP|RTF_GATEWAY) != RTF_UP|RTF_GATEWAY { + return nil + } + ipu32, err := mem.ParseUint(gwHex, 16, 32) + if err != nil { + return nil // ignore error, skip line and keep going + } + ip := netaddr.IPv4(byte(ipu32), byte(ipu32>>8), byte(ipu32>>16), byte(ipu32>>24)) + if isPrivateIP(ip) { + ret = ip + } + return nil + }) + return ret, !ret.IsZero() +} diff --git a/net/interfaces/interfaces_test.go b/net/interfaces/interfaces_test.go index ea0c96987..0cd2bbb98 100644 --- a/net/interfaces/interfaces_test.go +++ b/net/interfaces/interfaces_test.go @@ -47,3 +47,8 @@ func TestGetState(t *testing.T) { t.Fatal("two States back-to-back were not equal") } } + +func TestLikelyHomeRouterIP(t *testing.T) { + ip, ok := LikelyHomeRouterIP() + t.Logf("got %v, %v", ip, ok) +} diff --git a/net/interfaces/interfaces_windows.go b/net/interfaces/interfaces_windows.go new file mode 100644 index 000000000..e16c297d9 --- /dev/null +++ b/net/interfaces/interfaces_windows.go @@ -0,0 +1,71 @@ +// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package interfaces + +import ( + "os/exec" + + "go4.org/mem" + "inet.af/netaddr" + "tailscale.com/util/lineread" +) + +func init() { + likelyHomeRouterIP = likelyHomeRouterIPWindows +} + +/* +Parse out 10.0.0.1 from: + +Z:\>route print -4 +=========================================================================== +Interface List + 15...aa 15 48 ff 1c 72 ......Red Hat VirtIO Ethernet Adapter + 5...........................Tailscale Tunnel + 1...........................Software Loopback Interface 1 +=========================================================================== + +IPv4 Route Table +=========================================================================== +Active Routes: +Network Destination Netmask Gateway Interface Metric + 0.0.0.0 0.0.0.0 10.0.0.1 10.0.28.63 5 + 10.0.0.0 255.255.0.0 On-link 10.0.28.63 261 + 10.0.28.63 255.255.255.255 On-link 10.0.28.63 261 + 10.0.42.0 255.255.255.0 100.103.42.106 100.103.42.106 5 + 10.0.255.255 255.255.255.255 On-link 10.0.28.63 261 + 34.193.248.174 255.255.255.255 100.103.42.106 100.103.42.106 5 + +*/ +func likelyHomeRouterIPWindows() (ret netaddr.IP, ok bool) { + cmd := exec.Command("route", "print", "-4") + stdout, err := cmd.StdoutPipe() + if err != nil { + return + } + if err := cmd.Start(); err != nil { + return + } + defer cmd.Wait() + + var f []mem.RO + lineread.Reader(stdout, func(lineb []byte) error { + line := mem.B(lineb) + if !mem.Contains(line, mem.S("0.0.0.0")) { + return nil + } + f = mem.AppendFields(f[:0], line) + if len(f) < 3 || !f[0].EqualString("0.0.0.0") || !f[1].EqualString("0.0.0.0") { + return nil + } + ipm := f[2] + ip, err := netaddr.ParseIP(string(mem.Append(nil, ipm))) + if err == nil && isPrivateIP(ip) { + ret = ip + } + return nil + }) + return ret, !ret.IsZero() +}