wgengine: move link monitor to be owned by the engine, not the router

And make the monitor package portable with no-op implementations on
unsupported operating systems.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2020-02-17 09:00:38 -08:00
committed by Brad Fitzpatrick
parent 09fbae01a9
commit 7f5e3febe5
11 changed files with 96 additions and 55 deletions

View File

@@ -2,27 +2,33 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux freebsd
// Package monitor provides facilities for monitoring network
// interface changes.
package monitor
import (
"sync"
"time"
"tailscale.com/types/logger"
)
// Message represents a message returned from a connection.
// TODO(]|[): currently messages are being discarded, so the
// properties of the message haven't been defined.
type Message interface{}
// message represents a message returned from an osMon.
//
// TODO: currently messages are being discarded, so the properties of
// the message haven't been defined.
type message interface{}
// Conn represents the connection that is being monitored.
type Conn interface {
// osMon is the interface that each operating system-specific
// implementation of the link monitor must implement.
type osMon interface {
Close() error
Receive() (Message, error)
// Receive returns a new network interface change message. It
// should block until there's either something to return, or
// until the osMon is closed. After a Close, the returned
// error is ignored.
Receive() (message, error)
}
// ChangeFunc is a callback function that's called when
@@ -33,41 +39,68 @@ type ChangeFunc func()
type Mon struct {
logf logger.Logf
cb ChangeFunc
conn Conn
om osMon // nil means not supported on this platform
change chan struct{}
stop chan struct{}
onceStart sync.Once
started bool
goroutines sync.WaitGroup
}
// New instantiates and starts a monitoring instance. Change notifications
// are propagated to the callback function.
// The returned monitor is inactive until it's started by the Start method.
func New(logf logger.Logf, callback ChangeFunc) (*Mon, error) {
conn, err := NewConn()
om, err := newOSMon()
if err != nil {
return nil, err
}
ret := &Mon{
return &Mon{
logf: logf,
cb: callback,
conn: conn,
om: om,
change: make(chan struct{}, 1),
stop: make(chan struct{}),
}
go ret.pump()
go ret.debounce()
return ret, nil
}, nil
}
// Close is used to close the underlying connection.
// Start starts the monitor.
// A monitor can only be started & closed once.
func (m *Mon) Start() {
m.onceStart.Do(func() {
if m.om == nil {
return
}
m.started = true
m.goroutines.Add(2)
go m.pump()
go m.debounce()
})
}
// Close closes the monitor.
// It may only be called once.
func (m *Mon) Close() error {
close(m.stop)
return m.conn.Close()
var err error
if m.om != nil {
err = m.om.Close()
}
// If it was previously started, wait for those goroutines to finish.
m.onceStart.Do(func() {})
if m.started {
m.goroutines.Wait()
}
return err
}
// pump continuously retrieves messages from the connection, notifying
// the change channel of changes, and stopping when a stop is issued.
func (m *Mon) pump() {
defer m.goroutines.Done()
for {
_, err := m.conn.Receive()
_, err := m.om.Receive()
if err != nil {
select {
case <-m.stop:
@@ -90,6 +123,7 @@ func (m *Mon) pump() {
// debounce calls the callback function with a delay between events
// and exits when a stop is issued.
func (m *Mon) debounce() {
defer m.goroutines.Done()
for {
select {
case <-m.stop: