We basically already had the RIB-parsing Go code for this in both net/interfaces and wgengine/monitor, for other reasons. Fixes #1426 Fixes #1471 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>main
parent
deff20edc6
commit
974be2ec5c
@ -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 <netinet/in.h> |
||||
#import <sys/sysctl.h> |
||||
#import <stdlib.h> |
||||
#import <stdio.h> |
||||
|
||||
// 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 |
||||
} |
||||
@ -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, |
||||
) |
||||
} |
||||
} |
||||
@ -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 |
||||
} |
||||
@ -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") |
||||
Loading…
Reference in new issue