summaryrefslogtreecommitdiffhomepage
path: root/wgengine/userspace.go
diff options
context:
space:
mode:
Diffstat (limited to 'wgengine/userspace.go')
-rw-r--r--wgengine/userspace.go462
1 files changed, 262 insertions, 200 deletions
diff --git a/wgengine/userspace.go b/wgengine/userspace.go
index fccca8c8b..9202e4c7a 100644
--- a/wgengine/userspace.go
+++ b/wgengine/userspace.go
@@ -8,12 +8,12 @@ import (
"bufio"
"bytes"
"context"
+ crand "crypto/rand"
"errors"
"fmt"
"io"
"net"
"os"
- "os/exec"
"runtime"
"strconv"
"strings"
@@ -29,38 +29,28 @@ import (
"tailscale.com/health"
"tailscale.com/internal/deepprint"
"tailscale.com/ipn/ipnstate"
+ "tailscale.com/net/dns"
"tailscale.com/net/flowtrack"
"tailscale.com/net/interfaces"
"tailscale.com/net/packet"
"tailscale.com/net/tsaddr"
"tailscale.com/net/tshttpproxy"
+ "tailscale.com/net/tstun"
"tailscale.com/tailcfg"
+ "tailscale.com/types/ipproto"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"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"
"tailscale.com/wgengine/router"
- "tailscale.com/wgengine/tsdns"
- "tailscale.com/wgengine/tstun"
"tailscale.com/wgengine/wgcfg"
"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)
@@ -90,10 +80,10 @@ type userspaceEngine struct {
reqCh chan struct{}
waitCh chan struct{} // chan is closed when first Close call completes; contrast with closing bool
timeNow func() time.Time
- tundev *tstun.TUN
+ tundev *tstun.Wrapper
wgdev *device.Device
router router.Router
- resolver *tsdns.Resolver
+ resolver *dns.Resolver
magicConn *magicsock.Conn
linkMon *monitor.Mon
linkMonOwned bool // whether we created linkMon (and thus need to close it)
@@ -101,10 +91,10 @@ type userspaceEngine struct {
testMaybeReconfigHook func() // for tests; if non-nil, fires if maybeReconfigWireguardLocked called
- // localAddrs is the set of IP addresses assigned to the local
+ // isLocalAddr reports the whether an IP is assigned to the local
// tunnel interface. It's used to reflect local packets
// incorrectly sent to us.
- localAddrs atomic.Value // of map[netaddr.IP]bool
+ isLocalAddr atomic.Value // of func(netaddr.IP)bool
wgLock sync.Mutex // serializes all wgdev operations; see lock order comment below
lastCfgFull wgcfg.Config
@@ -117,8 +107,9 @@ type userspaceEngine struct {
destIPActivityFuncs map[netaddr.IP]func()
statusBufioReader *bufio.Reader // reusable for UAPI
- mu sync.Mutex // guards following; see lock order comment below
- closing bool // Close was called (even if we're still closing)
+ mu sync.Mutex // guards following; see lock order comment below
+ netMap *netmap.NetworkMap // or nil
+ closing bool // Close was called (even if we're still closing)
statusCallback StatusCallback
peerSequence []wgkey.Key
endpoints []string
@@ -126,36 +117,30 @@ type userspaceEngine struct {
pendOpen map[flowtrack.Tuple]*pendingOpenFlow // see pendopen.go
networkMapCallbacks map[*someHandle]NetworkMapCallback
tsIPByIPPort map[netaddr.IPPort]netaddr.IP // allows registration of IP:ports as belonging to a certain Tailscale IP for whois lookups
+ pongCallback map[[8]byte]func() // for TSMP pong responses
// Lock ordering: magicsock.Conn.mu, wgLock, then mu.
}
// InternalsGetter is implemented by Engines that can export their internals.
type InternalsGetter interface {
- GetInternals() (*tstun.TUN, *magicsock.Conn)
+ GetInternals() (*tstun.Wrapper, *magicsock.Conn)
}
-func (e *userspaceEngine) GetInternals() (*tstun.TUN, *magicsock.Conn) {
+func (e *userspaceEngine) GetInternals() (*tstun.Wrapper, *magicsock.Conn) {
return e.tundev, e.magicConn
}
-// RouterGen is the signature for a function that creates a
-// router.Router.
-type RouterGen func(logf logger.Logf, wgdev *device.Device, 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
+ // Tun is the device used by the Engine to exchange packets with
+ // the OS.
+ // If nil, a fake Device that does nothing is used.
+ Tun tun.Device
- // RouterGen is the function used to instantiate the router.
- // If nil, wgengine/router.New is used.
- RouterGen RouterGen
+ // Router interfaces the Engine to the OS network stack.
+ // If nil, a fake Router that does nothing is used.
+ Router router.Router
// LinkMonitor optionally provides an existing link monitor to re-use.
// If nil, a new link monitor is created.
@@ -165,59 +150,36 @@ type Config struct {
// If zero, a port is automatically selected.
ListenPort uint16
- // Fake determines whether this engine should automatically
- // reply to ICMP pings.
- Fake bool
+ // RespondToPing determines whether this engine should internally
+ // reply to ICMP pings, without involving the OS.
+ // Used in "fake" mode for development.
+ RespondToPing bool
}
func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16) (Engine, error) {
logf("Starting userspace wireguard engine (with fake TUN device)")
return NewUserspaceEngine(logf, Config{
- TUN: tstun.NewFakeTUN(),
- RouterGen: router.NewFake,
- ListenPort: listenPort,
- Fake: true,
+ ListenPort: listenPort,
+ RespondToPing: true,
})
}
// 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.")
+func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error) {
+ var closePool closeOnErrorPool
+ defer closePool.closeAllIfError(&reterr)
- if err := waitInterfaceUp(tunDev, 90*time.Second, logf); err != nil {
- return nil, err
- }
+ if conf.Tun == nil {
+ logf("[v1] using fake (no-op) tun device")
+ conf.Tun = tstun.NewFake()
}
-
- if conf.RouterGen == nil {
- conf.RouterGen = router.New
+ if conf.Router == nil {
+ logf("[v1] using fake (no-op) OS network configurator")
+ conf.Router = router.NewFake(logf)
}
- 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.Wrap(logf, conf.Tun)
closePool.add(tsTUNDev)
e := &userspaceEngine{
@@ -226,9 +188,10 @@ func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_
reqCh: make(chan struct{}, 1),
waitCh: make(chan struct{}),
tundev: tsTUNDev,
+ router: conf.Router,
pingers: make(map[wgkey.Key]*pinger),
}
- e.localAddrs.Store(map[netaddr.IP]bool{})
+ e.isLocalAddr.Store(genLocalAddrFunc(nil))
if conf.LinkMonitor != nil {
e.linkMon = conf.LinkMonitor
@@ -242,7 +205,7 @@ func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_
e.linkMonOwned = true
}
- e.resolver = tsdns.NewResolver(tsdns.ResolverConfig{
+ e.resolver = dns.NewResolver(dns.ResolverConfig{
Logf: logf,
Forward: true,
LinkMonitor: e.linkMon,
@@ -281,8 +244,7 @@ func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_
closePool.add(e.magicConn)
e.magicConn.SetNetworkUp(e.linkMon.InterfaceState().AnyInterfaceUp())
- // Respond to all pings only in fake mode.
- if conf.Fake {
+ if conf.RespondToPing {
e.tundev.PostFilterIn = echoRespondToAll
}
e.tundev.PreFilterOut = e.handleLocalPackets
@@ -300,7 +262,6 @@ func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_
e.wgLogger = wglog.NewLogger(logf)
opts := &device.DeviceOptions{
- Logger: e.wgLogger.DeviceLogger,
HandshakeDone: func(peerKey device.NoisePublicKey, peer *device.Peer, deviceAllowedIPs *device.AllowedIPs) {
// Send an unsolicited status event every time a
// handshake completes. This makes sure our UI can
@@ -349,20 +310,21 @@ func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_
SkipBindUpdate: true,
}
+ e.tundev.OnTSMPPongReceived = func(data [8]byte) {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ cb := e.pongCallback[data]
+ e.logf("wgengine: got TSMP pong %02x; cb=%v", data, cb != nil)
+ if cb != nil {
+ go cb()
+ }
+ }
+
// wgdev takes ownership of tundev, will close it when closed.
e.logf("Creating wireguard device...")
- e.wgdev = device.NewDevice(e.tundev, opts)
+ e.wgdev = device.NewDevice(e.tundev, e.wgLogger.DeviceLogger, opts)
closePool.addFunc(e.wgdev.Close)
- // Pass the underlying tun.(*NativeDevice) to the router:
- // routers do not Read or Write, but do access native interfaces.
- e.logf("Creating router...")
- e.router, err = conf.RouterGen(logf, e.wgdev, e.tundev.Unwrap())
- if err != nil {
- return nil, err
- }
- closePool.add(e.router)
-
go func() {
up := false
for event := range e.tundev.Events() {
@@ -411,7 +373,7 @@ func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_
}
// echoRespondToAll is an inbound post-filter responding to all echo requests.
-func echoRespondToAll(p *packet.Parsed, t *tstun.TUN) filter.Response {
+func echoRespondToAll(p *packet.Parsed, t *tstun.Wrapper) filter.Response {
if p.IsEchoRequest() {
header := p.ICMP4Header()
header.ToResponse()
@@ -432,44 +394,40 @@ func echoRespondToAll(p *packet.Parsed, t *tstun.TUN) filter.Response {
// stack, and intercepts any packets that should be handled by
// tailscaled directly. Other packets are allowed to proceed into the
// main ACL filter.
-func (e *userspaceEngine) handleLocalPackets(p *packet.Parsed, t *tstun.TUN) filter.Response {
+func (e *userspaceEngine) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper) filter.Response {
if verdict := e.handleDNS(p, t); verdict == filter.Drop {
// local DNS handled the packet.
return filter.Drop
}
- if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && e.isLocalAddr(p.Dst.IP) {
- // macOS NetworkExtension directs packets destined to the
- // tunnel's local IP address into the tunnel, instead of
- // looping back within the kernel network stack. We have to
- // notice that an outbound packet is actually destined for
- // ourselves, and loop it back into macOS.
- t.InjectInboundCopy(p.Buffer())
- return filter.Drop
+ if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
+ isLocalAddr, ok := e.isLocalAddr.Load().(func(netaddr.IP) bool)
+ if !ok {
+ e.logf("[unexpected] e.isLocalAddr was nil, can't check for loopback packet")
+ } else if isLocalAddr(p.Dst.IP) {
+ // macOS NetworkExtension directs packets destined to the
+ // tunnel's local IP address into the tunnel, instead of
+ // looping back within the kernel network stack. We have to
+ // notice that an outbound packet is actually destined for
+ // ourselves, and loop it back into macOS.
+ t.InjectInboundCopy(p.Buffer())
+ return filter.Drop
+ }
}
return filter.Accept
}
-func (e *userspaceEngine) isLocalAddr(ip netaddr.IP) bool {
- localAddrs, ok := e.localAddrs.Load().(map[netaddr.IP]bool)
- if !ok {
- e.logf("[unexpected] e.localAddrs was nil, can't check for loopback packet")
- return false
- }
- return localAddrs[ip]
-}
-
// handleDNS is an outbound pre-filter resolving Tailscale domains.
-func (e *userspaceEngine) handleDNS(p *packet.Parsed, t *tstun.TUN) filter.Response {
- if p.Dst.IP == magicDNSIP && p.Dst.Port == magicDNSPort && p.IPProto == packet.UDP {
- request := tsdns.Packet{
+func (e *userspaceEngine) handleDNS(p *packet.Parsed, t *tstun.Wrapper) filter.Response {
+ if p.Dst.IP == magicDNSIP && p.Dst.Port == magicDNSPort && p.IPProto == ipproto.UDP {
+ request := dns.Packet{
Payload: append([]byte(nil), p.Payload()...),
Addr: netaddr.IPPort{IP: p.Src.IP, Port: p.Src.Port},
}
err := e.resolver.EnqueueRequest(request)
if err != nil {
- e.logf("tsdns: enqueue: %v", err)
+ e.logf("dns: enqueue: %v", err)
}
return filter.Drop
}
@@ -480,11 +438,11 @@ func (e *userspaceEngine) handleDNS(p *packet.Parsed, t *tstun.TUN) filter.Respo
func (e *userspaceEngine) pollResolver() {
for {
resp, err := e.resolver.NextResponse()
- if err == tsdns.ErrClosed {
+ if err == dns.ErrClosed {
return
}
if err != nil {
- e.logf("tsdns: error: %v", err)
+ e.logf("dns: error: %v", err)
continue
}
@@ -498,7 +456,7 @@ func (e *userspaceEngine) pollResolver() {
}
hlen := h.Len()
- // TODO(dmytro): avoid this allocation without importing tstun quirks into tsdns.
+ // TODO(dmytro): avoid this allocation without importing tstun quirks into dns.
const offset = tstun.PacketStartOffset
buf := make([]byte, offset+hlen+len(resp.Payload))
copy(buf[offset+hlen:], resp.Payload)
@@ -925,16 +883,34 @@ func (e *userspaceEngine) updateActivityMapsLocked(trackDisco []tailcfg.DiscoKey
e.tundev.SetDestIPActivityFuncs(e.destIPActivityFuncs)
}
+// genLocalAddrFunc returns a func that reports whether an IP is in addrs.
+// addrs is assumed to be all /32 or /128 entries.
+func genLocalAddrFunc(addrs []netaddr.IPPrefix) func(netaddr.IP) bool {
+ // Specialize the three common cases: no address, just IPv4
+ // (or just IPv6), and both IPv4 and IPv6.
+ if len(addrs) == 0 {
+ return func(netaddr.IP) bool { return false }
+ }
+ if len(addrs) == 1 {
+ return func(t netaddr.IP) bool { return t == addrs[0].IP }
+ }
+ if len(addrs) == 2 {
+ return func(t netaddr.IP) bool { return t == addrs[0].IP || t == addrs[1].IP }
+ }
+ // Otherwise, the general implementation: a map lookup.
+ m := map[netaddr.IP]bool{}
+ for _, a := range addrs {
+ m[a.IP] = true
+ }
+ return func(t netaddr.IP) bool { return m[t] }
+}
+
func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config) error {
if routerCfg == nil {
panic("routerCfg must not be nil")
}
- localAddrs := map[netaddr.IP]bool{}
- for _, addr := range routerCfg.LocalAddrs {
- localAddrs[addr.IP] = true
- }
- e.localAddrs.Store(localAddrs)
+ e.isLocalAddr.Store(genLocalAddrFunc(routerCfg.LocalAddrs))
e.wgLock.Lock()
defer e.wgLock.Unlock()
@@ -1034,7 +1010,7 @@ func (e *userspaceEngine) SetFilter(filt *filter.Filter) {
e.tundev.SetFilter(filt)
}
-func (e *userspaceEngine) SetDNSMap(dm *tsdns.Map) {
+func (e *userspaceEngine) SetDNSMap(dm *dns.Map) {
e.resolver.SetMap(dm)
}
@@ -1270,6 +1246,7 @@ func (e *userspaceEngine) linkChange(changed bool, cur *interfaces.State) {
e.logf("[v1] LinkChange: minor")
}
+ health.SetAnyInterfaceUp(up)
e.magicConn.SetNetworkUp(up)
why := "link-change-minor"
@@ -1306,6 +1283,7 @@ func (e *userspaceEngine) SetDERPMap(dm *tailcfg.DERPMap) {
func (e *userspaceEngine) SetNetworkMap(nm *netmap.NetworkMap) {
e.magicConn.SetNetworkMap(nm)
e.mu.Lock()
+ e.netMap = nm
callbacks := make([]NetworkMapCallback, 0, 4)
for _, fn := range e.networkMapCallbacks {
callbacks = append(callbacks, fn)
@@ -1338,8 +1316,107 @@ func (e *userspaceEngine) UpdateStatus(sb *ipnstate.StatusBuilder) {
e.magicConn.UpdateStatus(sb)
}
-func (e *userspaceEngine) Ping(ip netaddr.IP, cb func(*ipnstate.PingResult)) {
- e.magicConn.Ping(ip, cb)
+func (e *userspaceEngine) Ping(ip netaddr.IP, useTSMP bool, cb func(*ipnstate.PingResult)) {
+ res := &ipnstate.PingResult{IP: ip.String()}
+ peer, err := e.peerForIP(ip)
+ if err != nil {
+ e.logf("ping(%v): %v", ip, err)
+ res.Err = err.Error()
+ cb(res)
+ return
+ }
+ if peer == nil {
+ e.logf("ping(%v): no matching peer", ip)
+ res.Err = "no matching peer"
+ cb(res)
+ return
+ }
+ pingType := "disco"
+ if useTSMP {
+ pingType = "TSMP"
+ }
+ e.logf("ping(%v): sending %v ping to %v %v ...", ip, pingType, peer.Key.ShortString(), peer.ComputedName)
+ if useTSMP {
+ e.sendTSMPPing(ip, peer, res, cb)
+ } else {
+ e.magicConn.Ping(peer, res, cb)
+ }
+}
+
+func (e *userspaceEngine) mySelfIPMatchingFamily(dst netaddr.IP) (src netaddr.IP, err error) {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ if e.netMap == nil {
+ return netaddr.IP{}, errors.New("no netmap")
+ }
+ for _, a := range e.netMap.Addresses {
+ if a.IsSingleIP() && a.IP.BitLen() == dst.BitLen() {
+ return a.IP, nil
+ }
+ }
+ if len(e.netMap.Addresses) == 0 {
+ return netaddr.IP{}, errors.New("no self address in netmap")
+ }
+ return netaddr.IP{}, errors.New("no self address in netmap matching address family")
+}
+
+func (e *userspaceEngine) sendTSMPPing(ip netaddr.IP, peer *tailcfg.Node, res *ipnstate.PingResult, cb func(*ipnstate.PingResult)) {
+ srcIP, err := e.mySelfIPMatchingFamily(ip)
+ if err != nil {
+ res.Err = err.Error()
+ cb(res)
+ return
+ }
+ var iph packet.Header
+ if srcIP.Is4() {
+ iph = packet.IP4Header{
+ IPProto: ipproto.TSMP,
+ Src: srcIP,
+ Dst: ip,
+ }
+ } else {
+ iph = packet.IP6Header{
+ IPProto: ipproto.TSMP,
+ Src: srcIP,
+ Dst: ip,
+ }
+ }
+
+ var data [8]byte
+ crand.Read(data[:])
+
+ expireTimer := time.AfterFunc(10*time.Second, func() {
+ e.setTSMPPongCallback(data, nil)
+ })
+ t0 := time.Now()
+ e.setTSMPPongCallback(data, func() {
+ expireTimer.Stop()
+ d := time.Since(t0)
+ res.LatencySeconds = d.Seconds()
+ res.NodeIP = ip.String()
+ res.NodeName = peer.ComputedName
+ cb(res)
+ })
+
+ var tsmpPayload [9]byte
+ tsmpPayload[0] = byte(packet.TSMPTypePing)
+ copy(tsmpPayload[1:], data[:])
+
+ tsmpPing := packet.Generate(iph, tsmpPayload[:])
+ e.tundev.InjectOutbound(tsmpPing)
+}
+
+func (e *userspaceEngine) setTSMPPongCallback(data [8]byte, cb func()) {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ if e.pongCallback == nil {
+ e.pongCallback = map[[8]byte]func(){}
+ }
+ if cb == nil {
+ delete(e.pongCallback, data)
+ } else {
+ e.pongCallback[data] = cb
+ }
}
func (e *userspaceEngine) RegisterIPPortIdentity(ipport netaddr.IPPort, tsIP netaddr.IP) {
@@ -1367,92 +1444,77 @@ func (e *userspaceEngine) WhoIsIPPort(ipport netaddr.IPPort) (tsIP netaddr.IP, o
return tsIP, ok
}
-// 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)
+// peerForIP returns the Node in the wireguard config
+// that's responsible for handling the given IP address.
+//
+// If none is found in the wireguard config but one is found in
+// the netmap, it's described in an error.
+//
+// If none is found in either place, (nil, nil) is returned.
+//
+// peerForIP acquires both e.mu and e.wgLock, but neither at the same
+// time.
+func (e *userspaceEngine) peerForIP(ip netaddr.IP) (n *tailcfg.Node, err error) {
+ e.mu.Lock()
+ nm := e.netMap
+ e.mu.Unlock()
+ if nm == nil {
+ return nil, errors.New("no network map")
}
-}
-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)
+ // Check for exact matches before looking for subnet matches.
+ var bestInNMPrefix netaddr.IPPrefix
+ var bestInNM *tailcfg.Node
+ for _, p := range nm.Peers {
+ for _, a := range p.Addresses {
+ if a.IP == ip && a.IsSingleIP() && tsaddr.IsTailscaleIP(ip) {
+ return p, nil
+ }
+ }
+ for _, cidr := range p.AllowedIPs {
+ if !cidr.Contains(ip) {
+ continue
+ }
+ if bestInNMPrefix.IsZero() || cidr.Bits > bestInNMPrefix.Bits {
+ bestInNMPrefix = cidr
+ bestInNM = p
+ }
+ }
}
-}
-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)
+ e.wgLock.Lock()
+ defer e.wgLock.Unlock()
- 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())
+ // TODO(bradfitz): this is O(n peers). Add ART to netaddr?
+ var best netaddr.IPPrefix
+ var bestKey tailcfg.NodeKey
+ for _, p := range e.lastCfgFull.Peers {
+ for _, cidr := range p.AllowedIPs {
+ if !cidr.Contains(ip) {
+ continue
+ }
+ if best.IsZero() || cidr.Bits > best.Bits {
+ best = cidr
+ bestKey = tailcfg.NodeKey(p.PublicKey)
+ }
}
-
- // 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)
+ // And another pass. Probably better than allocating a map per peerForIP
+ // call. But TODO(bradfitz): add a lookup map to netmap.NetworkMap.
+ if !bestKey.IsZero() {
+ for _, p := range nm.Peers {
+ if p.Key == bestKey {
+ return p, nil
}
}
}
+ if bestInNM == nil {
+ return nil, nil
+ }
+ if bestInNMPrefix.Bits == 0 {
+ return nil, errors.New("exit node found but not enabled")
+ }
+ return nil, fmt.Errorf("node %q found, but not using its %v route", bestInNM.ComputedNameWithHost, bestInNMPrefix)
}
type closeOnErrorPool []func()