From 6099ecf7f47f29d1824e6ac3785a27a7a48391b1 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 5 Feb 2021 08:46:12 -0800 Subject: [PATCH] cmd/tailscaled: run as a service on Windows Updates #1232 --- cmd/tailscaled/tailscaled.go | 10 ++++ cmd/tailscaled/tailscaled_notwindows.go | 13 +++++ cmd/tailscaled/tailscaled_windows.go | 64 +++++++++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 cmd/tailscaled/tailscaled_notwindows.go create mode 100644 cmd/tailscaled/tailscaled_windows.go diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 9e9aaced8..2e4dc05bd 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -132,6 +132,16 @@ func run() error { pol.Shutdown(ctx) }() + if isWindowsService() { + // Run the IPN server from the Windows service manager. + log.Printf("Running service...") + if err := runWindowsService(pol); err != nil { + log.Printf("runservice: %v", err) + } + log.Printf("Service ended.") + return nil + } + var logf logger.Logf = log.Printf if v, _ := strconv.ParseBool(os.Getenv("TS_DEBUG_MEMORY")); v { logf = logger.RusagePrefixLog(logf) diff --git a/cmd/tailscaled/tailscaled_notwindows.go b/cmd/tailscaled/tailscaled_notwindows.go new file mode 100644 index 000000000..eb6cd4e9c --- /dev/null +++ b/cmd/tailscaled/tailscaled_notwindows.go @@ -0,0 +1,13 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !windows + +package main // import "tailscale.com/cmd/tailscaled" + +import "tailscale.com/logpolicy" + +func isWindowsService() bool { return false } + +func runWindowsService(pol *logpolicy.Policy) error { panic("unreachable") } diff --git a/cmd/tailscaled/tailscaled_windows.go b/cmd/tailscaled/tailscaled_windows.go new file mode 100644 index 000000000..a7c69eb2c --- /dev/null +++ b/cmd/tailscaled/tailscaled_windows.go @@ -0,0 +1,64 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main // import "tailscale.com/cmd/tailscaled" + +import ( + "context" + "log" + + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/svc" + "tailscale.com/ipn/ipnserver" + "tailscale.com/logpolicy" +) + +const serviceName = "Tailscale IPN" + +func isWindowsService() bool { + v, err := svc.IsWindowsService() + if err != nil { + log.Fatalf("svc.IsWindowsService failed: %v", err) + } + return v +} + +func runWindowsService(pol *logpolicy.Policy) error { + return svc.Run(serviceName, &ipnService{Policy: pol}) +} + +type ipnService struct { + Policy *logpolicy.Policy +} + +// Called by Windows to execute the windows service. +func (service *ipnService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) { + changes <- svc.Status{State: svc.StartPending} + + ctx, cancel := context.WithCancel(context.Background()) + doneCh := make(chan struct{}) + go func() { + defer close(doneCh) + args := []string{"/subproc", service.Policy.PublicID.String()} + ipnserver.BabysitProc(ctx, args, log.Printf) + }() + + changes <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop} + + for ctx.Err() == nil { + select { + case <-doneCh: + case cmd := <-r: + switch cmd.Cmd { + case svc.Stop: + cancel() + case svc.Interrogate: + changes <- cmd.CurrentStatus + } + } + } + + changes <- svc.Status{State: svc.StopPending} + return false, windows.NO_ERROR +}