From 7461dded88b86e93e45a2fa375fa652f2519e211 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 2 Mar 2021 21:45:40 -0800 Subject: [PATCH] wgengine/monitor: on unsupported platforms, use a polling implementation Not great, but lets people working on new ports get going more quickly without having to do everything up front. As the link monitor is getting used more, I felt bad having a useless implementation. Updates #815 Updates #1427 Signed-off-by: Brad Fitzpatrick --- wgengine/monitor/monitor.go | 15 ++++-- wgengine/monitor/monitor_darwin.go | 2 +- wgengine/monitor/monitor_freebsd.go | 2 +- wgengine/monitor/monitor_linux.go | 2 +- wgengine/monitor/monitor_polling.go | 72 +++++++++++++++++++++++++ wgengine/monitor/monitor_unsupported.go | 11 ---- wgengine/monitor/monitor_windows.go | 2 +- 7 files changed, 86 insertions(+), 20 deletions(-) create mode 100644 wgengine/monitor/monitor_polling.go delete mode 100644 wgengine/monitor/monitor_unsupported.go diff --git a/wgengine/monitor/monitor.go b/wgengine/monitor/monitor.go index 75441b088..9f8610a06 100644 --- a/wgengine/monitor/monitor.go +++ b/wgengine/monitor/monitor.go @@ -8,6 +8,7 @@ package monitor import ( + "errors" "sync" "time" @@ -63,14 +64,9 @@ type Mon struct { // Use RegisterChangeCallback to get notified of network changes. func New(logf logger.Logf) (*Mon, error) { logf = logger.WithPrefix(logf, "monitor: ") - om, err := newOSMon(logf) - if err != nil { - return nil, err - } m := &Mon{ logf: logf, cbs: map[*callbackHandle]ChangeFunc{}, - om: om, change: make(chan struct{}, 1), stop: make(chan struct{}), } @@ -79,6 +75,15 @@ func New(logf logger.Logf) (*Mon, error) { return nil, err } m.ifState = st + + m.om, err = newOSMon(logf, m) + if err != nil { + return nil, err + } + if m.om == nil { + return nil, errors.New("newOSMon returned nil, nil") + } + return m, nil } diff --git a/wgengine/monitor/monitor_darwin.go b/wgengine/monitor/monitor_darwin.go index 8385d1342..1bf3a4d20 100644 --- a/wgengine/monitor/monitor_darwin.go +++ b/wgengine/monitor/monitor_darwin.go @@ -24,7 +24,7 @@ type unspecifiedMessage struct{ func (unspecifiedMessage) ignore() bool { return false } -func newOSMon(logf logger.Logf) (osMon, error) { +func newOSMon(logf logger.Logf, _ *Mon) (osMon, error) { fd, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0) if err != nil { return nil, err diff --git a/wgengine/monitor/monitor_freebsd.go b/wgengine/monitor/monitor_freebsd.go index 2d324c454..1230d56ff 100644 --- a/wgengine/monitor/monitor_freebsd.go +++ b/wgengine/monitor/monitor_freebsd.go @@ -25,7 +25,7 @@ type devdConn struct { conn net.Conn } -func newOSMon(logf logger.Logf) (osMon, error) { +func newOSMon(logf logger.Logf, _ *Mon) (osMon, error) { conn, err := net.Dial("unixpacket", "/var/run/devd.seqpacket.pipe") if err != nil { return nil, fmt.Errorf("devd dial error: %v", err) diff --git a/wgengine/monitor/monitor_linux.go b/wgengine/monitor/monitor_linux.go index aa7cded06..ef867bae3 100644 --- a/wgengine/monitor/monitor_linux.go +++ b/wgengine/monitor/monitor_linux.go @@ -37,7 +37,7 @@ type nlConn struct { buffered []netlink.Message } -func newOSMon(logf logger.Logf) (osMon, error) { +func newOSMon(logf logger.Logf, _ *Mon) (osMon, error) { conn, err := netlink.Dial(unix.NETLINK_ROUTE, &netlink.Config{ // Routes get us most of the events of interest, but we need // address as well to cover things like DHCP deciding to give diff --git a/wgengine/monitor/monitor_polling.go b/wgengine/monitor/monitor_polling.go new file mode 100644 index 000000000..079c956bb --- /dev/null +++ b/wgengine/monitor/monitor_polling.go @@ -0,0 +1,72 @@ +// 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 !linux,!freebsd,!windows,!darwin android + +package monitor + +import ( + "errors" + "runtime" + "sync" + "time" + + "tailscale.com/types/logger" +) + +func newOSMon(logf logger.Logf, m *Mon) (osMon, error) { + return &pollingMon{ + logf: logf, + m: m, + stop: make(chan struct{}), + }, nil +} + +// pollingMon is a bad but portable implementation of the link monitor +// that works by polling the interface state every 10 seconds, in lieu +// of anything to subscribe to. A good implementation +type pollingMon struct { + logf logger.Logf + m *Mon + + closeOnce sync.Once + stop chan struct{} +} + +func (pm *pollingMon) Close() error { + pm.closeOnce.Do(func() { + close(pm.stop) + }) + return nil +} + +func (pm *pollingMon) Receive() (message, error) { + d := 10 * time.Second + if runtime.GOOS == "android" { + // We'll have Android notify the link monitor to wake up earlier, + // so this can go very slowly there, to save battery. + // https://github.com/tailscale/tailscale/issues/1427 + d = 10 * time.Minute + } + ticker := time.NewTicker(d) + defer ticker.Stop() + base := pm.m.InterfaceState() + for { + if cur, err := pm.m.interfaceStateUncached(); err == nil && !cur.Equal(base) { + return unspecifiedMessage{}, nil + } + select { + case <-ticker.C: + case <-pm.stop: + return nil, errors.New("stopped") + } + } +} + +// 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 } diff --git a/wgengine/monitor/monitor_unsupported.go b/wgengine/monitor/monitor_unsupported.go deleted file mode 100644 index 4b7138d6b..000000000 --- a/wgengine/monitor/monitor_unsupported.go +++ /dev/null @@ -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 !linux,!freebsd,!windows,!darwin android - -package monitor - -import "tailscale.com/types/logger" - -func newOSMon(logger.Logf) (osMon, error) { return nil, nil } diff --git a/wgengine/monitor/monitor_windows.go b/wgengine/monitor/monitor_windows.go index b8297d2e8..3bb39cb74 100644 --- a/wgengine/monitor/monitor_windows.go +++ b/wgengine/monitor/monitor_windows.go @@ -65,7 +65,7 @@ type winMon struct { inFastPoll bool // recent net change event made us go into fast polling mode (to detect proxy changes) } -func newOSMon(logf logger.Logf) (osMon, error) { +func newOSMon(logf logger.Logf, _ *Mon) (osMon, error) { closeHandle, err := windows.CreateEvent(nil, 1 /* manual reset */, 0 /* unsignaled */, nil /* no name */) if err != nil { return nil, fmt.Errorf("CreateEvent: %w", err)