diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index c9c048af3..596152f48 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -515,7 +515,7 @@ func tryEngine(logf logger.Logf, linkMon *monitor.Mon, dialer *tsdial.Dialer, na } else { dev, devName, err := tstun.New(logf, name) if err != nil { - tstun.Diagnose(logf, name) + tstun.Diagnose(logf, name, err) return nil, false, fmt.Errorf("tstun.New(%q): %w", name, err) } conf.Tun = dev diff --git a/net/tstun/tun.go b/net/tstun/tun.go index bdf8d465a..3030112d5 100644 --- a/net/tstun/tun.go +++ b/net/tstun/tun.go @@ -74,15 +74,18 @@ func New(logf logger.Logf, tunName string) (tun.Device, string, error) { // tunDiagnoseFailure, if non-nil, does OS-specific diagnostics of why // TUN failed to work. -var tunDiagnoseFailure func(tunName string, logf logger.Logf) +var tunDiagnoseFailure func(tunName string, logf logger.Logf, err error) // Diagnose tries to explain a tuntap device creation failure. // It pokes around the system and logs some diagnostic info that might // help debug why tun creation failed. Because device creation has // already failed and the program's about to end, log a lot. -func Diagnose(logf logger.Logf, tunName string) { +// +// The tunName is the name of the tun device that was requested but failed. +// The err error is how the tun creation failed. +func Diagnose(logf logger.Logf, tunName string, err error) { if tunDiagnoseFailure != nil { - tunDiagnoseFailure(tunName, logf) + tunDiagnoseFailure(tunName, logf, err) } else { logf("no TUN failure diagnostics for OS %q", runtime.GOOS) } diff --git a/net/tstun/tun_linux.go b/net/tstun/tun_linux.go index 6be987c20..112d16b85 100644 --- a/net/tstun/tun_linux.go +++ b/net/tstun/tun_linux.go @@ -6,6 +6,7 @@ package tstun import ( "bytes" + "errors" "os" "os/exec" "strings" @@ -19,7 +20,14 @@ func init() { tunDiagnoseFailure = diagnoseLinuxTUNFailure } -func diagnoseLinuxTUNFailure(tunName string, logf logger.Logf) { +func diagnoseLinuxTUNFailure(tunName string, logf logger.Logf, createErr error) { + if errors.Is(createErr, syscall.EBUSY) { + logf("TUN device %s is busy; another process probably still has it open (from old version of Tailscale that had a bug)", tunName) + logf("To fix, kill the process that has it open. Find with:\n\n$ sudo lsof -n /dev/net/tun\n\n") + logf("... and then kill those PID(s)") + return + } + var un syscall.Utsname err := syscall.Uname(&un) if err != nil { diff --git a/net/tstun/tun_macos.go b/net/tstun/tun_macos.go index cb928eca4..58b7c68f1 100644 --- a/net/tstun/tun_macos.go +++ b/net/tstun/tun_macos.go @@ -17,7 +17,7 @@ func init() { tunDiagnoseFailure = diagnoseDarwinTUNFailure } -func diagnoseDarwinTUNFailure(tunName string, logf logger.Logf) { +func diagnoseDarwinTUNFailure(tunName string, logf logger.Logf, err error) { if os.Getuid() != 0 { logf("failed to create TUN device as non-root user; use 'sudo tailscaled', or run under launchd with 'sudo tailscaled install-system-daemon'") }