ipn: on transition from no PAC to PAC, reset state

So previous routes aren't shadowing resources that the operating
system might need (Windows Domain Controller, DNS server, corp HTTP
proxy, WinHTTP fetching the PAC file itself, etc).

This effectively detects when we're transitioning from, say, public
wifi to corp wifi and makes Tailscale remove all its routes and stops
its TCP connections and tries connecting to everything anew.

Updates tailscale/corp#653
This commit is contained in:
Brad Fitzpatrick 2020-10-01 22:03:25 -07:00
parent cab3eb995f
commit 12e28aa87d
1 changed files with 31 additions and 8 deletions

View File

@ -79,6 +79,7 @@ type LocalBackend struct {
blocked bool blocked bool
authURL string authURL string
interact int interact int
prevIfState *interfaces.State
// statusLock must be held before calling statusChanged.Wait() or // statusLock must be held before calling statusChanged.Wait() or
// statusChanged.Broadcast(). // statusChanged.Broadcast().
@ -119,15 +120,37 @@ func NewLocalBackend(logf logger.Logf, logid string, store StateStore, e wgengin
return b, nil return b, nil
} }
// linkChange is called (in a new goroutine) by wgengine when its link monitor
// detects a network change.
func (b *LocalBackend) linkChange(major bool, ifst *interfaces.State) { func (b *LocalBackend) linkChange(major bool, ifst *interfaces.State) {
// TODO(bradfitz): on a major link change, ask controlclient b.mu.Lock()
// whether its host (e.g. login.tailscale.com) is reachable. defer b.mu.Unlock()
// If not, down the world and poll for a bit. Windows' WinHTTP
// service might be unable to resolve its WPAD PAC URL if we // On transition from no PAC to PAC, assume we're
// have DNS/routes configured. So we need to remove that DNS // roaming into some corp network where the corp HTTP
// and those routes to let it figure out its proxy // proxy & Windows Domain Controller & DNS etc all
// settings. Once it's back up and happy, then we can resume // might be behind subnet routes that we've otherwise
// and our connection to the control server would work again. // shadowed.
//
// So remove all our routes and reset the control connections
gotPAC := ifst.PAC != "" && b.prevIfState != nil && b.prevIfState.PAC == ""
if gotPAC {
b.logf("linkChange: entering PAC network, resetting; state=%v", b.state)
b.e.Reconfig(&wgcfg.Config{}, &router.Config{})
b.logf("linkChange: did wg+router reset")
if b.c != nil && b.state != Stopped {
// Pause and unpause the client to reset its
// HTTP connections.
// TODO(bradfitz): this is somewhat gross. Add
// a more explicit method to the client.
b.c.SetPaused(true)
b.c.SetPaused(false)
b.logf("linkChange: did control client reset")
}
}
b.prevIfState = ifst
} }
// Shutdown halts the backend and all its sub-components. The backend // Shutdown halts the backend and all its sub-components. The backend