wgengine/monitor: add a poller to the Windows link change monitor

The poller is slow by default, but speeds up for a bit after a network
change, in case WPAD/PAC files are still loading.
crawshaw/e2etest
Brad Fitzpatrick 4 years ago
parent 1be6c6dd70
commit 24d1a38e81

@ -110,7 +110,7 @@ func (m *Mon) pump() {
default: default:
} }
// Keep retrying while we're not closed. // Keep retrying while we're not closed.
m.logf("error receiving from connection: %v", err) m.logf("error from link monitor: %v", err)
time.Sleep(time.Second) time.Sleep(time.Second)
continue continue
} }

@ -13,9 +13,16 @@ import (
"unsafe" "unsafe"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
"tailscale.com/net/interfaces"
"tailscale.com/types/logger" "tailscale.com/types/logger"
) )
const (
pollIntervalSlow = 15 * time.Second
pollIntervalFast = 3 * time.Second
pollFastFor = 30 * time.Second
)
var ( var (
iphlpapi = syscall.NewLazyDLL("iphlpapi.dll") iphlpapi = syscall.NewLazyDLL("iphlpapi.dll")
notifyAddrChangeProc = iphlpapi.NewProc("NotifyAddrChange") notifyAddrChangeProc = iphlpapi.NewProc("NotifyAddrChange")
@ -26,27 +33,45 @@ type unspecifiedMessage struct{}
func (unspecifiedMessage) ignore() bool { return false } func (unspecifiedMessage) ignore() bool { return false }
type winMon struct { type pollStateChangedMessage struct{}
ctx context.Context
cancel context.CancelFunc
logf logger.Logf func (pollStateChangedMessage) ignore() bool { return false }
type messageOrError struct {
message
error
}
mu sync.Mutex type winMon struct {
event windows.Handle ctx context.Context
cancel context.CancelFunc
messagec chan messageOrError
logf logger.Logf
pollTicker *time.Ticker
lastState *interfaces.State
mu sync.Mutex
event windows.Handle
lastNetChange time.Time
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) (osMon, error) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
return &winMon{ m := &winMon{
logf: logf, logf: logf,
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
}, nil messagec: make(chan messageOrError, 1),
pollTicker: time.NewTicker(pollIntervalSlow),
}
go m.awaitIPAndRouteChanges()
return m, nil
} }
func (m *winMon) Close() error { func (m *winMon) Close() error {
m.cancel() m.cancel()
m.pollTicker.Stop()
m.mu.Lock() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()
@ -61,6 +86,52 @@ func (m *winMon) Close() error {
var errClosed = errors.New("closed") var errClosed = errors.New("closed")
func (m *winMon) Receive() (message, error) { func (m *winMon) Receive() (message, error) {
for {
select {
case <-m.ctx.Done():
return nil, errClosed
case me := <-m.messagec:
return me.message, me.error
case <-m.pollTicker.C:
if m.stateChanged() {
m.logf("interface state changed (on poll)")
return pollStateChangedMessage{}, nil
}
m.mu.Lock()
if m.inFastPoll && time.Since(m.lastNetChange) > pollFastFor {
m.inFastPoll = false
m.pollTicker.Reset(pollIntervalSlow)
}
m.mu.Unlock()
}
}
}
func (m *winMon) stateChanged() bool {
st, err := interfaces.GetState()
if err != nil {
return false
}
changed := !st.Equal(m.lastState)
m.lastState = st
return changed
}
func (m *winMon) awaitIPAndRouteChanges() {
for {
msg, err := m.getIPOrRouteChangeMessage()
if err == errClosed {
return
}
select {
case m.messagec <- messageOrError{msg, err}:
case <-m.ctx.Done():
return
}
}
}
func (m *winMon) getIPOrRouteChangeMessage() (message, error) {
if m.ctx.Err() != nil { if m.ctx.Err() != nil {
return nil, errClosed return nil, errClosed
} }
@ -91,19 +162,29 @@ func (m *winMon) Receive() (message, error) {
} }
t0 := time.Now() t0 := time.Now()
evt, err := windows.WaitForSingleObject(o.HEvent, windows.INFINITE) _, err = windows.WaitForSingleObject(o.HEvent, windows.INFINITE)
if m.ctx.Err() != nil { if m.ctx.Err() != nil {
return nil, errClosed return nil, errClosed
} }
if err != nil { if err != nil {
m.logf("notifyRouteChange: %v", err) m.logf("waitForSingleObject: %v", err)
return nil, err return nil, err
} }
d := time.Since(t0) d := time.Since(t0)
m.logf("got windows change event after %v: %+v", d, evt) m.logf("got windows change event after %v", d)
m.mu.Lock() m.mu.Lock()
m.event = 0 {
m.lastNetChange = time.Now()
m.event = 0
// Something changed, so assume Windows is about to
// discover its new proxy settings from WPAD, which
// seems to take a bit. Poll heavily for awhile.
m.logf("starting quick poll, waiting for WPAD change")
m.inFastPoll = true
m.pollTicker.Reset(pollIntervalFast)
}
m.mu.Unlock() m.mu.Unlock()
return unspecifiedMessage{}, nil return unspecifiedMessage{}, nil

Loading…
Cancel
Save