diff --git a/net/interfaces/interfaces_darwin.go b/net/interfaces/interfaces_darwin.go index 9d0f0526f..72173e122 100644 --- a/net/interfaces/interfaces_darwin.go +++ b/net/interfaces/interfaces_darwin.go @@ -7,76 +7,15 @@ package interfaces import ( "errors" "fmt" + "log" "net" - "os/exec" "syscall" - "go4.org/mem" "golang.org/x/net/route" + "golang.org/x/sys/unix" "inet.af/netaddr" - "tailscale.com/util/lineread" - "tailscale.com/version" ) -/* -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 likelyHomeRouterIPDarwinExec() (ret netaddr.IP, ok bool) { - if version.IsMobile() { - // Don't try to do subprocesses on iOS. Ends up with log spam like: - // kernel: "Sandbox: IPNExtension(86580) deny(1) process-fork" - // This is why we have likelyHomeRouterIPDarwinSyscall. - return ret, false - } - 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 - // We've found what we're looking for. - return errStopReadingNetstatTable - } - return nil - }) - return ret, !ret.IsZero() -} - -var errStopReadingNetstatTable = errors.New("found private gateway") - func DefaultRouteInterface() (string, error) { idx, err := DefaultRouteInterfaceIndex() if err != nil { @@ -138,3 +77,48 @@ func DefaultRouteInterfaceIndex() (int, error) { } return 0, fmt.Errorf("ambiguous gateway interfaces found: %v", indexSeen) } + +func init() { + likelyHomeRouterIP = likelyHomeRouterIPDarwinFetchRIB +} + +func likelyHomeRouterIPDarwinFetchRIB() (ret netaddr.IP, ok bool) { + rib, err := route.FetchRIB(syscall.AF_UNSPEC, syscall.NET_RT_DUMP2, 0) + if err != nil { + log.Printf("routerIP/FetchRIB: %v", err) + return ret, false + } + msgs, err := route.ParseRIB(syscall.NET_RT_IFLIST2, rib) + if err != nil { + log.Printf("routerIP/ParseRIB: %v", err) + return ret, false + } + for _, m := range msgs { + rm, ok := m.(*route.RouteMessage) + if !ok { + continue + } + const RTF_GATEWAY = 0x2 + const RTF_IFSCOPE = 0x1000000 + if rm.Flags&RTF_GATEWAY == 0 { + continue + } + if rm.Flags&RTF_IFSCOPE != 0 { + continue + } + if len(rm.Addrs) > unix.RTAX_GATEWAY { + dst4, ok := rm.Addrs[unix.RTAX_DST].(*route.Inet4Addr) + if !ok || dst4.IP != ([4]byte{0, 0, 0, 0}) { + // Expect 0.0.0.0 as DST field. + continue + } + gw, ok := rm.Addrs[unix.RTAX_GATEWAY].(*route.Inet4Addr) + if !ok { + continue + } + return netaddr.IPv4(gw.IP[0], gw.IP[1], gw.IP[2], gw.IP[3]), true + } + } + + return ret, false +} diff --git a/net/interfaces/interfaces_darwin_cgo.go b/net/interfaces/interfaces_darwin_cgo.go deleted file mode 100644 index 91f245ca9..000000000 --- a/net/interfaces/interfaces_darwin_cgo.go +++ /dev/null @@ -1,126 +0,0 @@ -// 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. - -// +build darwin,cgo - -package interfaces - -/* -#import "route.h" -#import -#import -#import -#import - -// privateGatewayIPFromRoute returns the private gateway ip address from rtm, if it exists. -// Otherwise, it returns 0. -uint32_t privateGatewayIPFromRoute(struct rt_msghdr2 *rtm) -{ - // sockaddrs are after the message header - struct sockaddr* dst_sa = (struct sockaddr *)(rtm + 1); - - if((rtm->rtm_addrs & (RTA_DST|RTA_GATEWAY)) != (RTA_DST|RTA_GATEWAY)) - return 0; // missing dst or gateway addr - if (dst_sa->sa_family != AF_INET) - return 0; // dst not IPv4 - if ((rtm->rtm_flags & RTF_GATEWAY) == 0) - return 0; // gateway flag not set - - struct sockaddr_in* dst_si = (struct sockaddr_in *)dst_sa; - if (dst_si->sin_addr.s_addr != INADDR_ANY) - return 0; // not default route - - #define ROUNDUP(a) ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long)) - - struct sockaddr* gateway_sa = (struct sockaddr *)((char *)dst_sa + ROUNDUP(dst_sa->sa_len)); - if (gateway_sa->sa_family != AF_INET) - return 0; // gateway not IPv4 - - struct sockaddr_in* gateway_si= (struct sockaddr_in *)gateway_sa; - uint32_t ip; - ip = gateway_si->sin_addr.s_addr; - - unsigned char a, b; - a = (ip >> 0) & 0xff; - b = (ip >> 8) & 0xff; - - // Check whether ip is private, that is, whether it is - // in one of 10.0.0.0/8, 172.16.0.0/12, or 192.168.0.0/16. - if (a == 10) - return ip; // matches 10.0.0.0/8 - if (a == 172 && (b >> 4) == 1) - return ip; // matches 172.16.0.0/12 - if (a == 192 && b == 168) - return ip; // matches 192.168.0.0/16 - - // Not a private IP. - return 0; -} - -// privateGatewayIP returns the private gateway IP address, if it exists. -// If no private gateway IP address was found, it returns 0. -// On an error, it returns an error code in (0, 255]. -// Any private gateway IP address is > 255. -uint32_t privateGatewayIP() -{ - size_t needed; - int mib[6]; - char *buf; - - mib[0] = CTL_NET; - mib[1] = PF_ROUTE; - mib[2] = 0; - mib[3] = 0; - mib[4] = NET_RT_DUMP2; - mib[5] = 0; - - if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) - return 1; // route dump size estimation failed - if ((buf = malloc(needed)) == 0) - return 2; // malloc failed - if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) { - free(buf); - return 3; // route dump failed - } - - // Loop over all routes. - char *next, *lim; - lim = buf + needed; - struct rt_msghdr2 *rtm; - for (next = buf; next < lim; next += rtm->rtm_msglen) { - rtm = (struct rt_msghdr2 *)next; - uint32_t ip; - ip = privateGatewayIPFromRoute(rtm); - if (ip) { - free(buf); - return ip; - } - } - free(buf); - return 0; // no gateway found -} -*/ -import "C" - -import ( - "encoding/binary" - "log" - - "inet.af/netaddr" -) - -func init() { - likelyHomeRouterIP = likelyHomeRouterIPDarwinSyscall -} - -func likelyHomeRouterIPDarwinSyscall() (ret netaddr.IP, ok bool) { - ip := C.privateGatewayIP() - if ip < 255 { - log.Printf("likelyHomeRouterIPDarwinSyscall: error code %v", ip) - return netaddr.IP{}, false - } - var q [4]byte - binary.LittleEndian.PutUint32(q[:], uint32(ip)) - return netaddr.IPv4(q[0], q[1], q[2], q[3]), true -} diff --git a/net/interfaces/interfaces_darwin_cgo_test.go b/net/interfaces/interfaces_darwin_cgo_test.go deleted file mode 100644 index 094732f27..000000000 --- a/net/interfaces/interfaces_darwin_cgo_test.go +++ /dev/null @@ -1,20 +0,0 @@ -// 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. - -// +build cgo,darwin - -package interfaces - -import "testing" - -func TestLikelyHomeRouterIPSyscallExec(t *testing.T) { - syscallIP, syscallOK := likelyHomeRouterIPDarwinSyscall() - netstatIP, netstatOK := likelyHomeRouterIPDarwinExec() - if syscallOK != netstatOK || syscallIP != netstatIP { - t.Errorf("syscall() = %v, %v, netstat = %v, %v", - syscallIP, syscallOK, - netstatIP, netstatOK, - ) - } -} diff --git a/net/interfaces/interfaces_darwin_nocgo.go b/net/interfaces/interfaces_darwin_nocgo.go deleted file mode 100644 index da1db44e0..000000000 --- a/net/interfaces/interfaces_darwin_nocgo.go +++ /dev/null @@ -1,11 +0,0 @@ -// 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. - -// +build darwin,!cgo - -package interfaces - -func init() { - likelyHomeRouterIP = likelyHomeRouterIPDarwinExec -} diff --git a/net/interfaces/interfaces_darwin_test.go b/net/interfaces/interfaces_darwin_test.go new file mode 100644 index 000000000..310a8266f --- /dev/null +++ b/net/interfaces/interfaces_darwin_test.go @@ -0,0 +1,86 @@ +// 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 ( + "errors" + "os/exec" + "testing" + + "go4.org/mem" + "inet.af/netaddr" + "tailscale.com/util/lineread" + "tailscale.com/version" +) + +func TestLikelyHomeRouterIPSyscallExec(t *testing.T) { + syscallIP, syscallOK := likelyHomeRouterIPDarwinFetchRIB() + netstatIP, netstatOK := likelyHomeRouterIPDarwinExec() + if syscallOK != netstatOK || syscallIP != netstatIP { + t.Errorf("syscall() = %v, %v, netstat = %v, %v", + syscallIP, syscallOK, + netstatIP, netstatOK, + ) + } +} + +/* +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 likelyHomeRouterIPDarwinExec() (ret netaddr.IP, ok bool) { + if version.IsMobile() { + // Don't try to do subprocesses on iOS. Ends up with log spam like: + // kernel: "Sandbox: IPNExtension(86580) deny(1) process-fork" + // This is why we have likelyHomeRouterIPDarwinSyscall. + return ret, false + } + 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 + // We've found what we're looking for. + return errStopReadingNetstatTable + } + return nil + }) + return ret, !ret.IsZero() +} + +var errStopReadingNetstatTable = errors.New("found private gateway")