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