summaryrefslogtreecommitdiffhomepage
path: root/wgengine
diff options
context:
space:
mode:
authorAleksandar Pesic <peske.nis@gmail.com>2021-02-17 15:29:21 +0100
committerAleksandar Pesic <peske.nis@gmail.com>2021-02-17 15:29:21 +0100
commit419edfca05a253a44161915bd96c1cc1df5fa163 (patch)
tree86de633a8529d6cc9f275d567d529aecfe4a037b /wgengine
parent3e2d69a26c407093523edf352744c689ae155d52 (diff)
parent7038c09bc91c7f65ff33afb777187ef9acca214c (diff)
downloadtailscale-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.go78
-rw-r--r--wgengine/magicsock/magicsock.go288
-rw-r--r--wgengine/magicsock/magicsock_test.go374
-rw-r--r--wgengine/monitor/monitor_darwin_tailscaled.go72
-rw-r--r--wgengine/monitor/monitor_unsupported.go2
-rw-r--r--wgengine/netstack/netstack.go4
-rw-r--r--wgengine/pendopen.go32
-rw-r--r--wgengine/router/router.go30
-rw-r--r--wgengine/router/router_linux.go45
-rw-r--r--wgengine/router/router_linux_test.go18
-rw-r--r--wgengine/router/router_userspace_bsd.go111
-rw-r--r--wgengine/router/router_windows.go44
-rw-r--r--wgengine/tsdns/tsdns_server_test.go11
-rw-r--r--wgengine/tsdns/tsdns_test.go23
-rw-r--r--wgengine/tstun/tun.go22
-rw-r--r--wgengine/userspace.go69
-rw-r--r--wgengine/watchdog.go4
-rw-r--r--wgengine/wgcfg/config.go5
-rw-r--r--wgengine/wgcfg/nmcfg/nmcfg.go127
-rw-r--r--wgengine/wgengine.go21
-rw-r--r--wgengine/wglog/wglog.go6
-rw-r--r--wgengine/wglog/wglog_test.go3
-rw-r--r--wgengine/winnet/winnet.go5
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}"