2023-01-27 21:37:20 +00:00
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
2020-02-05 22:16:58 +00:00
2023-08-24 22:02:42 +00:00
//go:build go1.21
2022-03-18 14:44:05 +00:00
2021-03-25 15:59:00 +00:00
// The tailscaled program is the Tailscale client daemon. It's configured
// and controlled via the tailscale CLI program.
//
// It primarily supports Linux, though other systems will likely be
// supported in the future.
package main // import "tailscale.com/cmd/tailscaled"
2020-02-05 22:16:58 +00:00
import (
"context"
2021-03-12 21:04:47 +00:00
"errors"
2024-01-19 23:06:55 +00:00
"expvar"
2020-09-25 20:10:53 +00:00
"flag"
2020-10-27 04:23:58 +00:00
"fmt"
2020-02-05 22:16:58 +00:00
"log"
2021-03-01 18:08:53 +00:00
"net"
2020-02-05 22:16:58 +00:00
"net/http"
"net/http/pprof"
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"
2020-05-20 15:40:34 +00:00
"os"
2020-07-13 10:17:58 +00:00
"os/signal"
2021-11-03 16:03:11 +00:00
"path/filepath"
2020-04-30 20:20:09 +00:00
"runtime"
2022-09-17 16:24:26 +00:00
"strconv"
2021-03-12 21:04:47 +00:00
"strings"
2020-07-13 10:17:58 +00:00
"syscall"
2020-05-15 20:13:44 +00:00
"time"
2020-02-05 22:16:58 +00:00
2023-10-11 18:35:22 +00:00
"tailscale.com/client/tailscale"
2022-02-24 22:03:36 +00:00
"tailscale.com/cmd/tailscaled/childproc"
2022-02-18 20:55:22 +00:00
"tailscale.com/control/controlclient"
2024-04-02 20:32:30 +00:00
"tailscale.com/drive/driveimpl"
2022-01-24 18:52:57 +00:00
"tailscale.com/envknob"
2023-10-11 20:55:57 +00:00
"tailscale.com/ipn/conffile"
2022-11-27 21:07:41 +00:00
"tailscale.com/ipn/ipnlocal"
2020-02-05 22:16:58 +00:00
"tailscale.com/ipn/ipnserver"
2022-02-28 21:08:45 +00:00
"tailscale.com/ipn/store"
2020-02-05 22:16:58 +00:00
"tailscale.com/logpolicy"
2021-11-16 04:52:43 +00:00
"tailscale.com/logtail"
2021-04-02 05:35:26 +00:00
"tailscale.com/net/dns"
2022-11-27 21:07:41 +00:00
"tailscale.com/net/dnsfallback"
2023-04-18 21:26:58 +00:00
"tailscale.com/net/netmon"
2021-09-09 22:20:08 +00:00
"tailscale.com/net/netns"
2021-11-27 00:00:39 +00:00
"tailscale.com/net/proxymux"
2021-11-30 23:53:34 +00:00
"tailscale.com/net/socks5"
"tailscale.com/net/tsdial"
2023-03-05 02:49:05 +00:00
"tailscale.com/net/tshttpproxy"
2021-03-27 05:14:08 +00:00
"tailscale.com/net/tstun"
2020-03-03 17:33:09 +00:00
"tailscale.com/paths"
2021-11-05 21:05:13 +00:00
"tailscale.com/safesocket"
2022-11-28 04:40:36 +00:00
"tailscale.com/syncs"
2023-05-03 20:57:17 +00:00
"tailscale.com/tsd"
2023-04-10 10:28:16 +00:00
"tailscale.com/tsweb/varz"
2020-09-25 20:10:53 +00:00
"tailscale.com/types/flagtype"
2020-05-08 19:21:36 +00:00
"tailscale.com/types/logger"
2023-03-23 17:49:56 +00:00
"tailscale.com/types/logid"
2020-12-14 02:51:24 +00:00
"tailscale.com/util/clientmetric"
2021-11-02 21:30:48 +00:00
"tailscale.com/util/multierr"
2021-04-22 07:25:00 +00:00
"tailscale.com/util/osshare"
2020-10-27 04:23:58 +00:00
"tailscale.com/version"
2021-03-12 21:04:47 +00:00
"tailscale.com/version/distro"
2020-02-05 22:16:58 +00:00
"tailscale.com/wgengine"
2020-09-03 22:45:41 +00:00
"tailscale.com/wgengine/netstack"
2020-07-13 10:17:58 +00:00
"tailscale.com/wgengine/router"
2020-02-05 22:16:58 +00:00
)
2020-07-13 16:23:44 +00:00
// defaultTunName returns the default tun device name for the platform.
func defaultTunName ( ) string {
switch runtime . GOOS {
case "openbsd" :
return "tun"
case "windows" :
return "Tailscale"
2021-02-12 04:10:07 +00:00
case "darwin" :
// "utun" is recognized by wireguard-go/tun/tun_darwin.go
// as a magic value that uses/creates any free number.
return "utun"
2023-08-29 18:33:20 +00:00
case "plan9" :
return "userspace-networking"
2021-03-12 21:04:47 +00:00
case "linux" :
2022-03-02 03:49:24 +00:00
switch distro . Get ( ) {
case distro . Synology :
2021-03-12 21:04:47 +00:00
// Try TUN, but fall back to userspace networking if needed.
// See https://github.com/tailscale/tailscale-synology/issues/35
return "tailscale0,userspace-networking"
}
2022-03-02 03:49:24 +00:00
2020-07-13 16:23:44 +00:00
}
return "tailscale0"
}
2022-09-17 16:24:26 +00:00
// defaultPort returns the default UDP port to listen on for disco+wireguard.
// By default it returns 0, to pick one randomly from the kernel.
// If the environment variable PORT is set, that's used instead.
// The PORT environment variable is chosen to match what the Linux systemd
// unit uses, to make documentation more consistent.
func defaultPort ( ) uint16 {
if s := envknob . String ( "PORT" ) ; s != "" {
if p , err := strconv . ParseUint ( s , 10 , 16 ) ; err == nil {
return uint16 ( p )
}
}
2022-11-28 04:40:36 +00:00
if envknob . GOOS ( ) == "windows" {
return 41641
}
2022-09-17 16:24:26 +00:00
return 0
}
2020-07-23 18:58:28 +00:00
var args struct {
2021-07-23 16:45:04 +00:00
// tunname is a /dev/net/tun tunnel name ("tailscale0"), the
// string "userspace-networking", "tap:TAPNAME[:BRIDGENAME]"
// or comma-separated list thereof.
tunname string
2021-08-30 16:45:55 +00:00
cleanup bool
2023-10-11 20:55:57 +00:00
confFile string
2021-08-30 16:45:55 +00:00
debug string
port uint16
statepath string
2021-11-03 16:03:11 +00:00
statedir string
2021-08-30 16:45:55 +00:00
socketpath string
birdSocketPath string
verbose int
socksAddr string // listen address for SOCKS5 server
2021-09-28 17:16:05 +00:00
httpProxyAddr string // listen address for HTTP proxy server
2022-09-13 14:09:57 +00:00
disableLogs bool
2020-07-23 18:58:28 +00:00
}
2021-02-15 05:11:06 +00:00
var (
2021-08-30 16:45:55 +00:00
installSystemDaemon func ( [ ] string ) error // non-nil on some platforms
uninstallSystemDaemon func ( [ ] string ) error // non-nil on some platforms
createBIRDClient func ( string ) ( wgengine . BIRDClient , error ) // non-nil on some platforms
2021-02-15 05:11:06 +00:00
)
2024-02-10 17:32:25 +00:00
// Note - we use function pointers for subcommands so that subcommands like
// installSystemDaemon and uninstallSystemDaemon can be assigned platform-
// specific variants.
var subCommands = map [ string ] * func ( [ ] string ) error {
"install-system-daemon" : & installSystemDaemon ,
"uninstall-system-daemon" : & uninstallSystemDaemon ,
"debug" : & debugModeFunc ,
"be-child" : & beChildFunc ,
"serve-tailfs" : & serveTailFSFunc ,
2021-02-15 05:11:06 +00:00
}
2021-02-13 20:57:49 +00:00
2022-07-27 18:02:25 +00:00
var beCLI func ( ) // non-nil if CLI is linked in
2021-03-25 15:59:00 +00:00
func main ( ) {
2022-09-14 19:49:39 +00:00
envknob . PanicIfAnyEnvCheckedInInit ( )
2022-09-17 03:24:28 +00:00
envknob . ApplyDiskConfig ( )
2022-09-16 04:47:31 +00:00
2020-10-27 04:23:58 +00:00
printVersion := false
2020-12-21 18:53:18 +00:00
flag . IntVar ( & args . verbose , "verbose" , 0 , "log verbosity level; 0 is default, 1 or higher are increasingly verbose" )
2020-09-25 20:10:53 +00:00
flag . BoolVar ( & args . cleanup , "cleanup" , false , "clean up system state and exit" )
flag . StringVar ( & args . debug , "debug" , "" , "listen address ([ip]:port) of optional debug server" )
2021-03-01 18:08:53 +00:00
flag . StringVar ( & args . socksAddr , "socks5-server" , "" , ` optional [ip]:port to run a SOCK5 server (e.g. "localhost:1080") ` )
2021-09-29 18:23:05 +00:00
flag . StringVar ( & args . httpProxyAddr , "outbound-http-proxy-listen" , "" , ` optional [ip]:port to run an outbound HTTP proxy (e.g. "localhost:8080") ` )
2021-03-01 18:08:53 +00:00
flag . StringVar ( & args . tunname , "tun" , defaultTunName ( ) , ` tunnel interface name; use "userspace-networking" (beta) to not use TUN ` )
2022-09-17 16:24:26 +00:00
flag . Var ( flagtype . PortValue ( & args . port , defaultPort ( ) ) , "port" , "UDP port to listen on for WireGuard and peer-to-peer traffic; 0 means automatically select" )
2022-09-25 18:29:55 +00:00
flag . StringVar ( & args . statepath , "state" , "" , "absolute path of state file; use 'kube:<secret-name>' to use Kubernetes secrets or 'arn:aws:ssm:...' to store in AWS SSM; use 'mem:' to not store state and register as an ephemeral node. If empty and --statedir is provided, the default is <statedir>/tailscaled.state. Default: " + paths . DefaultTailscaledStateFile ( ) )
2021-11-03 16:03:11 +00:00
flag . StringVar ( & args . statedir , "statedir" , "" , "path to directory for storage of config state, TLS certs, temporary incoming Taildrop files, etc. If empty, it's derived from --state when possible." )
2020-09-25 20:10:53 +00:00
flag . StringVar ( & args . socketpath , "socket" , paths . DefaultTailscaledSocket ( ) , "path of the service unix socket" )
2021-08-30 16:45:55 +00:00
flag . StringVar ( & args . birdSocketPath , "bird-socket" , "" , "path of the bird unix socket" )
2020-10-27 04:23:58 +00:00
flag . BoolVar ( & printVersion , "version" , false , "print version information and exit" )
2022-09-13 14:09:57 +00:00
flag . BoolVar ( & args . disableLogs , "no-logs-no-support" , false , "disable log uploads; this also disables any technical support" )
2023-10-11 20:55:57 +00:00
flag . StringVar ( & args . confFile , "config" , "" , "path to config file" )
2020-02-05 22:16:58 +00:00
2022-07-27 18:02:25 +00:00
if len ( os . Args ) > 0 && filepath . Base ( os . Args [ 0 ] ) == "tailscale" && beCLI != nil {
beCLI ( )
return
}
2021-02-13 20:57:49 +00:00
if len ( os . Args ) > 1 {
2021-02-15 05:11:06 +00:00
sub := os . Args [ 1 ]
2024-02-10 17:32:25 +00:00
if fp , ok := subCommands [ sub ] ; ok {
if fp == nil {
2021-02-13 20:57:49 +00:00
log . SetFlags ( 0 )
2021-02-15 05:11:06 +00:00
log . Fatalf ( "%s not available on %v" , sub , runtime . GOOS )
}
2024-02-10 17:32:25 +00:00
if err := ( * fp ) ( os . Args [ 2 : ] ) ; err != nil {
2021-02-13 20:57:49 +00:00
log . SetFlags ( 0 )
log . Fatal ( err )
}
return
2021-02-04 20:20:07 +00:00
}
}
2020-09-25 20:10:53 +00:00
flag . Parse ( )
if flag . NArg ( ) > 0 {
2022-05-26 22:06:46 +00:00
// Windows subprocess is spawned with /subprocess, so we need to avoid this check there.
2022-07-13 14:43:55 +00:00
if runtime . GOOS != "windows" || ( flag . Arg ( 0 ) != "/subproc" && flag . Arg ( 0 ) != "/firewall" ) {
2022-05-26 22:06:46 +00:00
log . Fatalf ( "tailscaled does not take non-flag arguments: %q" , flag . Args ( ) )
}
2020-02-05 22:16:58 +00:00
}
2023-09-01 19:19:54 +00:00
if fd , ok := envknob . LookupInt ( "TS_PARENT_DEATH_FD" ) ; ok && fd > 2 {
go dieOnPipeReadErrorOfFD ( fd )
}
2020-10-27 04:23:58 +00:00
if printVersion {
fmt . Println ( version . String ( ) )
os . Exit ( 0 )
}
2021-08-05 22:38:38 +00:00
if runtime . GOOS == "darwin" && os . Getuid ( ) != 0 && ! strings . Contains ( args . tunname , "userspace-networking" ) && ! args . cleanup {
2021-02-17 23:45:50 +00:00
log . SetFlags ( 0 )
2021-03-02 16:36:25 +00:00
log . Fatalf ( "tailscaled requires root; use sudo tailscaled (or use --tun=userspace-networking)" )
2021-02-17 23:45:50 +00:00
}
2020-07-23 18:58:28 +00:00
if args . socketpath == "" && runtime . GOOS != "windows" {
2021-02-17 23:45:50 +00:00
log . SetFlags ( 0 )
2020-02-18 20:33:28 +00:00
log . Fatalf ( "--socket is required" )
}
2021-08-30 16:45:55 +00:00
if args . birdSocketPath != "" && createBIRDClient == nil {
log . SetFlags ( 0 )
log . Fatalf ( "--bird-socket is not supported on %s" , runtime . GOOS )
}
2022-05-05 01:50:35 +00:00
// Only apply a default statepath when neither have been provided, so that a
// user may specify only --statedir if they wish.
if args . statepath == "" && args . statedir == "" {
args . statepath = paths . DefaultTailscaledStateFile ( )
}
2022-09-13 14:09:57 +00:00
if args . disableLogs {
envknob . SetNoLogsNoSupport ( )
}
2022-05-26 22:06:46 +00:00
if beWindowsSubprocess ( ) {
return
}
2021-04-22 07:25:00 +00:00
err := run ( )
// Remove file sharing from Windows shell (noop in non-windows)
osshare . SetFileSharingEnabled ( false , logger . Discard )
if err != nil {
2021-12-08 23:13:39 +00:00
log . Fatal ( err )
2020-07-23 18:58:28 +00:00
}
}
2021-08-18 20:34:31 +00:00
func trySynologyMigration ( p string ) error {
if runtime . GOOS != "linux" || distro . Get ( ) != distro . Synology {
return nil
}
fi , err := os . Stat ( p )
if err == nil && fi . Size ( ) > 0 || ! os . IsNotExist ( err ) {
return err
}
// File is empty or doesn't exist, try reading from the old path.
const oldPath = "/var/packages/Tailscale/etc/tailscaled.state"
if _ , err := os . Stat ( oldPath ) ; err != nil {
if os . IsNotExist ( err ) {
return nil
}
return err
}
if err := os . Chown ( oldPath , os . Getuid ( ) , os . Getgid ( ) ) ; err != nil {
return err
}
if err := os . Rename ( oldPath , p ) ; err != nil {
return err
}
return nil
}
2021-11-03 16:03:11 +00:00
func statePathOrDefault ( ) string {
if args . statepath != "" {
return args . statepath
}
if args . statedir != "" {
return filepath . Join ( args . statedir , "tailscaled.state" )
}
return ""
}
2022-11-27 21:07:41 +00:00
// serverOptions is the configuration of the Tailscale node agent.
type serverOptions struct {
// VarRoot is the Tailscale daemon's private writable
// directory (usually "/var/lib/tailscale" on Linux) that
// contains the "tailscaled.state" file, the "certs" directory
// for TLS certs, and the "files" directory for incoming
// Taildrop files before they're moved to a user directory.
// If empty, Taildrop and TLS certs don't function.
VarRoot string
// LoginFlags specifies the LoginFlags to pass to the client.
LoginFlags controlclient . LoginFlags
}
func ipnServerOpts ( ) ( o serverOptions ) {
2022-11-23 23:40:47 +00:00
goos := envknob . GOOS ( )
2021-07-19 22:01:41 +00:00
2021-11-03 16:03:11 +00:00
o . VarRoot = args . statedir
// If an absolute --state is provided but not --statedir, try to derive
// a state directory.
if o . VarRoot == "" && filepath . IsAbs ( args . statepath ) {
if dir := filepath . Dir ( args . statepath ) ; strings . EqualFold ( filepath . Base ( dir ) , "tailscale" ) {
o . VarRoot = dir
}
}
2022-02-18 20:55:22 +00:00
if strings . HasPrefix ( statePathOrDefault ( ) , "mem:" ) {
// Register as an ephemeral node.
o . LoginFlags = controlclient . LoginEphemeral
}
2021-07-19 22:01:41 +00:00
switch goos {
2022-02-18 20:55:22 +00:00
case "js" :
// The js/wasm client has no state storage so for now
// treat all interactive logins as ephemeral.
// TODO(bradfitz): if we start using browser LocalStorage
// or something, then rethink this.
o . LoginFlags = controlclient . LoginEphemeral
2021-07-19 22:01:41 +00:00
case "windows" :
// Not those.
}
return o
}
2022-11-28 04:40:36 +00:00
var logPol * logpolicy . Policy
var debugMux * http . ServeMux
2020-07-23 18:58:28 +00:00
2024-01-03 19:45:49 +00:00
func run ( ) ( err error ) {
2023-04-17 23:01:41 +00:00
var logf logger . Logf = log . Printf
2023-05-03 20:57:17 +00:00
sys := new ( tsd . System )
2023-10-11 20:55:57 +00:00
// Parse config, if specified, to fail early if it's invalid.
var conf * conffile . Config
if args . confFile != "" {
conf , err = conffile . Load ( args . confFile )
if err != nil {
return fmt . Errorf ( "error reading config file: %w" , err )
}
sys . InitialConfig = conf
}
2024-01-03 19:45:49 +00:00
var netMon * netmon . Monitor
isWinSvc := isWindowsService ( )
if ! isWinSvc {
netMon , err = netmon . New ( func ( format string , args ... any ) {
logf ( format , args ... )
} )
if err != nil {
return fmt . Errorf ( "netmon.New: %w" , err )
}
sys . Set ( netMon )
2023-04-17 23:01:41 +00:00
}
2023-07-10 19:45:57 +00:00
pol := logpolicy . New ( logtail . CollectionNode , netMon , nil /* use log.Printf */ )
2020-12-21 18:53:18 +00:00
pol . SetVerbosityLevel ( args . verbose )
2022-11-28 04:40:36 +00:00
logPol = pol
2020-07-23 18:58:28 +00:00
defer func ( ) {
// Finish uploading logs after closing everything else.
ctx , cancel := context . WithTimeout ( context . Background ( ) , time . Second )
defer cancel ( )
pol . Shutdown ( ctx )
} ( )
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
}
2024-01-03 19:45:49 +00:00
if isWinSvc {
2021-02-05 16:46:12 +00:00
// Run the IPN server from the Windows service manager.
log . Printf ( "Running service..." )
if err := runWindowsService ( pol ) ; err != nil {
log . Printf ( "runservice: %v" , err )
}
log . Printf ( "Service ended." )
return nil
}
2022-01-24 18:52:57 +00:00
if envknob . Bool ( "TS_DEBUG_MEMORY" ) {
2020-10-19 14:56:23 +00:00
logf = logger . RusagePrefixLog ( logf )
}
2020-07-23 18:58:28 +00:00
logf = logger . RateLimitedFn ( logf , 5 * time . Second , 5 , 100 )
if args . cleanup {
2022-01-24 18:52:57 +00:00
if envknob . Bool ( "TS_PLEASE_PANIC" ) {
2021-07-28 22:17:31 +00:00
panic ( "TS_PLEASE_PANIC asked us to panic" )
}
2021-04-02 05:35:26 +00:00
dns . Cleanup ( logf , args . tunname )
2020-07-23 18:58:28 +00:00
router . Cleanup ( logf , args . tunname )
return nil
}
2021-11-03 16:03:11 +00:00
if args . statepath == "" && args . statedir == "" {
log . Fatalf ( "--statedir (or at least --state) is required" )
2021-01-22 19:35:22 +00:00
}
2021-11-03 16:03:11 +00:00
if err := trySynologyMigration ( statePathOrDefault ( ) ) ; err != nil {
2021-08-18 20:34:31 +00:00
log . Printf ( "error in synology migration: %v" , err )
}
2021-01-22 19:35:22 +00:00
2020-07-23 18:58:28 +00:00
if args . debug != "" {
2020-03-26 05:57:46 +00:00
debugMux = newDebugMux ( )
2020-02-05 22:16:58 +00:00
}
2024-04-02 20:32:30 +00:00
sys . Set ( driveimpl . NewFileSystemForRemote ( logf ) )
2024-02-09 17:26:43 +00:00
2023-05-03 20:57:17 +00:00
return startIPNServer ( context . Background ( ) , logf , pol . PublicID , sys )
2022-11-28 04:40:36 +00:00
}
2023-08-24 22:02:42 +00:00
var sigPipe os . Signal // set by sigpipe.go
2023-05-03 20:57:17 +00:00
func startIPNServer ( ctx context . Context , logf logger . Logf , logID logid . PublicID , sys * tsd . System ) error {
2023-01-30 17:34:51 +00:00
ln , err := safesocket . Listen ( args . socketpath )
2022-11-28 04:40:36 +00:00
if err != nil {
return fmt . Errorf ( "safesocket.Listen: %v" , err )
}
ctx , cancel := context . WithCancel ( ctx )
defer cancel ( )
// Exit gracefully by cancelling the ipnserver context in most common cases:
// interrupted from the TTY or killed by a service manager.
interrupt := make ( chan os . Signal , 1 )
signal . Notify ( interrupt , syscall . SIGINT , syscall . SIGTERM )
// SIGPIPE sometimes gets generated when CLIs disconnect from
// tailscaled. The default action is to terminate the process, we
// want to keep running.
2023-08-24 22:02:42 +00:00
if sigPipe != nil {
signal . Ignore ( sigPipe )
}
2024-02-24 01:55:08 +00:00
wgEngineCreated := make ( chan struct { } )
2022-11-28 04:40:36 +00:00
go func ( ) {
2024-02-24 01:55:08 +00:00
var wgEngineClosed <- chan struct { }
wgEngineCreated := wgEngineCreated // local shadow
for {
select {
case s := <- interrupt :
logf ( "tailscaled got signal %v; shutting down" , s )
cancel ( )
return
case <- wgEngineClosed :
logf ( "wgengine has been closed; shutting down" )
cancel ( )
return
case <- wgEngineCreated :
wgEngineClosed = sys . Engine . Get ( ) . Done ( )
wgEngineCreated = nil
case <- ctx . Done ( ) :
return
}
2022-11-28 04:40:36 +00:00
}
} ( )
2023-05-03 20:57:17 +00:00
srv := ipnserver . New ( logf , logID , sys . NetMon . Get ( ) )
2022-11-28 04:40:36 +00:00
if debugMux != nil {
debugMux . HandleFunc ( "/debug/ipn" , srv . ServeHTMLStatus )
}
var lbErr syncs . AtomicValue [ error ]
go func ( ) {
t0 := time . Now ( )
2022-11-29 05:07:33 +00:00
if s , ok := envknob . LookupInt ( "TS_DEBUG_BACKEND_DELAY_SEC" ) ; ok {
d := time . Duration ( s ) * time . Second
logf ( "sleeping %v before starting backend..." , d )
select {
case <- time . After ( d ) :
logf ( "slept %v; starting backend..." , d )
case <- ctx . Done ( ) :
return
}
}
2023-05-03 20:57:17 +00:00
lb , err := getLocalBackend ( ctx , logf , logID , sys )
2022-11-28 04:40:36 +00:00
if err == nil {
logf ( "got LocalBackend in %v" , time . Since ( t0 ) . Round ( time . Millisecond ) )
srv . SetLocalBackend ( lb )
2024-02-24 01:55:08 +00:00
close ( wgEngineCreated )
2022-11-28 04:40:36 +00:00
return
}
lbErr . Store ( err ) // before the following cancel
cancel ( ) // make srv.Run below complete
} ( )
err = srv . Run ( ctx , ln )
if err != nil && lbErr . Load ( ) != nil {
return fmt . Errorf ( "getLocalBackend error: %v" , lbErr . Load ( ) )
}
// Cancelation is not an error: it is the only way to stop ipnserver.
if err != nil && ! errors . Is ( err , context . Canceled ) {
return fmt . Errorf ( "ipnserver.Run: %w" , err )
}
return nil
}
2023-05-03 20:57:17 +00:00
func getLocalBackend ( ctx context . Context , logf logger . Logf , logID logid . PublicID , sys * tsd . System ) ( _ * ipnlocal . LocalBackend , retErr error ) {
2022-11-28 04:40:36 +00:00
if logPol != nil {
2023-05-03 20:57:17 +00:00
logPol . Logtail . SetNetMon ( sys . NetMon . Get ( ) )
2021-03-02 06:09:43 +00:00
}
2021-11-27 00:00:39 +00:00
socksListener , httpProxyListener := mustStartProxyListeners ( args . socksAddr , args . httpProxyAddr )
2021-03-01 18:08:53 +00:00
2022-09-30 21:15:17 +00:00
dialer := & tsdial . Dialer { Logf : logf } // mutated below (before used)
2023-05-03 20:57:17 +00:00
sys . Set ( dialer )
onlyNetstack , err := createEngine ( logf , sys )
2020-02-05 22:16:58 +00:00
if err != nil {
2022-11-28 04:40:36 +00:00
return nil , fmt . Errorf ( "createEngine: %w" , err )
2020-02-05 22:16:58 +00:00
}
2021-12-21 18:26:13 +00:00
if debugMux != nil {
2023-05-03 20:57:17 +00:00
if ms , ok := sys . MagicSock . GetOK ( ) ; ok {
debugMux . HandleFunc ( "/debug/magicsock" , ms . ServeHTTPDebug )
2021-12-21 18:26:13 +00:00
}
go runDebugServer ( debugMux , args . debug )
}
2021-03-01 18:08:53 +00:00
2023-05-03 20:57:17 +00:00
ns , err := newNetstack ( logf , sys )
2021-10-29 23:21:18 +00:00
if err != nil {
2022-11-28 04:40:36 +00:00
return nil , fmt . Errorf ( "newNetstack: %w" , err )
2021-10-29 23:21:18 +00:00
}
2023-09-12 20:37:51 +00:00
sys . Set ( ns )
2022-12-02 21:44:14 +00:00
ns . ProcessLocalIPs = onlyNetstack
ns . ProcessSubnets = onlyNetstack || handleSubnetsInNetstack ( )
2021-03-01 18:08:53 +00:00
2022-12-02 21:44:14 +00:00
if onlyNetstack {
2023-05-03 20:57:17 +00:00
e := sys . Engine . Get ( )
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
dialer . UseNetstackForIP = func ( ip netip . Addr ) bool {
2021-11-30 23:53:34 +00:00
_ , ok := e . PeerForIP ( ip )
return ok
}
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
dialer . NetstackDialTCP = func ( ctx context . Context , dst netip . AddrPort ) ( net . Conn , error ) {
2024-01-05 16:23:30 +00:00
// Note: don't just return ns.DialContextTCP or we'll
// return an interface containing a nil pointer.
tcpConn , err := ns . DialContextTCP ( ctx , dst )
if err != nil {
return nil , err
}
return tcpConn , nil
2021-11-30 23:53:34 +00:00
}
}
2021-09-28 17:16:05 +00:00
if socksListener != nil || httpProxyListener != nil {
2023-03-05 02:49:05 +00:00
var addrs [ ] string
2021-09-28 17:16:05 +00:00
if httpProxyListener != nil {
2021-12-01 04:39:12 +00:00
hs := & http . Server { Handler : httpProxyHandler ( dialer . UserDial ) }
2021-09-28 17:16:05 +00:00
go func ( ) {
log . Fatalf ( "HTTP proxy exited: %v" , hs . Serve ( httpProxyListener ) )
} ( )
2023-03-05 02:49:05 +00:00
addrs = append ( addrs , httpProxyListener . Addr ( ) . String ( ) )
2021-09-28 17:16:05 +00:00
}
if socksListener != nil {
2021-11-30 23:53:34 +00:00
ss := & socks5 . Server {
Logf : logger . WithPrefix ( logf , "socks5: " ) ,
2021-12-01 04:39:12 +00:00
Dialer : dialer . UserDial ,
2021-11-30 23:53:34 +00:00
}
2021-09-28 17:16:05 +00:00
go func ( ) {
2021-11-30 23:53:34 +00:00
log . Fatalf ( "SOCKS5 server exited: %v" , ss . Serve ( socksListener ) )
2021-09-28 17:16:05 +00:00
} ( )
2023-03-05 02:49:05 +00:00
addrs = append ( addrs , socksListener . Addr ( ) . String ( ) )
2021-09-28 17:16:05 +00:00
}
2023-03-05 02:49:05 +00:00
tshttpproxy . SetSelfProxy ( addrs ... )
2021-03-01 18:08:53 +00:00
}
2021-07-19 22:01:41 +00:00
opts := ipnServerOpts ( )
2021-11-05 19:40:07 +00:00
2022-02-28 21:08:45 +00:00
store , err := store . New ( logf , statePathOrDefault ( ) )
2021-11-05 19:40:07 +00:00
if err != nil {
2022-11-28 04:40:36 +00:00
return nil , fmt . Errorf ( "store.New: %w" , err )
2021-11-05 19:40:07 +00:00
}
2023-05-03 20:57:17 +00:00
sys . Set ( store )
2022-11-27 21:07:41 +00:00
2023-10-13 19:41:10 +00:00
if w , ok := sys . Tun . GetOK ( ) ; ok {
w . Start ( )
}
2023-05-03 20:57:17 +00:00
lb , err := ipnlocal . NewLocalBackend ( logf , logID , sys , opts . LoginFlags )
2021-11-05 19:40:07 +00:00
if err != nil {
2022-11-28 04:40:36 +00:00
return nil , fmt . Errorf ( "ipnlocal.NewLocalBackend: %w" , err )
2021-11-05 19:40:07 +00:00
}
2022-11-27 21:07:41 +00:00
lb . SetVarRoot ( opts . VarRoot )
2023-01-05 19:00:42 +00:00
if logPol != nil {
lb . SetLogFlusher ( logPol . Logtail . StartFlush )
}
2022-11-27 21:07:41 +00:00
if root := lb . TailscaleVarRoot ( ) ; root != "" {
2023-04-17 17:58:40 +00:00
dnsfallback . SetCachePath ( filepath . Join ( root , "derpmap.cached.json" ) , logf )
2022-11-27 21:07:41 +00:00
}
2023-12-11 17:35:28 +00:00
lb . ConfigureWebClient ( & tailscale . LocalClient {
Socket : args . socketpath ,
UseSocketOnly : args . socketpath != paths . DefaultTailscaledSocket ( ) ,
} )
2022-11-27 21:07:41 +00:00
configureTaildrop ( logf , lb )
2022-12-23 18:22:39 +00:00
if err := ns . Start ( lb ) ; err != nil {
2021-08-26 21:50:55 +00:00
log . Fatalf ( "failed to start netstack: %v" , err )
}
2022-11-28 04:40:36 +00:00
return lb , nil
2020-02-05 22:16:58 +00:00
}
2022-12-02 21:44:14 +00:00
// createEngine tries to the wgengine.Engine based on the order of tunnels
// specified in the command line flags.
//
// onlyNetstack is true if the user has explicitly requested that we use netstack
// for all networking.
2023-05-03 20:57:17 +00:00
func createEngine ( logf logger . Logf , sys * tsd . System ) ( onlyNetstack bool , err error ) {
2021-03-12 21:04:47 +00:00
if args . tunname == "" {
2023-05-03 20:57:17 +00:00
return false , errors . New ( "no --tun value specified" )
2021-03-12 21:04:47 +00:00
}
var errs [ ] error
for _ , name := range strings . Split ( args . tunname , "," ) {
logf ( "wgengine.NewUserspaceEngine(tun %q) ..." , name )
2023-05-03 20:57:17 +00:00
onlyNetstack , err = tryEngine ( logf , sys , name )
2021-03-12 21:04:47 +00:00
if err == nil {
2023-05-03 20:57:17 +00:00
return onlyNetstack , nil
2021-03-12 21:04:47 +00:00
}
logf ( "wgengine.NewUserspaceEngine(tun %q) error: %v" , name , err )
errs = append ( errs , err )
}
2023-05-03 20:57:17 +00:00
return false , multierr . New ( errs ... )
2021-03-12 21:04:47 +00:00
}
2022-12-02 21:44:14 +00:00
// handleSubnetsInNetstack reports whether netstack should handle subnet routers
// as opposed to the OS. We do this if the OS doesn't support subnet routers
// (e.g. Windows) or if the user has explicitly requested it (e.g.
// --tun=userspace-networking).
func handleSubnetsInNetstack ( ) bool {
if v , ok := envknob . LookupBool ( "TS_DEBUG_NETSTACK_SUBNETS" ) ; ok {
2021-04-01 16:35:41 +00:00
return v
}
2021-04-10 01:42:21 +00:00
if distro . Get ( ) == distro . Synology {
return true
}
2021-04-01 16:35:41 +00:00
switch runtime . GOOS {
2022-08-15 21:36:59 +00:00
case "windows" , "darwin" , "freebsd" , "openbsd" :
2021-04-01 16:35:41 +00:00
// Enable on Windows and tailscaled-on-macOS (this doesn't
2021-08-15 01:38:26 +00:00
// affect the GUI clients), and on FreeBSD.
2021-04-01 16:35:41 +00:00
return true
}
return false
}
2022-11-28 04:40:36 +00:00
var tstunNew = tstun . New
2023-05-03 20:57:17 +00:00
func tryEngine ( logf logger . Logf , sys * tsd . System , name string ) ( onlyNetstack bool , err error ) {
2021-03-27 04:03:21 +00:00
conf := wgengine . Config {
2024-02-09 17:26:43 +00:00
ListenPort : args . port ,
NetMon : sys . NetMon . Get ( ) ,
Dialer : sys . Dialer . Get ( ) ,
SetSubsystem : sys . Set ,
ControlKnobs : sys . ControlKnobs ( ) ,
2024-04-02 20:32:30 +00:00
TailFSForLocal : driveimpl . NewFileSystemForLocal ( logf ) ,
2021-03-27 04:03:21 +00:00
}
2021-09-11 05:24:30 +00:00
2022-12-05 19:40:40 +00:00
onlyNetstack = name == "userspace-networking"
2023-05-03 20:57:17 +00:00
netstackSubnetRouter := onlyNetstack // but mutated later on some platforms
2022-12-05 19:40:40 +00:00
netns . SetEnabled ( ! onlyNetstack )
2021-09-11 05:24:30 +00:00
2021-08-30 16:45:55 +00:00
if args . birdSocketPath != "" && createBIRDClient != nil {
log . Printf ( "Connecting to BIRD at %s ..." , args . birdSocketPath )
conf . BIRDClient , err = createBIRDClient ( args . birdSocketPath )
if err != nil {
2023-05-03 20:57:17 +00:00
return false , fmt . Errorf ( "createBIRDClient: %w" , err )
2021-08-30 16:45:55 +00:00
}
}
2022-12-05 19:40:40 +00:00
if onlyNetstack {
2022-02-23 17:25:14 +00:00
if runtime . GOOS == "linux" && distro . Get ( ) == distro . Synology {
// On Synology in netstack mode, still init a DNS
// manager (directManager) to avoid the health check
// warnings in 'tailscale status' about DNS base
// configuration being unavailable (from the noop
// manager). More in Issue 4017.
// TODO(bradfitz): add a Synology-specific DNS manager.
conf . DNS , err = dns . NewOSConfigurator ( logf , "" ) // empty interface name
if err != nil {
2023-05-03 20:57:17 +00:00
return false , fmt . Errorf ( "dns.NewOSConfigurator: %w" , err )
2022-02-23 17:25:14 +00:00
}
}
} else {
2022-11-28 04:40:36 +00:00
dev , devName , err := tstunNew ( logf , name )
2021-03-27 04:03:21 +00:00
if err != nil {
2022-07-16 21:10:20 +00:00
tstun . Diagnose ( logf , name , err )
2023-05-03 20:57:17 +00:00
return false , fmt . Errorf ( "tstun.New(%q): %w" , name , err )
2021-03-27 04:03:21 +00:00
}
2021-03-29 02:25:01 +00:00
conf . Tun = dev
2021-07-23 16:45:04 +00:00
if strings . HasPrefix ( name , "tap:" ) {
conf . IsTAP = true
e , err := wgengine . NewUserspaceEngine ( logf , conf )
2023-05-03 20:57:17 +00:00
if err != nil {
return false , err
}
sys . Set ( e )
return false , err
2021-07-23 16:45:04 +00:00
}
2023-05-03 20:57:17 +00:00
r , err := router . New ( logf , dev , sys . NetMon . Get ( ) )
2021-03-29 01:59:33 +00:00
if err != nil {
dev . Close ( )
2023-05-03 20:57:17 +00:00
return false , fmt . Errorf ( "creating router: %w" , err )
2021-03-29 01:59:33 +00:00
}
2023-05-03 20:57:17 +00:00
2021-04-12 22:51:37 +00:00
d , err := dns . NewOSConfigurator ( logf , devName )
if err != nil {
2022-09-26 22:37:27 +00:00
dev . Close ( )
r . Close ( )
2023-05-03 20:57:17 +00:00
return false , fmt . Errorf ( "dns.NewOSConfigurator: %w" , err )
2021-04-12 22:51:37 +00:00
}
conf . DNS = d
2021-04-01 16:35:41 +00:00
conf . Router = r
2022-12-02 21:44:14 +00:00
if handleSubnetsInNetstack ( ) {
2021-04-01 16:35:41 +00:00
conf . Router = netstack . NewSubnetRouterWrapper ( conf . Router )
2023-05-03 20:57:17 +00:00
netstackSubnetRouter = true
2021-04-01 16:35:41 +00:00
}
2023-05-06 06:04:37 +00:00
sys . Set ( conf . Router )
2021-03-27 04:03:21 +00:00
}
2023-05-03 20:57:17 +00:00
e , err := wgengine . NewUserspaceEngine ( logf , conf )
2021-03-27 04:03:21 +00:00
if err != nil {
2023-05-03 20:57:17 +00:00
return onlyNetstack , err
2021-03-27 04:03:21 +00:00
}
2023-05-03 20:57:17 +00:00
e = wgengine . NewWatchdog ( e )
sys . Set ( e )
sys . NetstackRouter . Set ( netstackSubnetRouter )
return onlyNetstack , nil
2021-03-27 04:03:21 +00:00
}
2020-03-26 05:57:46 +00:00
func newDebugMux ( ) * http . ServeMux {
2020-02-05 22:16:58 +00:00
mux := http . NewServeMux ( )
2020-12-14 02:51:24 +00:00
mux . HandleFunc ( "/debug/metrics" , servePrometheusMetrics )
2020-02-05 22:16:58 +00:00
mux . HandleFunc ( "/debug/pprof/" , pprof . Index )
mux . HandleFunc ( "/debug/pprof/cmdline" , pprof . Cmdline )
mux . HandleFunc ( "/debug/pprof/profile" , pprof . Profile )
mux . HandleFunc ( "/debug/pprof/symbol" , pprof . Symbol )
mux . HandleFunc ( "/debug/pprof/trace" , pprof . Trace )
2020-03-26 05:57:46 +00:00
return mux
}
2020-12-14 02:51:24 +00:00
func servePrometheusMetrics ( w http . ResponseWriter , r * http . Request ) {
w . Header ( ) . Set ( "Content-Type" , "text/plain" )
2023-04-10 10:28:16 +00:00
varz . Handler ( w , r )
2020-12-14 02:51:24 +00:00
clientmetric . WritePrometheusExpositionFormat ( w )
}
2020-03-26 05:57:46 +00:00
func runDebugServer ( mux * http . ServeMux , addr string ) {
srv := & http . Server {
2020-02-05 22:16:58 +00:00
Addr : addr ,
Handler : mux ,
}
if err := srv . ListenAndServe ( ) ; err != nil {
log . Fatal ( err )
}
}
2021-04-01 16:35:41 +00:00
2023-05-03 20:57:17 +00:00
func newNetstack ( logf logger . Logf , sys * tsd . System ) ( * netstack . Impl , error ) {
2024-02-09 17:26:43 +00:00
tfs , _ := sys . TailFSForLocal . GetOK ( )
2024-01-19 23:06:55 +00:00
ret , err := netstack . Create ( logf ,
2023-09-13 18:38:05 +00:00
sys . Tun . Get ( ) ,
sys . Engine . Get ( ) ,
sys . MagicSock . Get ( ) ,
sys . Dialer . Get ( ) ,
sys . DNSManager . Get ( ) ,
sys . ProxyMapper ( ) ,
2024-02-02 18:45:32 +00:00
tfs ,
2023-09-13 18:38:05 +00:00
)
2024-01-19 23:06:55 +00:00
if err != nil {
return nil , err
}
// Only register debug info if we have a debug mux
if debugMux != nil {
expvar . Publish ( "netstack" , ret . ExpVar ( ) )
}
return ret , nil
2021-04-01 16:35:41 +00:00
}
2021-09-28 17:16:05 +00:00
2021-11-27 00:00:39 +00:00
// mustStartProxyListeners creates listeners for local SOCKS and HTTP
// proxies, if the respective addresses are not empty. socksAddr and
// httpAddr can be the same, in which case socksListener will receive
// connections that look like they're speaking SOCKS and httpListener
// will receive everything else.
//
// socksListener and httpListener can be nil, if their respective
// addrs are empty.
func mustStartProxyListeners ( socksAddr , httpAddr string ) ( socksListener , httpListener net . Listener ) {
if socksAddr == httpAddr && socksAddr != "" && ! strings . HasSuffix ( socksAddr , ":0" ) {
ln , err := net . Listen ( "tcp" , socksAddr )
if err != nil {
log . Fatalf ( "proxy listener: %v" , err )
}
return proxymux . SplitSOCKSAndHTTP ( ln )
2021-09-28 17:16:05 +00:00
}
2021-11-27 00:00:39 +00:00
var err error
if socksAddr != "" {
socksListener , err = net . Listen ( "tcp" , socksAddr )
if err != nil {
log . Fatalf ( "SOCKS5 listener: %v" , err )
}
if strings . HasSuffix ( socksAddr , ":0" ) {
// Log kernel-selected port number so integration tests
// can find it portably.
log . Printf ( "SOCKS5 listening on %v" , socksListener . Addr ( ) )
}
2021-09-28 17:16:05 +00:00
}
2021-11-27 00:00:39 +00:00
if httpAddr != "" {
httpListener , err = net . Listen ( "tcp" , httpAddr )
if err != nil {
log . Fatalf ( "HTTP proxy listener: %v" , err )
}
if strings . HasSuffix ( httpAddr , ":0" ) {
// Log kernel-selected port number so integration tests
// can find it portably.
log . Printf ( "HTTP proxy listening on %v" , httpListener . Addr ( ) )
}
2021-09-28 17:16:05 +00:00
}
2021-11-27 00:00:39 +00:00
return socksListener , httpListener
2021-09-28 17:16:05 +00:00
}
2022-02-24 22:03:36 +00:00
2024-02-10 17:32:25 +00:00
var beChildFunc = beChild
2022-02-24 22:03:36 +00:00
func beChild ( args [ ] string ) error {
if len ( args ) == 0 {
return errors . New ( "missing mode argument" )
}
typ := args [ 0 ]
f , ok := childproc . Code [ typ ]
if ! ok {
return fmt . Errorf ( "unknown be-child mode %q" , typ )
}
return f ( args [ 1 : ] )
}
2023-09-01 19:19:54 +00:00
2024-02-10 17:32:25 +00:00
var serveTailFSFunc = serveTailFS
2024-02-09 17:26:43 +00:00
// serveTailFS serves one or more tailfs on localhost using the WebDAV
2024-02-02 18:45:32 +00:00
// protocol. On UNIX and MacOS tailscaled environment, tailfs spawns child
// tailscaled processes in serve-tailfs mode in order to access the fliesystem
// as specific (usually unprivileged) users.
//
2024-02-09 17:26:43 +00:00
// serveTailFS prints the address on which it's listening to stdout so that the
2024-02-02 18:45:32 +00:00
// parent process knows where to connect to.
2024-02-09 17:26:43 +00:00
func serveTailFS ( args [ ] string ) error {
2024-02-02 18:45:32 +00:00
if len ( args ) == 0 {
return errors . New ( "missing shares" )
}
if len ( args ) % 2 != 0 {
return errors . New ( "need <sharename> <path> pairs" )
}
2024-04-02 20:32:30 +00:00
s , err := driveimpl . NewFileServer ( )
2024-02-02 18:45:32 +00:00
if err != nil {
return fmt . Errorf ( "unable to start tailfs FileServer: %v" , err )
}
shares := make ( map [ string ] string )
for i := 0 ; i < len ( args ) ; i += 2 {
shares [ args [ i ] ] = args [ i + 1 ]
}
s . SetShares ( shares )
fmt . Printf ( "%v\n" , s . Addr ( ) )
return s . Serve ( )
}
2023-09-01 19:19:54 +00:00
// dieOnPipeReadErrorOfFD reads from the pipe named by fd and exit the process
// when the pipe becomes readable. We use this in tests as a somewhat more
// portable mechanism for the Linux PR_SET_PDEATHSIG, which we wish existed on
// macOS. This helps us clean up straggler tailscaled processes when the parent
// test driver dies unexpectedly.
func dieOnPipeReadErrorOfFD ( fd int ) {
f := os . NewFile ( uintptr ( fd ) , "TS_PARENT_DEATH_FD" )
f . Read ( make ( [ ] byte , 1 ) )
os . Exit ( 1 )
}