mirror of https://github.com/tailscale/tailscale/
net/interfaces: rewrite the darwin likelyHomeRouterIP from C to Go
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>pull/1506/head
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