From 9b07517f18114de4f72a072cd681c63eec76c701 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 27 Aug 2020 21:25:17 -0700 Subject: [PATCH] wgengine: add Engine.SetLinkChangeCallback Start of making the IPN state machine react to link changes and down its DNS & routes if necessary to unblock proxy resolution (e.g. for transitioning from public to corp networks where the corp network has mandatory proxies and WPAD PAC files that can't be resolved while using the DNS/routes configured previously) This change should be a no-op. Just some callback plumbing. --- ipn/local.go | 13 +++++++++++++ wgengine/userspace.go | 32 +++++++++++++++++++++----------- wgengine/watchdog.go | 4 ++++ wgengine/wgengine.go | 5 +++++ 4 files changed, 43 insertions(+), 11 deletions(-) diff --git a/ipn/local.go b/ipn/local.go index 7a576c63a..6e4b6981c 100644 --- a/ipn/local.go +++ b/ipn/local.go @@ -19,6 +19,7 @@ import ( "tailscale.com/internal/deepprint" "tailscale.com/ipn/ipnstate" "tailscale.com/ipn/policy" + "tailscale.com/net/interfaces" "tailscale.com/net/tsaddr" "tailscale.com/portlist" "tailscale.com/tailcfg" @@ -109,11 +110,23 @@ func NewLocalBackend(logf logger.Logf, logid string, store StateStore, e wgengin state: NoState, portpoll: portpoll, } + e.SetLinkChangeCallback(b.linkChange) b.statusChanged = sync.NewCond(&b.statusLock) return b, nil } +func (b *LocalBackend) linkChange(major bool, ifst *interfaces.State) { + // TODO(bradfitz): on a major link change, ask controlclient + // whether its host (e.g. login.tailscale.com) is reachable. + // If not, down the world and poll for a bit. Windows' WinHTTP + // service might be unable to resolve its WPAD PAC URL if we + // have DNS/routes configured. So we need to remove that DNS + // and those routes to let it figure out its proxy + // settings. Once it's back up and happy, then we can resume + // and our connection to the control server would work again. +} + // Shutdown halts the backend and all its sub-components. The backend // can no longer be used after Shutdown returns. func (b *LocalBackend) Shutdown() { diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 69eaff7d0..c3276631d 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -109,13 +109,14 @@ type userspaceEngine struct { sentActivityAt map[packet.IP]*int64 // value is atomic int64 of unixtime destIPActivityFuncs map[packet.IP]func() - mu sync.Mutex // guards following; see lock order comment below - closing bool // Close was called (even if we're still closing) - statusCallback StatusCallback - peerSequence []wgcfg.Key - endpoints []string - pingers map[wgcfg.Key]*pinger // legacy pingers for pre-discovery peers - linkState *interfaces.State + mu sync.Mutex // guards following; see lock order comment below + closing bool // Close was called (even if we're still closing) + statusCallback StatusCallback + linkChangeCallback func(major bool, newState *interfaces.State) + peerSequence []wgcfg.Key + endpoints []string + pingers map[wgcfg.Key]*pinger // legacy pingers for pre-discovery peers + linkState *interfaces.State // Lock ordering: magicsock.Conn.mu, wgLock, then mu. } @@ -1110,15 +1111,15 @@ func (e *userspaceEngine) Wait() { <-e.waitCh } -func (e *userspaceEngine) setLinkState(st *interfaces.State) (changed bool) { +func (e *userspaceEngine) setLinkState(st *interfaces.State) (changed bool, cb func(major bool, newState *interfaces.State)) { if st == nil { - return false + return false, nil } e.mu.Lock() defer e.mu.Unlock() changed = e.linkState == nil || !st.Equal(e.linkState) e.linkState = st - return changed + return changed, e.linkChangeCallback } func (e *userspaceEngine) LinkChange(isExpensive bool) { @@ -1128,7 +1129,7 @@ func (e *userspaceEngine) LinkChange(isExpensive bool) { return } cur.IsExpensive = isExpensive - needRebind := e.setLinkState(cur) + needRebind, linkChangeCallback := e.setLinkState(cur) if needRebind { e.logf("LinkChange: major, rebinding. New state: %+v", cur) @@ -1142,6 +1143,15 @@ func (e *userspaceEngine) LinkChange(isExpensive bool) { e.magicConn.Rebind() } e.magicConn.ReSTUN(why) + if linkChangeCallback != nil { + go linkChangeCallback(needRebind, cur) + } +} + +func (e *userspaceEngine) SetLinkChangeCallback(cb func(major bool, newState *interfaces.State)) { + e.mu.Lock() + defer e.mu.Unlock() + e.linkChangeCallback = cb } func getLinkState() (*interfaces.State, error) { diff --git a/wgengine/watchdog.go b/wgengine/watchdog.go index 5635c795b..6a8f2b698 100644 --- a/wgengine/watchdog.go +++ b/wgengine/watchdog.go @@ -16,6 +16,7 @@ import ( "inet.af/netaddr" "tailscale.com/control/controlclient" "tailscale.com/ipn/ipnstate" + "tailscale.com/net/interfaces" "tailscale.com/tailcfg" "tailscale.com/wgengine/filter" "tailscale.com/wgengine/router" @@ -100,6 +101,9 @@ func (e *watchdogEngine) RequestStatus() { func (e *watchdogEngine) LinkChange(isExpensive bool) { e.watchdog("LinkChange", func() { e.wrap.LinkChange(isExpensive) }) } +func (e *watchdogEngine) SetLinkChangeCallback(cb func(major bool, newState *interfaces.State)) { + e.watchdog("SetLinkChangeCallback", func() { e.wrap.SetLinkChangeCallback(cb) }) +} func (e *watchdogEngine) SetDERPMap(m *tailcfg.DERPMap) { e.watchdog("SetDERPMap", func() { e.wrap.SetDERPMap(m) }) } diff --git a/wgengine/wgengine.go b/wgengine/wgengine.go index 6b3af8088..768a2cf60 100644 --- a/wgengine/wgengine.go +++ b/wgengine/wgengine.go @@ -12,6 +12,7 @@ import ( "inet.af/netaddr" "tailscale.com/control/controlclient" "tailscale.com/ipn/ipnstate" + "tailscale.com/net/interfaces" "tailscale.com/tailcfg" "tailscale.com/wgengine/filter" "tailscale.com/wgengine/router" @@ -117,6 +118,10 @@ type Engine interface { // new NetInfo summary is available. SetNetInfoCallback(NetInfoCallback) + // SetLinkChangeCallback sets the function to call when the + // link state changes. + SetLinkChangeCallback(func(major bool, newState *interfaces.State)) + // DiscoPublicKey gets the public key used for path discovery // messages. DiscoPublicKey() tailcfg.DiscoKey