diff options
| author | Aleksandar Pesic <peske.nis@gmail.com> | 2021-02-17 15:29:21 +0100 |
|---|---|---|
| committer | Aleksandar Pesic <peske.nis@gmail.com> | 2021-02-17 15:29:21 +0100 |
| commit | 419edfca05a253a44161915bd96c1cc1df5fa163 (patch) | |
| tree | 86de633a8529d6cc9f275d567d529aecfe4a037b /wgengine | |
| parent | 3e2d69a26c407093523edf352744c689ae155d52 (diff) | |
| parent | 7038c09bc91c7f65ff33afb777187ef9acca214c (diff) | |
| download | tailscale-peske/elnotfound.tar.xz tailscale-peske/elnotfound.zip | |
Merge branch 'main' into peske/elnotfoundpeske/elnotfound
Diffstat (limited to 'wgengine')
| -rw-r--r-- | wgengine/magicsock/legacy.go | 78 | ||||
| -rw-r--r-- | wgengine/magicsock/magicsock.go | 288 | ||||
| -rw-r--r-- | wgengine/magicsock/magicsock_test.go | 374 | ||||
| -rw-r--r-- | wgengine/monitor/monitor_darwin_tailscaled.go | 72 | ||||
| -rw-r--r-- | wgengine/monitor/monitor_unsupported.go | 2 | ||||
| -rw-r--r-- | wgengine/netstack/netstack.go | 4 | ||||
| -rw-r--r-- | wgengine/pendopen.go | 32 | ||||
| -rw-r--r-- | wgengine/router/router.go | 30 | ||||
| -rw-r--r-- | wgengine/router/router_linux.go | 45 | ||||
| -rw-r--r-- | wgengine/router/router_linux_test.go | 18 | ||||
| -rw-r--r-- | wgengine/router/router_userspace_bsd.go | 111 | ||||
| -rw-r--r-- | wgengine/router/router_windows.go | 44 | ||||
| -rw-r--r-- | wgengine/tsdns/tsdns_server_test.go | 11 | ||||
| -rw-r--r-- | wgengine/tsdns/tsdns_test.go | 23 | ||||
| -rw-r--r-- | wgengine/tstun/tun.go | 22 | ||||
| -rw-r--r-- | wgengine/userspace.go | 69 | ||||
| -rw-r--r-- | wgengine/watchdog.go | 4 | ||||
| -rw-r--r-- | wgengine/wgcfg/config.go | 5 | ||||
| -rw-r--r-- | wgengine/wgcfg/nmcfg/nmcfg.go | 127 | ||||
| -rw-r--r-- | wgengine/wgengine.go | 21 | ||||
| -rw-r--r-- | wgengine/wglog/wglog.go | 6 | ||||
| -rw-r--r-- | wgengine/wglog/wglog_test.go | 3 | ||||
| -rw-r--r-- | wgengine/winnet/winnet.go | 5 |
23 files changed, 981 insertions, 413 deletions
diff --git a/wgengine/magicsock/legacy.go b/wgengine/magicsock/legacy.go index 7620cc1ce..eb4ce9da6 100644 --- a/wgengine/magicsock/legacy.go +++ b/wgengine/magicsock/legacy.go @@ -53,7 +53,6 @@ func (c *Conn) createLegacyEndpointLocked(pk key.Public, addrs string) (conn.End return nil, fmt.Errorf("bogus address %q", ep) } a.ipPorts = append(a.ipPorts, ipp) - a.addrs = append(a.addrs, *ipp.UDPAddr()) } } @@ -84,14 +83,14 @@ func (c *Conn) createLegacyEndpointLocked(pk key.Public, addrs string) (conn.End return a, nil } -func (c *Conn) findLegacyEndpointLocked(ipp netaddr.IPPort, addr *net.UDPAddr, packet []byte) conn.Endpoint { +func (c *Conn) findLegacyEndpointLocked(ipp netaddr.IPPort, packet []byte) conn.Endpoint { if c.disableLegacy { return nil } // Pre-disco: look up their addrSet. if as, ok := c.addrsByUDP[ipp]; ok { - as.updateDst(addr) + as.updateDst(ipp) return as } @@ -100,7 +99,7 @@ func (c *Conn) findLegacyEndpointLocked(ipp netaddr.IPPort, addr *net.UDPAddr, p // know. If this is a handshake packet, we can try to identify the // peer in question. if as := c.peerFromPacketLocked(packet); as != nil { - as.updateDst(addr) + as.updateDst(ipp) return as } @@ -268,14 +267,6 @@ func (as *addrSet) appendDests(dsts []netaddr.IPPort, b []byte) (_ []netaddr.IPP as.lastSend = now - // Some internal invariant checks. - if len(as.addrs) != len(as.ipPorts) { - panic(fmt.Sprintf("lena %d != leni %d", len(as.addrs), len(as.ipPorts))) - } - if n1, n2 := as.roamAddr != nil, as.roamAddrStd != nil; n1 != n2 { - panic(fmt.Sprintf("roamnil %v != roamstdnil %v", n1, n2)) - } - // Spray logic. // // After exchanging a handshake with a peer, we send some outbound @@ -320,8 +311,8 @@ func (as *addrSet) appendDests(dsts []netaddr.IPPort, b []byte) (_ []netaddr.IPP // roamAddr should be special like this. dsts = append(dsts, *as.roamAddr) case as.curAddr != -1: - if as.curAddr >= len(as.addrs) { - as.Logf("[unexpected] magicsock bug: as.curAddr >= len(as.addrs): %d >= %d", as.curAddr, len(as.addrs)) + if as.curAddr >= len(as.ipPorts) { + as.Logf("[unexpected] magicsock bug: as.curAddr >= len(as.ipPorts): %d >= %d", as.curAddr, len(as.ipPorts)) break } // No roaming addr, but we've seen packets from a known peer @@ -352,15 +343,14 @@ func (as *addrSet) appendDests(dsts []netaddr.IPPort, b []byte) (_ []netaddr.IPP type addrSet struct { publicKey key.Public // peer public key used for DERP communication - // addrs is an ordered priority list provided by wgengine, + // ipPorts is an ordered priority list provided by wgengine, // sorted from expensive+slow+reliable at the begnining to // fast+cheap at the end. More concretely, it's typically: // // [DERP fakeip:node, Global IP:port, LAN ip:port] // // But there could be multiple or none of each. - addrs []net.UDPAddr - ipPorts []netaddr.IPPort // same as addrs, in different form + ipPorts []netaddr.IPPort // clock, if non-nil, is used in tests instead of time.Now. clock func() time.Time @@ -376,8 +366,7 @@ type addrSet struct { // this should hopefully never be used (or at least used // rarely) in the case that all the components of Tailscale // are correctly learning/sharing the network map details. - roamAddr *netaddr.IPPort - roamAddrStd *net.UDPAddr + roamAddr *netaddr.IPPort // curAddr is an index into addrs of the highest-priority // address a valid packet has been received from so far. @@ -400,9 +389,9 @@ type addrSet struct { // derpID returns this addrSet's home DERP node, or 0 if none is found. func (as *addrSet) derpID() int { - for _, ua := range as.addrs { - if ua.IP.Equal(derpMagicIP) { - return ua.Port + for _, ua := range as.ipPorts { + if ua.IP == derpMagicIPAddr { + return int(ua.Port) } } return 0 @@ -424,7 +413,7 @@ func (a *addrSet) dst() netaddr.IPPort { if a.roamAddr != nil { return *a.roamAddr } - if len(a.addrs) == 0 { + if len(a.ipPorts) == 0 { return noAddr } i := a.curAddr @@ -439,7 +428,7 @@ func (a *addrSet) DstToBytes() []byte { } func (a *addrSet) DstToString() string { var addrs []string - for _, addr := range a.addrs { + for _, addr := range a.ipPorts { addrs = append(addrs, addr.String()) } @@ -459,8 +448,8 @@ func (a *addrSet) ClearSrc() {} // updateDst records receipt of a packet from new. This is used to // potentially update the transmit address used for this addrSet. -func (a *addrSet) updateDst(new *net.UDPAddr) error { - if new.IP.Equal(derpMagicIP) { +func (a *addrSet) updateDst(new netaddr.IPPort) error { + if new.IP == derpMagicIPAddr { // Never consider DERP addresses as a viable candidate for // either curAddr or roamAddr. It's only ever a last resort // choice, never a preferred choice. @@ -471,25 +460,20 @@ func (a *addrSet) updateDst(new *net.UDPAddr) error { a.mu.Lock() defer a.mu.Unlock() - if a.roamAddrStd != nil && equalUDPAddr(new, a.roamAddrStd) { + if a.roamAddr != nil && new == *a.roamAddr { // Packet from the current roaming address, no logging. // This is a hot path for established connections. return nil } - if a.roamAddr == nil && a.curAddr >= 0 && equalUDPAddr(new, &a.addrs[a.curAddr]) { + if a.roamAddr == nil && a.curAddr >= 0 && new == a.ipPorts[a.curAddr] { // Packet from current-priority address, no logging. // This is a hot path for established connections. return nil } - newa, ok := netaddr.FromStdAddr(new.IP, new.Port, new.Zone) - if !ok { - return nil - } - index := -1 - for i := range a.addrs { - if equalUDPAddr(new, &a.addrs[i]) { + for i := range a.ipPorts { + if new == a.ipPorts[i] { index = i break } @@ -499,7 +483,7 @@ func (a *addrSet) updateDst(new *net.UDPAddr) error { pk := publicKey.ShortString() old := "<none>" if a.curAddr >= 0 { - old = a.addrs[a.curAddr].String() + old = a.ipPorts[a.curAddr].String() } switch { @@ -509,18 +493,16 @@ func (a *addrSet) updateDst(new *net.UDPAddr) error { } else { a.Logf("magicsock: rx %s from roaming address %s, replaces roaming address %s", pk, new, a.roamAddr) } - a.roamAddr = &newa - a.roamAddrStd = new + a.roamAddr = &new case a.roamAddr != nil: a.Logf("magicsock: rx %s from known %s (%d), replaces roaming address %s", pk, new, index, a.roamAddr) a.roamAddr = nil - a.roamAddrStd = nil a.curAddr = index a.loggedLogPriMask = 0 case a.curAddr == -1: - a.Logf("magicsock: rx %s from %s (%d/%d), set as new priority", pk, new, index, len(a.addrs)) + a.Logf("magicsock: rx %s from %s (%d/%d), set as new priority", pk, new, index, len(a.ipPorts)) a.curAddr = index a.loggedLogPriMask = 0 @@ -531,7 +513,7 @@ func (a *addrSet) updateDst(new *net.UDPAddr) error { } default: // index > a.curAddr - a.Logf("magicsock: rx %s from %s (%d/%d), replaces old priority %s", pk, new, index, len(a.addrs), old) + a.Logf("magicsock: rx %s from %s (%d/%d), replaces old priority %s", pk, new, index, len(a.ipPorts), old) a.curAddr = index a.loggedLogPriMask = 0 } @@ -539,10 +521,6 @@ func (a *addrSet) updateDst(new *net.UDPAddr) error { return nil } -func equalUDPAddr(x, y *net.UDPAddr) bool { - return x.Port == y.Port && x.IP.Equal(y.IP) -} - func (a *addrSet) String() string { a.mu.Lock() defer a.mu.Unlock() @@ -551,9 +529,9 @@ func (a *addrSet) String() string { buf.WriteByte('[') if a.roamAddr != nil { buf.WriteString("roam:") - sbPrintAddr(buf, *a.roamAddrStd) + sbPrintAddr(buf, *a.roamAddr) } - for i, addr := range a.addrs { + for i, addr := range a.ipPorts { if i > 0 || a.roamAddr != nil { buf.WriteString(", ") } @@ -572,8 +550,8 @@ func (as *addrSet) populatePeerStatus(ps *ipnstate.PeerStatus) { defer as.mu.Unlock() ps.LastWrite = as.lastSend - for i, ua := range as.addrs { - if ua.IP.Equal(derpMagicIP) { + for i, ua := range as.ipPorts { + if ua.IP == derpMagicIPAddr { continue } uaStr := ua.String() @@ -583,7 +561,7 @@ func (as *addrSet) populatePeerStatus(ps *ipnstate.PeerStatus) { } } if as.roamAddr != nil { - ps.CurAddr = udpAddrDebugString(*as.roamAddrStd) + ps.CurAddr = ippDebugString(*as.roamAddr) } } diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index d28a07db3..407ca1720 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -12,6 +12,7 @@ import ( crand "crypto/rand" "encoding/binary" "errors" + "expvar" "fmt" "hash/fnv" "math" @@ -48,9 +49,11 @@ import ( "tailscale.com/tstime" "tailscale.com/types/key" "tailscale.com/types/logger" + "tailscale.com/types/netmap" "tailscale.com/types/nettype" "tailscale.com/types/wgkey" "tailscale.com/version" + "tailscale.com/wgengine/wgcfg" ) // Various debugging and experimental tweakables, set by environment @@ -153,13 +156,10 @@ type Conn struct { // derpRecvCh is used by ReceiveIPv4 to read DERP messages. derpRecvCh chan derpReadResult - // derpRecvCountAtomic is atomically incremented by runDerpReader whenever - // a DERP message arrives. It's incremented before runDerpReader is interrupted. + // derpRecvCountAtomic is how many derpRecvCh sends are pending. + // It's incremented by runDerpReader whenever a DERP message + // arrives and decremented when they're read. derpRecvCountAtomic int64 - // derpRecvCountLast is used by ReceiveIPv4 to compare against - // its last read value of derpRecvCountAtomic to determine - // whether a DERP channel read should be done. - derpRecvCountLast int64 // owned by ReceiveIPv4 // ippEndpoint4 and ippEndpoint6 are owned by ReceiveIPv4 and // ReceiveIPv6, respectively, to cache an IPPort->endpoint for @@ -272,7 +272,7 @@ type Conn struct { netInfoLast *tailcfg.NetInfo derpMap *tailcfg.DERPMap // nil (or zero regions/nodes) means DERP is disabled - netMap *controlclient.NetworkMap + netMap *netmap.NetworkMap privateKey key.Private // WireGuard private key for this node everHadKey bool // whether we ever had a non-zero private key myDerp int // nearest DERP region ID; 0 means none/unknown @@ -304,6 +304,9 @@ type Conn struct { // with IPv4 or IPv6). It's used to suppress log spam and prevent // new connection that'll fail. networkUp syncs.AtomicBool + + // havePrivateKey is whether privateKey is non-zero. + havePrivateKey syncs.AtomicBool } // derpRoute is a route entry for a public key, saying that a certain @@ -345,8 +348,7 @@ func (c *Conn) addDerpPeerRoute(peer key.Public, derpID int, dc *derphttp.Client // Mnemonic: 3.3.40 are numbers above the keys D, E, R, P. const DerpMagicIP = "127.3.3.40" -var derpMagicIP = net.ParseIP(DerpMagicIP).To4() -var derpMagicIPAddr = netaddr.IPv4(127, 3, 3, 40) +var derpMagicIPAddr = netaddr.MustParseIP(DerpMagicIP) // activeDerp contains fields for an active DERP connection. type activeDerp struct { @@ -355,7 +357,7 @@ type activeDerp struct { writeCh chan<- derpWriteRequest // lastWrite is the time of the last request for its write // channel (currently even if there was no write). - // It is always non-nil and initialized to a non-zero Time[ + // It is always non-nil and initialized to a non-zero Time. lastWrite *time.Time createTime time.Time } @@ -773,7 +775,7 @@ func (c *Conn) SetNetInfoCallback(fn func(*tailcfg.NetInfo)) { // peerForIP returns the Node in nm that's responsible for // handling the given IP address. -func peerForIP(nm *controlclient.NetworkMap, ip netaddr.IP) (n *tailcfg.Node, ok bool) { +func peerForIP(nm *netmap.NetworkMap, ip netaddr.IP) (n *tailcfg.Node, ok bool) { if nm == nil { return nil, false } @@ -960,6 +962,13 @@ func (c *Conn) setNearestDERP(derpNum int) (wantDERP bool) { return true } +// startDerpHomeConnectLocked starts connecting to our DERP home, if any. +// +// c.mu must be held. +func (c *Conn) startDerpHomeConnectLocked() { + c.goDerpConnect(c.myDerp) +} + // goDerpConnect starts a goroutine to start connecting to the given // DERP node. // @@ -1353,6 +1362,8 @@ type derpReadResult struct { // copyBuf is called to copy the data to dst. It returns how // much data was copied, which will be n if dst is large // enough. copyBuf can only be called once. + // If copyBuf is nil, that's a signal from the sender to ignore + // this message. copyBuf func(dst []byte) int } @@ -1440,25 +1451,59 @@ func (c *Conn) runDerpReader(ctx context.Context, derpFakeAddr netaddr.IPPort, d continue } - // Before we wake up ReceiveIPv4 with SetReadDeadline, - // note that a DERP packet has arrived. ReceiveIPv4 - // will read this field to note that its UDP read - // error is due to us. - atomic.AddInt64(&c.derpRecvCountAtomic, 1) - // Cancel the pconn read goroutine. - c.pconn4.SetReadDeadline(aLongTimeAgo) + if !c.sendDerpReadResult(ctx, res) { + return + } select { case <-ctx.Done(): return - case c.derpRecvCh <- res: - select { - case <-ctx.Done(): - return - case <-didCopy: - continue - } + case <-didCopy: + continue + } + } +} + +var ( + testCounterZeroDerpReadResultSend expvar.Int + testCounterZeroDerpReadResultRecv expvar.Int +) + +// sendDerpReadResult sends res to c.derpRecvCh and reports whether it +// was sent. (It reports false if ctx was done first.) +// +// This includes doing the whole wake-up dance to interrupt +// ReceiveIPv4's blocking UDP read. +func (c *Conn) sendDerpReadResult(ctx context.Context, res derpReadResult) (sent bool) { + // Before we wake up ReceiveIPv4 with SetReadDeadline, + // note that a DERP packet has arrived. ReceiveIPv4 + // will read this field to note that its UDP read + // error is due to us. + atomic.AddInt64(&c.derpRecvCountAtomic, 1) + // Cancel the pconn read goroutine. + c.pconn4.SetReadDeadline(aLongTimeAgo) + select { + case <-ctx.Done(): + select { + case <-c.donec: + // The whole Conn shut down. The reader of + // c.derpRecvCh also selects on c.donec, so it's + // safe to abort now. + case c.derpRecvCh <- (derpReadResult{}): + // Just this DERP reader is closing (perhaps + // the user is logging out, or the DERP + // connection is too idle for sends). Since we + // already incremented c.derpRecvCountAtomic, + // we need to send on the channel (unless the + // conn is going down). + // The receiver treats a derpReadResult zero value + // message as a skip. + testCounterZeroDerpReadResultSend.Add(1) + } + return false + case c.derpRecvCh <- res: + return true } } @@ -1493,7 +1538,6 @@ func (c *Conn) runDerpWriter(ctx context.Context, dc *derphttp.Client, ch <-chan // findEndpoint maps from a UDP address to a WireGuard endpoint, for // ReceiveIPv4/ReceiveIPv6. -// The provided addr and ipp must match. // // TODO(bradfitz): add a fast path that returns nil here for normal // wireguard-go transport packets; wireguard-go only uses this @@ -1501,7 +1545,7 @@ func (c *Conn) runDerpWriter(ctx context.Context, dc *derphttp.Client, ch <-chan // Endpoint to find the UDPAddr to return to wireguard anyway, so no // benefit unless we can, say, always return the same fake UDPAddr for // all packets. -func (c *Conn) findEndpoint(ipp netaddr.IPPort, addr *net.UDPAddr, packet []byte) conn.Endpoint { +func (c *Conn) findEndpoint(ipp netaddr.IPPort, packet []byte) conn.Endpoint { c.mu.Lock() defer c.mu.Unlock() @@ -1513,10 +1557,7 @@ func (c *Conn) findEndpoint(ipp netaddr.IPPort, addr *net.UDPAddr, packet []byte } } - if addr == nil { - addr = ipp.UDPAddr() - } - return c.findLegacyEndpointLocked(ipp, addr, packet) + return c.findLegacyEndpointLocked(ipp, packet) } // aLongTimeAgo is a non-zero time, far in the past, used for @@ -1540,31 +1581,31 @@ func (c *Conn) ReceiveIPv6(b []byte) (int, conn.Endpoint, error) { return 0, nil, syscall.EAFNOSUPPORT } for { - n, pAddr, err := c.pconn6.ReadFrom(b) + n, ipp, err := c.pconn6.ReadFromNetaddr(b) if err != nil { return 0, nil, err } - if ep, ok := c.receiveIP(b[:n], pAddr.(*net.UDPAddr), &c.ippEndpoint6); ok { + if ep, ok := c.receiveIP(b[:n], ipp, &c.ippEndpoint6); ok { return n, ep, nil } } } func (c *Conn) derpPacketArrived() bool { - rc := atomic.LoadInt64(&c.derpRecvCountAtomic) - if rc != c.derpRecvCountLast { - c.derpRecvCountLast = rc - return true - } - return false + return atomic.LoadInt64(&c.derpRecvCountAtomic) > 0 } // ReceiveIPv4 is called by wireguard-go to receive an IPv4 packet. // In Tailscale's case, that packet might also arrive via DERP. A DERP packet arrival // aborts the pconn4 read deadline to make it fail. func (c *Conn) ReceiveIPv4(b []byte) (n int, ep conn.Endpoint, err error) { + var ipp netaddr.IPPort for { - n, pAddr, err := c.pconn4.ReadFrom(b) + // Drain DERP queues before reading new UDP packets. + if c.derpPacketArrived() { + goto ReadDERP + } + n, ipp, err = c.pconn4.ReadFromNetaddr(b) if err != nil { // If the pconn4 read failed, the likely reason is a DERP reader received // a packet and interrupted us. @@ -1572,27 +1613,29 @@ func (c *Conn) ReceiveIPv4(b []byte) (n int, ep conn.Endpoint, err error) { // and for there to have also had a DERP packet arrive, but that's fine: // we'll get the same error from ReadFrom later. if c.derpPacketArrived() { - c.pconn4.SetReadDeadline(time.Time{}) // restore - n, ep, err = c.receiveIPv4DERP(b) - if err == errLoopAgain { - continue - } - return n, ep, err + goto ReadDERP } return 0, nil, err } - if ep, ok := c.receiveIP(b[:n], pAddr.(*net.UDPAddr), &c.ippEndpoint4); ok { + if ep, ok := c.receiveIP(b[:n], ipp, &c.ippEndpoint4); ok { return n, ep, nil + } else { + continue } + ReadDERP: + n, ep, err = c.receiveIPv4DERP(b) + if err == errLoopAgain { + continue + } + return n, ep, err } } // receiveIP is the shared bits of ReceiveIPv4 and ReceiveIPv6. -func (c *Conn) receiveIP(b []byte, ua *net.UDPAddr, cache *ippEndpointCache) (ep conn.Endpoint, ok bool) { - ipp, ok := netaddr.FromStdAddr(ua.IP, ua.Port, ua.Zone) - if !ok { - return nil, false - } +// +// ok is whether this read should be reported up to wireguard-go (our +// caller). +func (c *Conn) receiveIP(b []byte, ipp netaddr.IPPort, cache *ippEndpointCache) (ep conn.Endpoint, ok bool) { if stun.Is(b) { c.stunReceiveFunc.Load().(func([]byte, netaddr.IPPort))(b, ipp) return nil, false @@ -1600,10 +1643,17 @@ func (c *Conn) receiveIP(b []byte, ua *net.UDPAddr, cache *ippEndpointCache) (ep if c.handleDiscoMessage(b, ipp) { return nil, false } + if !c.havePrivateKey.Get() { + // If we have no private key, we're logged out or + // stopped. Don't try to pass these wireguard packets + // up to wireguard-go; it'll just complain (Issue + // 1167). + return nil, false + } if cache.ipp == ipp && cache.de != nil && cache.gen == cache.de.numStopAndReset() { ep = cache.de } else { - ep = c.findEndpoint(ipp, ua, b) + ep = c.findEndpoint(ipp, b) if ep == nil { return nil, false } @@ -1641,6 +1691,13 @@ func (c *Conn) receiveIPv4DERP(b []byte) (n int, ep conn.Endpoint, err error) { case dm = <-c.derpRecvCh: // Below. } + if atomic.AddInt64(&c.derpRecvCountAtomic, -1) == 0 { + c.pconn4.SetReadDeadline(time.Time{}) + } + if dm.copyBuf == nil { + testCounterZeroDerpReadResultRecv.Add(1) + return 0, nil, errLoopAgain + } var regionID int n, regionID = dm.n, dm.regionID @@ -1693,7 +1750,7 @@ func (c *Conn) receiveIPv4DERP(b []byte) (n int, ep conn.Endpoint, err error) { } else { key := wgkey.Key(dm.src) c.logf("magicsock: DERP packet from unknown key: %s", key.ShortString()) - ep = c.findEndpoint(ipp, nil, b[:n]) + ep = c.findEndpoint(ipp, b[:n]) if ep == nil { return 0, nil, errLoopAgain } @@ -1750,8 +1807,8 @@ func (c *Conn) sendDiscoMessage(dst netaddr.IPPort, dstKey tailcfg.NodeKey, dstD return sent, err } -// handleDiscoMessage reports whether msg was a Tailscale inter-node discovery message -// that was handled. +// handleDiscoMessage handles a discovery message and reports whether +// msg was a Tailscale inter-node discovery message. // // A discovery message has the form: // @@ -1762,11 +1819,18 @@ func (c *Conn) sendDiscoMessage(dst netaddr.IPPort, dstKey tailcfg.NodeKey, dstD // // For messages received over DERP, the addr will be derpMagicIP (with // port being the region) -func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool { +func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) (isDiscoMsg bool) { const headerLen = len(disco.Magic) + len(tailcfg.DiscoKey{}) + disco.NonceLen if len(msg) < headerLen || string(msg[:len(disco.Magic)]) != disco.Magic { return false } + + // If the first four parts are the prefix of disco.Magic + // (0x5453f09f) then it's definitely not a valid Wireguard + // packet (which starts with little-endian uint32 1, 2, 3, 4). + // Use naked returns for all following paths. + isDiscoMsg = true + var sender tailcfg.DiscoKey copy(sender[:], msg[len(disco.Magic):]) @@ -1774,20 +1838,21 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool { defer c.mu.Unlock() if c.closed { - return true + return } if debugDisco { c.logf("magicsock: disco: got disco-looking frame from %v", sender.ShortString()) } if c.privateKey.IsZero() { // Ignore disco messages when we're stopped. - return false + // Still return true, to not pass it down to wireguard. + return } if c.discoPrivate.IsZero() { if debugDisco { c.logf("magicsock: disco: ignoring disco-looking frame, no local key") } - return false + return } peerNode, ok := c.nodeOfDisco[sender] @@ -1795,9 +1860,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool { if debugDisco { c.logf("magicsock: disco: ignoring disco-looking frame, don't know node for %v", sender.ShortString()) } - // Returning false keeps passing it down, to WireGuard. - // WireGuard will almost surely reject it, but give it a chance. - return false + return } needsRecvActivityCall := false @@ -1810,7 +1873,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool { c.logf("magicsock: got disco message from idle peer, starting lazy conf for %v, %v", peerNode.Key.ShortString(), sender.ShortString()) if c.noteRecvActivity == nil { c.logf("magicsock: [unexpected] have node without endpoint, without c.noteRecvActivity hook") - return false + return } needsRecvActivityCall = true } else { @@ -1829,7 +1892,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool { // Now, recheck invariants that might've changed while we'd // released the lock, which isn't much: if c.closed || c.privateKey.IsZero() { - return true + return } de, ok = c.endpointOfDisco[sender] if !ok { @@ -1838,7 +1901,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool { return false } c.logf("magicsock: [unexpected] lazy endpoint not created for %v, %v", peerNode.Key.ShortString(), sender.ShortString()) - return false + return } if !endpointFound0 { c.logf("magicsock: lazy endpoint created via disco message for %v, %v", peerNode.Key.ShortString(), sender.ShortString()) @@ -1865,7 +1928,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool { c.logf("magicsock: disco: failed to open naclbox from %v (wrong rcpt?)", sender) } // TODO(bradfitz): add some counter for this that logs rarely - return false + return } dm, err := disco.Parse(payload) @@ -1879,7 +1942,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool { // understand. Not even worth logging about, lest it // be too spammy for old clients. // TODO(bradfitz): add some counter for this that logs rarely - return true + return } switch dm := dm.(type) { @@ -1887,14 +1950,14 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool { c.handlePingLocked(dm, de, src, sender, peerNode) case *disco.Pong: if de == nil { - return true + return } de.handlePongConnLocked(dm, src) case *disco.CallMeMaybe: if src.IP != derpMagicIPAddr { // CallMeMaybe messages should only come via DERP. c.logf("[unexpected] CallMeMaybe packets should only come via DERP") - return true + return } if de != nil { c.logf("magicsock: disco: %v<-%v (%v, %v) got call-me-maybe, %d endpoints", @@ -1904,8 +1967,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool { go de.handleCallMeMaybe(dm) } } - - return true + return } func (c *Conn) handlePingLocked(dm *disco.Ping, de *discoEndpoint, src netaddr.IPPort, sender tailcfg.DiscoKey, peerNode *tailcfg.Node) { @@ -2061,7 +2123,9 @@ func (c *Conn) SetNetworkUp(up bool) { c.logf("magicsock: SetNetworkUp(%v)", up) c.networkUp.Set(up) - if !up { + if up { + c.startDerpHomeConnectLocked() + } else { c.closeAllDerpLocked("network-down") } } @@ -2082,6 +2146,7 @@ func (c *Conn) SetPrivateKey(privateKey wgkey.Private) error { return nil } c.privateKey = newKey + c.havePrivateKey.Set(!newKey.IsZero()) if oldKey.IsZero() { c.everHadKey = true @@ -2102,7 +2167,7 @@ func (c *Conn) SetPrivateKey(privateKey wgkey.Private) error { // Key changed. Close existing DERP connections and reconnect to home. if c.myDerp != 0 && !newKey.IsZero() { c.logf("magicsock: private key changed, reconnecting to home derp-%d", c.myDerp) - c.goDerpConnect(c.myDerp) + c.startDerpHomeConnectLocked() } if newKey.IsZero() { @@ -2178,7 +2243,7 @@ func nodesEqual(x, y []*tailcfg.Node) bool { // // It should not use the DERPMap field of NetworkMap; that's // conditionally sent to SetDERPMap instead. -func (c *Conn) SetNetworkMap(nm *controlclient.NetworkMap) { +func (c *Conn) SetNetworkMap(nm *netmap.NetworkMap) { c.mu.Lock() defer c.mu.Unlock() @@ -2565,12 +2630,11 @@ func (c *Conn) Rebind() { c.mu.Lock() c.closeAllDerpLocked("rebind") - haveKey := !c.privateKey.IsZero() + if !c.privateKey.IsZero() { + c.startDerpHomeConnectLocked() + } c.mu.Unlock() - if haveKey { - c.goDerpConnect(c.myDerp) - } c.resetEndpointStates() } @@ -2624,11 +2688,11 @@ func (c *Conn) CreateEndpoint(pubKey [32]byte, addrs string) (conn.Endpoint, err pk := key.Public(pubKey) c.logf("magicsock: CreateEndpoint: key=%s: %s", pk.ShortString(), derpStr(addrs)) - if !strings.HasSuffix(addrs, controlclient.EndpointDiscoSuffix) { + if !strings.HasSuffix(addrs, wgcfg.EndpointDiscoSuffix) { return c.createLegacyEndpointLocked(pk, addrs) } - discoHex := strings.TrimSuffix(addrs, controlclient.EndpointDiscoSuffix) + discoHex := strings.TrimSuffix(addrs, wgcfg.EndpointDiscoSuffix) discoKey, err := key.NewPublicFromHexMem(mem.S(discoHex)) if err != nil { return nil, fmt.Errorf("magicsock: invalid discokey endpoint %q for %v: %w", addrs, pk.ShortString(), err) @@ -2666,6 +2730,8 @@ func (c *RebindingUDPConn) Reset(pconn net.PacketConn) { } } +// ReadFromNetaddr reads a packet from c into b. +// It returns the number of bytes copied and the source address. func (c *RebindingUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { for { c.mu.Lock() @@ -2686,6 +2752,58 @@ func (c *RebindingUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { } } +// ReadFromNetaddr reads a packet from c into b. +// It returns the number of bytes copied and the return address. +// It is identical to c.ReadFrom, except that it returns a netaddr.IPPort instead of a net.Addr. +// ReadFromNetaddr is designed to work with specific underlying connection types. +// If c's underlying connection returns a non-*net.UPDAddr return address, ReadFromNetaddr will return an error. +// ReadFromNetaddr exists because it removes an allocation per read, +// when c's underlying connection is a net.UDPConn. +func (c *RebindingUDPConn) ReadFromNetaddr(b []byte) (n int, ipp netaddr.IPPort, err error) { + for { + c.mu.Lock() + pconn := c.pconn + c.mu.Unlock() + + // Optimization: Treat *net.UDPConn specially. + // ReadFromUDP gets partially inlined, avoiding allocating a *net.UDPAddr, + // as long as pAddr itself doesn't escape. + // The non-*net.UDPConn case works, but it allocates. + var pAddr *net.UDPAddr + if udpConn, ok := pconn.(*net.UDPConn); ok { + n, pAddr, err = udpConn.ReadFromUDP(b) + } else { + var addr net.Addr + n, addr, err = pconn.ReadFrom(b) + if addr != nil { + pAddr, ok = addr.(*net.UDPAddr) + if !ok { + return 0, netaddr.IPPort{}, fmt.Errorf("RebindingUDPConn.ReadFromNetaddr: underlying connection returned address of type %T, want *netaddr.UDPAddr", addr) + } + } + } + + if err != nil { + c.mu.Lock() + pconn2 := c.pconn + c.mu.Unlock() + + if pconn != pconn2 { + continue + } + } else { + // Convert pAddr to a netaddr.IPPort. + // This prevents pAddr from escaping. + var ok bool + ipp, ok = netaddr.FromStdAddr(pAddr.IP, pAddr.Port, pAddr.Zone) + if !ok { + return 0, netaddr.IPPort{}, errors.New("netaddr.FromStdAddr failed") + } + } + return n, ipp, err + } +} + func (c *RebindingUDPConn) LocalAddr() *net.UDPAddr { c.mu.Lock() defer c.mu.Unlock() @@ -2760,8 +2878,8 @@ func peerShort(k key.Public) string { return k2.ShortString() } -func sbPrintAddr(sb *strings.Builder, a net.UDPAddr) { - is6 := a.IP.To4() == nil +func sbPrintAddr(sb *strings.Builder, a netaddr.IPPort) { + is6 := a.IP.Is6() if is6 { sb.WriteByte('[') } @@ -2858,8 +2976,8 @@ func (c *Conn) UpdateStatus(sb *ipnstate.StatusBuilder) { }) } -func udpAddrDebugString(ua net.UDPAddr) string { - if ua.IP.Equal(derpMagicIP) { +func ippDebugString(ua netaddr.IPPort) string { + if ua.IP == derpMagicIPAddr { return fmt.Sprintf("derp-%d", ua.Port) } return ua.String() diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 998b9bb27..26c157d16 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -11,12 +11,14 @@ import ( "crypto/tls" "encoding/binary" "encoding/json" + "errors" "fmt" "io/ioutil" "net" "net/http" "net/http/httptest" "os" + "runtime" "strconv" "strings" "sync" @@ -30,7 +32,6 @@ import ( "github.com/tailscale/wireguard-go/tun/tuntest" "golang.org/x/crypto/nacl/box" "inet.af/netaddr" - "tailscale.com/control/controlclient" "tailscale.com/derp" "tailscale.com/derp/derphttp" "tailscale.com/derp/derpmap" @@ -41,11 +42,14 @@ import ( "tailscale.com/tstest/natlab" "tailscale.com/types/key" "tailscale.com/types/logger" + "tailscale.com/types/netmap" "tailscale.com/types/nettype" "tailscale.com/types/wgkey" + "tailscale.com/util/cibuild" "tailscale.com/wgengine/filter" "tailscale.com/wgengine/tstun" "tailscale.com/wgengine/wgcfg" + "tailscale.com/wgengine/wgcfg/nmcfg" "tailscale.com/wgengine/wglog" ) @@ -251,9 +255,9 @@ func meshStacks(logf logger.Logf, ms []*magicStack) (cleanup func()) { eps = make([][]string, len(ms)) ) - buildNetmapLocked := func(myIdx int) *controlclient.NetworkMap { + buildNetmapLocked := func(myIdx int) *netmap.NetworkMap { me := ms[myIdx] - nm := &controlclient.NetworkMap{ + nm := &netmap.NetworkMap{ PrivateKey: me.privateKey, NodeKey: tailcfg.NodeKey(me.privateKey.Public()), Addresses: []netaddr.IPPrefix{{IP: netaddr.IPv4(1, 0, 0, byte(myIdx+1)), Bits: 32}}, @@ -286,14 +290,14 @@ func meshStacks(logf logger.Logf, ms []*magicStack) (cleanup func()) { eps[idx] = newEps for i, m := range ms { - netmap := buildNetmapLocked(i) - m.conn.SetNetworkMap(netmap) - peerSet := make(map[key.Public]struct{}, len(netmap.Peers)) - for _, peer := range netmap.Peers { + nm := buildNetmapLocked(i) + m.conn.SetNetworkMap(nm) + peerSet := make(map[key.Public]struct{}, len(nm.Peers)) + for _, peer := range nm.Peers { peerSet[key.Public(peer.Key)] = struct{}{} } m.conn.UpdatePeers(peerSet) - wg, err := netmap.WGCfg(logf, controlclient.AllowSingleHosts) + wg, err := nmcfg.WGCfg(nm, logf, netmap.AllowSingleHosts) if err != nil { // We're too far from the *testing.T to be graceful, // blow up. Shouldn't happen anyway. @@ -395,18 +399,6 @@ func pickPort(t testing.TB) uint16 { return uint16(conn.LocalAddr().(*net.UDPAddr).Port) } -func TestDerpIPConstant(t *testing.T) { - tstest.PanicOnLog() - tstest.ResourceCheck(t) - - if DerpMagicIP != derpMagicIP.String() { - t.Errorf("str %q != IP %v", DerpMagicIP, derpMagicIP) - } - if len(derpMagicIP) != 4 { - t.Errorf("derpMagicIP is len %d; want 4", len(derpMagicIP)) - } -} - func TestPickDERPFallback(t *testing.T) { tstest.PanicOnLog() tstest.ResourceCheck(t) @@ -449,7 +441,7 @@ func TestPickDERPFallback(t *testing.T) { // But move if peers are elsewhere. const otherNode = 789 c.addrsByKey = map[key.Public]*addrSet{ - key.Public{1}: &addrSet{addrs: []net.UDPAddr{{IP: derpMagicIP, Port: otherNode}}}, + key.Public{1}: &addrSet{ipPorts: []netaddr.IPPort{{IP: derpMagicIPAddr, Port: otherNode}}}, } if got := c.pickDERPFallback(); got != otherNode { t.Errorf("didn't join peers: got %v; want %v", got, someNode) @@ -925,30 +917,56 @@ func testTwoDevicePing(t *testing.T, d *devices) { t.Fatal(err) } + // In the normal case, pings succeed immediately. + // However, in the case of a handshake race, we need to retry. + // With very bad luck, we can need to retry multiple times. + allowedRetries := 3 + if cibuild.On() { + // Allow extra retries on small/flaky/loaded CI machines. + allowedRetries *= 2 + } + // Retries take 5s each. Add 1s for some processing time. + pingTimeout := 5*time.Second*time.Duration(allowedRetries) + time.Second + + // sendWithTimeout sends msg using send, checking that it is received unchanged from in. + // It resends once per second until the send succeeds, or pingTimeout time has elapsed. + sendWithTimeout := func(msg []byte, in chan []byte, send func()) error { + start := time.Now() + for time.Since(start) < pingTimeout { + send() + select { + case recv := <-in: + if !bytes.Equal(msg, recv) { + return errors.New("ping did not transit correctly") + } + return nil + case <-time.After(time.Second): + // try again + } + } + return errors.New("ping timed out") + } + ping1 := func(t *testing.T) { msg2to1 := tuntest.Ping(net.ParseIP("1.0.0.1"), net.ParseIP("1.0.0.2")) - m2.tun.Outbound <- msg2to1 - t.Log("ping1 sent") - select { - case msgRecv := <-m1.tun.Inbound: - if !bytes.Equal(msg2to1, msgRecv) { - t.Error("ping did not transit correctly") - } - case <-time.After(3 * time.Second): - t.Error("ping did not transit") + send := func() { + m2.tun.Outbound <- msg2to1 + t.Log("ping1 sent") + } + in := m1.tun.Inbound + if err := sendWithTimeout(msg2to1, in, send); err != nil { + t.Error(err) } } ping2 := func(t *testing.T) { msg1to2 := tuntest.Ping(net.ParseIP("1.0.0.2"), net.ParseIP("1.0.0.1")) - m1.tun.Outbound <- msg1to2 - t.Log("ping2 sent") - select { - case msgRecv := <-m2.tun.Inbound: - if !bytes.Equal(msg1to2, msgRecv) { - t.Error("return ping did not transit correctly") - } - case <-time.After(3 * time.Second): - t.Error("return ping did not transit") + send := func() { + m1.tun.Outbound <- msg1to2 + t.Log("ping2 sent") + } + in := m2.tun.Inbound + if err := sendWithTimeout(msg1to2, in, send); err != nil { + t.Error(err) } } @@ -969,17 +987,15 @@ func testTwoDevicePing(t *testing.T, d *devices) { setT(t) defer setT(outerT) msg1to2 := tuntest.Ping(net.ParseIP("1.0.0.2"), net.ParseIP("1.0.0.1")) - if err := m1.tsTun.InjectOutbound(msg1to2); err != nil { - t.Fatal(err) - } - t.Log("SendPacket sent") - select { - case msgRecv := <-m2.tun.Inbound: - if !bytes.Equal(msg1to2, msgRecv) { - t.Error("return ping did not transit correctly") + send := func() { + if err := m1.tsTun.InjectOutbound(msg1to2); err != nil { + t.Fatal(err) } - case <-time.After(3 * time.Second): - t.Error("return ping did not transit") + t.Log("SendPacket sent") + } + in := m2.tun.Inbound + if err := sendWithTimeout(msg1to2, in, send); err != nil { + t.Error(err) } }) @@ -1041,7 +1057,7 @@ func testTwoDevicePing(t *testing.T, d *devices) { t.Errorf("return ping %d did not transit correctly: %s", i, cmp.Diff(b, msgRecv)) } } - case <-time.After(3 * time.Second): + case <-time.After(pingTimeout): if strict { t.Errorf("return ping %d did not transit", i) } @@ -1142,20 +1158,13 @@ func TestAddrSet(t *testing.T) { tstest.ResourceCheck(t) mustIPPortPtr := func(s string) *netaddr.IPPort { - t.Helper() - ipp, err := netaddr.ParseIPPort(s) - if err != nil { - t.Fatal(err) - } + ipp := netaddr.MustParseIPPort(s) return &ipp } - mustUDPAddr := func(s string) *net.UDPAddr { - return mustIPPortPtr(s).UDPAddr() - } - udpAddrs := func(ss ...string) (ret []net.UDPAddr) { + ipps := func(ss ...string) (ret []netaddr.IPPort) { t.Helper() for _, s := range ss { - ret = append(ret, *mustUDPAddr(s)) + ret = append(ret, netaddr.MustParseIPPort(s)) } return ret } @@ -1187,7 +1196,7 @@ func TestAddrSet(t *testing.T) { // updateDst, if set, does an UpdateDst call and // b+want are ignored. - updateDst *net.UDPAddr + updateDst *netaddr.IPPort b []byte want string // comma-separated @@ -1201,7 +1210,7 @@ func TestAddrSet(t *testing.T) { { name: "reg_packet_no_curaddr", as: &addrSet{ - addrs: udpAddrs("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"), + ipPorts: ipps("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"), curAddr: -1, // unknown roamAddr: nil, }, @@ -1212,7 +1221,7 @@ func TestAddrSet(t *testing.T) { { name: "reg_packet_have_curaddr", as: &addrSet{ - addrs: udpAddrs("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"), + ipPorts: ipps("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"), curAddr: 1, // global IP roamAddr: nil, }, @@ -1223,36 +1232,36 @@ func TestAddrSet(t *testing.T) { { name: "reg_packet_have_roamaddr", as: &addrSet{ - addrs: udpAddrs("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"), + ipPorts: ipps("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"), curAddr: 2, // should be ignored roamAddr: mustIPPortPtr("5.6.7.8:123"), }, steps: []step{ {b: regPacket, want: "5.6.7.8:123"}, - {updateDst: mustUDPAddr("10.0.0.1:123")}, // no more roaming + {updateDst: mustIPPortPtr("10.0.0.1:123")}, // no more roaming {b: regPacket, want: "10.0.0.1:123"}, }, }, { name: "start_roaming", as: &addrSet{ - addrs: udpAddrs("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"), + ipPorts: ipps("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"), curAddr: 2, }, steps: []step{ {b: regPacket, want: "10.0.0.1:123"}, - {updateDst: mustUDPAddr("4.5.6.7:123")}, + {updateDst: mustIPPortPtr("4.5.6.7:123")}, {b: regPacket, want: "4.5.6.7:123"}, - {updateDst: mustUDPAddr("5.6.7.8:123")}, + {updateDst: mustIPPortPtr("5.6.7.8:123")}, {b: regPacket, want: "5.6.7.8:123"}, - {updateDst: mustUDPAddr("123.45.67.89:123")}, // end roaming + {updateDst: mustIPPortPtr("123.45.67.89:123")}, // end roaming {b: regPacket, want: "123.45.67.89:123"}, }, }, { name: "spray_packet", as: &addrSet{ - addrs: udpAddrs("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"), + ipPorts: ipps("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"), curAddr: 2, // should be ignored roamAddr: mustIPPortPtr("5.6.7.8:123"), }, @@ -1261,19 +1270,19 @@ func TestAddrSet(t *testing.T) { {advance: 300 * time.Millisecond, b: regPacket, want: "127.3.3.40:1,123.45.67.89:123,10.0.0.1:123,5.6.7.8:123"}, {advance: 300 * time.Millisecond, b: regPacket, want: "127.3.3.40:1,123.45.67.89:123,10.0.0.1:123,5.6.7.8:123"}, {advance: 3, b: regPacket, want: "5.6.7.8:123"}, - {advance: 2 * time.Millisecond, updateDst: mustUDPAddr("10.0.0.1:123")}, + {advance: 2 * time.Millisecond, updateDst: mustIPPortPtr("10.0.0.1:123")}, {advance: 3, b: regPacket, want: "10.0.0.1:123"}, }, }, { name: "low_pri", as: &addrSet{ - addrs: udpAddrs("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"), + ipPorts: ipps("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"), curAddr: 2, }, steps: []step{ - {updateDst: mustUDPAddr("123.45.67.89:123")}, - {updateDst: mustUDPAddr("123.45.67.89:123")}, + {updateDst: mustIPPortPtr("123.45.67.89:123")}, + {updateDst: mustIPPortPtr("123.45.67.89:123")}, }, logCheck: func(t *testing.T, logged []byte) { if n := bytes.Count(logged, []byte(", keeping current ")); n != 1 { @@ -1292,12 +1301,11 @@ func TestAddrSet(t *testing.T) { t.Logf(format, args...) } tt.as.clock = func() time.Time { return faket } - initAddrSet(tt.as) for i, st := range tt.steps { faket = faket.Add(st.advance) if st.updateDst != nil { - if err := tt.as.updateDst(st.updateDst); err != nil { + if err := tt.as.updateDst(*st.updateDst); err != nil { t.Fatal(err) } continue @@ -1314,23 +1322,6 @@ func TestAddrSet(t *testing.T) { } } -// initAddrSet initializes fields in the provided incomplete addrSet -// to satisfying invariants within magicsock. -func initAddrSet(as *addrSet) { - if as.roamAddr != nil && as.roamAddrStd == nil { - as.roamAddrStd = as.roamAddr.UDPAddr() - } - if len(as.ipPorts) == 0 { - for _, ua := range as.addrs { - ipp, ok := netaddr.FromStdAddr(ua.IP, ua.Port, ua.Zone) - if !ok { - panic(fmt.Sprintf("bogus UDPAddr %+v", ua)) - } - as.ipPorts = append(as.ipPorts, ipp) - } - } -} - func TestDiscoMessage(t *testing.T) { c := newConn() c.logf = t.Logf @@ -1407,62 +1398,235 @@ func Test32bitAlignment(t *testing.T) { atomic.AddInt64(&c.derpRecvCountAtomic, 1) } -func BenchmarkReceiveFrom(b *testing.B) { - port := pickPort(b) +// newNonLegacyTestConn returns a new Conn with DisableLegacyNetworking set true. +func newNonLegacyTestConn(t testing.TB) *Conn { + t.Helper() + port := pickPort(t) conn, err := NewConn(Options{ - Logf: b.Logf, + Logf: t.Logf, Port: port, EndpointsFunc: func(eps []string) { - b.Logf("endpoints: %q", eps) + t.Logf("endpoints: %q", eps) }, DisableLegacyNetworking: true, }) if err != nil { - b.Fatal(err) + t.Fatal(err) } + return conn +} + +// Tests concurrent DERP readers pushing DERP data into ReceiveIPv4 +// (which should blend all DERP reads into UDP reads). +func TestDerpReceiveFromIPv4(t *testing.T) { + conn := newNonLegacyTestConn(t) defer conn.Close() sendConn, err := net.ListenPacket("udp4", "127.0.0.1:0") if err != nil { - b.Fatal(err) + t.Fatal(err) } defer sendConn.Close() + nodeKey, _ := addTestEndpoint(conn, sendConn) + + var sends int = 250e3 // takes about a second + if testing.Short() { + sends /= 10 + } + senders := runtime.NumCPU() + sends -= (sends % senders) + var wg sync.WaitGroup + defer wg.Wait() + t.Logf("doing %v sends over %d senders", sends, senders) + + ctx, cancel := context.WithCancel(context.Background()) + defer conn.Close() + defer cancel() + + doneCtx, cancelDoneCtx := context.WithCancel(context.Background()) + cancelDoneCtx() + + for i := 0; i < senders; i++ { + wg.Add(1) + regionID := i + 1 + go func() { + defer wg.Done() + for i := 0; i < sends/senders; i++ { + res := derpReadResult{ + regionID: regionID, + n: 123, + src: key.Public(nodeKey), + copyBuf: func(dst []byte) int { return 123 }, + } + // First send with the closed context. ~50% of + // these should end up going through the + // send-a-zero-derpReadResult path, returning + // true, in which case we don't want to send again. + // We test later that we hit the other path. + if conn.sendDerpReadResult(doneCtx, res) { + continue + } + if !conn.sendDerpReadResult(ctx, res) { + t.Error("unexpected false") + return + } + } + }() + } + + zeroSendsStart := testCounterZeroDerpReadResultSend.Value() + + buf := make([]byte, 1500) + for i := 0; i < sends; i++ { + n, ep, err := conn.ReceiveIPv4(buf) + if err != nil { + t.Fatal(err) + } + _ = n + _ = ep + } + + t.Logf("did %d ReceiveIPv4 calls", sends) + + zeroSends, zeroRecv := testCounterZeroDerpReadResultSend.Value(), testCounterZeroDerpReadResultRecv.Value() + if zeroSends != zeroRecv { + t.Errorf("did %d zero sends != %d corresponding receives", zeroSends, zeroRecv) + } + zeroSendDelta := zeroSends - zeroSendsStart + if zeroSendDelta == 0 { + t.Errorf("didn't see any sends of derpReadResult zero value") + } + if zeroSendDelta == int64(sends) { + t.Errorf("saw %v sends of the derpReadResult zero value which was unexpectedly high (100%% of our %v sends)", zeroSendDelta, sends) + } +} + +// addTestEndpoint sets conn's network map to a single peer expected +// to receive packets from sendConn (or DERP), and returns that peer's +// nodekey and discokey. +func addTestEndpoint(conn *Conn, sendConn net.PacketConn) (tailcfg.NodeKey, tailcfg.DiscoKey) { // Give conn just enough state that it'll recognize sendConn as a // valid peer and not fall through to the legacy magicsock // codepath. discoKey := tailcfg.DiscoKey{31: 1} - conn.SetNetworkMap(&controlclient.NetworkMap{ + nodeKey := tailcfg.NodeKey{0: 'N', 1: 'K'} + conn.SetNetworkMap(&netmap.NetworkMap{ Peers: []*tailcfg.Node{ { + Key: nodeKey, DiscoKey: discoKey, Endpoints: []string{sendConn.LocalAddr().String()}, }, }, }) - conn.CreateEndpoint([32]byte{1: 1}, "0000000000000000000000000000000000000000000000000000000000000001.disco.tailscale:12345") + conn.SetPrivateKey(wgkey.Private{0: 1}) + conn.CreateEndpoint([32]byte(nodeKey), "0000000000000000000000000000000000000000000000000000000000000001.disco.tailscale:12345") conn.addValidDiscoPathForTest(discoKey, netaddr.MustParseIPPort(sendConn.LocalAddr().String())) + return nodeKey, discoKey +} + +func setUpReceiveFrom(tb testing.TB) (roundTrip func()) { + conn := newNonLegacyTestConn(tb) + tb.Cleanup(func() { conn.Close() }) + conn.logf = logger.Discard + + sendConn, err := net.ListenPacket("udp4", "127.0.0.1:0") + if err != nil { + tb.Fatal(err) + } + tb.Cleanup(func() { sendConn.Close() }) + + addTestEndpoint(conn, sendConn) var dstAddr net.Addr = conn.pconn4.LocalAddr() sendBuf := make([]byte, 1<<10) for i := range sendBuf { sendBuf[i] = 'x' } - buf := make([]byte, 2<<10) - for i := 0; i < b.N; i++ { + return func() { if _, err := sendConn.WriteTo(sendBuf, dstAddr); err != nil { - b.Fatalf("WriteTo: %v", err) + tb.Fatalf("WriteTo: %v", err) } n, ep, err := conn.ReceiveIPv4(buf) if err != nil { - b.Fatal(err) + tb.Fatal(err) } _ = n _ = ep } } +// goMajorVersion reports the major Go version and whether it is a Tailscale fork. +// If parsing fails, goMajorVersion returns 0, false. +func goMajorVersion(s string) (version int, isTS bool) { + if !strings.HasPrefix(s, "go1.") { + return 0, false + } + mm := s[len("go1."):] + var major, rest string + for _, sep := range []string{".", "rc", "beta"} { + i := strings.Index(mm, sep) + if i > 0 { + major, rest = mm[:i], mm[i:] + break + } + } + if major == "" { + major = mm + } + n, err := strconv.Atoi(major) + if err != nil { + return 0, false + } + return n, strings.Contains(rest, "ts") +} + +func TestGoMajorVersion(t *testing.T) { + tests := []struct { + version string + wantN int + wantTS bool + }{ + {"go1.15.8", 15, false}, + {"go1.16rc1", 16, false}, + {"go1.16rc1", 16, false}, + {"go1.15.5-ts3bd89195a3", 15, true}, + {"go1.15", 15, false}, + } + + for _, tt := range tests { + n, ts := goMajorVersion(tt.version) + if tt.wantN != n || tt.wantTS != ts { + t.Errorf("goMajorVersion(%s) = %v, %v, want %v, %v", tt.version, n, ts, tt.wantN, tt.wantTS) + } + } +} + +func TestReceiveFromAllocs(t *testing.T) { + // Go 1.16 and before: allow 3 allocs. + // Go Tailscale fork, Go 1.17+: only allow 2 allocs. + major, ts := goMajorVersion(runtime.Version()) + maxAllocs := 3 + if major >= 17 || ts { + maxAllocs = 2 + } + t.Logf("allowing %d allocs for Go version %q", maxAllocs, runtime.Version()) + roundTrip := setUpReceiveFrom(t) + avg := int(testing.AllocsPerRun(100, roundTrip)) + if avg > maxAllocs { + t.Fatalf("expected %d allocs in ReceiveFrom, got %v", maxAllocs, avg) + } +} + +func BenchmarkReceiveFrom(b *testing.B) { + roundTrip := setUpReceiveFrom(b) + for i := 0; i < b.N; i++ { + roundTrip() + } +} + func BenchmarkReceiveFrom_Native(b *testing.B) { recvConn, err := net.ListenPacket("udp4", "127.0.0.1:0") if err != nil { diff --git a/wgengine/monitor/monitor_darwin_tailscaled.go b/wgengine/monitor/monitor_darwin_tailscaled.go new file mode 100644 index 000000000..f7123cf65 --- /dev/null +++ b/wgengine/monitor/monitor_darwin_tailscaled.go @@ -0,0 +1,72 @@ +// 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 darwin,!redo + +package monitor + +import ( + "bufio" + "errors" + "os/exec" + + "tailscale.com/syncs" + "tailscale.com/types/logger" +) + +// unspecifiedMessage is a minimal message implementation that should not +// be ignored. In general, OS-specific implementations should use better +// types and avoid this if they can. +type unspecifiedMessage struct{} + +func (unspecifiedMessage) ignore() bool { return false } + +func newOSMon(logf logger.Logf) (osMon, error) { + return new(routeMonitorSubProcMon), nil +} + +// routeMonitorSubProcMon is a very simple (temporary? but I know +// better) monitor implementation for darwin in tailscaled-mode where +// we can just shell out to "route -n monitor". It waits for any input +// but doesn't parse it. Then we poll to see if something is different. +type routeMonitorSubProcMon struct { + closed syncs.AtomicBool + cmd *exec.Cmd // of "/sbin/route -n monitor" + br *bufio.Reader + buf []byte +} + +func (m *routeMonitorSubProcMon) Close() error { + m.closed.Set(true) + if m.cmd != nil { + m.cmd.Process.Kill() + m.cmd = nil + } + return nil +} + +func (m *routeMonitorSubProcMon) Receive() (message, error) { + if m.closed.Get() { + return nil, errors.New("monitor closed") + } + if m.cmd == nil { + cmd := exec.Command("/sbin/route", "-n", "monitor") + outPipe, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + if err := cmd.Start(); err != nil { + return nil, err + } + m.br = bufio.NewReader(outPipe) + m.cmd = cmd + m.buf = make([]byte, 16<<10) + } + _, err := m.br.Read(m.buf) + if err != nil { + m.Close() + return nil, err + } + return unspecifiedMessage{}, nil +} diff --git a/wgengine/monitor/monitor_unsupported.go b/wgengine/monitor/monitor_unsupported.go index a54990c02..a779536e6 100644 --- a/wgengine/monitor/monitor_unsupported.go +++ b/wgengine/monitor/monitor_unsupported.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. -// +build !linux,!freebsd,!windows android +// +build !linux,!freebsd,!windows,!darwin android darwin,redo package monitor diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go index 72651bd41..b2b21fcba 100644 --- a/wgengine/netstack/netstack.go +++ b/wgengine/netstack/netstack.go @@ -28,9 +28,9 @@ import ( "gvisor.dev/gvisor/pkg/tcpip/transport/udp" "gvisor.dev/gvisor/pkg/waiter" "inet.af/netaddr" - "tailscale.com/control/controlclient" "tailscale.com/net/packet" "tailscale.com/types/logger" + "tailscale.com/types/netmap" "tailscale.com/wgengine" "tailscale.com/wgengine/filter" "tailscale.com/wgengine/magicsock" @@ -63,7 +63,7 @@ func Impl(logf logger.Logf, tundev *tstun.TUN, e wgengine.Engine, mc *magicsock. log.Fatal(err) } - e.AddNetworkMapCallback(func(nm *controlclient.NetworkMap) { + e.AddNetworkMapCallback(func(nm *netmap.NetworkMap) { oldIPs := make(map[tcpip.Address]bool) for _, ip := range ipstack.AllAddresses()[nicID] { oldIPs[ip.AddressWithPrefix.Address] = true diff --git a/wgengine/pendopen.go b/wgengine/pendopen.go index a4d0a6f8b..2ede0429a 100644 --- a/wgengine/pendopen.go +++ b/wgengine/pendopen.go @@ -9,6 +9,7 @@ import ( "strconv" "time" + "tailscale.com/ipn/ipnstate" "tailscale.com/net/flowtrack" "tailscale.com/net/packet" "tailscale.com/wgengine/filter" @@ -30,6 +31,12 @@ func debugConnectFailures() bool { type pendingOpenFlow struct { timer *time.Timer // until giving up on the flow + + // guarded by userspaceEngine.mu: + + // problem is non-zero if we got a MaybeBroken (non-terminal) + // TSMP "reject" header. + problem packet.TailscaleRejectReason } func (e *userspaceEngine) removeFlow(f flowtrack.Tuple) (removed bool) { @@ -45,6 +52,17 @@ func (e *userspaceEngine) removeFlow(f flowtrack.Tuple) (removed bool) { return true } +func (e *userspaceEngine) noteFlowProblemFromPeer(f flowtrack.Tuple, problem packet.TailscaleRejectReason) { + e.mu.Lock() + defer e.mu.Unlock() + of, ok := e.pendOpen[f] + if !ok { + // Not a tracked flow (likely already removed) + return + } + of.problem = problem +} + func (e *userspaceEngine) trackOpenPreFilterIn(pp *packet.Parsed, t *tstun.TUN) (res filter.Response) { res = filter.Accept // always @@ -54,7 +72,9 @@ func (e *userspaceEngine) trackOpenPreFilterIn(pp *packet.Parsed, t *tstun.TUN) if !ok { return } - if f := rh.Flow(); e.removeFlow(f) { + if rh.MaybeBroken { + e.noteFlowProblemFromPeer(rh.Flow(), rh.Reason) + } else if f := rh.Flow(); e.removeFlow(f) { e.logf("open-conn-track: flow %v %v > %v rejected due to %v", rh.Proto, rh.Src, rh.Dst, rh.Reason) } return @@ -106,14 +126,20 @@ func (e *userspaceEngine) trackOpenPostFilterOut(pp *packet.Parsed, t *tstun.TUN func (e *userspaceEngine) onOpenTimeout(flow flowtrack.Tuple) { e.mu.Lock() - if _, ok := e.pendOpen[flow]; !ok { + of, ok := e.pendOpen[flow] + if !ok { // Not a tracked flow, or already handled & deleted. e.mu.Unlock() return } delete(e.pendOpen, flow) + problem := of.problem e.mu.Unlock() + if !problem.IsZero() { + e.logf("open-conn-track: timeout opening %v; peer reported problem: %v", flow, problem) + } + // Diagnose why it might've timed out. n, ok := e.magicConn.PeerForIP(flow.Dst.IP) if !ok { @@ -133,7 +159,7 @@ func (e *userspaceEngine) onOpenTimeout(flow flowtrack.Tuple) { lastSeen = *n.LastSeen } - var ps *PeerStatus + var ps *ipnstate.PeerStatusLite if st, err := e.getStatus(); err == nil { for _, v := range st.Peers { if v.NodeKey == n.Key { diff --git a/wgengine/router/router.go b/wgengine/router/router.go index c65a0b806..9c3f1003f 100644 --- a/wgengine/router/router.go +++ b/wgengine/router/router.go @@ -11,6 +11,7 @@ import ( "github.com/tailscale/wireguard-go/tun" "inet.af/netaddr" "tailscale.com/types/logger" + "tailscale.com/types/preftype" "tailscale.com/wgengine/router/dns" ) @@ -53,29 +54,6 @@ func Cleanup(logf logger.Logf, interfaceName string) { cleanup(logf, interfaceName) } -// NetfilterMode is the firewall management mode to use when -// programming the Linux network stack. -type NetfilterMode int - -const ( - NetfilterOff NetfilterMode = iota // remove all tailscale netfilter state - NetfilterNoDivert // manage tailscale chains, but don't call them - NetfilterOn // manage tailscale chains and call them from main chains -) - -func (m NetfilterMode) String() string { - switch m { - case NetfilterOff: - return "off" - case NetfilterNoDivert: - return "nodivert" - case NetfilterOn: - return "on" - default: - return "???" - } -} - // Config is the subset of Tailscale configuration that is relevant to // the OS's network stack. type Config struct { @@ -86,9 +64,9 @@ type Config struct { // Linux-only things below, ignored on other platforms. - SubnetRoutes []netaddr.IPPrefix // subnets being advertised to other Tailscale nodes - SNATSubnetRoutes bool // SNAT traffic to local subnets - NetfilterMode NetfilterMode // how much to manage netfilter rules + SubnetRoutes []netaddr.IPPrefix // subnets being advertised to other Tailscale nodes + SNATSubnetRoutes bool // SNAT traffic to local subnets + NetfilterMode preftype.NetfilterMode // how much to manage netfilter rules } // shutdownConfig is a routing configuration that removes all router diff --git a/wgengine/router/router_linux.go b/wgengine/router/router_linux.go index d6e10dac1..c3724d77f 100644 --- a/wgengine/router/router_linux.go +++ b/wgengine/router/router_linux.go @@ -21,10 +21,17 @@ import ( "inet.af/netaddr" "tailscale.com/net/tsaddr" "tailscale.com/types/logger" + "tailscale.com/types/preftype" "tailscale.com/version/distro" "tailscale.com/wgengine/router/dns" ) +const ( + netfilterOff = preftype.NetfilterOff + netfilterNoDivert = preftype.NetfilterNoDivert + netfilterOn = preftype.NetfilterOn +) + // The following bits are added to packet marks for Tailscale use. // // We tried to pick bits sufficiently out of the way that it's @@ -89,7 +96,7 @@ type linuxRouter struct { addrs map[netaddr.IPPrefix]bool routes map[netaddr.IPPrefix]bool snatSubnetRoutes bool - netfilterMode NetfilterMode + netfilterMode preftype.NetfilterMode // Various feature checks for the network stack. ipRuleAvailable bool @@ -148,7 +155,7 @@ func newUserspaceRouterAdvanced(logf logger.Logf, tunname string, netfilter4, ne return &linuxRouter{ logf: logf, tunname: tunname, - netfilterMode: NetfilterOff, + netfilterMode: netfilterOff, ipRuleAvailable: ipRuleAvailable, v6Available: supportsV6, @@ -168,7 +175,7 @@ func (r *linuxRouter) Up() error { if err := r.addIPRules(); err != nil { return err } - if err := r.setNetfilterMode(NetfilterOff); err != nil { + if err := r.setNetfilterMode(netfilterOff); err != nil { return err } if err := r.upInterface(); err != nil { @@ -188,7 +195,7 @@ func (r *linuxRouter) Close() error { if err := r.delIPRules(); err != nil { return err } - if err := r.setNetfilterMode(NetfilterOff); err != nil { + if err := r.setNetfilterMode(netfilterOff); err != nil { return err } @@ -246,9 +253,9 @@ func (r *linuxRouter) Set(cfg *Config) error { // mode. Netfilter state is created or deleted appropriately to // reflect the new mode, and r.snatSubnetRoutes is updated to reflect // the current state of subnet SNATing. -func (r *linuxRouter) setNetfilterMode(mode NetfilterMode) error { +func (r *linuxRouter) setNetfilterMode(mode preftype.NetfilterMode) error { if distro.Get() == distro.Synology { - mode = NetfilterOff + mode = netfilterOff } if r.netfilterMode == mode { return nil @@ -264,9 +271,9 @@ func (r *linuxRouter) setNetfilterMode(mode NetfilterMode) error { reprocess := false switch mode { - case NetfilterOff: + case netfilterOff: switch r.netfilterMode { - case NetfilterNoDivert: + case netfilterNoDivert: if err := r.delNetfilterBase(); err != nil { return err } @@ -276,7 +283,7 @@ func (r *linuxRouter) setNetfilterMode(mode NetfilterMode) error { // This can happen if someone left a ref to // this table somewhere else. } - case NetfilterOn: + case netfilterOn: if err := r.delNetfilterHooks(); err != nil { return err } @@ -291,9 +298,9 @@ func (r *linuxRouter) setNetfilterMode(mode NetfilterMode) error { } } r.snatSubnetRoutes = false - case NetfilterNoDivert: + case netfilterNoDivert: switch r.netfilterMode { - case NetfilterOff: + case netfilterOff: reprocess = true if err := r.addNetfilterChains(); err != nil { return err @@ -302,12 +309,12 @@ func (r *linuxRouter) setNetfilterMode(mode NetfilterMode) error { return err } r.snatSubnetRoutes = false - case NetfilterOn: + case netfilterOn: if err := r.delNetfilterHooks(); err != nil { return err } } - case NetfilterOn: + case netfilterOn: // Because of bugs in old version of iptables-compat, // we can't add a "-j ts-forward" rule to FORWARD // while ts-forward contains an "-m mark" rule. But @@ -315,7 +322,7 @@ func (r *linuxRouter) setNetfilterMode(mode NetfilterMode) error { // So we have to delNetFilterBase, then add the hooks, // then re-addNetFilterBase, just in case. switch r.netfilterMode { - case NetfilterOff: + case netfilterOff: reprocess = true if err := r.addNetfilterChains(); err != nil { return err @@ -330,7 +337,7 @@ func (r *linuxRouter) setNetfilterMode(mode NetfilterMode) error { return err } r.snatSubnetRoutes = false - case NetfilterNoDivert: + case netfilterNoDivert: reprocess = true if err := r.delNetfilterBase(); err != nil { return err @@ -397,7 +404,7 @@ func (r *linuxRouter) delAddress(addr netaddr.IPPrefix) error { // addLoopbackRule adds a firewall rule to permit loopback traffic to // a local Tailscale IP. func (r *linuxRouter) addLoopbackRule(addr netaddr.IP) error { - if r.netfilterMode == NetfilterOff { + if r.netfilterMode == netfilterOff { return nil } @@ -419,7 +426,7 @@ func (r *linuxRouter) addLoopbackRule(addr netaddr.IP) error { // delLoopbackRule removes the firewall rule permitting loopback // traffic to a Tailscale IP. func (r *linuxRouter) delLoopbackRule(addr netaddr.IP) error { - if r.netfilterMode == NetfilterOff { + if r.netfilterMode == netfilterOff { return nil } @@ -903,7 +910,7 @@ func (r *linuxRouter) delNetfilterHooks() error { // addSNATRule adds a netfilter rule to SNAT traffic destined for // local subnets. func (r *linuxRouter) addSNATRule() error { - if r.netfilterMode == NetfilterOff { + if r.netfilterMode == netfilterOff { return nil } @@ -922,7 +929,7 @@ func (r *linuxRouter) addSNATRule() error { // delSNATRule removes the netfilter rule to SNAT traffic destined for // local subnets. Fails if the rule does not exist. func (r *linuxRouter) delSNATRule() error { - if r.netfilterMode == NetfilterOff { + if r.netfilterMode == netfilterOff { return nil } diff --git a/wgengine/router/router_linux_test.go b/wgengine/router/router_linux_test.go index bcc93af8f..8298c6d07 100644 --- a/wgengine/router/router_linux_test.go +++ b/wgengine/router/router_linux_test.go @@ -58,7 +58,7 @@ up` + basic, name: "local addr only", in: &Config{ LocalAddrs: mustCIDRs("100.101.102.103/10"), - NetfilterMode: NetfilterOff, + NetfilterMode: netfilterOff, }, want: ` up @@ -70,7 +70,7 @@ ip addr add 100.101.102.103/10 dev tailscale0` + basic, in: &Config{ LocalAddrs: mustCIDRs("100.101.102.103/10"), Routes: mustCIDRs("100.100.100.100/32", "192.168.16.0/24"), - NetfilterMode: NetfilterOff, + NetfilterMode: netfilterOff, }, want: ` up @@ -85,7 +85,7 @@ ip route add 192.168.16.0/24 dev tailscale0 table 52` + basic, LocalAddrs: mustCIDRs("100.101.102.103/10"), Routes: mustCIDRs("100.100.100.100/32", "192.168.16.0/24"), SubnetRoutes: mustCIDRs("200.0.0.0/8"), - NetfilterMode: NetfilterOff, + NetfilterMode: netfilterOff, }, want: ` up @@ -101,7 +101,7 @@ ip route add 192.168.16.0/24 dev tailscale0 table 52` + basic, Routes: mustCIDRs("100.100.100.100/32", "10.0.0.0/8"), SubnetRoutes: mustCIDRs("200.0.0.0/8"), SNATSubnetRoutes: true, - NetfilterMode: NetfilterOn, + NetfilterMode: netfilterOn, }, want: ` up @@ -133,7 +133,7 @@ v6/nat/ts-postrouting -m mark --mark 0x40000 -j MASQUERADE in: &Config{ LocalAddrs: mustCIDRs("100.101.102.104/10"), Routes: mustCIDRs("100.100.100.100/32", "10.0.0.0/8"), - NetfilterMode: NetfilterOn, + NetfilterMode: netfilterOn, }, want: ` up @@ -166,7 +166,7 @@ v6/nat/POSTROUTING -j ts-postrouting Routes: mustCIDRs("100.100.100.100/32", "10.0.0.0/8"), SubnetRoutes: mustCIDRs("200.0.0.0/8"), SNATSubnetRoutes: false, - NetfilterMode: NetfilterOn, + NetfilterMode: netfilterOn, }, want: ` up @@ -196,7 +196,7 @@ v6/nat/POSTROUTING -j ts-postrouting in: &Config{ LocalAddrs: mustCIDRs("100.101.102.104/10"), Routes: mustCIDRs("100.100.100.100/32", "10.0.0.0/8"), - NetfilterMode: NetfilterOn, + NetfilterMode: netfilterOn, }, want: ` up @@ -227,7 +227,7 @@ v6/nat/POSTROUTING -j ts-postrouting in: &Config{ LocalAddrs: mustCIDRs("100.101.102.104/10"), Routes: mustCIDRs("100.100.100.100/32", "10.0.0.0/8"), - NetfilterMode: NetfilterNoDivert, + NetfilterMode: netfilterNoDivert, }, want: ` up @@ -251,7 +251,7 @@ v6/filter/ts-forward -o tailscale0 -j ACCEPT in: &Config{ LocalAddrs: mustCIDRs("100.101.102.104/10"), Routes: mustCIDRs("100.100.100.100/32", "10.0.0.0/8"), - NetfilterMode: NetfilterOn, + NetfilterMode: netfilterOn, }, want: ` up diff --git a/wgengine/router/router_userspace_bsd.go b/wgengine/router/router_userspace_bsd.go index 33848c8bb..fb81d62fb 100644 --- a/wgengine/router/router_userspace_bsd.go +++ b/wgengine/router/router_userspace_bsd.go @@ -7,7 +7,6 @@ package router import ( - "errors" "fmt" "log" "os/exec" @@ -23,7 +22,7 @@ import ( type userspaceBSDRouter struct { logf logger.Logf tunname string - local netaddr.IPPrefix + local []netaddr.IPPrefix routes map[netaddr.IPPrefix]struct{} dns *dns.Manager @@ -47,6 +46,38 @@ func newUserspaceBSDRouter(logf logger.Logf, _ *device.Device, tundev tun.Device }, nil } +func (r *userspaceBSDRouter) addrsToRemove(newLocalAddrs []netaddr.IPPrefix) (remove []netaddr.IPPrefix) { + for _, cur := range r.local { + found := false + for _, v := range newLocalAddrs { + found = (v == cur) + if found { + break + } + } + if !found { + remove = append(remove, cur) + } + } + return +} + +func (r *userspaceBSDRouter) addrsToAdd(newLocalAddrs []netaddr.IPPrefix) (add []netaddr.IPPrefix) { + for _, cur := range newLocalAddrs { + found := false + for _, v := range r.local { + found = (v == cur) + if found { + break + } + } + if !found { + add = append(add, cur) + } + } + return +} + func cmd(args ...string) *exec.Cmd { if len(args) == 0 { log.Fatalf("exec.Cmd(%#v) invalid; need argv[0]", args) @@ -63,45 +94,40 @@ func (r *userspaceBSDRouter) Up() error { return nil } -func (r *userspaceBSDRouter) Set(cfg *Config) error { +func inet(p netaddr.IPPrefix) string { + if p.IP.Is6() { + return "inet6" + } + return "inet" +} + +func (r *userspaceBSDRouter) Set(cfg *Config) (reterr error) { if cfg == nil { cfg = &shutdownConfig } - if len(cfg.LocalAddrs) == 0 { - return nil - } - // TODO: support configuring multiple local addrs on interface. - if len(cfg.LocalAddrs) != 1 { - return errors.New("freebsd doesn't support setting multiple local addrs yet") - } - localAddr := cfg.LocalAddrs[0] var errq error - - // Update the address. - if localAddr != r.local { - // If the interface is already set, remove it. - if !r.local.IsZero() { - addrdel := []string{"ifconfig", r.tunname, - "inet", r.local.String(), "-alias"} - out, err := cmd(addrdel...).CombinedOutput() - if err != nil { - r.logf("addr del failed: %v: %v\n%s", addrdel, err, out) - if errq == nil { - errq = err - } - } + setErr := func(err error) { + if errq == nil { + errq = err } + } - // Add the interface. - addradd := []string{"ifconfig", r.tunname, - "inet", localAddr.String(), localAddr.IP.String()} - out, err := cmd(addradd...).CombinedOutput() + // Update the addresses. + for _, addr := range r.addrsToRemove(cfg.LocalAddrs) { + arg := []string{"ifconfig", r.tunname, inet(addr), addr.String(), "-alias"} + out, err := cmd(arg...).CombinedOutput() if err != nil { - r.logf("addr add failed: %v: %v\n%s", addradd, err, out) - if errq == nil { - errq = err - } + r.logf("addr del failed: %v => %v\n%s", arg, err, out) + setErr(err) + } + } + for _, addr := range r.addrsToAdd(cfg.LocalAddrs) { + arg := []string{"ifconfig", r.tunname, inet(addr), addr.String(), addr.IP.String()} + out, err := cmd(arg...).CombinedOutput() + if err != nil { + r.logf("addr add failed: %v => %v\n%s", arg, err, out) + setErr(err) } } @@ -120,14 +146,12 @@ func (r *userspaceBSDRouter) Set(cfg *Config) error { del = "delete" } routedel := []string{"route", "-q", "-n", - del, "-inet", nstr, + del, "-" + inet(route), nstr, "-iface", r.tunname} out, err := cmd(routedel...).CombinedOutput() if err != nil { r.logf("route del failed: %v: %v\n%s", routedel, err, out) - if errq == nil { - errq = err - } + setErr(err) } } } @@ -138,24 +162,25 @@ func (r *userspaceBSDRouter) Set(cfg *Config) error { nip := net.IP.Mask(net.Mask) nstr := fmt.Sprintf("%v/%d", nip, route.Bits) routeadd := []string{"route", "-q", "-n", - "add", "-inet", nstr, + "add", "-" + inet(route), nstr, "-iface", r.tunname} out, err := cmd(routeadd...).CombinedOutput() if err != nil { r.logf("addr add failed: %v: %v\n%s", routeadd, err, out) - if errq == nil { - errq = err - } + setErr(err) } } } // Store the interface and routes so we know what to change on an update. - r.local = localAddr + if errq == nil { + r.local = append([]netaddr.IPPrefix{}, cfg.LocalAddrs...) + } r.routes = newRoutes if err := r.dns.Set(cfg.DNS); err != nil { - errq = fmt.Errorf("dns set: %v", err) + r.logf("DNS set: %v", err) + setErr(err) } return errq diff --git a/wgengine/router/router_windows.go b/wgengine/router/router_windows.go index 0194ef0a1..b600709d3 100644 --- a/wgengine/router/router_windows.go +++ b/wgengine/router/router_windows.go @@ -7,6 +7,7 @@ package router import ( "context" "fmt" + "os" "os/exec" "sync" "syscall" @@ -121,11 +122,12 @@ func cleanup(logf logger.Logf, interfaceName string) { type firewallTweaker struct { logf logger.Logf - mu sync.Mutex - running bool // doAsyncSet goroutine is running - known bool // firewall is in known state (in lastVal) - want []string // next value we want, or "" to delete the firewall rule - lastVal []string // last set value, if known + mu sync.Mutex + didProcRule bool + running bool // doAsyncSet goroutine is running + known bool // firewall is in known state (in lastVal) + want []string // next value we want, or "" to delete the firewall rule + lastVal []string // last set value, if known } func (ft *firewallTweaker) clear() { ft.set(nil) } @@ -177,6 +179,7 @@ func (ft *firewallTweaker) doAsyncSet() { return } needClear := !ft.known || len(ft.lastVal) > 0 || len(val) == 0 + needProcRule := !ft.didProcRule ft.mu.Unlock() if needClear { @@ -189,6 +192,37 @@ func (ft *firewallTweaker) doAsyncSet() { d, _ := ft.runFirewall("delete", "rule", "name=Tailscale-In", "dir=in") ft.logf("cleared Tailscale-In firewall rules in %v", d) } + if needProcRule { + ft.logf("deleting any prior Tailscale-Process rule...") + d, err := ft.runFirewall("delete", "rule", "name=Tailscale-Process", "dir=in") // best effort + if err == nil { + ft.logf("removed old Tailscale-Process rule in %v", d) + } + var exe string + exe, err = os.Executable() + if err != nil { + ft.logf("failed to find Executable for Tailscale-Process rule: %v", err) + } else { + ft.logf("adding Tailscale-Process rule to allow UDP for %q ...", exe) + d, err = ft.runFirewall("add", "rule", "name=Tailscale-Process", + "dir=in", + "action=allow", + "edge=yes", + "program="+exe, + "protocol=udp", + "profile=any", + "enable=yes", + ) + if err != nil { + ft.logf("error adding Tailscale-Process rule: %v", err) + } else { + ft.mu.Lock() + ft.didProcRule = true + ft.mu.Unlock() + ft.logf("added Tailscale-Process rule in %v", d) + } + } + } var err error for _, cidr := range val { ft.logf("adding Tailscale-In rule to allow %v ...", cidr) diff --git a/wgengine/tsdns/tsdns_server_test.go b/wgengine/tsdns/tsdns_server_test.go index bffb8b869..df9047fc6 100644 --- a/wgengine/tsdns/tsdns_server_test.go +++ b/wgengine/tsdns/tsdns_server_test.go @@ -5,6 +5,9 @@ package tsdns import ( + "log" + "testing" + "github.com/miekg/dns" "inet.af/netaddr" ) @@ -71,7 +74,7 @@ func resolveToNXDOMAIN(w dns.ResponseWriter, req *dns.Msg) { w.WriteMsg(m) } -func serveDNS(addr string) (*dns.Server, chan error) { +func serveDNS(tb testing.TB, addr string) (*dns.Server, chan error) { server := &dns.Server{Addr: addr, Net: "udp"} waitch := make(chan struct{}) @@ -79,7 +82,11 @@ func serveDNS(addr string) (*dns.Server, chan error) { errch := make(chan error, 1) go func() { - errch <- server.ListenAndServe() + err := server.ListenAndServe() + if err != nil { + log.Printf("ListenAndServe(%q): %v", addr, err) + } + errch <- err close(errch) }() diff --git a/wgengine/tsdns/tsdns_test.go b/wgengine/tsdns/tsdns_test.go index 95d32dfbb..a2f56a168 100644 --- a/wgengine/tsdns/tsdns_test.go +++ b/wgengine/tsdns/tsdns_test.go @@ -274,14 +274,27 @@ func TestResolveReverse(t *testing.T) { } } +func ipv6Works() bool { + c, err := net.Listen("tcp", "[::1]:0") + if err != nil { + return false + } + c.Close() + return true +} + func TestDelegate(t *testing.T) { tstest.ResourceCheck(t) + if !ipv6Works() { + t.Skip("skipping test that requires localhost IPv6") + } + dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6, "dns.test.site.")) dnsHandleFunc("nxdomain.site.", resolveToNXDOMAIN) - v4server, v4errch := serveDNS("127.0.0.1:0") - v6server, v6errch := serveDNS("[::1]:0") + v4server, v4errch := serveDNS(t, "127.0.0.1:0") + v6server, v6errch := serveDNS(t, "[::1]:0") defer func() { if err := <-v4errch; err != nil { @@ -371,7 +384,7 @@ func TestDelegate(t *testing.T) { func TestDelegateCollision(t *testing.T) { dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6, "dns.test.site.")) - server, errch := serveDNS("127.0.0.1:0") + server, errch := serveDNS(t, "127.0.0.1:0") defer func() { if err := <-errch; err != nil { t.Errorf("server error: %v", err) @@ -473,7 +486,7 @@ func TestConcurrentSetMap(t *testing.T) { func TestConcurrentSetUpstreams(t *testing.T) { dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6, "dns.test.site.")) - server, errch := serveDNS("127.0.0.1:0") + server, errch := serveDNS(t, "127.0.0.1:0") defer func() { if err := <-errch; err != nil { t.Errorf("server error: %v", err) @@ -752,7 +765,7 @@ func TestTrimRDNSBonjourPrefix(t *testing.T) { func BenchmarkFull(b *testing.B) { dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6, "dns.test.site.")) - server, errch := serveDNS("127.0.0.1:0") + server, errch := serveDNS(b, "127.0.0.1:0") defer func() { if err := <-errch; err != nil { b.Errorf("server error: %v", err) diff --git a/wgengine/tstun/tun.go b/wgengine/tstun/tun.go index 8a68f40f0..92af1b8b0 100644 --- a/wgengine/tstun/tun.go +++ b/wgengine/tstun/tun.go @@ -215,7 +215,17 @@ func (t *TUN) poll() { } } +var magicDNSIPPort = netaddr.MustParseIPPort("100.100.100.100:0") + func (t *TUN) filterOut(p *packet.Parsed) filter.Response { + // Fake ICMP echo responses to MagicDNS (100.100.100.100). + if p.IsEchoRequest() && p.Dst == magicDNSIPPort { + header := p.ICMP4Header() + header.ToResponse() + outp := packet.Generate(&header, p.Payload()) + t.InjectInboundCopy(outp) + return filter.DropSilently // don't pass on to OS; already handled + } if t.PreFilterOut != nil { if res := t.PreFilterOut(p, t); res.IsDrop() { @@ -259,6 +269,8 @@ func (t *TUN) IdleDuration() time.Duration { func (t *TUN) Read(buf []byte, offset int) (int, error) { var n int + wasInjectedPacket := false + select { case <-t.closed: return 0, io.EOF @@ -273,9 +285,7 @@ func (t *TUN) Read(buf []byte, offset int) (int, error) { t.bufferConsumed <- struct{}{} } else { // If the packet is not from t.buffer, then it is an injected packet. - // In this case, we return early to bypass filtering - t.noteActivity() - return n, nil + wasInjectedPacket = true } } @@ -289,6 +299,12 @@ func (t *TUN) Read(buf []byte, offset int) (int, error) { } } + // For injected packets, we return early to bypass filtering. + if wasInjectedPacket { + t.noteActivity() + return n, nil + } + if !t.disableFilter { response := t.filterOut(p) if response != filter.Accept { diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 8638b3d38..f3ce131c9 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -36,6 +36,7 @@ import ( "tailscale.com/tailcfg" "tailscale.com/types/key" "tailscale.com/types/logger" + "tailscale.com/types/netmap" "tailscale.com/types/wgkey" "tailscale.com/version" "tailscale.com/version/distro" @@ -170,16 +171,16 @@ func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16, impl FakeImplFu // NewUserspaceEngine creates the named tun device and returns a // Tailscale Engine running on it. -func NewUserspaceEngine(logf logger.Logf, tunname string, listenPort uint16) (Engine, error) { - if tunname == "" { +func NewUserspaceEngine(logf logger.Logf, tunName string, listenPort uint16) (Engine, error) { + if tunName == "" { return nil, fmt.Errorf("--tun name must not be blank") } - logf("Starting userspace wireguard engine with tun device %q", tunname) + logf("Starting userspace wireguard engine with tun device %q", tunName) - tun, err := tun.CreateTUN(tunname, minimalMTU) + tun, err := tun.CreateTUN(tunName, minimalMTU) if err != nil { - diagnoseTUNFailure(logf) + diagnoseTUNFailure(tunName, logf) logf("CreateTUN: %v", err) return nil, err } @@ -308,16 +309,20 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) { // Ping every single-IP that peer routes. // These synthetic packets are used to traverse NATs. var ips []netaddr.IP - allowedIPs := deviceAllowedIPs.EntriesForPeer(peer) - for _, ipNet := range allowedIPs { - if ones, bits := ipNet.Mask.Size(); ones == bits && ones != 0 { - ip, ok := netaddr.FromStdIP(ipNet.IP) - if !ok { - continue - } + var allowedIPs []netaddr.IPPrefix + deviceAllowedIPs.EntriesForPeer(peer, func(stdIP net.IP, cidr uint) bool { + ip, ok := netaddr.FromStdIP(stdIP) + if !ok { + logf("[unexpected] bad IP from deviceAllowedIPs.EntriesForPeer: %v", stdIP) + return true + } + ipp := netaddr.IPPrefix{IP: ip, Bits: uint8(cidr)} + allowedIPs = append(allowedIPs, ipp) + if ipp.IsSingleIP() { ips = append(ips, ip) } - } + return true + }) if len(ips) > 0 { go e.pinger(peerWGKey, ips) } else { @@ -1070,20 +1075,15 @@ func (e *userspaceEngine) getStatus() (*Status, error) { defer pw.Close() // TODO(apenwarr): get rid of silly uapi stuff for in-process comms // FIXME: get notified of status changes instead of polling. - filter := device.IPCGetFilter{ - // The allowed_ips are somewhat expensive to compute and they're - // unused below; request that they not be sent instead. - FilterAllowedIPs: true, - } - err := e.wgdev.IpcGetOperationFiltered(pw, filter) + err := e.wgdev.IpcGetOperation(pw) if err != nil { err = fmt.Errorf("IpcGetOperation: %w", err) } errc <- err }() - pp := make(map[wgkey.Key]*PeerStatus) - p := &PeerStatus{} + pp := make(map[wgkey.Key]*ipnstate.PeerStatusLite) + p := &ipnstate.PeerStatusLite{} var hst1, hst2, n int64 @@ -1115,20 +1115,20 @@ func (e *userspaceEngine) getStatus() (*Status, error) { if err != nil { return nil, fmt.Errorf("IpcGetOperation: invalid key in line %q", line) } - p = &PeerStatus{} + p = &ipnstate.PeerStatusLite{} pp[wgkey.Key(pk)] = p key := tailcfg.NodeKey(pk) p.NodeKey = key case "rx_bytes": n, err = mem.ParseInt(v, 10, 64) - p.RxBytes = ByteCount(n) + p.RxBytes = n if err != nil { return nil, fmt.Errorf("IpcGetOperation: rx_bytes invalid: %#v", line) } case "tx_bytes": n, err = mem.ParseInt(v, 10, 64) - p.TxBytes = ByteCount(n) + p.TxBytes = n if err != nil { return nil, fmt.Errorf("IpcGetOperation: tx_bytes invalid: %#v", line) } @@ -1154,7 +1154,7 @@ func (e *userspaceEngine) getStatus() (*Status, error) { e.mu.Lock() defer e.mu.Unlock() - var peers []PeerStatus + var peers []ipnstate.PeerStatusLite for _, pk := range e.peerSequence { if p, ok := pp[pk]; ok { // ignore idle ones not in wireguard-go's config peers = append(peers, *p) @@ -1320,7 +1320,7 @@ func (e *userspaceEngine) SetDERPMap(dm *tailcfg.DERPMap) { e.magicConn.SetDERPMap(dm) } -func (e *userspaceEngine) SetNetworkMap(nm *controlclient.NetworkMap) { +func (e *userspaceEngine) SetNetworkMap(nm *netmap.NetworkMap) { e.magicConn.SetNetworkMap(nm) e.mu.Lock() callbacks := make([]NetworkMapCallback, 0, 4) @@ -1363,16 +1363,27 @@ func (e *userspaceEngine) Ping(ip netaddr.IP, cb func(*ipnstate.PingResult)) { // 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(logf logger.Logf) { +func diagnoseTUNFailure(tunName string, logf logger.Logf) { switch runtime.GOOS { case "linux": - diagnoseLinuxTUNFailure(logf) + diagnoseLinuxTUNFailure(tunName, logf) + case "darwin": + diagnoseDarwinTUNFailure(tunName, logf) default: logf("no TUN failure diagnostics for OS %q", runtime.GOOS) } } -func diagnoseLinuxTUNFailure(logf logger.Logf) { +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 { diff --git a/wgengine/watchdog.go b/wgengine/watchdog.go index 91b5fe04e..130ce4610 100644 --- a/wgengine/watchdog.go +++ b/wgengine/watchdog.go @@ -13,10 +13,10 @@ import ( "time" "inet.af/netaddr" - "tailscale.com/control/controlclient" "tailscale.com/ipn/ipnstate" "tailscale.com/net/interfaces" "tailscale.com/tailcfg" + "tailscale.com/types/netmap" "tailscale.com/wgengine/filter" "tailscale.com/wgengine/router" "tailscale.com/wgengine/tsdns" @@ -107,7 +107,7 @@ func (e *watchdogEngine) SetLinkChangeCallback(cb func(major bool, newState *int func (e *watchdogEngine) SetDERPMap(m *tailcfg.DERPMap) { e.watchdog("SetDERPMap", func() { e.wrap.SetDERPMap(m) }) } -func (e *watchdogEngine) SetNetworkMap(nm *controlclient.NetworkMap) { +func (e *watchdogEngine) SetNetworkMap(nm *netmap.NetworkMap) { e.watchdog("SetNetworkMap", func() { e.wrap.SetNetworkMap(nm) }) } func (e *watchdogEngine) AddNetworkMapCallback(callback NetworkMapCallback) func() { diff --git a/wgengine/wgcfg/config.go b/wgengine/wgcfg/config.go index af86b36d6..2928e47d2 100644 --- a/wgengine/wgcfg/config.go +++ b/wgengine/wgcfg/config.go @@ -9,6 +9,11 @@ import ( "inet.af/netaddr" ) +// EndpointDiscoSuffix is appended to the hex representation of a peer's discovery key +// and is then the sole wireguard endpoint for peers with a non-zero discovery key. +// This form is then recognize by magicsock's CreateEndpoint. +const EndpointDiscoSuffix = ".disco.tailscale:12345" + // Config is a WireGuard configuration. // It only supports the set of things Tailscale uses. type Config struct { diff --git a/wgengine/wgcfg/nmcfg/nmcfg.go b/wgengine/wgcfg/nmcfg/nmcfg.go new file mode 100644 index 000000000..36dc065c8 --- /dev/null +++ b/wgengine/wgcfg/nmcfg/nmcfg.go @@ -0,0 +1,127 @@ +// Copyright (c) 2020 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 nmcfg converts a controlclient.NetMap into a wgcfg config. +package nmcfg + +import ( + "fmt" + "net" + "strconv" + "strings" + + "inet.af/netaddr" + "tailscale.com/control/controlclient" + "tailscale.com/net/tsaddr" + "tailscale.com/tailcfg" + "tailscale.com/types/logger" + "tailscale.com/types/netmap" + "tailscale.com/wgengine/wgcfg" +) + +func nodeDebugName(n *tailcfg.Node) string { + name := n.Name + if name == "" { + name = n.Hostinfo.Hostname + } + if i := strings.Index(name, "."); i != -1 { + name = name[:i] + } + if name == "" && len(n.Addresses) != 0 { + return n.Addresses[0].String() + } + return name +} + +// cidrIsSubnet reports whether cidr is a non-default-route subnet +// exported by node that is not one of its own self addresses. +func cidrIsSubnet(node *tailcfg.Node, cidr netaddr.IPPrefix) bool { + if cidr.Bits == 0 { + return false + } + if !cidr.IsSingleIP() { + return true + } + for _, selfCIDR := range node.Addresses { + if cidr == selfCIDR { + return false + } + } + return true +} + +// WGCfg returns the NetworkMaps's Wireguard configuration. +func WGCfg(nm *netmap.NetworkMap, logf logger.Logf, flags netmap.WGConfigFlags) (*wgcfg.Config, error) { + cfg := &wgcfg.Config{ + Name: "tailscale", + PrivateKey: wgcfg.PrivateKey(nm.PrivateKey), + Addresses: nm.Addresses, + ListenPort: nm.LocalPort, + Peers: make([]wgcfg.Peer, 0, len(nm.Peers)), + } + + for _, peer := range nm.Peers { + if controlclient.Debug.OnlyDisco && peer.DiscoKey.IsZero() { + continue + } + cfg.Peers = append(cfg.Peers, wgcfg.Peer{ + PublicKey: wgcfg.Key(peer.Key), + }) + cpeer := &cfg.Peers[len(cfg.Peers)-1] + if peer.KeepAlive { + cpeer.PersistentKeepalive = 25 // seconds + } + + if !peer.DiscoKey.IsZero() { + if err := appendEndpoint(cpeer, fmt.Sprintf("%x%s", peer.DiscoKey[:], wgcfg.EndpointDiscoSuffix)); err != nil { + return nil, err + } + cpeer.Endpoints = fmt.Sprintf("%x.disco.tailscale:12345", peer.DiscoKey[:]) + } else { + if err := appendEndpoint(cpeer, peer.DERP); err != nil { + return nil, err + } + for _, ep := range peer.Endpoints { + if err := appendEndpoint(cpeer, ep); err != nil { + return nil, err + } + } + } + for _, allowedIP := range peer.AllowedIPs { + if allowedIP.IsSingleIP() && tsaddr.IsTailscaleIP(allowedIP.IP) && (flags&netmap.AllowSingleHosts) == 0 { + logf("[v1] wgcfg: skipping node IP %v from %q (%v)", + allowedIP.IP, nodeDebugName(peer), peer.Key.ShortString()) + continue + } else if cidrIsSubnet(peer, allowedIP) { + if (flags & netmap.AllowSubnetRoutes) == 0 { + logf("[v1] wgcfg: not accepting subnet route %v from %q (%v)", + allowedIP, nodeDebugName(peer), peer.Key.ShortString()) + continue + } + } + cpeer.AllowedIPs = append(cpeer.AllowedIPs, allowedIP) + } + } + + return cfg, nil +} + +func appendEndpoint(peer *wgcfg.Peer, epStr string) error { + if epStr == "" { + return nil + } + _, port, err := net.SplitHostPort(epStr) + if err != nil { + return fmt.Errorf("malformed endpoint %q for peer %v", epStr, peer.PublicKey.ShortString()) + } + _, err = strconv.ParseUint(port, 10, 16) + if err != nil { + return fmt.Errorf("invalid port in endpoint %q for peer %v", epStr, peer.PublicKey.ShortString()) + } + if peer.Endpoints != "" { + peer.Endpoints += "," + } + peer.Endpoints += epStr + return nil +} diff --git a/wgengine/wgengine.go b/wgengine/wgengine.go index 563888083..257d59f26 100644 --- a/wgengine/wgengine.go +++ b/wgengine/wgengine.go @@ -6,36 +6,23 @@ package wgengine import ( "errors" - "time" "inet.af/netaddr" - "tailscale.com/control/controlclient" "tailscale.com/ipn/ipnstate" "tailscale.com/net/interfaces" "tailscale.com/tailcfg" + "tailscale.com/types/netmap" "tailscale.com/wgengine/filter" "tailscale.com/wgengine/router" "tailscale.com/wgengine/tsdns" "tailscale.com/wgengine/wgcfg" ) -// ByteCount is the number of bytes that have been sent or received. -// -// TODO: why is this a type? remove? -// TODO: document whether it's payload bytes only or if it includes framing overhead. -type ByteCount int64 - -type PeerStatus struct { - TxBytes, RxBytes ByteCount - LastHandshake time.Time - NodeKey tailcfg.NodeKey -} - // Status is the Engine status. // // TODO(bradfitz): remove this, subset of ipnstate? Need to migrate users. type Status struct { - Peers []PeerStatus + Peers []ipnstate.PeerStatusLite LocalAddrs []string // the set of possible endpoints for the magic conn DERPs int // number of active DERP connections } @@ -51,7 +38,7 @@ type NetInfoCallback func(*tailcfg.NetInfo) // NetworkMapCallback is the type used by callbacks that hook // into network map updates. -type NetworkMapCallback func(*controlclient.NetworkMap) +type NetworkMapCallback func(*netmap.NetworkMap) // someHandle is allocated so its pointer address acts as a unique // map key handle. (It needs to have non-zero size for Go to guarantee @@ -121,7 +108,7 @@ type Engine interface { // ignored as as it might be disabled; get it from SetDERPMap // instead. // The network map should only be read from. - SetNetworkMap(*controlclient.NetworkMap) + SetNetworkMap(*netmap.NetworkMap) // AddNetworkMapCallback adds a function to a list of callbacks // that are called when the network map updates. It returns a diff --git a/wgengine/wglog/wglog.go b/wgengine/wglog/wglog.go index 7786edd82..ed3827b4e 100644 --- a/wgengine/wglog/wglog.go +++ b/wgengine/wglog/wglog.go @@ -59,11 +59,9 @@ func NewLogger(logf logger.Logf) *Logger { // but there's not much we can do about that. logf("%s", new) } - std := logger.StdLogger(wrapper) ret.DeviceLogger = &device.Logger{ - Debug: std, - Info: std, - Error: std, + Verbosef: logger.WithPrefix(wrapper, "[v2] "), + Errorf: wrapper, } return ret } diff --git a/wgengine/wglog/wglog_test.go b/wgengine/wglog/wglog_test.go index 0b93a130a..077981e41 100644 --- a/wgengine/wglog/wglog_test.go +++ b/wgengine/wglog/wglog_test.go @@ -46,12 +46,11 @@ func TestLogger(t *testing.T) { // Then if logf also attempts to write into the channel, it'll fail. c <- "" } - x.DeviceLogger.Info.Println(tt.in) + x.DeviceLogger.Errorf(tt.in) got := <-c if tt.omit { continue } - tt.want += "\n" if got != tt.want { t.Errorf("Println(%q) = %q want %q", tt.in, got, tt.want) } diff --git a/wgengine/winnet/winnet.go b/wgengine/winnet/winnet.go index be76fd9ca..086b07638 100644 --- a/wgengine/winnet/winnet.go +++ b/wgengine/winnet/winnet.go @@ -2,13 +2,16 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build windows + package winnet import ( "fmt" + "unsafe" + "github.com/go-ole/go-ole" "github.com/go-ole/go-ole/oleutil" - "unsafe" ) const CLSID_NetworkListManager = "{DCB00C01-570F-4A9B-8D69-199FDBA5723B}" |
