diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index cc9d54e12..cf2763092 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -105,6 +105,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/net/tlsdial from tailscale.com/control/controlclient+ tailscale.com/net/tsaddr from tailscale.com/ipn/ipnlocal+ 💣 tailscale.com/net/tshttpproxy from tailscale.com/control/controlclient+ + tailscale.com/net/tun from tailscale.com/cmd/tailscaled tailscale.com/paths from tailscale.com/cmd/tailscaled+ tailscale.com/portlist from tailscale.com/ipn/ipnlocal tailscale.com/safesocket from tailscale.com/ipn/ipnserver diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index a760db708..f0215f6fb 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -32,6 +32,7 @@ import ( "tailscale.com/ipn/ipnserver" "tailscale.com/logpolicy" "tailscale.com/net/socks5" + "tailscale.com/net/tun" "tailscale.com/paths" "tailscale.com/types/flagtype" "tailscale.com/types/logger" @@ -316,18 +317,7 @@ func createEngine(logf logger.Logf, linkMon *monitor.Mon) (e wgengine.Engine, is var errs []error for _, name := range strings.Split(args.tunname, ",") { logf("wgengine.NewUserspaceEngine(tun %q) ...", name) - conf := wgengine.Config{ - ListenPort: args.port, - LinkMonitor: linkMon, - } - isUserspace = name == "userspace-networking" - if isUserspace { - conf.TUN = tstun.NewFakeTUN() - conf.RouterGen = router.NewFake - } else { - conf.TUNName = name - } - e, err := wgengine.NewUserspaceEngine(logf, conf) + e, isUserspace, err = tryEngine(logf, linkMon, name) if err == nil { return e, isUserspace, nil } @@ -337,6 +327,29 @@ func createEngine(logf logger.Logf, linkMon *monitor.Mon) (e wgengine.Engine, is return nil, false, multierror.New(errs) } +func tryEngine(logf logger.Logf, linkMon *monitor.Mon, name string) (e wgengine.Engine, isUserspace bool, err error) { + conf := wgengine.Config{ + ListenPort: args.port, + LinkMonitor: linkMon, + } + isUserspace = name == "userspace-networking" + if isUserspace { + conf.TUN = tstun.NewFakeTUN() + conf.RouterGen = router.NewFake + } else { + dev, err := tun.New(logf, name) + if err != nil { + return nil, false, err + } + conf.TUN = dev + } + e, err = wgengine.NewUserspaceEngine(logf, conf) + if err != nil { + return nil, isUserspace, err + } + return e, isUserspace, nil +} + func newDebugMux() *http.ServeMux { mux := http.NewServeMux() mux.HandleFunc("/debug/pprof/", pprof.Index) diff --git a/cmd/tailscaled/tailscaled_windows.go b/cmd/tailscaled/tailscaled_windows.go index cf97bef4a..fa7f5e760 100644 --- a/cmd/tailscaled/tailscaled_windows.go +++ b/cmd/tailscaled/tailscaled_windows.go @@ -30,6 +30,7 @@ import ( "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" "tailscale.com/ipn/ipnserver" "tailscale.com/logpolicy" + "tailscale.com/net/tun" "tailscale.com/tempfork/wireguard-windows/firewall" "tailscale.com/types/logger" "tailscale.com/version" @@ -159,11 +160,16 @@ func startIPNServer(ctx context.Context, logid string) error { var err error getEngine := func() (wgengine.Engine, error) { + dev, err := tun.New(logf, "Tailscale") + if err != nil { + return nil, err + } eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{ - TUNName: "Tailscale", + TUN: dev, ListenPort: 41641, }) if err != nil { + dev.Close() return nil, err } return wgengine.NewWatchdog(eng), nil diff --git a/wgengine/ifstatus_noop.go b/net/tun/ifstatus_noop.go similarity index 96% rename from wgengine/ifstatus_noop.go rename to net/tun/ifstatus_noop.go index 7564d67ec..e40c8c15e 100644 --- a/wgengine/ifstatus_noop.go +++ b/net/tun/ifstatus_noop.go @@ -4,7 +4,7 @@ // +build !windows -package wgengine +package tun import ( "time" diff --git a/wgengine/ifstatus_windows.go b/net/tun/ifstatus_windows.go similarity index 99% rename from wgengine/ifstatus_windows.go rename to net/tun/ifstatus_windows.go index 840b6cf39..d5b05ff40 100644 --- a/wgengine/ifstatus_windows.go +++ b/net/tun/ifstatus_windows.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package wgengine +package tun import ( "fmt" diff --git a/net/tun/tun.go b/net/tun/tun.go new file mode 100644 index 000000000..61f10ad76 --- /dev/null +++ b/net/tun/tun.go @@ -0,0 +1,128 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package tun creates a tuntap device, working around OS-specific +// quirks if necessary. +package tun + +import ( + "bytes" + "os" + "os/exec" + "runtime" + "time" + + "github.com/tailscale/wireguard-go/tun" + "tailscale.com/types/logger" + "tailscale.com/version/distro" +) + +// minimalMTU is the MTU we set on tailscale's TUN +// interface. wireguard-go defaults to 1420 bytes, which only works if +// the "outer" MTU is 1500 bytes. This breaks on DSL connections +// (typically 1492 MTU) and on GCE (1460 MTU?!). +// +// 1280 is the smallest MTU allowed for IPv6, which is a sensible +// "probably works everywhere" setting until we develop proper PMTU +// discovery. +const minimalMTU = 1280 + +func New(logf logger.Logf, tunName string) (tun.Device, error) { + dev, err := tun.CreateTUN(tunName, minimalMTU) + if err != nil { + return nil, err + } + if err := waitInterfaceUp(dev, 90*time.Second, logf); err != nil { + return nil, err + } + return dev, nil +} + +// 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) { + switch runtime.GOOS { + case "linux": + diagnoseLinuxTUNFailure(tunName, logf) + case "darwin": + diagnoseDarwinTUNFailure(tunName, logf) + default: + logf("no TUN failure diagnostics for OS %q", runtime.GOOS) + } +} + +func diagnoseDarwinTUNFailure(tunName string, logf logger.Logf) { + 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'") + } + if tunName != "utun" { + logf("failed to create TUN device %q; try using tun device \"utun\" instead for automatic selection", tunName) + } +} + +func diagnoseLinuxTUNFailure(tunName string, logf logger.Logf) { + kernel, err := exec.Command("uname", "-r").Output() + kernel = bytes.TrimSpace(kernel) + if err != nil { + logf("no TUN, and failed to look up kernel version: %v", err) + return + } + logf("Linux kernel version: %s", kernel) + + modprobeOut, err := exec.Command("/sbin/modprobe", "tun").CombinedOutput() + if err == nil { + logf("'modprobe tun' successful") + // Either tun is currently loaded, or it's statically + // compiled into the kernel (which modprobe checks + // with /lib/modules/$(uname -r)/modules.builtin) + // + // So if there's a problem at this point, it's + // probably because /dev/net/tun doesn't exist. + const dev = "/dev/net/tun" + if fi, err := os.Stat(dev); err != nil { + logf("tun module loaded in kernel, but %s does not exist", dev) + } else { + logf("%s: %v", dev, fi.Mode()) + } + + // We failed to find why it failed. Just let our + // caller report the error it got from wireguard-go. + return + } + logf("is CONFIG_TUN enabled in your kernel? `modprobe tun` failed with: %s", modprobeOut) + + switch distro.Get() { + case distro.Debian: + dpkgOut, err := exec.Command("dpkg", "-S", "kernel/drivers/net/tun.ko").CombinedOutput() + if len(bytes.TrimSpace(dpkgOut)) == 0 || err != nil { + logf("tun module not loaded nor found on disk") + return + } + if !bytes.Contains(dpkgOut, kernel) { + logf("kernel/drivers/net/tun.ko found on disk, but not for current kernel; are you in middle of a system update and haven't rebooted? found: %s", dpkgOut) + } + case distro.Arch: + findOut, err := exec.Command("find", "/lib/modules/", "-path", "*/net/tun.ko*").CombinedOutput() + if len(bytes.TrimSpace(findOut)) == 0 || err != nil { + logf("tun module not loaded nor found on disk") + return + } + if !bytes.Contains(findOut, kernel) { + logf("kernel/drivers/net/tun.ko found on disk, but not for current kernel; are you in middle of a system update and haven't rebooted? found: %s", findOut) + } + case distro.OpenWrt: + out, err := exec.Command("opkg", "list-installed").CombinedOutput() + if err != nil { + logf("error querying OpenWrt installed packages: %s", out) + return + } + for _, pkg := range []string{"kmod-tun", "ca-bundle"} { + if !bytes.Contains(out, []byte(pkg+" - ")) { + logf("Missing required package %s; run: opkg install %s", pkg, pkg) + } + } + } +} diff --git a/wgengine/userspace.go b/wgengine/userspace.go index f0ca94f48..0a524e868 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -14,7 +14,6 @@ import ( "io" "net" "os" - "os/exec" "runtime" "strconv" "strings" @@ -43,7 +42,6 @@ import ( "tailscale.com/types/netmap" "tailscale.com/types/wgkey" "tailscale.com/version" - "tailscale.com/version/distro" "tailscale.com/wgengine/filter" "tailscale.com/wgengine/magicsock" "tailscale.com/wgengine/monitor" @@ -53,16 +51,6 @@ import ( "tailscale.com/wgengine/wglog" ) -// minimalMTU is the MTU we set on tailscale's TUN -// interface. wireguard-go defaults to 1420 bytes, which only works if -// the "outer" MTU is 1500 bytes. This breaks on DSL connections -// (typically 1492 MTU) and on GCE (1460 MTU?!). -// -// 1280 is the smallest MTU allowed for IPv6, which is a sensible -// "probably works everywhere" setting until we develop proper PMTU -// discovery. -const minimalMTU = 1280 - const magicDNSPort = 53 var magicDNSIP = netaddr.IPv4(100, 100, 100, 100) @@ -150,13 +138,8 @@ type RouterGen func(logf logger.Logf, tundev tun.Device) (router.Router, error) // Config is the engine configuration. type Config struct { // TUN is the TUN device used by the engine. - // Exactly one of either TUN or TUNName must be specified. TUN tun.Device - // TUNName is the TUN device to create. - // Exactly one of either TUN or TUNName must be specified. - TUNName string - // RouterGen is the function used to instantiate the router. // If nil, wgengine/router.New is used. RouterGen RouterGen @@ -186,42 +169,18 @@ func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16) (Engine, error) // NewUserspaceEngine creates the named tun device and returns a // Tailscale Engine running on it. -func NewUserspaceEngine(logf logger.Logf, conf Config) (Engine, error) { - if conf.TUN != nil && conf.TUNName != "" { - return nil, errors.New("TUN and TUNName are mutually exclusive") - } - if conf.TUN == nil && conf.TUNName == "" { - return nil, errors.New("either TUN or TUNName are required") - } - tunDev := conf.TUN - var err error - if tunName := conf.TUNName; tunName != "" { - logf("Starting userspace wireguard engine with tun device %q", tunName) - tunDev, err = tun.CreateTUN(tunName, minimalMTU) - if err != nil { - diagnoseTUNFailure(tunName, logf) - logf("CreateTUN: %v", err) - return nil, err - } - logf("CreateTUN ok.") - - if err := waitInterfaceUp(tunDev, 90*time.Second, logf); err != nil { - return nil, err - } +func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error) { + if conf.TUN == nil { + return nil, errors.New("TUN is required") } - if conf.RouterGen == nil { conf.RouterGen = router.New } - return newUserspaceEngine(logf, tunDev, conf) -} - -func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_ Engine, reterr error) { var closePool closeOnErrorPool defer closePool.closeAllIfError(&reterr) - tsTUNDev := tstun.WrapTUN(logf, rawTUNDev) + tsTUNDev := tstun.WrapTUN(logf, conf.TUN) closePool.add(tsTUNDev) e := &userspaceEngine{ @@ -1554,94 +1513,6 @@ func (e *userspaceEngine) peerForIP(ip netaddr.IP) (n *tailcfg.Node, err error) return nil, fmt.Errorf("node %q found, but not using its %v route", bestInNM.ComputedNameWithHost, bestInNMPrefix) } -// diagnoseTUNFailure is called if tun.CreateTUN fails, to poke around -// the system and log some diagnostic info that might help debug why -// TUN failed. Because TUN's already failed and things the program's -// about to end, we might as well log a lot. -func diagnoseTUNFailure(tunName string, logf logger.Logf) { - switch runtime.GOOS { - case "linux": - diagnoseLinuxTUNFailure(tunName, logf) - case "darwin": - diagnoseDarwinTUNFailure(tunName, logf) - default: - logf("no TUN failure diagnostics for OS %q", runtime.GOOS) - } -} - -func diagnoseDarwinTUNFailure(tunName string, logf logger.Logf) { - 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'") - } - if tunName != "utun" { - logf("failed to create TUN device %q; try using tun device \"utun\" instead for automatic selection", tunName) - } -} - -func diagnoseLinuxTUNFailure(tunName string, logf logger.Logf) { - kernel, err := exec.Command("uname", "-r").Output() - kernel = bytes.TrimSpace(kernel) - if err != nil { - logf("no TUN, and failed to look up kernel version: %v", err) - return - } - logf("Linux kernel version: %s", kernel) - - modprobeOut, err := exec.Command("/sbin/modprobe", "tun").CombinedOutput() - if err == nil { - logf("'modprobe tun' successful") - // Either tun is currently loaded, or it's statically - // compiled into the kernel (which modprobe checks - // with /lib/modules/$(uname -r)/modules.builtin) - // - // So if there's a problem at this point, it's - // probably because /dev/net/tun doesn't exist. - const dev = "/dev/net/tun" - if fi, err := os.Stat(dev); err != nil { - logf("tun module loaded in kernel, but %s does not exist", dev) - } else { - logf("%s: %v", dev, fi.Mode()) - } - - // We failed to find why it failed. Just let our - // caller report the error it got from wireguard-go. - return - } - logf("is CONFIG_TUN enabled in your kernel? `modprobe tun` failed with: %s", modprobeOut) - - switch distro.Get() { - case distro.Debian: - dpkgOut, err := exec.Command("dpkg", "-S", "kernel/drivers/net/tun.ko").CombinedOutput() - if len(bytes.TrimSpace(dpkgOut)) == 0 || err != nil { - logf("tun module not loaded nor found on disk") - return - } - if !bytes.Contains(dpkgOut, kernel) { - logf("kernel/drivers/net/tun.ko found on disk, but not for current kernel; are you in middle of a system update and haven't rebooted? found: %s", dpkgOut) - } - case distro.Arch: - findOut, err := exec.Command("find", "/lib/modules/", "-path", "*/net/tun.ko*").CombinedOutput() - if len(bytes.TrimSpace(findOut)) == 0 || err != nil { - logf("tun module not loaded nor found on disk") - return - } - if !bytes.Contains(findOut, kernel) { - logf("kernel/drivers/net/tun.ko found on disk, but not for current kernel; are you in middle of a system update and haven't rebooted? found: %s", findOut) - } - case distro.OpenWrt: - out, err := exec.Command("opkg", "list-installed").CombinedOutput() - if err != nil { - logf("error querying OpenWrt installed packages: %s", out) - return - } - for _, pkg := range []string{"kmod-tun", "ca-bundle"} { - if !bytes.Contains(out, []byte(pkg+" - ")) { - logf("Missing required package %s; run: opkg install %s", pkg, pkg) - } - } - } -} - type closeOnErrorPool []func() func (p *closeOnErrorPool) add(c io.Closer) { *p = append(*p, func() { c.Close() }) }