2023-01-27 21:37:20 +00:00
|
|
|
// Copyright (c) Tailscale Inc & AUTHORS
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
2021-02-05 16:46:12 +00:00
|
|
|
|
2022-08-02 18:34:03 +00:00
|
|
|
//go:build go1.19
|
2022-03-18 14:44:05 +00:00
|
|
|
|
2021-03-25 15:59:00 +00:00
|
|
|
package main // import "tailscale.com/cmd/tailscaled"
|
2021-02-05 16:46:12 +00:00
|
|
|
|
2021-02-05 17:53:54 +00:00
|
|
|
// TODO: check if administrator, like tswin does.
|
|
|
|
//
|
|
|
|
// TODO: try to load wintun.dll early at startup, before wireguard/tun
|
|
|
|
// does (which panics) and if we'd fail (e.g. due to access
|
|
|
|
// denied, even if administrator), use 'tasklist /m wintun.dll'
|
|
|
|
// to see if something else is currently using it and tell user.
|
|
|
|
//
|
|
|
|
// TODO: check if Tailscale service is already running, and fail early
|
|
|
|
// like tswin does.
|
|
|
|
//
|
|
|
|
// TODO: on failure, check if on a UNC drive and recommend copying it
|
|
|
|
// to C:\ to run it, like tswin does.
|
|
|
|
|
2021-02-05 16:46:12 +00:00
|
|
|
import (
|
2022-11-25 19:59:24 +00:00
|
|
|
"bufio"
|
2021-02-05 16:46:12 +00:00
|
|
|
"context"
|
2021-06-16 15:53:08 +00:00
|
|
|
"encoding/json"
|
2022-09-19 21:49:58 +00:00
|
|
|
"errors"
|
2021-02-05 17:53:54 +00:00
|
|
|
"fmt"
|
2022-12-09 17:14:39 +00:00
|
|
|
"io"
|
2021-02-05 16:46:12 +00:00
|
|
|
"log"
|
all: convert more code to use net/netip directly
perl -i -npe 's,netaddr.IPPrefixFrom,netip.PrefixFrom,' $(git grep -l -F netaddr.)
perl -i -npe 's,netaddr.IPPortFrom,netip.AddrPortFrom,' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPrefix,netip.Prefix,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPort,netip.AddrPort,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IP\b,netip.Addr,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPv6Raw\b,netip.AddrFrom16,g' $(git grep -l -F netaddr. )
goimports -w .
Then delete some stuff from the net/netaddr shim package which is no
longer neeed.
Updates #5162
Change-Id: Ia7a86893fe21c7e3ee1ec823e8aba288d4566cd8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-26 04:14:09 +00:00
|
|
|
"net/netip"
|
2021-02-05 17:53:54 +00:00
|
|
|
"os"
|
2022-11-25 19:59:24 +00:00
|
|
|
"os/exec"
|
|
|
|
"os/signal"
|
|
|
|
"sync"
|
|
|
|
"syscall"
|
2021-02-05 17:53:54 +00:00
|
|
|
"time"
|
2021-02-05 16:46:12 +00:00
|
|
|
|
2022-11-14 23:51:09 +00:00
|
|
|
"github.com/dblohm7/wingoes/com"
|
2022-12-09 23:12:20 +00:00
|
|
|
"github.com/tailscale/wireguard-go/tun"
|
2021-02-05 16:46:12 +00:00
|
|
|
"golang.org/x/sys/windows"
|
|
|
|
"golang.org/x/sys/windows/svc"
|
2022-04-29 21:18:13 +00:00
|
|
|
"golang.org/x/sys/windows/svc/eventlog"
|
2022-12-06 19:23:11 +00:00
|
|
|
"golang.zx2c4.com/wintun"
|
2021-03-02 00:24:26 +00:00
|
|
|
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
2022-01-24 18:52:57 +00:00
|
|
|
"tailscale.com/envknob"
|
2021-02-05 16:46:12 +00:00
|
|
|
"tailscale.com/logpolicy"
|
2022-11-25 19:59:24 +00:00
|
|
|
"tailscale.com/logtail/backoff"
|
2021-04-06 04:45:56 +00:00
|
|
|
"tailscale.com/net/dns"
|
2021-03-27 05:07:19 +00:00
|
|
|
"tailscale.com/net/tstun"
|
2021-02-05 17:53:54 +00:00
|
|
|
"tailscale.com/types/logger"
|
2021-09-28 22:33:08 +00:00
|
|
|
"tailscale.com/util/winutil"
|
2021-02-05 17:53:54 +00:00
|
|
|
"tailscale.com/version"
|
2021-05-10 16:56:15 +00:00
|
|
|
"tailscale.com/wf"
|
2021-02-05 16:46:12 +00:00
|
|
|
)
|
|
|
|
|
2022-11-14 23:51:09 +00:00
|
|
|
func init() {
|
|
|
|
// Initialize COM process-wide.
|
|
|
|
comProcessType := com.Service
|
|
|
|
if !isWindowsService() {
|
|
|
|
comProcessType = com.ConsoleApp
|
|
|
|
}
|
|
|
|
if err := com.StartRuntime(comProcessType); err != nil {
|
|
|
|
log.Printf("wingoes.com.StartRuntime(%d) failed: %v", comProcessType, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-05 19:13:34 +00:00
|
|
|
const serviceName = "Tailscale"
|
2021-02-05 16:46:12 +00:00
|
|
|
|
2022-12-06 19:23:11 +00:00
|
|
|
// Application-defined command codes between 128 and 255
|
|
|
|
// See https://web.archive.org/web/20221007222822/https://learn.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-controlservice
|
|
|
|
const (
|
|
|
|
cmdUninstallWinTun = svc.Cmd(128 + iota)
|
|
|
|
)
|
|
|
|
|
2022-11-28 04:40:36 +00:00
|
|
|
func init() {
|
|
|
|
tstunNew = tstunNewWithWindowsRetries
|
|
|
|
}
|
|
|
|
|
|
|
|
// tstunNewOrRetry is a wrapper around tstun.New that retries on Windows for certain
|
|
|
|
// errors.
|
|
|
|
//
|
|
|
|
// TODO(bradfitz): move this into tstun and/or just fix the problems so it doesn't
|
|
|
|
// require a few tries to work.
|
|
|
|
func tstunNewWithWindowsRetries(logf logger.Logf, tunName string) (_ tun.Device, devName string, _ error) {
|
|
|
|
bo := backoff.NewBackoff("tstunNew", logf, 10*time.Second)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
|
|
|
defer cancel()
|
|
|
|
for {
|
|
|
|
dev, devName, err := tstun.New(logf, tunName)
|
|
|
|
if err == nil {
|
|
|
|
return dev, devName, err
|
|
|
|
}
|
|
|
|
if errors.Is(err, windows.ERROR_DEVICE_NOT_AVAILABLE) || windowsUptime() < 10*time.Minute {
|
|
|
|
// Wintun is not installing correctly. Dump the state of NetSetupSvc
|
|
|
|
// (which is a user-mode service that must be active for network devices
|
|
|
|
// to install) and its dependencies to the log.
|
|
|
|
winutil.LogSvcState(logf, "NetSetupSvc")
|
|
|
|
}
|
|
|
|
bo.BackOff(ctx, err)
|
|
|
|
if ctx.Err() != nil {
|
|
|
|
return nil, "", ctx.Err()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-05 16:46:12 +00:00
|
|
|
func isWindowsService() bool {
|
|
|
|
v, err := svc.IsWindowsService()
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("svc.IsWindowsService failed: %v", err)
|
|
|
|
}
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
2022-04-29 21:18:13 +00:00
|
|
|
// 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
|
|
|
|
|
2021-12-16 04:44:51 +00:00
|
|
|
// runWindowsService starts running Tailscale under the Windows
|
|
|
|
// Service environment.
|
|
|
|
//
|
|
|
|
// At this point we're still the parent process that
|
|
|
|
// Windows started.
|
2021-02-05 16:46:12 +00:00
|
|
|
func runWindowsService(pol *logpolicy.Policy) error {
|
2022-04-29 21:18:13 +00:00
|
|
|
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")
|
2021-02-05 16:46:12 +00:00
|
|
|
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) {
|
2022-04-29 21:18:13 +00:00
|
|
|
defer syslogf("SvcStopped notification imminent")
|
|
|
|
|
2021-02-05 16:46:12 +00:00
|
|
|
changes <- svc.Status{State: svc.StartPending}
|
2022-04-29 21:18:13 +00:00
|
|
|
syslogf("Service start pending")
|
2021-02-05 16:46:12 +00:00
|
|
|
|
2021-10-06 15:41:34 +00:00
|
|
|
svcAccepts := svc.AcceptStop
|
2022-01-10 20:10:02 +00:00
|
|
|
if winutil.GetPolicyInteger("FlushDNSOnSessionUnlock", 0) != 0 {
|
2021-10-06 15:41:34 +00:00
|
|
|
svcAccepts |= svc.AcceptSessionChange
|
|
|
|
}
|
|
|
|
|
2021-02-05 16:46:12 +00:00
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
2022-11-28 04:40:36 +00:00
|
|
|
defer cancel()
|
2021-02-05 16:46:12 +00:00
|
|
|
doneCh := make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
defer close(doneCh)
|
|
|
|
args := []string{"/subproc", service.Policy.PublicID.String()}
|
2021-11-18 18:13:21 +00:00
|
|
|
// Make a logger without a date prefix, as filelogger
|
|
|
|
// and logtail both already add their own. All we really want
|
|
|
|
// from the log package is the automatic newline.
|
2021-12-14 22:30:05 +00:00
|
|
|
// We start with log.Default().Writer(), which is the logtail
|
|
|
|
// writer that logpolicy already installed as the global
|
|
|
|
// output.
|
|
|
|
logger := log.New(log.Default().Writer(), "", 0)
|
2022-11-25 19:59:24 +00:00
|
|
|
babysitProc(ctx, args, logger.Printf)
|
2021-02-05 16:46:12 +00:00
|
|
|
}()
|
|
|
|
|
2021-10-06 15:41:34 +00:00
|
|
|
changes <- svc.Status{State: svc.Running, Accepts: svcAccepts}
|
2022-04-29 21:18:13 +00:00
|
|
|
syslogf("Service running")
|
2021-02-05 16:46:12 +00:00
|
|
|
|
2022-04-29 21:18:13 +00:00
|
|
|
for {
|
2021-02-05 16:46:12 +00:00
|
|
|
select {
|
|
|
|
case <-doneCh:
|
2022-04-29 21:18:13 +00:00
|
|
|
return false, windows.NO_ERROR
|
2021-02-05 16:46:12 +00:00
|
|
|
case cmd := <-r:
|
2021-12-16 04:44:51 +00:00
|
|
|
log.Printf("Got Windows Service event: %v", cmdName(cmd.Cmd))
|
2021-02-05 16:46:12 +00:00
|
|
|
switch cmd.Cmd {
|
|
|
|
case svc.Stop:
|
2022-04-29 21:18:13 +00:00
|
|
|
changes <- svc.Status{State: svc.StopPending}
|
|
|
|
syslogf("Service stop pending")
|
|
|
|
cancel() // so BabysitProc will kill the child process
|
2021-02-05 16:46:12 +00:00
|
|
|
case svc.Interrogate:
|
2022-04-29 21:18:13 +00:00
|
|
|
syslogf("Service interrogation")
|
2021-02-05 16:46:12 +00:00
|
|
|
changes <- cmd.CurrentStatus
|
2021-09-28 22:33:08 +00:00
|
|
|
case svc.SessionChange:
|
2022-04-29 21:18:13 +00:00
|
|
|
syslogf("Service session change notification")
|
2021-09-28 22:33:08 +00:00
|
|
|
handleSessionChange(cmd)
|
|
|
|
changes <- cmd.CurrentStatus
|
2022-12-06 19:23:11 +00:00
|
|
|
case cmdUninstallWinTun:
|
|
|
|
syslogf("Stopping tailscaled child process and uninstalling WinTun")
|
|
|
|
// At this point, doneCh is the channel which will be closed when the
|
|
|
|
// tailscaled subprocess exits. We save that to childDoneCh.
|
|
|
|
childDoneCh := doneCh
|
|
|
|
// We reset doneCh to a new channel that will keep the event loop
|
|
|
|
// running until the uninstallation is done.
|
|
|
|
doneCh = make(chan struct{})
|
|
|
|
// Trigger subprocess shutdown.
|
|
|
|
cancel()
|
|
|
|
go func() {
|
|
|
|
// When this goroutine completes, tell the service to break out of its
|
|
|
|
// event loop.
|
|
|
|
defer close(doneCh)
|
|
|
|
// Wait for the subprocess to shutdown.
|
|
|
|
<-childDoneCh
|
|
|
|
// Now uninstall WinTun.
|
|
|
|
uninstallWinTun(log.Printf)
|
|
|
|
}()
|
|
|
|
changes <- svc.Status{State: svc.StopPending}
|
2021-02-05 16:46:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-02-05 17:53:54 +00:00
|
|
|
|
2021-12-16 04:44:51 +00:00
|
|
|
func cmdName(c svc.Cmd) string {
|
|
|
|
switch c {
|
|
|
|
case svc.Stop:
|
|
|
|
return "Stop"
|
|
|
|
case svc.Pause:
|
|
|
|
return "Pause"
|
|
|
|
case svc.Continue:
|
|
|
|
return "Continue"
|
|
|
|
case svc.Interrogate:
|
|
|
|
return "Interrogate"
|
|
|
|
case svc.Shutdown:
|
|
|
|
return "Shutdown"
|
|
|
|
case svc.ParamChange:
|
|
|
|
return "ParamChange"
|
|
|
|
case svc.NetBindAdd:
|
|
|
|
return "NetBindAdd"
|
|
|
|
case svc.NetBindRemove:
|
|
|
|
return "NetBindRemove"
|
|
|
|
case svc.NetBindEnable:
|
|
|
|
return "NetBindEnable"
|
|
|
|
case svc.NetBindDisable:
|
|
|
|
return "NetBindDisable"
|
|
|
|
case svc.DeviceEvent:
|
|
|
|
return "DeviceEvent"
|
|
|
|
case svc.HardwareProfileChange:
|
|
|
|
return "HardwareProfileChange"
|
|
|
|
case svc.PowerEvent:
|
|
|
|
return "PowerEvent"
|
|
|
|
case svc.SessionChange:
|
|
|
|
return "SessionChange"
|
|
|
|
case svc.PreShutdown:
|
|
|
|
return "PreShutdown"
|
2022-12-06 19:23:11 +00:00
|
|
|
case cmdUninstallWinTun:
|
|
|
|
return "(Application Defined) Uninstall WinTun"
|
2021-12-16 04:44:51 +00:00
|
|
|
}
|
|
|
|
return fmt.Sprintf("Unknown-Service-Cmd-%d", c)
|
|
|
|
}
|
|
|
|
|
2021-02-05 17:53:54 +00:00
|
|
|
func beWindowsSubprocess() bool {
|
2021-03-02 00:24:26 +00:00
|
|
|
if beFirewallKillswitch() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-02-05 17:53:54 +00:00
|
|
|
if len(os.Args) != 3 || os.Args[1] != "/subproc" {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
logid := os.Args[2]
|
|
|
|
|
2022-09-25 18:29:55 +00:00
|
|
|
// Remove the date/time prefix; the logtail + file loggers add it.
|
2021-12-14 22:30:05 +00:00
|
|
|
log.SetFlags(0)
|
|
|
|
|
2023-02-11 06:20:36 +00:00
|
|
|
log.Printf("Program starting: v%v: %#v", version.Long(), os.Args)
|
2021-02-05 17:53:54 +00:00
|
|
|
log.Printf("subproc mode: logid=%v", logid)
|
2022-09-17 03:24:28 +00:00
|
|
|
if err := envknob.ApplyDiskConfigError(); err != nil {
|
|
|
|
log.Printf("Error reading environment config: %v", err)
|
2022-09-16 04:47:31 +00:00
|
|
|
}
|
2021-02-05 17:53:54 +00:00
|
|
|
|
2022-12-09 17:14:39 +00:00
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
2021-02-05 17:53:54 +00:00
|
|
|
go func() {
|
|
|
|
b := make([]byte, 16)
|
|
|
|
for {
|
|
|
|
_, err := os.Stdin.Read(b)
|
2022-12-09 17:14:39 +00:00
|
|
|
if err == io.EOF {
|
|
|
|
// Parent wants us to shut down gracefully.
|
|
|
|
log.Printf("subproc received EOF from stdin")
|
|
|
|
cancel()
|
|
|
|
return
|
|
|
|
}
|
2021-02-05 17:53:54 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("stdin err (parent process died): %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2022-12-09 17:14:39 +00:00
|
|
|
err := startIPNServer(ctx, log.Printf, logid)
|
2021-02-05 17:53:54 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("ipnserver: %v", err)
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-03-02 00:24:26 +00:00
|
|
|
func beFirewallKillswitch() bool {
|
|
|
|
if len(os.Args) != 3 || os.Args[1] != "/firewall" {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
log.SetFlags(0)
|
|
|
|
log.Printf("killswitch subprocess starting, tailscale GUID is %s", os.Args[2])
|
|
|
|
|
|
|
|
guid, err := windows.GUIDFromString(os.Args[2])
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("invalid GUID %q: %v", os.Args[2], err)
|
|
|
|
}
|
|
|
|
|
|
|
|
luid, err := winipcfg.LUIDFromGUID(&guid)
|
|
|
|
if err != nil {
|
2021-05-10 16:56:15 +00:00
|
|
|
log.Fatalf("no interface with GUID %q: %v", guid, err)
|
2021-03-02 00:24:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
start := time.Now()
|
2021-06-16 15:53:08 +00:00
|
|
|
fw, err := wf.New(uint64(luid))
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("failed to enable firewall: %v", err)
|
2021-05-10 16:56:15 +00:00
|
|
|
}
|
2021-03-02 00:24:26 +00:00
|
|
|
log.Printf("killswitch enabled, took %s", time.Since(start))
|
|
|
|
|
2021-06-16 15:53:08 +00:00
|
|
|
// Note(maisem): when local lan access toggled, tailscaled needs to
|
|
|
|
// inform the firewall to let local routes through. The set of routes
|
|
|
|
// is passed in via stdin encoded in json.
|
|
|
|
dcd := json.NewDecoder(os.Stdin)
|
|
|
|
for {
|
all: convert more code to use net/netip directly
perl -i -npe 's,netaddr.IPPrefixFrom,netip.PrefixFrom,' $(git grep -l -F netaddr.)
perl -i -npe 's,netaddr.IPPortFrom,netip.AddrPortFrom,' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPrefix,netip.Prefix,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPort,netip.AddrPort,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IP\b,netip.Addr,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPv6Raw\b,netip.AddrFrom16,g' $(git grep -l -F netaddr. )
goimports -w .
Then delete some stuff from the net/netaddr shim package which is no
longer neeed.
Updates #5162
Change-Id: Ia7a86893fe21c7e3ee1ec823e8aba288d4566cd8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-26 04:14:09 +00:00
|
|
|
var routes []netip.Prefix
|
2021-06-16 15:53:08 +00:00
|
|
|
if err := dcd.Decode(&routes); err != nil {
|
|
|
|
log.Fatalf("parent process died or requested exit, exiting (%v)", err)
|
|
|
|
}
|
|
|
|
if err := fw.UpdatePermittedRoutes(routes); err != nil {
|
|
|
|
log.Fatalf("failed to update routes (%v)", err)
|
|
|
|
}
|
|
|
|
}
|
2021-03-02 00:24:26 +00:00
|
|
|
}
|
|
|
|
|
2021-09-28 22:33:08 +00:00
|
|
|
func handleSessionChange(chgRequest svc.ChangeRequest) {
|
2021-10-06 15:41:34 +00:00
|
|
|
if chgRequest.Cmd != svc.SessionChange || chgRequest.EventType != windows.WTS_SESSION_UNLOCK {
|
2021-09-28 22:33:08 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("Received WTS_SESSION_UNLOCK event, initiating DNS flush.")
|
|
|
|
go func() {
|
|
|
|
err := dns.Flush()
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Error flushing DNS on session unlock: %v", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
cmd/tailscaled: don't block ipnserver startup behind engine init on Windows
With this change, the ipnserver's safesocket.Listen (the localhost
tcp.Listen) happens right away, before any synchronous
TUN/DNS/Engine/etc setup work, which might be slow, especially on
early boot on Windows.
Because the safesocket.Listen starts up early, that means localhost
TCP dials (the safesocket.Connect from the GUI) complete successfully
and thus the GUI avoids the MessageBox error. (I verified that
pacifies it, even without a Listener.Accept; I'd feared that Windows
localhost was maybe special and avoided the normal listener backlog).
Once the GUI can then connect immediately without errors, the various
timeouts then matter less, because the backend is no longer trying to
race against the GUI's timeout. So keep retrying on errors for a
minute, or 10 minutes if the system just booted in the past 10
minutes.
This should fix the problem with Windows 10 desktops auto-logging in
and starting the Tailscale frontend which was then showing a
MessageBox error about failing to connect to tailscaled, which was
slow coming up because the Windows networking stack wasn't up
yet. Fingers crossed.
Fixes #1313 (previously #1187, etc)
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-04-19 20:45:55 +00:00
|
|
|
var (
|
|
|
|
kernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
|
|
|
getTickCount64Proc = kernel32.NewProc("GetTickCount64")
|
|
|
|
)
|
|
|
|
|
|
|
|
func windowsUptime() time.Duration {
|
|
|
|
r, _, _ := getTickCount64Proc.Call()
|
|
|
|
return time.Duration(int64(r)) * time.Millisecond
|
|
|
|
}
|
2022-11-25 19:59:24 +00:00
|
|
|
|
|
|
|
// babysitProc runs the current executable as a child process with the
|
|
|
|
// provided args, capturing its output, writing it to files, and
|
|
|
|
// restarting the process on any crashes.
|
|
|
|
func babysitProc(ctx context.Context, args []string, logf logger.Logf) {
|
|
|
|
|
|
|
|
executable, err := os.Executable()
|
|
|
|
if err != nil {
|
|
|
|
panic("cannot determine executable: " + err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
var proc struct {
|
2022-12-09 17:14:39 +00:00
|
|
|
mu sync.Mutex
|
|
|
|
p *os.Process
|
|
|
|
wStdin *os.File
|
2022-11-25 19:59:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
done := make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
interrupt := make(chan os.Signal, 1)
|
|
|
|
signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
var sig os.Signal
|
|
|
|
select {
|
|
|
|
case sig = <-interrupt:
|
|
|
|
logf("babysitProc: got signal: %v", sig)
|
|
|
|
close(done)
|
2022-12-09 17:14:39 +00:00
|
|
|
proc.mu.Lock()
|
|
|
|
proc.p.Signal(sig)
|
|
|
|
proc.mu.Unlock()
|
2022-11-25 19:59:24 +00:00
|
|
|
case <-ctx.Done():
|
|
|
|
logf("babysitProc: context done")
|
|
|
|
close(done)
|
2022-12-09 17:14:39 +00:00
|
|
|
proc.mu.Lock()
|
|
|
|
// Closing wStdin gives the subprocess a chance to shut down cleanly,
|
|
|
|
// which is important for cleaning up DNS settings etc.
|
|
|
|
proc.wStdin.Close()
|
|
|
|
proc.mu.Unlock()
|
2022-11-25 19:59:24 +00:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
bo := backoff.NewBackoff("babysitProc", logf, 30*time.Second)
|
|
|
|
|
|
|
|
for {
|
|
|
|
startTime := time.Now()
|
|
|
|
log.Printf("exec: %#v %v", executable, args)
|
|
|
|
cmd := exec.Command(executable, args...)
|
|
|
|
|
|
|
|
// Create a pipe object to use as the subproc's stdin.
|
|
|
|
// When the writer goes away, the reader gets EOF.
|
|
|
|
// A subproc can watch its stdin and exit when it gets EOF;
|
|
|
|
// this is a very reliable way to have a subproc die when
|
|
|
|
// its parent (us) disappears.
|
|
|
|
// We never need to actually write to wStdin.
|
|
|
|
rStdin, wStdin, err := os.Pipe()
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("os.Pipe 1: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a pipe object to use as the subproc's stdout/stderr.
|
|
|
|
// We'll read from this pipe and send it to logf, line by line.
|
|
|
|
// We can't use os.exec's io.Writer for this because it
|
|
|
|
// doesn't care about lines, and thus ends up merging multiple
|
|
|
|
// log lines into one or splitting one line into multiple
|
|
|
|
// logf() calls. bufio is more appropriate.
|
|
|
|
rStdout, wStdout, err := os.Pipe()
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("os.Pipe 2: %v", err)
|
|
|
|
}
|
|
|
|
go func(r *os.File) {
|
|
|
|
defer r.Close()
|
|
|
|
rb := bufio.NewReader(r)
|
|
|
|
for {
|
|
|
|
s, err := rb.ReadString('\n')
|
|
|
|
if s != "" {
|
|
|
|
logf("%s", s)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}(rStdout)
|
|
|
|
|
|
|
|
cmd.Stdin = rStdin
|
|
|
|
cmd.Stdout = wStdout
|
|
|
|
cmd.Stderr = wStdout
|
|
|
|
err = cmd.Start()
|
|
|
|
|
|
|
|
// Now that the subproc is started, get rid of our copy of the
|
|
|
|
// pipe reader. Bad things happen on Windows if more than one
|
|
|
|
// process owns the read side of a pipe.
|
|
|
|
rStdin.Close()
|
|
|
|
wStdout.Close()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("starting subprocess failed: %v", err)
|
|
|
|
} else {
|
|
|
|
proc.mu.Lock()
|
|
|
|
proc.p = cmd.Process
|
2022-12-09 17:14:39 +00:00
|
|
|
proc.wStdin = wStdin
|
2022-11-25 19:59:24 +00:00
|
|
|
proc.mu.Unlock()
|
|
|
|
|
|
|
|
err = cmd.Wait()
|
|
|
|
log.Printf("subprocess exited: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the process finishes, clean up the write side of the
|
|
|
|
// pipe. We'll make a new one when we restart the subproc.
|
|
|
|
wStdin.Close()
|
|
|
|
|
|
|
|
if os.Getenv("TS_DEBUG_RESTART_CRASHED") == "0" {
|
|
|
|
log.Fatalf("Process ended.")
|
|
|
|
}
|
|
|
|
|
|
|
|
if time.Since(startTime) < 60*time.Second {
|
|
|
|
bo.BackOff(ctx, fmt.Errorf("subproc early exit: %v", err))
|
|
|
|
} else {
|
|
|
|
// Reset the timeout, since the process ran for a while.
|
|
|
|
bo.BackOff(ctx, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-done:
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-12-06 19:23:11 +00:00
|
|
|
|
|
|
|
func uninstallWinTun(logf logger.Logf) {
|
|
|
|
dll := windows.NewLazyDLL("wintun.dll")
|
|
|
|
if err := dll.Load(); err != nil {
|
|
|
|
logf("Cannot load wintun.dll for uninstall: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
logf("Removing wintun driver...")
|
|
|
|
err := wintun.Uninstall()
|
|
|
|
logf("Uninstall: %v", err)
|
|
|
|
}
|