diff --git a/wgengine/monitor/monitor_darwin.go b/wgengine/monitor/monitor_darwin.go new file mode 100644 index 000000000..1d7a106a4 --- /dev/null +++ b/wgengine/monitor/monitor_darwin.go @@ -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) +} diff --git a/wgengine/monitor/monitor_darwin_tailscaled.go b/wgengine/monitor/monitor_darwin_tailscaled.go deleted file mode 100644 index f7123cf65..000000000 --- a/wgengine/monitor/monitor_darwin_tailscaled.go +++ /dev/null @@ -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 -} diff --git a/wgengine/monitor/monitor_unsupported.go b/wgengine/monitor/monitor_unsupported.go index a779536e6..4b7138d6b 100644 --- a/wgengine/monitor/monitor_unsupported.go +++ b/wgengine/monitor/monitor_unsupported.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // 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