mirror of
https://github.com/tailscale/tailscale.git
synced 2025-10-26 22:01:09 +01:00
feature/featuretags: add LazyWG modular feature
Due to iOS memory limitations in 2020 (see https://tailscale.com/blog/go-linker, etc) and wireguard-go using multiple goroutines per peer, commit 16a9cfe2f4ce7d introduced some convoluted pathsways through Tailscale to look at packets before they're delivered to wireguard-go and lazily reconfigure wireguard on the fly before delivering a packet, only telling wireguard about peers that are active. We eventually want to remove that code and integrate wireguard-go's configuration with Tailscale's existing netmap tracking. To make it easier to find that code later, this makes it modular. It saves 12 KB (of disk) to turn it off (at the expense of lots of RAM), but that's not really the point. The point is rather making it obvious (via the new constants) where this code even is. Updates #12614 Change-Id: I113b040f3e35f7d861c457eaa710d35f47cee1cb Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
f80c7e7c23
commit
cf520a3371
13
feature/buildfeatures/feature_lazywg_disabled.go
Normal file
13
feature/buildfeatures/feature_lazywg_disabled.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
// Code generated by gen.go; DO NOT EDIT.
|
||||||
|
|
||||||
|
//go:build ts_omit_lazywg
|
||||||
|
|
||||||
|
package buildfeatures
|
||||||
|
|
||||||
|
// HasLazyWG is whether the binary was built with support for modular feature "Lazy WireGuard configuration for memory-constrained devices with large netmaps".
|
||||||
|
// Specifically, it's whether the binary was NOT built with the "ts_omit_lazywg" build tag.
|
||||||
|
// It's a const so it can be used for dead code elimination.
|
||||||
|
const HasLazyWG = false
|
||||||
13
feature/buildfeatures/feature_lazywg_enabled.go
Normal file
13
feature/buildfeatures/feature_lazywg_enabled.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
// Code generated by gen.go; DO NOT EDIT.
|
||||||
|
|
||||||
|
//go:build !ts_omit_lazywg
|
||||||
|
|
||||||
|
package buildfeatures
|
||||||
|
|
||||||
|
// HasLazyWG is whether the binary was built with support for modular feature "Lazy WireGuard configuration for memory-constrained devices with large netmaps".
|
||||||
|
// Specifically, it's whether the binary was NOT built with the "ts_omit_lazywg" build tag.
|
||||||
|
// It's a const so it can be used for dead code elimination.
|
||||||
|
const HasLazyWG = true
|
||||||
@ -159,6 +159,7 @@ var Features = map[FeatureTag]FeatureMeta{
|
|||||||
"hujsonconf": {Sym: "HuJSONConf", Desc: "HuJSON config file support"},
|
"hujsonconf": {Sym: "HuJSONConf", Desc: "HuJSON config file support"},
|
||||||
"iptables": {Sym: "IPTables", Desc: "Linux iptables support"},
|
"iptables": {Sym: "IPTables", Desc: "Linux iptables support"},
|
||||||
"kube": {Sym: "Kube", Desc: "Kubernetes integration"},
|
"kube": {Sym: "Kube", Desc: "Kubernetes integration"},
|
||||||
|
"lazywg": {Sym: "LazyWG", Desc: "Lazy WireGuard configuration for memory-constrained devices with large netmaps"},
|
||||||
"linuxdnsfight": {Sym: "LinuxDNSFight", Desc: "Linux support for detecting DNS fights (inotify watching of /etc/resolv.conf)"},
|
"linuxdnsfight": {Sym: "LinuxDNSFight", Desc: "Linux support for detecting DNS fights (inotify watching of /etc/resolv.conf)"},
|
||||||
"listenrawdisco": {
|
"listenrawdisco": {
|
||||||
Sym: "ListenRawDisco",
|
Sym: "ListenRawDisco",
|
||||||
|
|||||||
@ -312,7 +312,9 @@ func (t *Wrapper) now() time.Time {
|
|||||||
//
|
//
|
||||||
// The map ownership passes to the Wrapper. It must be non-nil.
|
// The map ownership passes to the Wrapper. It must be non-nil.
|
||||||
func (t *Wrapper) SetDestIPActivityFuncs(m map[netip.Addr]func()) {
|
func (t *Wrapper) SetDestIPActivityFuncs(m map[netip.Addr]func()) {
|
||||||
t.destIPActivity.Store(m)
|
if buildfeatures.HasLazyWG {
|
||||||
|
t.destIPActivity.Store(m)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDiscoKey sets the current discovery key.
|
// SetDiscoKey sets the current discovery key.
|
||||||
@ -948,12 +950,14 @@ func (t *Wrapper) Read(buffs [][]byte, sizes []int, offset int) (int, error) {
|
|||||||
for _, data := range res.data {
|
for _, data := range res.data {
|
||||||
p.Decode(data[res.dataOffset:])
|
p.Decode(data[res.dataOffset:])
|
||||||
|
|
||||||
if m := t.destIPActivity.Load(); m != nil {
|
if buildfeatures.HasLazyWG {
|
||||||
if fn := m[p.Dst.Addr()]; fn != nil {
|
if m := t.destIPActivity.Load(); m != nil {
|
||||||
fn()
|
if fn := m[p.Dst.Addr()]; fn != nil {
|
||||||
|
fn()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if captHook != nil {
|
if buildfeatures.HasCapture && captHook != nil {
|
||||||
captHook(packet.FromLocal, t.now(), p.Buffer(), p.CaptureMeta)
|
captHook(packet.FromLocal, t.now(), p.Buffer(), p.CaptureMeta)
|
||||||
}
|
}
|
||||||
if !t.disableFilter {
|
if !t.disableFilter {
|
||||||
@ -1085,9 +1089,11 @@ func (t *Wrapper) injectedRead(res tunInjectedRead, outBuffs [][]byte, sizes []i
|
|||||||
pc.snat(p)
|
pc.snat(p)
|
||||||
invertGSOChecksum(pkt, gso)
|
invertGSOChecksum(pkt, gso)
|
||||||
|
|
||||||
if m := t.destIPActivity.Load(); m != nil {
|
if buildfeatures.HasLazyWG {
|
||||||
if fn := m[p.Dst.Addr()]; fn != nil {
|
if m := t.destIPActivity.Load(); m != nil {
|
||||||
fn()
|
if fn := m[p.Dst.Addr()]; fn != nil {
|
||||||
|
fn()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -468,7 +468,8 @@ type Options struct {
|
|||||||
// NoteRecvActivity, if provided, is a func for magicsock to call
|
// NoteRecvActivity, if provided, is a func for magicsock to call
|
||||||
// whenever it receives a packet from a a peer if it's been more
|
// whenever it receives a packet from a a peer if it's been more
|
||||||
// than ~10 seconds since the last one. (10 seconds is somewhat
|
// than ~10 seconds since the last one. (10 seconds is somewhat
|
||||||
// arbitrary; the sole user just doesn't need or want it called on
|
// arbitrary; the sole user, lazy WireGuard configuration,
|
||||||
|
// just doesn't need or want it called on
|
||||||
// every packet, just every minute or two for WireGuard timeouts,
|
// every packet, just every minute or two for WireGuard timeouts,
|
||||||
// and 10 seconds seems like a good trade-off between often enough
|
// and 10 seconds seems like a good trade-off between often enough
|
||||||
// and not too often.)
|
// and not too often.)
|
||||||
|
|||||||
@ -404,19 +404,21 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
magicsockOpts := magicsock.Options{
|
magicsockOpts := magicsock.Options{
|
||||||
EventBus: e.eventBus,
|
EventBus: e.eventBus,
|
||||||
Logf: logf,
|
Logf: logf,
|
||||||
Port: conf.ListenPort,
|
Port: conf.ListenPort,
|
||||||
EndpointsFunc: endpointsFn,
|
EndpointsFunc: endpointsFn,
|
||||||
DERPActiveFunc: e.RequestStatus,
|
DERPActiveFunc: e.RequestStatus,
|
||||||
IdleFunc: e.tundev.IdleDuration,
|
IdleFunc: e.tundev.IdleDuration,
|
||||||
NoteRecvActivity: e.noteRecvActivity,
|
NetMon: e.netMon,
|
||||||
NetMon: e.netMon,
|
HealthTracker: e.health,
|
||||||
HealthTracker: e.health,
|
Metrics: conf.Metrics,
|
||||||
Metrics: conf.Metrics,
|
ControlKnobs: conf.ControlKnobs,
|
||||||
ControlKnobs: conf.ControlKnobs,
|
OnPortUpdate: onPortUpdate,
|
||||||
OnPortUpdate: onPortUpdate,
|
PeerByKeyFunc: e.PeerByKey,
|
||||||
PeerByKeyFunc: e.PeerByKey,
|
}
|
||||||
|
if buildfeatures.HasLazyWG {
|
||||||
|
magicsockOpts.NoteRecvActivity = e.noteRecvActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@ -748,15 +750,22 @@ func (e *userspaceEngine) maybeReconfigWireguardLocked(discoChanged map[key.Node
|
|||||||
// the past 5 minutes. That's more than WireGuard's key
|
// the past 5 minutes. That's more than WireGuard's key
|
||||||
// rotation time anyway so it's no harm if we remove it
|
// rotation time anyway so it's no harm if we remove it
|
||||||
// later if it's been inactive.
|
// later if it's been inactive.
|
||||||
activeCutoff := e.timeNow().Add(-lazyPeerIdleThreshold)
|
var activeCutoff mono.Time
|
||||||
|
if buildfeatures.HasLazyWG {
|
||||||
|
activeCutoff = e.timeNow().Add(-lazyPeerIdleThreshold)
|
||||||
|
}
|
||||||
|
|
||||||
// Not all peers can be trimmed from the network map (see
|
// Not all peers can be trimmed from the network map (see
|
||||||
// isTrimmablePeer). For those that are trimmable, keep track of
|
// isTrimmablePeer). For those that are trimmable, keep track of
|
||||||
// their NodeKey and Tailscale IPs. These are the ones we'll need
|
// their NodeKey and Tailscale IPs. These are the ones we'll need
|
||||||
// to install tracking hooks for to watch their send/receive
|
// to install tracking hooks for to watch their send/receive
|
||||||
// activity.
|
// activity.
|
||||||
trackNodes := make([]key.NodePublic, 0, len(full.Peers))
|
var trackNodes []key.NodePublic
|
||||||
trackIPs := make([]netip.Addr, 0, len(full.Peers))
|
var trackIPs []netip.Addr
|
||||||
|
if buildfeatures.HasLazyWG {
|
||||||
|
trackNodes = make([]key.NodePublic, 0, len(full.Peers))
|
||||||
|
trackIPs = make([]netip.Addr, 0, len(full.Peers))
|
||||||
|
}
|
||||||
|
|
||||||
// Don't re-alloc the map; the Go compiler optimizes map clears as of
|
// Don't re-alloc the map; the Go compiler optimizes map clears as of
|
||||||
// Go 1.11, so we can re-use the existing + allocated map.
|
// Go 1.11, so we can re-use the existing + allocated map.
|
||||||
@ -770,7 +779,7 @@ func (e *userspaceEngine) maybeReconfigWireguardLocked(discoChanged map[key.Node
|
|||||||
for i := range full.Peers {
|
for i := range full.Peers {
|
||||||
p := &full.Peers[i]
|
p := &full.Peers[i]
|
||||||
nk := p.PublicKey
|
nk := p.PublicKey
|
||||||
if !e.isTrimmablePeer(p, len(full.Peers)) {
|
if !buildfeatures.HasLazyWG || !e.isTrimmablePeer(p, len(full.Peers)) {
|
||||||
min.Peers = append(min.Peers, *p)
|
min.Peers = append(min.Peers, *p)
|
||||||
if discoChanged[nk] {
|
if discoChanged[nk] {
|
||||||
needRemoveStep = true
|
needRemoveStep = true
|
||||||
@ -803,7 +812,9 @@ func (e *userspaceEngine) maybeReconfigWireguardLocked(discoChanged map[key.Node
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
e.updateActivityMapsLocked(trackNodes, trackIPs)
|
if buildfeatures.HasLazyWG {
|
||||||
|
e.updateActivityMapsLocked(trackNodes, trackIPs)
|
||||||
|
}
|
||||||
|
|
||||||
if needRemoveStep {
|
if needRemoveStep {
|
||||||
minner := min
|
minner := min
|
||||||
@ -839,6 +850,9 @@ func (e *userspaceEngine) maybeReconfigWireguardLocked(discoChanged map[key.Node
|
|||||||
//
|
//
|
||||||
// e.wgLock must be held.
|
// e.wgLock must be held.
|
||||||
func (e *userspaceEngine) updateActivityMapsLocked(trackNodes []key.NodePublic, trackIPs []netip.Addr) {
|
func (e *userspaceEngine) updateActivityMapsLocked(trackNodes []key.NodePublic, trackIPs []netip.Addr) {
|
||||||
|
if !buildfeatures.HasLazyWG {
|
||||||
|
return
|
||||||
|
}
|
||||||
// Generate the new map of which nodekeys we want to track
|
// Generate the new map of which nodekeys we want to track
|
||||||
// receive times for.
|
// receive times for.
|
||||||
mr := map[key.NodePublic]mono.Time{} // TODO: only recreate this if set of keys changed
|
mr := map[key.NodePublic]mono.Time{} // TODO: only recreate this if set of keys changed
|
||||||
@ -943,7 +957,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config,
|
|||||||
peerMTUEnable := e.magicConn.ShouldPMTUD()
|
peerMTUEnable := e.magicConn.ShouldPMTUD()
|
||||||
|
|
||||||
isSubnetRouter := false
|
isSubnetRouter := false
|
||||||
if e.birdClient != nil && nm != nil && nm.SelfNode.Valid() {
|
if buildfeatures.HasBird && e.birdClient != nil && nm != nil && nm.SelfNode.Valid() {
|
||||||
isSubnetRouter = hasOverlap(nm.SelfNode.PrimaryRoutes(), nm.SelfNode.Hostinfo().RoutableIPs())
|
isSubnetRouter = hasOverlap(nm.SelfNode.PrimaryRoutes(), nm.SelfNode.Hostinfo().RoutableIPs())
|
||||||
e.logf("[v1] Reconfig: hasOverlap(%v, %v) = %v; isSubnetRouter=%v lastIsSubnetRouter=%v",
|
e.logf("[v1] Reconfig: hasOverlap(%v, %v) = %v; isSubnetRouter=%v lastIsSubnetRouter=%v",
|
||||||
nm.SelfNode.PrimaryRoutes(), nm.SelfNode.Hostinfo().RoutableIPs(),
|
nm.SelfNode.PrimaryRoutes(), nm.SelfNode.Hostinfo().RoutableIPs(),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user