diff --git a/ipn/ipnserver/server.go b/ipn/ipnserver/server.go index 776d690f8..4ebb33fd1 100644 --- a/ipn/ipnserver/server.go +++ b/ipn/ipnserver/server.go @@ -33,15 +33,19 @@ type Options struct { // SocketPath, on unix systems, is the unix socket path to listen // on for frontend connections. SocketPath string + // Port, on windows, is the localhost TCP port to listen on for // frontend connections. Port int + // StatePath is the path to the stored agent state. StatePath string + // AutostartStateKey, if non-empty, immediately starts the agent // using the given StateKey. If empty, the agent stays idle and // waits for a frontend to start it. AutostartStateKey ipn.StateKey + // LegacyConfigPath optionally specifies the old-style relaynode // relay.conf location. If both LegacyConfigPath and // AutostartStateKey are specified and the requested state doesn't @@ -51,6 +55,7 @@ type Options struct { // TODO(danderson): remove some time after the transition to // tailscaled is done. LegacyConfigPath string + // SurviveDisconnects specifies how the server reacts to its // frontend disconnecting. If true, the server keeps running on // its existing state, and accepts new frontend connections. If @@ -60,6 +65,10 @@ type Options struct { // DebugMux, if non-nil, specifies an HTTP ServeMux in which // to register a debug handler. 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 } func pump(logf logger.Logf, ctx context.Context, bs *ipn.BackendServer, s net.Conn) { @@ -79,9 +88,9 @@ func pump(logf logger.Logf, ctx context.Context, bs *ipn.BackendServer, s net.Co } } -func Run(rctx context.Context, logf logger.Logf, logid string, opts Options, e wgengine.Engine) (err error) { - runDone := make(chan error, 1) - defer func() { runDone <- err }() +func Run(rctx context.Context, logf logger.Logf, logid string, opts Options, e wgengine.Engine) error { + runDone := make(chan struct{}) + defer close(runDone) listen, _, err := safesocket.Listen(opts.SocketPath, uint16(opts.Port)) if err != nil { @@ -98,6 +107,29 @@ func Run(rctx context.Context, logf logger.Logf, logid string, opts Options, e w }() logf("Listening on %v", listen.Addr()) + bo := backoff.NewBackoff("ipnserver", logf) + + if opts.ErrorMessage != "" { + for i := 1; rctx.Err() == nil; i++ { + s, err := listen.Accept() + if err != nil { + logf("%d: Accept: %v", i, err) + bo.BackOff(rctx, err) + continue + } + serverToClient := func(b []byte) { + ipn.WriteMsg(s, b) + } + go func() { + defer s.Close() + bs := ipn.NewBackendServer(logf, nil, serverToClient) + bs.SendErrorMessage(opts.ErrorMessage) + s.Read(make([]byte, 1)) + }() + } + return rctx.Err() + } + var store ipn.StateStore if opts.StatePath != "" { store, err = ipn.NewFileStore(opts.StatePath) @@ -164,8 +196,6 @@ func Run(rctx context.Context, logf logger.Logf, logid string, opts Options, e w } } - bo := backoff.NewBackoff("ipnserver", logf) - for i := 1; rctx.Err() == nil; i++ { s, err = listen.Accept() if err != nil { diff --git a/ipn/message.go b/ipn/message.go index 3e1146247..cac0eb07f 100644 --- a/ipn/message.go +++ b/ipn/message.go @@ -80,6 +80,10 @@ func (bs *BackendServer) send(n Notify) { bs.sendNotifyMsg(b) } +func (bs *BackendServer) SendErrorMessage(msg string) { + bs.send(Notify{ErrMessage: &msg}) +} + // GotCommandMsg parses the incoming message b as a JSON Command and // calls GotCommand with it. func (bs *BackendServer) GotCommandMsg(b []byte) error {