mirror of https://github.com/tailscale/tailscale/
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>pull/1409/head
parent
a55a03d5ff
commit
14dc790137
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue