cmd/tailscaled: move more of the Windows server setup code into tailscaled

Updates #1232
pull/1280/head
Brad Fitzpatrick 4 years ago
parent 6f7974b7f2
commit a7562be5e1

@ -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 from github.com/alexbrainman/sspi/negotiate
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy 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 L github.com/coreos/go-iptables/iptables from tailscale.com/wgengine/router
LW github.com/go-multierror/multierror 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+ 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/sync/singleflight from tailscale.com/net/dnscache
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+ golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
LD golang.org/x/sys/unix from github.com/jsimonetti/rtnetlink/internal/unix+ 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/registry from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg+
W golang.org/x/sys/windows/svc from tailscale.com/cmd/tailscaled W golang.org/x/sys/windows/svc from tailscale.com/cmd/tailscaled
golang.org/x/term from tailscale.com/logpolicy golang.org/x/term from tailscale.com/logpolicy

@ -24,7 +24,6 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/apenwarr/fixconsole"
"tailscale.com/ipn/ipnserver" "tailscale.com/ipn/ipnserver"
"tailscale.com/logpolicy" "tailscale.com/logpolicy"
"tailscale.com/paths" "tailscale.com/paths"
@ -88,11 +87,6 @@ func main() {
flag.StringVar(&args.socketpath, "socket", paths.DefaultTailscaledSocket(), "path of the service unix socket") flag.StringVar(&args.socketpath, "socket", paths.DefaultTailscaledSocket(), "path of the service unix socket")
flag.BoolVar(&printVersion, "version", false, "print version information and exit") 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 len(os.Args) > 1 && os.Args[1] == "debug" {
if err := debugMode(os.Args[2:]); err != nil { if err := debugMode(os.Args[2:]); err != nil {
log.Fatal(err) log.Fatal(err)
@ -100,6 +94,10 @@ func main() {
return return
} }
if beWindowsSubprocess() {
return
}
flag.Parse() flag.Parse()
if flag.NArg() > 0 { if flag.NArg() > 0 {
log.Fatalf("tailscaled does not take non-flag arguments: %q", flag.Args()) log.Fatalf("tailscaled does not take non-flag arguments: %q", flag.Args())

@ -11,3 +11,5 @@ import "tailscale.com/logpolicy"
func isWindowsService() bool { return false } func isWindowsService() bool { return false }
func runWindowsService(pol *logpolicy.Policy) error { panic("unreachable") } func runWindowsService(pol *logpolicy.Policy) error { panic("unreachable") }
func beWindowsSubprocess() bool { return false }

@ -4,14 +4,33 @@
package main // import "tailscale.com/cmd/tailscaled" 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 ( import (
"context" "context"
"fmt"
"log" "log"
"os"
"time"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc" "golang.org/x/sys/windows/svc"
"tailscale.com/ipn/ipnserver" "tailscale.com/ipn/ipnserver"
"tailscale.com/logpolicy" "tailscale.com/logpolicy"
"tailscale.com/types/logger"
"tailscale.com/version"
"tailscale.com/wgengine"
) )
const serviceName = "Tailscale IPN" 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} changes <- svc.Status{State: svc.StopPending}
return false, windows.NO_ERROR 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
}

@ -8,6 +8,7 @@ package paths
import ( import (
"os" "os"
"path/filepath"
"runtime" "runtime"
) )
@ -42,5 +43,8 @@ func DefaultTailscaledStateFile() string {
if f := stateFileFunc; f != nil { if f := stateFileFunc; f != nil {
return f() return f()
} }
if runtime.GOOS == "windows" {
return filepath.Join(os.Getenv("LocalAppData"), "Tailscale", "server-state.conf")
}
return "" return ""
} }

Loading…
Cancel
Save