From 40858a61fe1c6a0e90add476e5c9aa8d366dfb24 Mon Sep 17 00:00:00 2001 From: Michael Ben-Ami Date: Mon, 2 Mar 2026 11:18:08 -0500 Subject: [PATCH] ipnext,ipnlocal: add ExtraWireGuardAllowedIPs hook This hook addition is motivated by the Connectors 2025 work, in which NATed "Transit IPs" are used to route interesting traffic to the appropriate peer, without advertising the actual real IPs. It overlaps with #17858, and specifically with the WIP PR #17861. If that work completes, this hook may be replaced by other ones that fit the new WireGuard configuration paradigm. Fixes tailscale/corp#37146 Signed-off-by: Michael Ben-Ami --- ipn/ipnext/ipnext.go | 26 ++++++++++++++++++++++++++ ipn/ipnlocal/local.go | 10 ++++++++++ 2 files changed, 36 insertions(+) diff --git a/ipn/ipnext/ipnext.go b/ipn/ipnext/ipnext.go index 6dea49939..bf8d8a7a6 100644 --- a/ipn/ipnext/ipnext.go +++ b/ipn/ipnext/ipnext.go @@ -19,8 +19,10 @@ import ( "tailscale.com/tailcfg" "tailscale.com/tsd" "tailscale.com/tstime" + "tailscale.com/types/key" "tailscale.com/types/logger" "tailscale.com/types/mapx" + "tailscale.com/types/views" "tailscale.com/wgengine/filter" ) @@ -382,6 +384,30 @@ type Hooks struct { // Filter contains hooks for the packet filter. // See [filter.Filter] for details on how these hooks are invoked. Filter FilterHooks + + // ExtraWireGuardAllowedIPs is called with each peer's public key + // from the initial [wgcfg.Config], and returns a view of prefixes to + // append to each peer's AllowedIPs. + // + // The extra AllowedIPs are added after the [router.Config] is generated, but + // before the WireGuard config is sent to the engine, so the extra IPs are + // given to WireGuard, but not the OS routing table. + // + // The prefixes returned from the hook should not contain duplicates, either + // internally, or with netmap peer prefixes. Returned prefixes should only + // contain host routes, and not contain default or subnet routes. + // Subsequent calls that return an unchanged set of prefixes for a given peer, + // should return the prefixes in the same order for that peer, + // to prevent configuration churn. + // + // The returned slice should not be mutated by the extension after it is returned. + // + // The hook is called with LocalBackend's mutex locked. + // + // TODO(#17858): This hook may not be needed and can possibly be replaced by + // new hooks that fit into the new architecture that make use of new + // WireGuard APIs. + ExtraWireGuardAllowedIPs feature.Hook[func(key.NodePublic) views.Slice[netip.Prefix]] } // FilterHooks contains hooks that extensions can use to customize the packet diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 596a51bd7..b8f355039 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -5129,6 +5129,16 @@ func (b *LocalBackend) authReconfigLocked() { oneCGNATRoute := shouldUseOneCGNATRoute(b.logf, b.sys.NetMon.Get(), b.sys.ControlKnobs(), version.OS()) rcfg := b.routerConfigLocked(cfg, prefs, nm, oneCGNATRoute) + // Add these extra Allowed IPs after router configuration, because the expected + // extension (features/conn25), does not want these routes installed on the OS. + // See also [Hooks.ExtraWireGuardAllowedIPs]. + if extraAllowedIPsFn, ok := b.extHost.hooks.ExtraWireGuardAllowedIPs.GetOk(); ok { + for i := range cfg.Peers { + extras := extraAllowedIPsFn(cfg.Peers[i].PublicKey) + cfg.Peers[i].AllowedIPs = extras.AppendTo(cfg.Peers[i].AllowedIPs) + } + } + err = b.e.Reconfig(cfg, rcfg, dcfg) if err == wgengine.ErrNoChanges { return