tailscale/wgengine/monitor/monitor_darwin.go
Brad Fitzpatrick 14dc790137 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>
2021-02-28 21:14:51 -08:00

132 lines
3.1 KiB
Go

// 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)
}