diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 982121d28..cf4e13c10 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -2,8 +2,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/negotiate W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy - github.com/apenwarr/fixconsole from tailscale.com/cmd/tailscaled - W 💣 github.com/apenwarr/w32 from github.com/apenwarr/fixconsole L github.com/coreos/go-iptables/iptables from tailscale.com/wgengine/router LW github.com/go-multierror/multierror from tailscale.com/wgengine/router W 💣 github.com/go-ole/go-ole from github.com/go-ole/go-ole/oleutil+ @@ -163,7 +161,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de golang.org/x/sync/singleflight from tailscale.com/net/dnscache golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+ LD golang.org/x/sys/unix from github.com/jsimonetti/rtnetlink/internal/unix+ - W golang.org/x/sys/windows from github.com/apenwarr/fixconsole+ + W golang.org/x/sys/windows from github.com/tailscale/wireguard-go/conn+ W golang.org/x/sys/windows/registry from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg+ W golang.org/x/sys/windows/svc from tailscale.com/cmd/tailscaled golang.org/x/term from tailscale.com/logpolicy diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 2e4dc05bd..64e2723bb 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -24,7 +24,6 @@ import ( "syscall" "time" - "github.com/apenwarr/fixconsole" "tailscale.com/ipn/ipnserver" "tailscale.com/logpolicy" "tailscale.com/paths" @@ -88,11 +87,6 @@ func main() { flag.StringVar(&args.socketpath, "socket", paths.DefaultTailscaledSocket(), "path of the service unix socket") flag.BoolVar(&printVersion, "version", false, "print version information and exit") - err := fixconsole.FixConsoleIfNeeded() - if err != nil { - log.Fatalf("fixConsoleOutput: %v", err) - } - if len(os.Args) > 1 && os.Args[1] == "debug" { if err := debugMode(os.Args[2:]); err != nil { log.Fatal(err) @@ -100,6 +94,10 @@ func main() { return } + if beWindowsSubprocess() { + return + } + flag.Parse() if flag.NArg() > 0 { log.Fatalf("tailscaled does not take non-flag arguments: %q", flag.Args()) diff --git a/cmd/tailscaled/tailscaled_notwindows.go b/cmd/tailscaled/tailscaled_notwindows.go index eb6cd4e9c..58221a2ea 100644 --- a/cmd/tailscaled/tailscaled_notwindows.go +++ b/cmd/tailscaled/tailscaled_notwindows.go @@ -11,3 +11,5 @@ import "tailscale.com/logpolicy" func isWindowsService() bool { return false } func runWindowsService(pol *logpolicy.Policy) error { panic("unreachable") } + +func beWindowsSubprocess() bool { return false } diff --git a/cmd/tailscaled/tailscaled_windows.go b/cmd/tailscaled/tailscaled_windows.go index a7c69eb2c..bb1554c20 100644 --- a/cmd/tailscaled/tailscaled_windows.go +++ b/cmd/tailscaled/tailscaled_windows.go @@ -4,14 +4,33 @@ package main // import "tailscale.com/cmd/tailscaled" +// 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. + import ( "context" + "fmt" "log" + "os" + "time" "golang.org/x/sys/windows" "golang.org/x/sys/windows/svc" "tailscale.com/ipn/ipnserver" "tailscale.com/logpolicy" + "tailscale.com/types/logger" + "tailscale.com/version" + "tailscale.com/wgengine" ) const serviceName = "Tailscale IPN" @@ -62,3 +81,100 @@ func (service *ipnService) Execute(args []string, r <-chan svc.ChangeRequest, ch changes <- svc.Status{State: svc.StopPending} return false, windows.NO_ERROR } + +func beWindowsSubprocess() bool { + if len(os.Args) != 3 || os.Args[1] != "/subproc" { + return false + } + logid := os.Args[2] + + log.Printf("Program starting: v%v: %#v", version.Long, os.Args) + log.Printf("subproc mode: logid=%v", logid) + + go func() { + b := make([]byte, 16) + for { + _, err := os.Stdin.Read(b) + if err != nil { + log.Fatalf("stdin err (parent process died): %v", err) + } + } + }() + + err := startIPNServer(context.Background(), logid) + if err != nil { + log.Fatalf("ipnserver: %v", err) + } + return true +} + +func startIPNServer(ctx context.Context, logid string) error { + var logf logger.Logf = log.Printf + var eng wgengine.Engine + var err error + + getEngine := func() (wgengine.Engine, error) { + eng, err := wgengine.NewUserspaceEngine(logf, "Tailscale", 41641) + if err != nil { + return nil, err + } + return wgengine.NewWatchdog(eng), nil + } + + if msg := os.Getenv("TS_DEBUG_WIN_FAIL"); msg != "" { + err = fmt.Errorf("pretending to be a service failure: %v", msg) + } else { + // We have a bunch of bug reports of wgengine.NewUserspaceEngine returning a few different errors, + // all intermittently. A few times I (Brad) have also seen sporadic failures that simply + // restarting fixed. So try a few times. + for try := 1; try <= 5; try++ { + if try > 1 { + // Only sleep a bit. Don't do some massive backoff because + // the frontend GUI has a 30 second timeout on connecting to us, + // but even 5 seconds is too long for them to get any results. + // 5 tries * 1 second each seems fine. + time.Sleep(time.Second) + } + eng, err = getEngine() + if err != nil { + logf("wgengine.NewUserspaceEngine: (try %v) %v", try, err) + continue + } + if try > 1 { + logf("wgengine.NewUserspaceEngine: ended up working on try %v", try) + } + break + } + } + if err != nil { + // Log the error, but don't fatalf. We want to + // propagate the error message to the UI frontend. So + // we continue and tell the ipnserver to return that + // in a Notify message. + logf("wgengine.NewUserspaceEngine: %v", err) + } + opts := ipnserver.Options{ + Port: 41112, + SurviveDisconnects: false, + StatePath: args.statepath, + } + if err != nil { + // Return nicer errors to users, annotated with logids, which helps + // when they file bugs. + rawGetEngine := getEngine // raw == without verbose logid-containing error + getEngine = func() (wgengine.Engine, error) { + eng, err := rawGetEngine() + if err != nil { + return nil, fmt.Errorf("wgengine.NewUserspaceEngine: %v\n\nlogid: %v", err, logid) + } + return eng, nil + } + } else { + getEngine = ipnserver.FixedEngine(eng) + } + err = ipnserver.Run(ctx, logf, logid, getEngine, opts) + if err != nil { + logf("ipnserver.Run: %v", err) + } + return err +} diff --git a/paths/paths.go b/paths/paths.go index 9e583713a..0bb028085 100644 --- a/paths/paths.go +++ b/paths/paths.go @@ -8,6 +8,7 @@ package paths import ( "os" + "path/filepath" "runtime" ) @@ -42,5 +43,8 @@ func DefaultTailscaledStateFile() string { if f := stateFileFunc; f != nil { return f() } + if runtime.GOOS == "windows" { + return filepath.Join(os.Getenv("LocalAppData"), "Tailscale", "server-state.conf") + } return "" }