wgengine,ipn/ipnlocal: remove Engine.PeerKeyForIP and the engine's peer route table

The engine kept its own longest-prefix-match table (peerByIPRoute),
rebuilt from the full peer list on every reconfig, to route outbound
packets and answer PeerKeyForIP. That's now the route manager's job:
LocalBackend already installs a PeerByIPPacketFunc backed by the
RouteManager's incrementally-maintained outbound table, so the
engine's copy was redundant state with redundant O(n peers) rebuild
work.

Delete the table, the PeerKeyForIP interface method, and the BART-only
default callback. LocalBackend's peerForIP now queries the
RouteManager's outbound table directly for the subnet-route and
exit-node fallback. Engines running without a LocalBackend (such as
wgengine/bench) must install their own outbound peer lookup, since the
device's standard AllowedIPs trie only covers peers that already
exist and can't lazily create them.

Updates #12542

Change-Id: I25100399e273ed6c2bb1f6136b7cd81bc83e7313
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2026-07-13 14:38:55 -07:00
committed by Brad Fitzpatrick
parent 3c800dcd71
commit 2506ede862
6 changed files with 37 additions and 89 deletions
+14 -68
View File
@@ -18,7 +18,6 @@ import (
"sync/atomic"
"time"
"github.com/gaissmai/bart"
"github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/tun"
"go4.org/mem"
@@ -104,19 +103,6 @@ type userspaceEngine struct {
wgLock sync.Mutex // serializes all wgdev operations; see lock order comment below
// peerByIPRoute is a longest-prefix-match table built from
// lastCfgFull.Peers AllowedIPs. It's the slow path for
// SetPeerByIPPacketFunc, used when LocalBackend's exact-IP fast path
// (nodeByAddr) misses — i.e. for subnet routes and exit-node default
// routes. Built from lastCfgFull (the wireguard-filtered peer list)
// rather than the netmap so that exit-node selection is honored: the
// netmap has 0.0.0.0/0 in AllowedIPs for every exit-capable peer, but
// lastCfgFull only has it for the currently-selected exit node.
//
// Replaced (not mutated) by maybeReconfigWireguardLocked. Read by
// the per-packet wgdev callback without locking.
peerByIPRoute atomic.Pointer[bart.Table[key.NodePublic]]
// peerForIP, if non-nil, is the callback installed via
// [userspaceEngine.SetPeerForIPFunc]. PeerForIP delegates to it
// for the cold-path control lookups (Ping, TSMP, pendopen, etc).
@@ -525,16 +511,6 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
e.logf("Creating WireGuard device...")
e.wgdev = wgcfg.NewDevice(e.tundev, e.magicConn.Bind(), e.wgLogger.DeviceLogger)
closePool.addFunc(e.wgdev.Close)
// Install a default outbound-packet peer lookup callback. It uses only
// the engine's BART table, which is rebuilt from the wireguard-filtered
// peer list on every Reconfig. Consumers (e.g. LocalBackend) may later
// call SetPeerByIPPacketFunc to additionally install a fast path for
// exact node-address matches; the BART remains the slow-path fallback.
// Without this default, callers that don't run a LocalBackend would
// have no way to route outbound packets to peers, since peers are
// created lazily from inbound packets only via SetPeerLookupFunc.
e.SetPeerByIPPacketFunc(nil)
closePool.addFunc(func() {
if err := e.magicConn.Close(); err != nil {
e.logf("error closing magicconn: %v", err)
@@ -721,16 +697,6 @@ func (e *userspaceEngine) maybeReconfigWireguardLocked() error {
// against the current lookup.
e.wgLogger.Invalidate()
// Rebuild the prefix-match peer routing table from the current
// (wireguard-filtered) peer list and publish it atomically.
rt := &bart.Table[key.NodePublic]{}
for _, p := range full.Peers {
for _, pfx := range p.AllowedIPs {
rt.Insert(pfx, p.PublicKey)
}
}
e.peerByIPRoute.Store(rt)
e.logf("wgengine: Reconfig: configuring userspace WireGuard config (with %d peers)", len(full.Peers))
if e.peerConfigFn.Load() != nil {
// The device has a long-lived PeerLookupFunc backed by the
@@ -803,29 +769,23 @@ func (e *userspaceEngine) SyncDevicePeer(k key.NodePublic) {
// SetPeerByIPPacketFunc installs a callback used by wireguard-go to look up
// which peer should handle an outbound packet by destination IP.
//
// If fn is non-nil it is authoritative: LocalBackend's implementation
// consults both the exact node-address fast path and the RouteManager's
// outbound table (covering subnet routes and exit-node default routes),
// and stays correct under incremental netmap deltas. The engine's own
// BART table ([userspaceEngine.peerByIPRoute], rebuilt only on full
// reconfigs) is used only when no fn is installed (e.g. engines running
// without a LocalBackend).
// LocalBackend's implementation consults both the exact node-address fast
// path and the RouteManager's outbound table (covering subnet routes and
// exit-node default routes), and stays correct under incremental netmap
// deltas.
//
// [NewUserspaceEngine] installs a BART-only default at engine creation time,
// so callers that don't call SetPeerByIPPacketFunc (e.g. those not running
// a LocalBackend) still get working outbound packet routing.
// A nil fn uninstalls the callback, reverting the device to its standard
// WireGuard AllowedIPs trie, which only contains peers that already exist
// in the device. Callers without a LocalBackend that need outbound packets
// to lazily create peers must install their own callback.
func (e *userspaceEngine) SetPeerByIPPacketFunc(fn func(netip.Addr) (_ key.NodePublic, ok bool)) {
if fn == nil {
e.wgdev.SetPeerByIPPacketFunc(nil)
return
}
e.wgdev.SetPeerByIPPacketFunc(func(_, dst netip.Addr, _ []byte) (device.NoisePublicKey, bool) {
if fn != nil {
if pk, ok := fn(dst); ok {
return pk.Raw32(), true
}
return device.NoisePublicKey{}, false
}
if rt := e.peerByIPRoute.Load(); rt != nil {
if pk, ok := rt.Lookup(dst); ok {
return pk.Raw32(), true
}
if pk, ok := fn(dst); ok {
return pk.Raw32(), true
}
return device.NoisePublicKey{}, false
})
@@ -1635,20 +1595,6 @@ func (e *userspaceEngine) SetPeerForIPFunc(fn func(netip.Addr) (PeerForIP, bool)
e.peerForIP.Store(&fn)
}
// PeerKeyForIP looks up ip in the engine's AllowedIPs table
// ([userspaceEngine.peerByIPRoute]). See [Engine.PeerKeyForIP].
func (e *userspaceEngine) PeerKeyForIP(ip netip.Addr) (pk key.NodePublic, route netip.Prefix, ok bool) {
if !ip.IsValid() {
return pk, route, false
}
rt := e.peerByIPRoute.Load()
if rt == nil {
return pk, route, false
}
route, pk, ok = rt.LookupPrefixLPM(netip.PrefixFrom(ip, ip.BitLen()))
return pk, route, ok
}
// PeerForIP returns the node responsible for handling the given IP.
// It delegates to the callback installed via [SetPeerForIPFunc]; engines
// without an installed callback return (zero, false).