2020-02-12 20:53:55 +00:00
|
|
|
// 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.
|
|
|
|
|
|
|
|
// Package monitor provides facilities for monitoring network
|
|
|
|
// interface changes.
|
|
|
|
package monitor
|
|
|
|
|
|
|
|
import (
|
2020-03-10 18:02:30 +00:00
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"runtime"
|
|
|
|
"strings"
|
2020-02-17 17:00:38 +00:00
|
|
|
"sync"
|
2020-02-12 20:53:55 +00:00
|
|
|
"time"
|
|
|
|
|
2020-03-10 18:02:30 +00:00
|
|
|
"tailscale.com/net/interfaces"
|
2020-02-15 03:23:16 +00:00
|
|
|
"tailscale.com/types/logger"
|
2020-02-12 20:53:55 +00:00
|
|
|
)
|
|
|
|
|
2020-02-17 17:00:38 +00:00
|
|
|
// 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{}
|
2020-02-12 20:53:55 +00:00
|
|
|
|
2020-02-17 17:00:38 +00:00
|
|
|
// osMon is the interface that each operating system-specific
|
|
|
|
// implementation of the link monitor must implement.
|
|
|
|
type osMon interface {
|
2020-02-12 20:53:55 +00:00
|
|
|
Close() error
|
2020-02-17 17:00:38 +00:00
|
|
|
|
|
|
|
// 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)
|
2020-02-12 20:53:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ChangeFunc is a callback function that's called when
|
|
|
|
// an interface status changes.
|
|
|
|
type ChangeFunc func()
|
|
|
|
|
|
|
|
// Mon represents a monitoring instance.
|
|
|
|
type Mon struct {
|
|
|
|
logf logger.Logf
|
|
|
|
cb ChangeFunc
|
2020-02-17 17:00:38 +00:00
|
|
|
om osMon // nil means not supported on this platform
|
2020-02-12 20:53:55 +00:00
|
|
|
change chan struct{}
|
|
|
|
stop chan struct{}
|
2020-02-17 17:00:38 +00:00
|
|
|
|
|
|
|
onceStart sync.Once
|
|
|
|
started bool
|
|
|
|
goroutines sync.WaitGroup
|
2020-02-12 20:53:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// New instantiates and starts a monitoring instance. Change notifications
|
|
|
|
// are propagated to the callback function.
|
2020-02-17 17:00:38 +00:00
|
|
|
// The returned monitor is inactive until it's started by the Start method.
|
2020-02-12 20:53:55 +00:00
|
|
|
func New(logf logger.Logf, callback ChangeFunc) (*Mon, error) {
|
2020-02-17 17:00:38 +00:00
|
|
|
om, err := newOSMon()
|
2020-02-12 20:53:55 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-02-17 17:00:38 +00:00
|
|
|
return &Mon{
|
2020-02-12 20:53:55 +00:00
|
|
|
logf: logf,
|
|
|
|
cb: callback,
|
2020-02-17 17:00:38 +00:00
|
|
|
om: om,
|
2020-02-12 20:53:55 +00:00
|
|
|
change: make(chan struct{}, 1),
|
|
|
|
stop: make(chan struct{}),
|
2020-02-17 17:00:38 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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()
|
|
|
|
})
|
2020-02-12 20:53:55 +00:00
|
|
|
}
|
|
|
|
|
2020-02-17 17:00:38 +00:00
|
|
|
// Close closes the monitor.
|
|
|
|
// It may only be called once.
|
2020-02-12 20:53:55 +00:00
|
|
|
func (m *Mon) Close() error {
|
|
|
|
close(m.stop)
|
2020-02-17 17:00:38 +00:00
|
|
|
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
|
2020-02-12 20:53:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// pump continuously retrieves messages from the connection, notifying
|
|
|
|
// the change channel of changes, and stopping when a stop is issued.
|
|
|
|
func (m *Mon) pump() {
|
2020-02-17 17:00:38 +00:00
|
|
|
defer m.goroutines.Done()
|
2020-03-10 18:02:30 +00:00
|
|
|
last := interfaceSummary()
|
2020-02-12 20:53:55 +00:00
|
|
|
for {
|
2020-02-17 17:00:38 +00:00
|
|
|
_, err := m.om.Receive()
|
2020-02-12 20:53:55 +00:00
|
|
|
if err != nil {
|
|
|
|
select {
|
|
|
|
case <-m.stop:
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
// Keep retrying while we're not closed.
|
|
|
|
m.logf("Error receiving from connection: %v", err)
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-03-10 18:02:30 +00:00
|
|
|
cur := interfaceSummary()
|
|
|
|
if cur == last {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
m.logf("wgengine/monitor: now %v (was %v)", cur, last)
|
|
|
|
last = cur
|
|
|
|
|
2020-02-12 20:53:55 +00:00
|
|
|
select {
|
|
|
|
case m.change <- struct{}{}:
|
2020-03-10 18:02:30 +00:00
|
|
|
case <-m.stop:
|
|
|
|
return
|
2020-02-12 20:53:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// debounce calls the callback function with a delay between events
|
|
|
|
// and exits when a stop is issued.
|
|
|
|
func (m *Mon) debounce() {
|
2020-02-17 17:00:38 +00:00
|
|
|
defer m.goroutines.Done()
|
2020-02-12 20:53:55 +00:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-m.stop:
|
|
|
|
return
|
|
|
|
case <-m.change:
|
|
|
|
}
|
|
|
|
|
|
|
|
m.cb()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-m.stop:
|
|
|
|
return
|
|
|
|
case <-time.After(100 * time.Millisecond):
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-03-10 18:02:30 +00:00
|
|
|
|
|
|
|
func interfaceSummary() string {
|
|
|
|
var sb strings.Builder
|
|
|
|
_ = interfaces.ForeachInterfaceAddress(func(ni interfaces.Interface, addr net.IP) {
|
|
|
|
if runtime.GOOS == "linux" && strings.HasPrefix(ni.Name, "tailscale") {
|
|
|
|
// Skip tailscale0, etc on Linux.
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if ni.IsUp() {
|
|
|
|
fmt.Fprintf(&sb, "%s=%s ", ni.Name, addr)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
return strings.TrimSpace(sb.String())
|
|
|
|
}
|