ipn/ipnserver: make Engine argument a func that tries again for each connection

So a backend in server-an-error state (as used by Windows) can try to
create a new Engine again each time somebody re-connects, relaunching
the GUI app.

(The proper fix is actually fixing Windows issues, but this makes things better
in the short term)

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick 2020-07-29 13:38:09 -07:00 committed by Brad Fitzpatrick
parent d55fdd4669
commit 4aba86cc03
3 changed files with 24 additions and 12 deletions

View File

@ -169,7 +169,7 @@ func run() error {
SurviveDisconnects: true, SurviveDisconnects: true,
DebugMux: debugMux, DebugMux: debugMux,
} }
err = ipnserver.Run(ctx, logf, pol.PublicID.String(), opts, e) err = ipnserver.Run(ctx, logf, pol.PublicID.String(), ipnserver.FixedEngine(e), opts)
// Cancelation is not an error: it is the only way to stop ipnserver. // Cancelation is not an error: it is the only way to stop ipnserver.
if err != nil && err != context.Canceled { if err != nil && err != context.Canceled {
logf("ipnserver.Run: %v", err) logf("ipnserver.Run: %v", err)

View File

@ -69,10 +69,6 @@ type Options struct {
// DebugMux, if non-nil, specifies an HTTP ServeMux in which // DebugMux, if non-nil, specifies an HTTP ServeMux in which
// to register a debug handler. // to register a debug handler.
DebugMux *http.ServeMux DebugMux *http.ServeMux
// ErrorMessage, if not empty, signals that the server will exist
// only to relay the provided critical error message to the user.
ErrorMessage string
} }
// server is an IPN backend and its set of 0 or more active connections // server is an IPN backend and its set of 0 or more active connections
@ -152,7 +148,9 @@ func (s *server) writeToClients(b []byte) {
} }
} }
func Run(ctx context.Context, logf logger.Logf, logid string, opts Options, e wgengine.Engine) error { // Run runs a Tailscale backend service.
// The getEngine func is called repeatedly, once per connection, until it returns an engine successfully.
func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (wgengine.Engine, error), opts Options) error {
runDone := make(chan struct{}) runDone := make(chan struct{})
defer close(runDone) defer close(runDone)
@ -179,7 +177,9 @@ func Run(ctx context.Context, logf logger.Logf, logid string, opts Options, e wg
bo := backoff.NewBackoff("ipnserver", logf) bo := backoff.NewBackoff("ipnserver", logf)
if opts.ErrorMessage != "" { eng, err := getEngine()
if err != nil {
logf("Initial getEngine call: %v", err)
for i := 1; ctx.Err() == nil; i++ { for i := 1; ctx.Err() == nil; i++ {
s, err := listen.Accept() s, err := listen.Accept()
if err != nil { if err != nil {
@ -187,13 +187,20 @@ func Run(ctx context.Context, logf logger.Logf, logid string, opts Options, e wg
bo.BackOff(ctx, err) bo.BackOff(ctx, err)
continue continue
} }
serverToClient := func(b []byte) { logf("%d: trying getEngine again...", i)
ipn.WriteMsg(s, b) //lint:ignore SA4006 staticcheck is wrong
eng, err = getEngine()
if err == nil {
logf("%d: GetEngine worked; exiting failure loop", i)
break
} }
logf("%d: getEngine failed again: %v", i, err)
errMsg := err.Error()
go func() { go func() {
defer s.Close() defer s.Close()
serverToClient := func(b []byte) { ipn.WriteMsg(s, b) }
bs := ipn.NewBackendServer(logf, nil, serverToClient) bs := ipn.NewBackendServer(logf, nil, serverToClient)
bs.SendErrorMessage(opts.ErrorMessage) bs.SendErrorMessage(errMsg)
s.Read(make([]byte, 1)) s.Read(make([]byte, 1))
}() }()
} }
@ -210,7 +217,7 @@ func Run(ctx context.Context, logf logger.Logf, logid string, opts Options, e wg
store = &ipn.MemoryStore{} store = &ipn.MemoryStore{}
} }
b, err := ipn.NewLocalBackend(logf, logid, store, e) b, err := ipn.NewLocalBackend(logf, logid, store, eng)
if err != nil { if err != nil {
return fmt.Errorf("NewLocalBackend: %v", err) return fmt.Errorf("NewLocalBackend: %v", err)
} }
@ -371,3 +378,8 @@ func BabysitProc(ctx context.Context, args []string, logf logger.Logf) {
} }
} }
} }
// FixedEngine returns a func that returns eng and a nil error.
func FixedEngine(eng wgengine.Engine) func() (wgengine.Engine, error) {
return func() (wgengine.Engine, error) { return eng, nil }
}

View File

@ -72,6 +72,6 @@ func TestRunMultipleAccepts(t *testing.T) {
SocketPath: socketPath, SocketPath: socketPath,
} }
t.Logf("pre-Run") t.Logf("pre-Run")
err = ipnserver.Run(ctx, logTriggerTestf, "dummy_logid", opts, eng) err = ipnserver.Run(ctx, logTriggerTestf, "dummy_logid", ipnserver.FixedEngine(eng), opts)
t.Logf("ipnserver.Run = %v", err) t.Logf("ipnserver.Run = %v", err)
} }