mirror of
https://github.com/tailscale/tailscale.git
synced 2026-07-15 01:53:08 -04:00
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:
@@ -9489,6 +9489,7 @@ func TestEnginePeerForIPAdjustsForPrefs(t *testing.T) {
|
||||
nm := buildNetmapWithPeers(selfNode, exitA, exitB, subnetBig, subnetSmall)
|
||||
|
||||
var eng wgengine.Engine
|
||||
var curLB *LocalBackend
|
||||
var curT *testing.T // active subtest, for test helpers
|
||||
|
||||
wantPeer := func(ip string, n tailcfg.NodeView) {
|
||||
@@ -9515,19 +9516,19 @@ func TestEnginePeerForIPAdjustsForPrefs(t *testing.T) {
|
||||
wantKey := func(ip string, n tailcfg.NodeView) {
|
||||
t := curT
|
||||
t.Helper()
|
||||
pk, _, ok := eng.PeerKeyForIP(netip.MustParseAddr(ip))
|
||||
pr, ok := curLB.currentNode().routeMgr.Outbound().Lookup(netip.MustParseAddr(ip))
|
||||
if !ok {
|
||||
t.Fatalf("PeerKeyForIP(%s): ok=false, want true", ip)
|
||||
t.Fatalf("routeMgr.Outbound().Lookup(%s): ok=false, want true", ip)
|
||||
}
|
||||
if pk != n.Key() {
|
||||
t.Fatalf("PeerKeyForIP(%s): key=%v, want %v", ip, pk, n.Key())
|
||||
if pr.Key != n.Key() {
|
||||
t.Fatalf("routeMgr.Outbound().Lookup(%s): key=%v, want %v", ip, pr.Key, n.Key())
|
||||
}
|
||||
}
|
||||
wantNotKey := func(ip string) {
|
||||
t := curT
|
||||
t.Helper()
|
||||
if _, _, ok := eng.PeerKeyForIP(netip.MustParseAddr(ip)); ok {
|
||||
t.Fatalf("PeerKeyForIP(%s): ok=true, want false", ip)
|
||||
if _, ok := curLB.currentNode().routeMgr.Outbound().Lookup(netip.MustParseAddr(ip)); ok {
|
||||
t.Fatalf("routeMgr.Outbound().Lookup(%s): ok=true, want false", ip)
|
||||
}
|
||||
}
|
||||
wantSelf := func(ip string) {
|
||||
@@ -9663,6 +9664,7 @@ func TestEnginePeerForIPAdjustsForPrefs(t *testing.T) {
|
||||
})
|
||||
|
||||
eng = lb.sys.Engine.Get()
|
||||
curLB = lb
|
||||
curT = t
|
||||
tt.check()
|
||||
})
|
||||
|
||||
@@ -143,11 +143,11 @@ func (b *LocalBackend) peerForIP(ip netip.Addr) (_ wgengine.PeerForIP, ok bool)
|
||||
}
|
||||
}
|
||||
|
||||
pk, route, ok := b.e.PeerKeyForIP(ip)
|
||||
route, pr, ok := nb.routeMgr.Outbound().LookupPrefixLPM(netip.PrefixFrom(ip, ip.BitLen()))
|
||||
if !ok {
|
||||
return wgengine.PeerForIP{}, false
|
||||
}
|
||||
nid, ok := nb.NodeByKey(pk)
|
||||
nid, ok := nb.NodeByKey(pr.Key)
|
||||
if !ok {
|
||||
return wgengine.PeerForIP{}, false
|
||||
}
|
||||
|
||||
@@ -2019,9 +2019,6 @@ func (e *mockEngine) SetPeerForIPFunc(func(netip.Addr) (_ wgengine.PeerForIP, ok
|
||||
func (e *mockEngine) SetPeerConfigFunc(func(key.NodePublic) (allowedIPs []netip.Prefix, ok bool)) {
|
||||
}
|
||||
func (e *mockEngine) SyncDevicePeer(key.NodePublic) {}
|
||||
func (e *mockEngine) PeerKeyForIP(netip.Addr) (_ key.NodePublic, _ netip.Prefix, ok bool) {
|
||||
return key.NodePublic{}, netip.Prefix{}, false
|
||||
}
|
||||
func (e *mockEngine) SetPeerSessionStateFunc(func(key.NodePublic, wgengine.PeerWireGuardState)) {
|
||||
}
|
||||
func (e *mockEngine) SetNetLogSource(wgengine.NetLogSource) {}
|
||||
|
||||
@@ -82,6 +82,17 @@ func setupWGTest(b *testing.B, logf logger.Logf, traf *TrafficGen, a1, a2 netip.
|
||||
e1.SetFilter(filter.NewAllowAllForTest(l1))
|
||||
e2.SetFilter(filter.NewAllowAllForTest(l2))
|
||||
|
||||
// There is no LocalBackend in this benchmark, so install trivial
|
||||
// outbound peer lookups; without one, outbound packets can't
|
||||
// lazily create their WireGuard peer.
|
||||
k1pub, k2pub := k1.Public(), k2.Public()
|
||||
e1.SetPeerByIPPacketFunc(func(dst netip.Addr) (_ key.NodePublic, ok bool) {
|
||||
return k2pub, a2.Contains(dst)
|
||||
})
|
||||
e2.SetPeerByIPPacketFunc(func(dst netip.Addr) (_ key.NodePublic, ok bool) {
|
||||
return k1pub, a1.Contains(dst)
|
||||
})
|
||||
|
||||
var wait sync.WaitGroup
|
||||
wait.Add(2)
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
"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
|
||||
@@ -800,29 +766,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
|
||||
})
|
||||
@@ -1632,20 +1592,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).
|
||||
|
||||
@@ -126,18 +126,10 @@ type Engine interface {
|
||||
// If fn is nil, PeerForIP returns (zero, false) for every IP.
|
||||
//
|
||||
// LocalBackend installs a func backed by the live nodeBackend for
|
||||
// exact-match and self addresses, with [Engine.PeerKeyForIP]
|
||||
// supplying the subnet-route / exit-node fallback.
|
||||
// exact-match and self addresses, with the RouteManager's outbound
|
||||
// table supplying the subnet-route / exit-node fallback.
|
||||
SetPeerForIPFunc(fn func(netip.Addr) (_ PeerForIP, ok bool))
|
||||
|
||||
// PeerKeyForIP returns the peer's NodePublic and the matched prefix
|
||||
// for the longest-prefix match of ip in the engine's AllowedIPs
|
||||
// table (the wireguard config most recently installed via
|
||||
// [Engine.Reconfig]). Exit-node selection is honored: an unselected
|
||||
// exit node's 0.0.0.0/0 is not matched. It is the same table the
|
||||
// outbound packet hot path consults via [Engine.SetPeerByIPPacketFunc].
|
||||
PeerKeyForIP(netip.Addr) (_ key.NodePublic, _ netip.Prefix, ok bool)
|
||||
|
||||
// GetFilter returns the current packet filter, if any.
|
||||
GetFilter() *filter.Filter
|
||||
|
||||
|
||||
Reference in New Issue
Block a user