mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-13 22:47:30 +00:00
wgengine: remove Config.TUNName, require caller to create device.
Also factors out device creation and associated OS workarounds to net/tun. Signed-off-by: David Anderson <danderson@tailscale.com>
This commit is contained in:

committed by
Dave Anderson

parent
0a84aaca0a
commit
44d9929208
@@ -1,19 +0,0 @@
|
||||
// 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.
|
||||
|
||||
// +build !windows
|
||||
|
||||
package wgengine
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/tailscale/wireguard-go/tun"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
// Dummy implementation that does nothing.
|
||||
func waitInterfaceUp(iface tun.Device, timeout time.Duration, logf logger.Logf) error {
|
||||
return nil
|
||||
}
|
@@ -1,111 +0,0 @@
|
||||
// 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 wgengine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/tailscale/wireguard-go/tun"
|
||||
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
// ifaceWatcher waits for an interface to be up.
|
||||
type ifaceWatcher struct {
|
||||
logf logger.Logf
|
||||
luid winipcfg.LUID
|
||||
|
||||
mu sync.Mutex // guards following
|
||||
done bool
|
||||
sig chan bool
|
||||
}
|
||||
|
||||
// callback is the callback we register with Windows to call when IP interface changes.
|
||||
func (iw *ifaceWatcher) callback(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) {
|
||||
// Probably should check only when MibParameterNotification, but just in case included MibAddInstance also.
|
||||
if notificationType == winipcfg.MibParameterNotification || notificationType == winipcfg.MibAddInstance {
|
||||
// Out of paranoia, start a goroutine to finish our work, to return to Windows out of this callback.
|
||||
go iw.isUp()
|
||||
}
|
||||
}
|
||||
|
||||
func (iw *ifaceWatcher) isUp() bool {
|
||||
iw.mu.Lock()
|
||||
defer iw.mu.Unlock()
|
||||
|
||||
if iw.done {
|
||||
// We already know that it's up
|
||||
return true
|
||||
}
|
||||
|
||||
if iw.getOperStatus() != winipcfg.IfOperStatusUp {
|
||||
return false
|
||||
}
|
||||
|
||||
iw.done = true
|
||||
iw.sig <- true
|
||||
return true
|
||||
}
|
||||
|
||||
func (iw *ifaceWatcher) getOperStatus() winipcfg.IfOperStatus {
|
||||
ifc, err := iw.luid.Interface()
|
||||
if err != nil {
|
||||
iw.logf("iw.luid.Interface error: %v", err)
|
||||
return 0
|
||||
}
|
||||
return ifc.OperStatus
|
||||
}
|
||||
|
||||
func waitInterfaceUp(iface tun.Device, timeout time.Duration, logf logger.Logf) error {
|
||||
iw := &ifaceWatcher{
|
||||
luid: winipcfg.LUID(iface.(*tun.NativeTun).LUID()),
|
||||
logf: logger.WithPrefix(logf, "waitInterfaceUp: "),
|
||||
}
|
||||
|
||||
// Just in case check the status first
|
||||
if iw.getOperStatus() == winipcfg.IfOperStatusUp {
|
||||
iw.logf("TUN interface already up; no need to wait")
|
||||
return nil
|
||||
}
|
||||
|
||||
iw.sig = make(chan bool, 1)
|
||||
cb, err := winipcfg.RegisterInterfaceChangeCallback(iw.callback)
|
||||
if err != nil {
|
||||
iw.logf("RegisterInterfaceChangeCallback error: %v", err)
|
||||
return err
|
||||
}
|
||||
defer cb.Unregister()
|
||||
|
||||
t0 := time.Now()
|
||||
expires := t0.Add(timeout)
|
||||
ticker := time.NewTicker(10 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
iw.logf("waiting for TUN interface to come up...")
|
||||
|
||||
select {
|
||||
case <-iw.sig:
|
||||
iw.logf("TUN interface is up after %v", time.Since(t0))
|
||||
return nil
|
||||
case <-ticker.C:
|
||||
break
|
||||
}
|
||||
|
||||
if iw.isUp() {
|
||||
// Very unlikely to happen - either NotifyIpInterfaceChange doesn't work
|
||||
// or it came up in the same moment as tick. Indicate this in the log message.
|
||||
iw.logf("TUN interface is up after %v (on poll, without notification)", time.Since(t0))
|
||||
return nil
|
||||
}
|
||||
|
||||
if expires.Before(time.Now()) {
|
||||
iw.logf("timeout waiting %v for TUN interface to come up", timeout)
|
||||
return fmt.Errorf("timeout waiting for TUN interface to come up")
|
||||
}
|
||||
}
|
||||
}
|
@@ -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")
|
||||
func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error) {
|
||||
if conf.TUN == nil {
|
||||
return nil, errors.New("TUN is required")
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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() }) }
|
||||
|
Reference in New Issue
Block a user