wgengine: expand lazy config to work with dual-stacked peers.

Lazy wg configuration now triggers if a peer has only endpoint
addresses (/32 for IPv4, /128 for IPv6). Subnet routers still
trigger eager configuration to avoid the need for a CIDR match
in the hot packet path.

Signed-off-by: David Anderson <danderson@tailscale.com>
This commit is contained in:
David Anderson 2020-12-15 02:31:33 -08:00 committed by Dave Anderson
parent aa353b8d0f
commit 891110e64c
2 changed files with 105 additions and 56 deletions

View File

@ -67,7 +67,8 @@ type TUN struct {
lastActivityAtomic int64 // unix seconds of last send or receive lastActivityAtomic int64 // unix seconds of last send or receive
destIPActivity atomic.Value // of map[packet.IP]func() destIPActivity4 atomic.Value // of map[packet.IP4]func()
destIPActivity6 atomic.Value // of map[packet.IP6]func()
// buffer stores the oldest unconsumed packet from tdev. // buffer stores the oldest unconsumed packet from tdev.
// It is made a static buffer in order to avoid allocations. // It is made a static buffer in order to avoid allocations.
@ -136,8 +137,9 @@ func WrapTUN(logf logger.Logf, tdev tun.Device) *TUN {
// destination (the map keys). // destination (the map keys).
// //
// The map ownership passes to the TUN. It must be non-nil. // The map ownership passes to the TUN. It must be non-nil.
func (t *TUN) SetDestIPActivityFuncs(m map[packet.IP4]func()) { func (t *TUN) SetDestIPActivityFuncs(m4 map[packet.IP4]func(), m6 map[packet.IP6]func()) {
t.destIPActivity.Store(m) t.destIPActivity4.Store(m4)
t.destIPActivity6.Store(m6)
} }
func (t *TUN) Close() error { func (t *TUN) Close() error {
@ -282,9 +284,18 @@ func (t *TUN) Read(buf []byte, offset int) (int, error) {
defer parsedPacketPool.Put(p) defer parsedPacketPool.Put(p)
p.Decode(buf[offset : offset+n]) p.Decode(buf[offset : offset+n])
if m, ok := t.destIPActivity.Load().(map[packet.IP4]func()); ok { switch p.IPVersion {
if fn := m[p.DstIP4]; fn != nil { case 4:
fn() if m, ok := t.destIPActivity4.Load().(map[packet.IP4]func()); ok {
if fn := m[p.DstIP4]; fn != nil {
fn()
}
}
case 6:
if m, ok := t.destIPActivity6.Load().(map[packet.IP6]func()); ok {
if fn := m[p.DstIP6]; fn != nil {
fn()
}
} }
} }

View File

@ -101,15 +101,17 @@ type userspaceEngine struct {
// incorrectly sent to us. // incorrectly sent to us.
localAddrs atomic.Value // of map[packet.IP4]bool localAddrs atomic.Value // of map[packet.IP4]bool
wgLock sync.Mutex // serializes all wgdev operations; see lock order comment below wgLock sync.Mutex // serializes all wgdev operations; see lock order comment below
lastCfgFull wgcfg.Config lastCfgFull wgcfg.Config
lastRouterSig string // of router.Config lastRouterSig string // of router.Config
lastEngineSigFull string // of full wireguard config lastEngineSigFull string // of full wireguard config
lastEngineSigTrim string // of trimmed wireguard config lastEngineSigTrim string // of trimmed wireguard config
recvActivityAt map[tailcfg.DiscoKey]time.Time recvActivityAt map[tailcfg.DiscoKey]time.Time
trimmedDisco map[tailcfg.DiscoKey]bool // set of disco keys of peers currently excluded from wireguard config trimmedDisco map[tailcfg.DiscoKey]bool // set of disco keys of peers currently excluded from wireguard config
sentActivityAt map[packet.IP4]*int64 // value is atomic int64 of unixtime sentActivityAt4 map[packet.IP4]*int64 // value is atomic int64 of unixtime
destIPActivityFuncs map[packet.IP4]func() destIPActivityFuncs4 map[packet.IP4]func()
sentActivityAt6 map[packet.IP6]*int64 // value is atomic int64 of unixtime
destIPActivityFuncs6 map[packet.IP6]func()
mu sync.Mutex // guards following; see lock order comment below mu sync.Mutex // guards following; see lock order comment below
closing bool // Close was called (even if we're still closing) closing bool // Close was called (even if we're still closing)
@ -631,21 +633,26 @@ func forceFullWireguardConfig(numPeers int) bool {
// simplicity, have only one IP address (an IPv4 /32), which is the // simplicity, have only one IP address (an IPv4 /32), which is the
// common case for most peers. Subnet router nodes will just always be // common case for most peers. Subnet router nodes will just always be
// created in the wireguard-go config. // created in the wireguard-go config.
//
// XXXXXXX DO NOT SUBMIT fix docstring
func isTrimmablePeer(p *wgcfg.Peer, numPeers int) bool { func isTrimmablePeer(p *wgcfg.Peer, numPeers int) bool {
if forceFullWireguardConfig(numPeers) { if forceFullWireguardConfig(numPeers) {
return false return false
} }
if len(p.AllowedIPs) != 1 || len(p.Endpoints) != 1 { if len(p.Endpoints) != 1 {
return false return false
} }
if !strings.HasSuffix(p.Endpoints[0].Host, ".disco.tailscale") { if !strings.HasSuffix(p.Endpoints[0].Host, ".disco.tailscale") {
return false return false
} }
aip := p.AllowedIPs[0]
// TODO: IPv6 support, once we support IPv6 within the tunnel. In that case, // AllowedIPs must all be single IPs, not subnets.
// len(p.AllowedIPs) probably will be more than 1. for _, aip := range p.AllowedIPs {
if aip.Mask != 32 || !aip.IP.Is4() { if aip.IP.Is4() && aip.Mask != 32 {
return false return false
} else if aip.IP.Is6() && aip.Mask != 128 {
return false
}
} }
return true return true
} }
@ -687,8 +694,17 @@ func (e *userspaceEngine) isActiveSince(dk tailcfg.DiscoKey, ip wgcfg.IP, t time
if e.recvActivityAt[dk].After(t) { if e.recvActivityAt[dk].After(t) {
return true return true
} }
pip := packet.IP4(binary.BigEndian.Uint32(ip.Addr[12:])) var (
timePtr, ok := e.sentActivityAt[pip] timePtr *int64
ok bool
)
if ip.Is4() {
pip := packet.IP4(binary.BigEndian.Uint32(ip.Addr[12:]))
timePtr, ok = e.sentActivityAt4[pip]
} else {
pip := packet.IP6FromRaw16(ip.Addr)
timePtr, ok = e.sentActivityAt6[pip]
}
if !ok { if !ok {
return false return false
} }
@ -829,45 +845,67 @@ func (e *userspaceEngine) updateActivityMapsLocked(trackDisco []tailcfg.DiscoKey
} }
e.recvActivityAt = mr e.recvActivityAt = mr
oldTime := e.sentActivityAt oldTime4 := e.sentActivityAt4
e.sentActivityAt = make(map[packet.IP4]*int64, len(oldTime)) e.sentActivityAt4 = make(map[packet.IP4]*int64, len(oldTime4))
oldFunc := e.destIPActivityFuncs oldFunc4 := e.destIPActivityFuncs4
e.destIPActivityFuncs = make(map[packet.IP4]func(), len(oldFunc)) e.destIPActivityFuncs4 = make(map[packet.IP4]func(), len(oldFunc4))
oldTime6 := e.sentActivityAt6
e.sentActivityAt6 = make(map[packet.IP6]*int64, len(oldTime6))
oldFunc6 := e.destIPActivityFuncs6
e.destIPActivityFuncs6 = make(map[packet.IP6]func(), len(oldFunc6))
for _, wip := range trackIPs { updateFn := func(timePtr *int64) func() {
pip := packet.IP4(binary.BigEndian.Uint32(wip.Addr[12:])) return func() {
timePtr := oldTime[pip] now := e.timeNow().Unix()
if timePtr == nil { old := atomic.LoadInt64(timePtr)
timePtr = new(int64)
}
e.sentActivityAt[pip] = timePtr
fn := oldFunc[pip] // How long's it been since we last sent a packet?
if fn == nil { // For our first packet, old is Unix epoch time 0 (1970).
// This is the func that gets run on every outgoing packet for tracked IPs: elapsedSec := now - old
fn = func() {
now := e.timeNow().Unix()
old := atomic.LoadInt64(timePtr)
// How long's it been since we last sent a packet? if elapsedSec >= int64(packetSendTimeUpdateFrequency/time.Second) {
// For our first packet, old is Unix epoch time 0 (1970). atomic.StoreInt64(timePtr, now)
elapsedSec := now - old }
// On a big jump, assume we might no longer be in the wireguard
if elapsedSec >= int64(packetSendTimeUpdateFrequency/time.Second) { // config and go check.
atomic.StoreInt64(timePtr, now) if elapsedSec >= int64(packetSendRecheckWireguardThreshold/time.Second) {
} e.wgLock.Lock()
// On a big jump, assume we might no longer be in the wireguard defer e.wgLock.Unlock()
// config and go check. e.maybeReconfigWireguardLocked(nil)
if elapsedSec >= int64(packetSendRecheckWireguardThreshold/time.Second) {
e.wgLock.Lock()
defer e.wgLock.Unlock()
e.maybeReconfigWireguardLocked(nil)
}
} }
} }
e.destIPActivityFuncs[pip] = fn
} }
e.tundev.SetDestIPActivityFuncs(e.destIPActivityFuncs)
for _, wip := range trackIPs {
if wip.Is4() {
pip := packet.IP4(binary.BigEndian.Uint32(wip.Addr[12:]))
timePtr := oldTime4[pip]
if timePtr == nil {
timePtr = new(int64)
}
e.sentActivityAt4[pip] = timePtr
fn := oldFunc4[pip]
if fn == nil {
fn = updateFn(timePtr)
}
e.destIPActivityFuncs4[pip] = fn
} else {
pip := packet.IP6FromRaw16(wip.Addr)
timePtr := oldTime6[pip]
if timePtr == nil {
timePtr = new(int64)
}
e.sentActivityAt6[pip] = timePtr
fn := oldFunc6[pip]
if fn == nil {
fn = updateFn(timePtr)
}
e.destIPActivityFuncs6[pip] = fn
}
}
e.tundev.SetDestIPActivityFuncs(e.destIPActivityFuncs4, e.destIPActivityFuncs6)
} }
func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config) error { func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config) error {