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 }