wgengine/monitor: make the darwin link monitor work in the sandbox too

Previously tailscaled on macOS was running "/sbin/route monitor" as a
child process, but child processes aren't allowed in the Network
Extension / App Store sandbox. Instead, just do what "/sbin/route monitor"
itself does: unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0) and read that.

We also parse it now, but don't do anything with the parsed results yet.

We will over time, as we have with Linux netlink messages over time.

Currently any message is considered a signal to poll and see what changed.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick 2021-02-28 20:52:50 -08:00
parent a55a03d5ff
commit 14dc790137
3 changed files with 132 additions and 73 deletions

View File

@ -0,0 +1,131 @@
// Copyright (c) 2021 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 monitor
import (
"fmt"
"log"
"os"
"golang.org/x/net/route"
"golang.org/x/sys/unix"
"inet.af/netaddr"
"tailscale.com/types/logger"
)
const debugRouteMessages = false
// unspecifiedMessage is a minimal message implementation that should not
// be ignored. In general, OS-specific implementations should use better
// types and avoid this if they can.
type unspecifiedMessage struct{}
func (unspecifiedMessage) ignore() bool { return false }
func newOSMon(logf logger.Logf) (osMon, error) {
fd, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0)
if err != nil {
return nil, err
}
return &darwinRouteMon{
logf: logf,
f: os.NewFile(uintptr(fd), "AF_ROUTE"),
}, nil
}
type darwinRouteMon struct {
logf logger.Logf
f *os.File // AF_ROUTE socket
buf [2 << 10]byte
}
func (m *darwinRouteMon) Close() error {
return m.f.Close()
}
func (m *darwinRouteMon) Receive() (message, error) {
n, err := m.f.Read(m.buf[:])
if err != nil {
return nil, err
}
msgs, err := route.ParseRIB(route.RIBTypeRoute, m.buf[:n])
if err != nil {
m.logf("read %d bytes (% 02x), failed to parse RIB: %v", n, m.buf[:n], err)
return nil, nil
}
if debugRouteMessages {
m.logf("read: %d bytes, %d msgs", n, len(msgs))
m.logMessages(msgs)
}
return unspecifiedMessage{}, nil
}
func (m *darwinRouteMon) logMessages(msgs []route.Message) {
for i, msg := range msgs {
switch msg := msg.(type) {
default:
m.logf(" [%d] %T", i, msg)
case *route.InterfaceMulticastAddrMessage:
m.logf(" [%d] InterfaceMulticastAddrMessage: ver=%d, type=%v, flags=0x%x, idx=%v",
i, msg.Version, msg.Type, msg.Flags, msg.Index)
m.logAddrs(msg.Addrs)
case *route.RouteMessage:
log.Printf(" [%d] RouteMessage: ver=%d, type=%v, flags=0x%x, idx=%v, id=%v, seq=%v, err=%v",
i, msg.Version, msg.Type, msg.Flags, msg.Index, msg.ID, msg.Seq, msg.Err)
m.logAddrs(msg.Addrs)
}
}
}
func (m *darwinRouteMon) logAddrs(addrs []route.Addr) {
for i, a := range addrs {
if a == nil {
continue
}
m.logf(" %v = %v", rtaxName(i), fmtAddr(a))
}
}
func fmtAddr(a route.Addr) interface{} {
if a == nil {
return nil
}
switch a := a.(type) {
case *route.Inet4Addr:
return netaddr.IPv4(a.IP[0], a.IP[1], a.IP[2], a.IP[3])
case *route.Inet6Addr:
ip := netaddr.IPv6Raw(a.IP)
if a.ZoneID != 0 {
ip = ip.WithZone(fmt.Sprint(a.ZoneID)) // TODO: look up net.InterfaceByIndex? but it might be changing?
}
return ip
case *route.LinkAddr:
return fmt.Sprintf("[LinkAddr idx=%v name=%q addr=%x]", a.Index, a.Name, a.Addr)
default:
return fmt.Sprintf("%T: %+v", a, a)
}
}
func rtaxName(i int) string {
switch i {
case unix.RTAX_DST:
return "dst"
case unix.RTAX_GATEWAY:
return "gateway"
case unix.RTAX_NETMASK:
return "netmask"
case unix.RTAX_GENMASK:
return "genmask"
case unix.RTAX_IFP:
return "IFP"
case unix.RTAX_IFA:
return "IFA"
case unix.RTAX_AUTHOR:
return "author"
case unix.RTAX_BRD:
return "BRD"
}
return fmt.Sprint(i)
}

View File

@ -1,72 +0,0 @@
// Copyright (c) 2021 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,!redo
package monitor
import (
"bufio"
"errors"
"os/exec"
"tailscale.com/syncs"
"tailscale.com/types/logger"
)
// unspecifiedMessage is a minimal message implementation that should not
// be ignored. In general, OS-specific implementations should use better
// types and avoid this if they can.
type unspecifiedMessage struct{}
func (unspecifiedMessage) ignore() bool { return false }
func newOSMon(logf logger.Logf) (osMon, error) {
return new(routeMonitorSubProcMon), nil
}
// routeMonitorSubProcMon is a very simple (temporary? but I know
// better) monitor implementation for darwin in tailscaled-mode where
// we can just shell out to "route -n monitor". It waits for any input
// but doesn't parse it. Then we poll to see if something is different.
type routeMonitorSubProcMon struct {
closed syncs.AtomicBool
cmd *exec.Cmd // of "/sbin/route -n monitor"
br *bufio.Reader
buf []byte
}
func (m *routeMonitorSubProcMon) Close() error {
m.closed.Set(true)
if m.cmd != nil {
m.cmd.Process.Kill()
m.cmd = nil
}
return nil
}
func (m *routeMonitorSubProcMon) Receive() (message, error) {
if m.closed.Get() {
return nil, errors.New("monitor closed")
}
if m.cmd == nil {
cmd := exec.Command("/sbin/route", "-n", "monitor")
outPipe, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
if err := cmd.Start(); err != nil {
return nil, err
}
m.br = bufio.NewReader(outPipe)
m.cmd = cmd
m.buf = make([]byte, 16<<10)
}
_, err := m.br.Read(m.buf)
if err != nil {
m.Close()
return nil, err
}
return unspecifiedMessage{}, nil
}

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build !linux,!freebsd,!windows,!darwin android darwin,redo // +build !linux,!freebsd,!windows,!darwin android
package monitor package monitor