From 2d5962f5249848168e259a01781ce9a5f1310246 Mon Sep 17 00:00:00 2001 From: Fran Bull Date: Wed, 25 Mar 2026 13:37:26 -0700 Subject: [PATCH] feature/conn25,ipn/ipnext,ipn/ipnlocal: add ExtraRouterConfigRoutes hook conn25 needs to add routes to the operating system to direct handling of the addresses in the magic IP range to the tailscale0 TUN and tailscaled. The way we do this for exit nodes and VIP services is that we add routes to the Routes field of router.Config, and then the config is passed to the WireGuard engine Reconfig. conn25 is implemented as an ipnext.Extension and so this commit adds a hook to ipnext.Hooks to allow any extension to provide routes to the config. The hook if provided is called in routerConfigLocked, similarly to exit nodes and VIP services. Fixes tailscale/corp#38123 Signed-off-by: Fran Bull --- feature/conn25/conn25.go | 15 ++++++++++++++- ipn/ipnext/ipnext.go | 12 ++++++++++++ ipn/ipnlocal/local.go | 5 +++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/feature/conn25/conn25.go b/feature/conn25/conn25.go index d2cad6b49..fb83e86ff 100644 --- a/feature/conn25/conn25.go +++ b/feature/conn25/conn25.go @@ -33,6 +33,7 @@ import ( "tailscale.com/tailcfg" "tailscale.com/types/appctype" "tailscale.com/types/logger" + "tailscale.com/types/views" "tailscale.com/util/dnsname" "tailscale.com/util/mak" "tailscale.com/util/set" @@ -123,12 +124,18 @@ func (e *extension) Init(host ipnext.Host) error { } e.host = host host.Hooks().OnSelfChange.Add(e.onSelfChange) + host.Hooks().ExtraRouterConfigRoutes.Set(e.getMagicRange) ctx, cancel := context.WithCancelCause(context.Background()) e.ctxCancel = cancel go e.sendLoop(ctx) return nil } +func (e *extension) getMagicRange() views.Slice[netip.Prefix] { + cfg := e.conn25.client.getConfig() + return views.SliceOf(cfg.magicIPSet.Prefixes()) +} + // Shutdown implements [ipnlocal.Extension]. func (e *extension) Shutdown() error { if e.ctxCancel != nil { @@ -498,6 +505,12 @@ type client struct { config config } +func (c *client) getConfig() config { + c.mu.Lock() + defer c.mu.Unlock() + return c.config +} + // ClientTransitIPForMagicIP is part of the implementation of the IPMapper interface for dataflows lookups. func (c *client) ClientTransitIPForMagicIP(magicIP netip.Addr) (netip.Addr, error) { c.mu.Lock() @@ -673,7 +686,7 @@ func makePeerAPIReq(ctx context.Context, httpClient *http.Client, urlBase string } func (e *extension) sendAddressAssignment(ctx context.Context, as addrs) error { - app, ok := e.conn25.client.config.appsByName[as.app] + app, ok := e.conn25.client.getConfig().appsByName[as.app] if !ok { e.conn25.client.logf("App not found for app: %s (domain: %s)", as.app, as.domain) return errors.New("app not found") diff --git a/ipn/ipnext/ipnext.go b/ipn/ipnext/ipnext.go index a628e4e2a..5ca50498a 100644 --- a/ipn/ipnext/ipnext.go +++ b/ipn/ipnext/ipnext.go @@ -418,6 +418,18 @@ type Hooks struct { // 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]] + + // ExtraRouterConfigRoutes returns a view of prefixes to append to [router.Config.Routes]. + // + // Routes goes through the WireGuard engine which makes efforts to avoid + // unnecessary reconfiguration by checking that things have actually changed. + // So implementors should make sure that the order of the prefixes is stable + // and that we don't have duplicate entries. + // + // The returned slice should not be mutated by the extension after it is returned. + // + // The hook is called with LocalBackend's mutex locked. + ExtraRouterConfigRoutes feature.Hook[func() 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 49e1f00c7..7cd4e2afb 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -5667,6 +5667,11 @@ func (b *LocalBackend) routerConfigLocked(cfg *wgcfg.Config, prefs ipn.PrefsView } } + // Get any extra Routes an extension may want installed. + if extensionRoutesFx, ok := b.extHost.hooks.ExtraRouterConfigRoutes.GetOk(); ok { + rs.Routes = extensionRoutesFx().AppendTo(rs.Routes) + } + return rs }