net/tstun,wgengine,ipn/ipnlocal: make tstun's peerConfigTable use RouteManager table

Previously tstun.Wrapper.SetWGConfig walked wgcfg.Config.Peers on every
netmap to rebuild its own IP-to-peer table for masquerade NAT rewrites
and jailed-peer classification. Now the tun layer instead consumes the
route manager's shared immutable outbound snapshot directly, via a new
Engine.SetPeerRoutes method: LocalBackend pushes the snapshot (plus this
node's native Tailscale addresses) after every route manager commit that
can change it, and per-packet lookups read the interned PeerRoute
attributes from that table.

When no current peer is jailed or masqueraded, LocalBackend installs a
nil table (gated on RouteManager.HasDataPlaneAttrs), preserving the
per-packet nil-check fast path. The exitNodeRequiresMasq machinery is
deleted: its purpose was populating the table with all peers so that
more-specific entries shadow an exit node's /0, and the always-full
route manager table gives that shadowing inherently.

This is the last step before removing the Peers field from wgcfg.Config.

Updates #12542

Change-Id: Ifce09ca929a3f2511303ca1d6efdd583739494ce
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2026-07-14 12:41:11 -07:00
committed by Brad Fitzpatrick
parent e4144230f4
commit f0ce89b715
15 changed files with 318 additions and 245 deletions
+37 -5
View File
@@ -31,6 +31,7 @@ import (
"sync/atomic"
"time"
"github.com/gaissmai/bart"
"go4.org/mem"
"go4.org/netipx"
"golang.org/x/net/dns/dnsmessage"
@@ -62,6 +63,7 @@ import (
"tailscale.com/net/netns"
"tailscale.com/net/netutil"
"tailscale.com/net/packet"
"tailscale.com/net/routemanager"
"tailscale.com/net/traffic"
"tailscale.com/net/tsaddr"
"tailscale.com/net/tsdial"
@@ -2516,6 +2518,7 @@ func (b *LocalBackend) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bo
for k := range deltaRes.ChangedAllowedIPs {
b.e.SyncDevicePeer(k)
}
b.setDataPlanePeerRoutes()
// Reset the WireGuard session for peers whose disco key changed in
// a way that indicates a restart, flushing their dead session keys;
@@ -2530,11 +2533,9 @@ func (b *LocalBackend) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bo
// and its lazy peer creation reads live state via
// [wgengine.Engine.SetPeerConfigFunc]. What still rides
// authReconfig is everything else derived from the full peer set:
// OS routes (router.Config), the quad-100 resolver's MagicDNS
// hosts map (dnsConfigForNetmap), and tstun's per-peer config
// (masquerade addresses and jailed peers, via SetWGConfig). Once
// those become delta-aware too, this can be gated on the route
// manager's OS-routes changes and the tstun-relevant fields
// OS routes (router.Config) and the quad-100 resolver's MagicDNS
// hosts map (dnsConfigForNetmap). Once those become delta-aware
// too, this can be gated on the route manager's OS-routes changes
// instead of firing on every peer change.
needsAuthReconfig = needsAuthReconfig || peersUpsertedOrRemoved
if needsAuthReconfig {
@@ -6183,6 +6184,13 @@ func (b *LocalBackend) authReconfigLocked() {
cfg.Peers[i].AllowedIPs = extras.AppendTo(cfg.Peers[i].AllowedIPs)
}
}
// The prefs and extras commits above can both change the outbound
// table (such as installing the selected exit node's /0 routes),
// so refresh the data plane's view of it. This must stay after
// updateRouteManagerExtras, so the pushed snapshot includes its
// commit, and before the Reconfig below, so packets flowing under
// the new config never see a stale peer table.
b.setDataPlanePeerRoutes()
err = b.e.Reconfig(cfg, rcfg, dcfg)
if err == wgengine.ErrNoChanges {
@@ -6196,6 +6204,29 @@ func (b *LocalBackend) authReconfigLocked() {
}
}
// setDataPlanePeerRoutes pushes the route manager's outbound table and
// this node's native Tailscale addresses into the engine's tun-layer
// data plane, which uses them for per-packet NAT rewrites and
// jailed-filter selection. It must be called after every route manager
// commit that can change the outbound table or the set of peers with
// data-plane attributes: netmap updates (full or delta), prefs changes,
// and extra-allowed-IP changes.
//
// When no current peer is jailed or masqueraded, it installs nil, which
// keeps the data plane's per-packet fast path to a nil check.
func (b *LocalBackend) setDataPlanePeerRoutes() {
nb := b.currentNode()
var native4, native6 netip.Addr
var routes *bart.Table[*routemanager.PeerRoute]
if nb.routeMgr.HasDataPlaneAttrs() {
routes = nb.routeMgr.Outbound()
if nm := nb.NetMap(); nm != nil {
native4, native6 = tsaddr.FirstTailscaleAddrs(nm.GetAddresses().All())
}
}
b.e.SetPeerRoutes(native4, native6, routes)
}
// shouldUseOneCGNATRoute reports whether we should prefer to make one big
// CGNAT /10 route rather than a /32 per peer.
//
@@ -7312,6 +7343,7 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) {
login = cmp.Or(profileFromView(nm.UserProfiles[nm.User()]).LoginName, "<missing-profile>")
}
discoChanged := b.currentNode().SetNetMap(nm)
b.setDataPlanePeerRoutes()
if ms, ok := b.sys.MagicSock.GetOK(); ok {
if nm != nil {
if nm.Cached {
+5
View File
@@ -17,6 +17,7 @@ import (
"time"
qt "github.com/frankban/quicktest"
"github.com/gaissmai/bart"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
@@ -29,6 +30,7 @@ import (
"tailscale.com/net/dns"
"tailscale.com/net/netmon"
"tailscale.com/net/packet"
"tailscale.com/net/routemanager"
"tailscale.com/net/tsdial"
"tailscale.com/tailcfg"
"tailscale.com/tsd"
@@ -1961,6 +1963,9 @@ func (e *mockEngine) SetJailedFilter(f *filter.Filter) {
e.mu.Unlock()
}
func (e *mockEngine) SetPeerRoutes(native4, native6 netip.Addr, routes *bart.Table[*routemanager.PeerRoute]) {
}
func (e *mockEngine) SetStatusCallback(cb wgengine.StatusCallback) {
e.mu.Lock()
e.statusCb = cb