ipn/ipnlocal: add a LocalBackend.Start fast path if already running

Updates tailscale/corp#1621

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick 2021-04-23 10:26:25 -07:00 committed by Brad Fitzpatrick
parent ad1a595a75
commit fe53a714bd
2 changed files with 53 additions and 13 deletions

View File

@ -55,17 +55,21 @@ type EngineStatus struct {
// that they have not changed. // that they have not changed.
// They are JSON-encoded on the wire, despite the lack of struct tags. // They are JSON-encoded on the wire, despite the lack of struct tags.
type Notify struct { type Notify struct {
_ structs.Incomparable _ structs.Incomparable
Version string // version number of IPN backend Version string // version number of IPN backend
ErrMessage *string // critical error message, if any; for InUseOtherUser, the details
LoginFinished *empty.Message // event: non-nil when login process succeeded // ErrMessage, if non-nil, contains a critical error message.
State *State // current IPN state has changed // For State InUseOtherUser, ErrMessage is not critical and just contains the details.
Prefs *Prefs // preferences were changed ErrMessage *string
NetMap *netmap.NetworkMap // new netmap received
Engine *EngineStatus // wireguard engine stats LoginFinished *empty.Message // non-nil when/if the login process succeeded
BrowseToURL *string // UI should open a browser right now State *State // if non-nil, the new or current IPN state
BackendLogID *string // public logtail id used by backend Prefs *Prefs // if non-nil, the new or current preferences
PingResult *ipnstate.PingResult NetMap *netmap.NetworkMap // if non-nil, the new or current netmap
Engine *EngineStatus // if non-nil, the new or urrent wireguard stats
BrowseToURL *string // if non-nil, UI should open a browser right now
BackendLogID *string // if non-nil, the public logtail ID used by backend
PingResult *ipnstate.PingResult // if non-nil, a ping response arrived
// FilesWaiting if non-nil means that files are buffered in // FilesWaiting if non-nil means that files are buffered in
// the Tailscale daemon and ready for local transfer to the // the Tailscale daemon and ready for local transfer to the

View File

@ -590,6 +590,24 @@ func (b *LocalBackend) SetHTTPTestClient(c *http.Client) {
b.httpTestClient = c b.httpTestClient = c
} }
// startIsNoopLocked reports whether a Start call on this LocalBackend
// with the provided Start Options would be a useless no-op.
//
// b.mu must be held.
func (b *LocalBackend) startIsNoopLocked(opts ipn.Options) bool {
// Options has 4 fields; check all of them:
// * FrontendLogID
// * StateKey
// * Prefs
// * AuthKey
return b.state == ipn.Running &&
b.hostinfo != nil &&
b.hostinfo.FrontendLogID == opts.FrontendLogID &&
b.stateKey == opts.StateKey &&
opts.Prefs == nil &&
opts.AuthKey == ""
}
// Start applies the configuration specified in opts, and starts the // Start applies the configuration specified in opts, and starts the
// state machine. // state machine.
// //
@ -612,12 +630,30 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
b.logf("Start") b.logf("Start")
} }
b.mu.Lock()
// The iOS client sends a "Start" whenever its UI screen comes
// up, just because it wants a netmap. That should be fixed,
// but meanwhile we can make Start cheaper here for such a
// case and not restart the world (which takes a few seconds).
// Instead, just send a notify with the state that iOS needs.
if b.startIsNoopLocked(opts) {
b.logf("Start: already running; sending notify")
nm := b.netMap
state := b.state
b.mu.Unlock()
b.send(ipn.Notify{
State: &state,
NetMap: nm,
LoginFinished: new(empty.Message),
})
return nil
}
hostinfo := controlclient.NewHostinfo() hostinfo := controlclient.NewHostinfo()
hostinfo.BackendLogID = b.backendLogID hostinfo.BackendLogID = b.backendLogID
hostinfo.FrontendLogID = opts.FrontendLogID hostinfo.FrontendLogID = opts.FrontendLogID
b.mu.Lock()
if b.cc != nil { if b.cc != nil {
// TODO(apenwarr): avoid the need to reinit controlclient. // TODO(apenwarr): avoid the need to reinit controlclient.
// This will trigger a full relogin/reconfigure cycle every // This will trigger a full relogin/reconfigure cycle every