diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 998536d0d..97c4fd28e 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -323,6 +323,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de W golang.org/x/sys/windows from github.com/go-ole/go-ole+ W golang.org/x/sys/windows/registry from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg+ W golang.org/x/sys/windows/svc from golang.org/x/sys/windows/svc/mgr+ + W golang.org/x/sys/windows/svc/eventlog from tailscale.com/cmd/tailscaled W golang.org/x/sys/windows/svc/mgr from tailscale.com/cmd/tailscaled golang.org/x/term from tailscale.com/logpolicy golang.org/x/text/secure/bidirule from golang.org/x/net/idna diff --git a/cmd/tailscaled/tailscaled_windows.go b/cmd/tailscaled/tailscaled_windows.go index 771a8b18f..db54bfcb6 100644 --- a/cmd/tailscaled/tailscaled_windows.go +++ b/cmd/tailscaled/tailscaled_windows.go @@ -30,6 +30,7 @@ import ( "golang.org/x/sys/windows" "golang.org/x/sys/windows/svc" + "golang.org/x/sys/windows/svc/eventlog" "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" "inet.af/netaddr" "tailscale.com/envknob" @@ -60,12 +61,31 @@ func isWindowsService() bool { return v } +// syslogf is a logger function that writes to the Windows event log (ie, the +// one that you see in the Windows Event Viewer). tailscaled may optionally +// generate diagnostic messages in the same event timeline as the Windows +// Service Control Manager to assist with diagnosing issues with tailscaled's +// lifetime (such as slow shutdowns). +var syslogf logger.Logf = logger.Discard + // runWindowsService starts running Tailscale under the Windows // Service environment. // // At this point we're still the parent process that // Windows started. func runWindowsService(pol *logpolicy.Policy) error { + if winutil.GetPolicyInteger("LogSCMInteractions", 0) != 0 { + syslog, err := eventlog.Open(serviceName) + if err == nil { + syslogf = func(format string, args ...any) { + syslog.Info(0, fmt.Sprintf(format, args...)) + } + defer syslog.Close() + } + } + + syslogf("Service entering svc.Run") + defer syslogf("Service exiting svc.Run") return svc.Run(serviceName, &ipnService{Policy: pol}) } @@ -75,7 +95,10 @@ type ipnService struct { // Called by Windows to execute the windows service. func (service *ipnService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) { + defer syslogf("SvcStopped notification imminent") + changes <- svc.Status{State: svc.StartPending} + syslogf("Service start pending") svcAccepts := svc.AcceptStop if winutil.GetPolicyInteger("FlushDNSOnSessionUnlock", 0) != 0 { @@ -98,26 +121,29 @@ func (service *ipnService) Execute(args []string, r <-chan svc.ChangeRequest, ch }() changes <- svc.Status{State: svc.Running, Accepts: svcAccepts} + syslogf("Service running") - for ctx.Err() == nil { + for { select { case <-doneCh: + return false, windows.NO_ERROR case cmd := <-r: log.Printf("Got Windows Service event: %v", cmdName(cmd.Cmd)) switch cmd.Cmd { case svc.Stop: - cancel() + changes <- svc.Status{State: svc.StopPending} + syslogf("Service stop pending") + cancel() // so BabysitProc will kill the child process case svc.Interrogate: + syslogf("Service interrogation") changes <- cmd.CurrentStatus case svc.SessionChange: + syslogf("Service session change notification") handleSessionChange(cmd) changes <- cmd.CurrentStatus } } } - - changes <- svc.Status{State: svc.StopPending} - return false, windows.NO_ERROR } func cmdName(c svc.Cmd) string { diff --git a/tstest/integration/tailscaled_deps_test_windows.go b/tstest/integration/tailscaled_deps_test_windows.go index dc3e1df7d..c2f6e67b8 100644 --- a/tstest/integration/tailscaled_deps_test_windows.go +++ b/tstest/integration/tailscaled_deps_test_windows.go @@ -13,6 +13,7 @@ import ( // process and can cache a prior success when a dependency changes. _ "golang.org/x/sys/windows" _ "golang.org/x/sys/windows/svc" + _ "golang.org/x/sys/windows/svc/eventlog" _ "golang.org/x/sys/windows/svc/mgr" _ "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" _ "inet.af/netaddr"