ipn, ipn/ipnserver: add IPN state for server in use, handle explicitly

On Windows, we were previously treating a server used by different
users as a fatal error, which meant the second user (upon starting
Tailscale, explicitly or via Start Up programs) got an invasive error
message dialog.

Instead, give it its own IPN state and change the Notify.ErrMessage to
be details in that state. Then the Windows GUI can be less aggresive
about that happening.

Also,

* wait to close the IPN connection until the server ownership state
  changes so the GUI doesn't need to repeatedly reconnect to discover
  changes.

* fix a bug discovered during testing: on system reboot, the
  ipnserver's serverModeUser was getting cleared while the state
  transitioned from Unknown to Running. Instead, track 'inServerMode'
  explicitly and remove the old accessor method which was error prone.

* fix a rare bug where the client could start up and set the server
  mode prefs in its Start call and we wouldn't persist that to the
  StateStore storage's prefs start key. (Previously it was only via a
  prefs toggle at runtime)
This commit is contained in:
Brad Fitzpatrick
2020-11-02 09:52:59 -08:00
parent 437142daa5
commit 20a357b386
4 changed files with 170 additions and 52 deletions
+36 -25
View File
@@ -81,6 +81,7 @@ type LocalBackend struct {
stateKey StateKey // computed in part from user-provided value
userID string // current controlling user ID (for Windows, primarily)
prefs *Prefs
inServerMode bool
machinePrivKey wgcfg.PrivateKey
state State
// hostinfo is mutated in-place while mu is held.
@@ -414,6 +415,7 @@ func (b *LocalBackend) Start(opts Options) error {
return fmt.Errorf("loading requested state: %v", err)
}
b.inServerMode = b.stateKey != ""
b.serverURL = b.prefs.ControlURL
hostinfo.RoutableIPs = append(hostinfo.RoutableIPs, b.prefs.AdvertiseRoutes...)
hostinfo.RequestTags = append(hostinfo.RequestTags, b.prefs.AdvertiseTags...)
@@ -766,6 +768,35 @@ func (b *LocalBackend) initMachineKeyLocked() (err error) {
return nil
}
// writeServerModeStartState stores the ServerModeStartKey value based on the current
// user and prefs. If userID is blank or prefs is blank, no work is done.
//
// b.mu may either be held or not.
func (b *LocalBackend) writeServerModeStartState(userID string, prefs *Prefs) {
if userID == "" || prefs == nil {
return
}
if prefs.ForceDaemon {
stateKey := StateKey("user-" + userID)
if err := b.store.WriteState(ServerModeStartKey, []byte(stateKey)); err != nil {
b.logf("WriteState error: %v", err)
}
// It's important we do this here too, even if it looks
// redundant with the one in the 'if stateKey != ""'
// check block above. That one won't fire in the case
// where the Windows client started up in client mode.
// This happens when we transition into server mode:
if err := b.store.WriteState(stateKey, prefs.ToBytes()); err != nil {
b.logf("WriteState error: %v", err)
}
} else {
if err := b.store.WriteState(ServerModeStartKey, nil); err != nil {
b.logf("WriteState error: %v", err)
}
}
}
// loadStateLocked sets b.prefs and b.stateKey based on a complex
// combination of key, prefs, and legacyPath. b.mu must be held when
// calling.
@@ -786,6 +817,7 @@ func (b *LocalBackend) loadStateLocked(key StateKey, prefs *Prefs, legacyPath st
return fmt.Errorf("initMachineKeyLocked: %w", err)
}
b.stateKey = ""
b.writeServerModeStartState(b.userID, b.prefs)
return nil
}
@@ -843,13 +875,10 @@ func (b *LocalBackend) State() State {
return b.state
}
// RunningAndDaemonForced reports whether the backend is currently
// running and the preferences say that Tailscale should run in
// "server mode" (ForceDaemon).
func (b *LocalBackend) RunningAndDaemonForced() bool {
func (b *LocalBackend) InServerMode() bool {
b.mu.Lock()
defer b.mu.Unlock()
return b.state == Running && b.prefs != nil && b.prefs.ForceDaemon
return b.inServerMode
}
// getEngineStatus returns a copy of b.engineStatus.
@@ -1001,6 +1030,7 @@ func (b *LocalBackend) SetPrefs(newp *Prefs) {
oldp := b.prefs
newp.Persist = oldp.Persist // caller isn't allowed to override this
b.prefs = newp
b.inServerMode = newp.ForceDaemon
// We do this to avoid holding the lock while doing everything else.
newp = b.prefs.Clone()
@@ -1019,26 +1049,7 @@ func (b *LocalBackend) SetPrefs(newp *Prefs) {
b.logf("Failed to save new controlclient state: %v", err)
}
}
if userID != "" { // e.g. on Windows
if newp.ForceDaemon {
stateKey := StateKey("user-" + userID)
if err := b.store.WriteState(ServerModeStartKey, []byte(stateKey)); err != nil {
b.logf("WriteState error: %v", err)
}
// It's important we do this here too, even if it looks
// redundant with the one in the 'if stateKey != ""'
// check block above. That one won't fire in the case
// where the Windows client started up in client mode.
// This happens when we transition into server mode:
if err := b.store.WriteState(stateKey, newp.ToBytes()); err != nil {
b.logf("WriteState error: %v", err)
}
} else {
if err := b.store.WriteState(ServerModeStartKey, nil); err != nil {
b.logf("WriteState error: %v", err)
}
}
}
b.writeServerModeStartState(userID, newp)
// [GRINDER STATS LINE] - please don't remove (used for log parsing)
b.logf("SetPrefs: %v", newp.Pretty())