cmd/tailscaled: change Windows implementation to shut down subprocess via closing its stdin

We've been doing a hard kill of the subprocess, which is only safe as long as
both the cli and gui are not running and the subprocess has had the opportunity
to clean up DNS settings etc. If unattended mode is turned on, this is definitely
unsafe.

I changed babysitProc to close the subprocess's stdin to make it shut down, and
then I plumbed a cancel function into the stdin reader on the subprocess side.

Fixes https://github.com/tailscale/corp/issues/5621

Signed-off-by: Aaron Klotz <aaron@tailscale.com>
clairew/tsnet_get_own_ip
Aaron Klotz 2 years ago
parent 9c67395334
commit 53e2010b8a

@ -25,6 +25,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"log" "log"
"net/netip" "net/netip"
"os" "os"
@ -273,17 +274,24 @@ func beWindowsSubprocess() bool {
log.Printf("Error reading environment config: %v", err) log.Printf("Error reading environment config: %v", err)
} }
ctx, cancel := context.WithCancel(context.Background())
go func() { go func() {
b := make([]byte, 16) b := make([]byte, 16)
for { for {
_, err := os.Stdin.Read(b) _, err := os.Stdin.Read(b)
if err == io.EOF {
// Parent wants us to shut down gracefully.
log.Printf("subproc received EOF from stdin")
cancel()
return
}
if err != nil { if err != nil {
log.Fatalf("stdin err (parent process died): %v", err) log.Fatalf("stdin err (parent process died): %v", err)
} }
} }
}() }()
err := startIPNServer(context.Background(), log.Printf, logid) err := startIPNServer(ctx, log.Printf, logid)
if err != nil { if err != nil {
log.Fatalf("ipnserver: %v", err) log.Fatalf("ipnserver: %v", err)
} }
@ -367,6 +375,7 @@ func babysitProc(ctx context.Context, args []string, logf logger.Logf) {
var proc struct { var proc struct {
mu sync.Mutex mu sync.Mutex
p *os.Process p *os.Process
wStdin *os.File
} }
done := make(chan struct{}) done := make(chan struct{})
@ -378,15 +387,18 @@ func babysitProc(ctx context.Context, args []string, logf logger.Logf) {
case sig = <-interrupt: case sig = <-interrupt:
logf("babysitProc: got signal: %v", sig) logf("babysitProc: got signal: %v", sig)
close(done) close(done)
proc.mu.Lock()
proc.p.Signal(sig)
proc.mu.Unlock()
case <-ctx.Done(): case <-ctx.Done():
logf("babysitProc: context done") logf("babysitProc: context done")
sig = os.Kill
close(done) close(done)
}
proc.mu.Lock() proc.mu.Lock()
proc.p.Signal(sig) // 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() proc.mu.Unlock()
}
}() }()
bo := backoff.NewBackoff("babysitProc", logf, 30*time.Second) bo := backoff.NewBackoff("babysitProc", logf, 30*time.Second)
@ -448,6 +460,7 @@ func babysitProc(ctx context.Context, args []string, logf logger.Logf) {
} else { } else {
proc.mu.Lock() proc.mu.Lock()
proc.p = cmd.Process proc.p = cmd.Process
proc.wStdin = wStdin
proc.mu.Unlock() proc.mu.Unlock()
err = cmd.Wait() err = cmd.Wait()

Loading…
Cancel
Save