diff --git a/wgengine/monitor/monitor_linux.go b/wgengine/monitor/monitor_linux.go index ef867bae3..aef6d7ed9 100644 --- a/wgengine/monitor/monitor_linux.go +++ b/wgengine/monitor/monitor_linux.go @@ -7,7 +7,6 @@ package monitor import ( - "fmt" "net" "time" @@ -37,7 +36,7 @@ type nlConn struct { buffered []netlink.Message } -func newOSMon(logf logger.Logf, _ *Mon) (osMon, error) { +func newOSMon(logf logger.Logf, m *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 @@ -46,7 +45,9 @@ func newOSMon(logf logger.Logf, _ *Mon) (osMon, error) { Groups: unix.RTMGRP_IPV4_IFADDR | unix.RTMGRP_IPV6_IFADDR | unix.RTMGRP_IPV4_ROUTE | unix.RTMGRP_IPV6_ROUTE, }) if err != nil { - return nil, fmt.Errorf("dialing netlink socket: %v", err) + // Google Cloud Run does not implement NETLINK_ROUTE RTMGRP support + logf("monitor_linux: AF_NETLINK RTMGRP failed, falling back to polling") + return newPollingMon(logf, m) } return &nlConn{logf: logf, conn: conn}, nil } diff --git a/wgengine/monitor/monitor_polling.go b/wgengine/monitor/monitor_polling.go index cdc995ca4..1bbbe2d90 100644 --- a/wgengine/monitor/monitor_polling.go +++ b/wgengine/monitor/monitor_polling.go @@ -7,62 +7,11 @@ package monitor import ( - "errors" - "runtime" - "sync" - "time" - - "tailscale.com/net/interfaces" "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.EqualFiltered(base, interfaces.FilterInteresting) { - return unspecifiedMessage{}, nil - } - select { - case <-ticker.C: - case <-pm.stop: - return nil, errors.New("stopped") - } - } + return newPollingMon(logf, m) } // unspecifiedMessage is a minimal message implementation that should not diff --git a/wgengine/monitor/polling.go b/wgengine/monitor/polling.go new file mode 100644 index 000000000..4b8f81291 --- /dev/null +++ b/wgengine/monitor/polling.go @@ -0,0 +1,68 @@ +// 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 !freebsd,!windows,!darwin + +package monitor + +import ( + "errors" + "runtime" + "sync" + "time" + + "tailscale.com/net/interfaces" + "tailscale.com/types/logger" +) + +func newPollingMon(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. +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 + } + // TODO: detect if we're running in Cloud Run, and reduce frequency of + // polling as its routes never change. + ticker := time.NewTicker(d) + defer ticker.Stop() + base := pm.m.InterfaceState() + for { + if cur, err := pm.m.interfaceStateUncached(); err == nil && !cur.EqualFiltered(base, interfaces.FilterInteresting) { + return unspecifiedMessage{}, nil + } + select { + case <-ticker.C: + case <-pm.stop: + return nil, errors.New("stopped") + } + } +}