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:
David Anderson
2021-03-26 21:03:21 -07:00
committed by Dave Anderson
parent 0a84aaca0a
commit 44d9929208
7 changed files with 167 additions and 148 deletions

19
net/tun/ifstatus_noop.go Normal file
View File

@@ -0,0 +1,19 @@
// 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 tun
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
}

111
net/tun/ifstatus_windows.go Normal file
View File

@@ -0,0 +1,111 @@
// 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
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")
}
}
}

128
net/tun/tun.go Normal file
View File

@@ -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)
}
}
}
}