From 4d3c09ced44d1de3df811c6cf61bc85752d8efc4 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 25 Jan 2021 14:53:31 -0800 Subject: [PATCH] ipn/ipnserver: on Windows in unattended mode, wait for Engine forever Updates #1187 --- ipn/ipnserver/server.go | 118 ++++++++++++++++++++++++++++++---------- 1 file changed, 90 insertions(+), 28 deletions(-) diff --git a/ipn/ipnserver/server.go b/ipn/ipnserver/server.go index c4fd95a2b..7518c6e2a 100644 --- a/ipn/ipnserver/server.go +++ b/ipn/ipnserver/server.go @@ -503,12 +503,80 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() ( }() logf("Listening on %v", listen.Addr()) + var store ipn.StateStore + if opts.StatePath != "" { + store, err = ipn.NewFileStore(opts.StatePath) + if err != nil { + return fmt.Errorf("ipn.NewFileStore(%q): %v", opts.StatePath, err) + } + if opts.AutostartStateKey == "" { + autoStartKey, err := store.ReadState(ipn.ServerModeStartKey) + if err != nil && err != ipn.ErrStateNotExist { + return fmt.Errorf("calling ReadState on %s: %w", opts.StatePath, err) + } + key := string(autoStartKey) + if strings.HasPrefix(key, "user-") { + uid := strings.TrimPrefix(key, "user-") + u, err := server.lookupUserFromID(uid) + if err != nil { + logf("ipnserver: found server mode auto-start key %q; failed to load user: %v", key, err) + } else { + logf("ipnserver: found server mode auto-start key %q (user %s)", key, u.Username) + server.serverModeUser = u + } + opts.AutostartStateKey = ipn.StateKey(key) + } + } + } else { + store = &ipn.MemoryStore{} + } + bo := backoff.NewBackoff("ipnserver", logf, 30*time.Second) var unservedConn net.Conn // if non-nil, accepted, but hasn't served yet eng, err := getEngine() if err != nil { logf("ipnserver: initial getEngine call: %v", err) + + // Issue 1187: on Windows, in unattended mode, + // sometimes we try 5 times and fail to create the + // engine before the system's ready. Hack until the + // bug if fixed properly: if we're running in + // unattended mode on Windows, keep trying forever, + // waiting for the machine to be ready (networking to + // come up?) and then dial our own safesocket TCP + // listener to wake up the usual mechanism that lets + // us surface getEngine errors to UI clients. (We + // don't want to just call getEngine in a loop without + // the listener.Accept, as we do want to handle client + // connections so we can tell them about errors) + + bootRaceWaitForEngine, bootRaceWaitForEngineCancel := context.WithTimeout(context.Background(), time.Minute) + if runtime.GOOS == "windows" && opts.AutostartStateKey != "" { + logf("ipnserver: in unattended mode, waiting for engine availability") + getEngine = getEngineUntilItWorksWrapper(getEngine) + // Wait for it to be ready. + go func() { + defer bootRaceWaitForEngineCancel() + t0 := time.Now() + for { + time.Sleep(10 * time.Second) + if _, err := getEngine(); err != nil { + logf("ipnserver: unattended mode engine load: %v", err) + continue + } + c, err := net.Dial("tcp", listen.Addr().String()) + logf("ipnserver: engine created after %v; waking up Accept: Dial error: %v", time.Since(t0).Round(time.Second), err) + if err == nil { + c.Close() + } + break + } + }() + } else { + bootRaceWaitForEngineCancel() + } + for i := 1; ctx.Err() == nil; i++ { c, err := listen.Accept() if err != nil { @@ -516,6 +584,7 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() ( bo.BackOff(ctx, err) continue } + <-bootRaceWaitForEngine.Done() logf("ipnserver: try%d: trying getEngine again...", i) eng, err = getEngine() if err == nil { @@ -538,34 +607,6 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() ( } } - var store ipn.StateStore - if opts.StatePath != "" { - store, err = ipn.NewFileStore(opts.StatePath) - if err != nil { - return fmt.Errorf("ipn.NewFileStore(%q): %v", opts.StatePath, err) - } - if opts.AutostartStateKey == "" { - autoStartKey, err := store.ReadState(ipn.ServerModeStartKey) - if err != nil && err != ipn.ErrStateNotExist { - return fmt.Errorf("calling ReadState on %s: %w", opts.StatePath, err) - } - key := string(autoStartKey) - if strings.HasPrefix(key, "user-") { - uid := strings.TrimPrefix(key, "user-") - u, err := server.lookupUserFromID(uid) - if err != nil { - logf("ipnserver: found server mode auto-start key %q; failed to load user: %v", key, err) - } else { - logf("ipnserver: found server mode auto-start key %q (user %s)", key, u.Username) - server.serverModeUser = u - } - opts.AutostartStateKey = ipn.StateKey(key) - } - } - } else { - store = &ipn.MemoryStore{} - } - b, err := ipn.NewLocalBackend(logf, logid, store, eng) if err != nil { return fmt.Errorf("NewLocalBackend: %v", err) @@ -756,6 +797,27 @@ func FixedEngine(eng wgengine.Engine) func() (wgengine.Engine, error) { return func() (wgengine.Engine, error) { return eng, nil } } +// getEngineUntilItWorksWrapper returns a getEngine wrapper that does +// not call getEngine concurrently and stops calling getEngine once +// it's returned a working engine. +func getEngineUntilItWorksWrapper(getEngine func() (wgengine.Engine, error)) func() (wgengine.Engine, error) { + var mu sync.Mutex + var engGood wgengine.Engine + return func() (wgengine.Engine, error) { + mu.Lock() + defer mu.Unlock() + if engGood != nil { + return engGood, nil + } + e, err := getEngine() + if err != nil { + return nil, err + } + engGood = e + return e, nil + } +} + type dummyAddr string type oneConnListener struct { conn net.Conn