Commit Graph
19 Commits
Author SHA1 Message Date
Michael Ben-Ami 1b2062f3c1 net/tstun: invoke conn25 app connector hook on injected reads
The primary purpose is that return packets from the target app get
properly SNATed on connectors with --tun=userspace-networking, matching
the NAT behavior in the kernel tun path.

This is also necessary but not sufficient for clients of connectors in
userspace networking mode. The hook will DNAT MagicIPs, but won't
actually be sent MagicIPs until conn25 app connector DNS works with
userspace networking.

Fixes tailscale/corp#43201

Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-06-24 16:59:58 -04:00
Michael Ben-Ami a9ea6336fa wgengine: delete Conn25 packet hooks
Package features/conn25 wires up the hooks directly on the tun wrapper
without needing to go through the userspace engine, so this codepath is
unused and not needed.

Updates #cleanup

Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-06-12 13:43:55 -04:00
Michael Ben-Ami 6f281ccbcd feature/conn25: add on-remove hook for flows in FlowTable
The hook fires when a flow is removed for any reason (LRU capacity eviction,
tuple-collision displacement, or idle-time expiry). The hook is invoked
exactly once per flow, after the flow table mutex is released, so callbacks
may safely acquire other locks.

We rename the IPMapper interface to Conn25Datapath, and add
ClientFlowCreated/ClientFlowRemoved methods so *Conn25 can keep client-side
address assignments alive while traffic is in flight. Those methods are
currently stubbed for future work.

Connector flows do not currently call these methods.

Updates tailscale/corp#38630
Updates tailscale/corp#43180

Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-06-12 10:44:42 -04:00
Michael Ben-Ami 2a0eafc20f feature/conn25: drop returned error from NewFlow signature
The returned error in the signature is left over from previous
implementations and was only returning nil.

If we know NewFlow will succeed we can fire a create hook (implemented
in a future commit) before NewFlow, which will prevent a remove hook for
a flow from firing before the create hook for the same flow.

Updates tailscale/corp#38630

Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-06-12 10:44:42 -04:00
Michael Ben-Ami 618b606b46 feature/conn25: expire idle flows from FlowTable
Track lastSeen on each cached flow and add a sweeper goroutine
that periodically removes flows idle past the idle timeout.

Introduce tunables for idle timeout, maximum flows removed per sweep (to
limit mutex hold time), and the sweeper interval.

Also cap the previously-unlimited tables: 10k client flows, 100k
connector flows.

Updates tailscale/corp#38630

Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-06-08 11:03:25 -04:00
Michael Ben-Ami 5877809097 feature/conn25: unify FlowTable storage to prepare for expiry
Previously we had two maps keyed on a direction-specific tuple, with
distinct values containing the data (action) for that direction.
Values pointed at each other across maps to ensure they were removed
at the same time in the case of tuple overwrite, but LRU eviction
was per-map. So if LRU was turned on, it was possible for one
direction's data (action) to be evicted and leave the other direction
dangling.

NewFlow replaces the two direction-specific flow constructors, and
lookups return the direction-specific PacketAction directly.

Now the values in each map point to the same element, with data for both
directions in the element. A linked list also points to the elements to
implement LRU. The previous flowtrack.Cache is removed.

The single LRU structure will allow us to implement idle time expiration
by walking the list backward starting with the least recently used flow, and
stopping after a fixed number of flows, or at the first non-expired flow.

We add commented-out unused placeholder fields for tracking the
"last seen" timestamp, and an on-removal hook, to document the intent for
the follow-up expiry work.

Updates tailscale/corp#38630

Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-05-26 10:09:48 -04:00
Michael Ben-Ami 822299642b feature/conn25: centralize config on Conn25 with atomic access
We have two sources of truth for configuration state: the node view
(from the netmap/policy) and prefs (the --advertise-connector option).
These come with two independent update paths: onSelfChange for node view
changes and profileStateChange for pref changes.

Centralize config on Conn25 so that onSelfChange and profileStateChange
can update their independent parts without bundling changes together.
The old bundled approach required read-modify-write, which opened the
door to potential TOCTOU bugs. The node view config is
stored as an atomic.Pointer[config] and the prefs-derived field
(advertise-connector) becomes an independent atomic.Bool. onSelfChange
creates a fresh config and stores it atomically. profileStateChange sets
the bool.

This also establishes clearer lines of responsibility:

 - Configuration state lives on Conn25. Methods that need to read
   config (isConnectorDomain, mapDNSResponse, the IPMapper methods)
   are on Conn25, and use the atomics for synchronization.

 - "Active" state (address allocations, transit IP mappings) lives on
   client and connector, and use a mutex for synchronization on that
   state, without conflicting with configuration synchronization.
   It's fine for active state to be out of sync with config — e.g. a
   transit IP allocated for an app should still be tracked, and gracefully
   expired, even if the app is removed from the node view.
   Removing config responsibility from client/connector makes these
   cases clearer to handle.

 - In cases where the client or connector does need access to
   config-derived state, e.g. a client reconfiguring its IP pools from
   the IPSets in the config, we can use closures for the
   client or connector to get just the latest state it needs from the
   config. See getIPSets() in this commit.

 - As of this commit, the connector doesn't need config-derived state at
   all.

Fixes tailscale/corp#40872

Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-04-30 16:29:56 -04:00
Michael Ben-Ami 1dc08f4d41 appc,feature/conn25: prevent clients from forwarding DNS requests and
modifying DNS responses for domains they are also connectors for

For Connectors 2025, determine if a client is configured as a
connector and what domains it is a connector for. When acting as a
client, don't install Split DNS routes to other connectors for those
domains, and don't alter DNS responses for those domains. The responses
are forwarded back to the original client, which in turn does the alteration,
swapping the real IP for a Magic IP.

A client is also a connector for a domain if it has tags that overlap
with tags in the configured policy, and --advertise-connector=true
in the prefs (not in the self-node Hostinfo from the netmap). We use the prefs
as the source of truth because control only gets a copy from the prefs, and
may drift. And the AppConnector field is currently zeroed out in the
self-node Hostinfo from control.

The extension adds a ProfileStateChange hook to process prefs changes,
and the config type is split into prefs and nodeview sub-configs.

Fixes tailscale/corp#39317

Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-04-16 09:41:54 -04:00
Michael Ben-Ami 156e6ae5cd feature/conn25: install all the hooks
Install the previously uninstalled hooks for the filter and tstun
intercepts. Move the DNS manager hook installation into Init() with all
the others. Protect all implementations with a short-circuit if the node
is not configured to use Connectors 2025. The short-circuit pattern
replaces the previous pattern used in managing the DNS manager hook, of
setting it to nil in response to CapMap changes.

Fixes tailscale/corp#38716

Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-03-27 11:52:34 -04:00
Michael Ben-Ami a57c6457c9 ipn/ipnlocal: debounce extra enqueues in ExtensionHost.AuthReconfigAsync
Fixes tailscale/corp#39065

Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-03-25 09:11:15 -04:00
Michael Ben-Ami bdcf976477 feature/conn25: guard extension Init() and PeerAPI handler with opt-in env var
Fixes tailscale/corp#39003

Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-03-24 12:26:14 -04:00
Michael Ben-Ami ea7040eea2 ipn/{ipnext,ipnlocal}: expose authReconfig in ipnext.Host as AuthReconfigAsync
Also implement a limit of one on the number of goroutines that can be
waiting to do a reconfig via AuthReconfig, to prevent extensions from
calling too fast and taxing resources.

Even with the protection, the new method should only be used in
experimental or proof-of-concept contexts. The current intended use is
for an extension to be able force a reconfiguration of WireGuard, and
have the reconfiguration call back into the extension for extra Allowed
IPs.

If in the future if WireGuard is able to reconfigure individual peers more
dynamically, an extension might be able to hook into that process, and
this method on ipnext.Host may be deprecated.

Fixes tailscale/corp#38120
Updates tailscale/corp#38124
Updates tailscale/corp#38125

Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-03-20 17:29:11 -04:00
Michael Ben-AmiandFran Bull ce7789071f feature/conn25: add NATing support with flow caching
Introduce a datapathHandler that implements hooks that will
receive packets from the tstun.Wrapper. This commit does not wire
those up just yet.

Perform DNAT from Magic IP to Transit IP on outbound flows on clients,
and reverse SNAT in the reverse direction.

Perform DNAT from Transit IP to final destination IP on outbound flows
on connectors, and reverse SNAT in the reverse direction.

Introduce FlowTable to cache validated flows by 5-tuple for fast lookups
after the first packet.

Flow expiration is not covered, and is intended as future work before
the feature is officially released.

Fixes tailscale/corp#34249
Fixes tailscale/corp#35995

Co-authored-by: Fran Bull <fran@tailscale.com>
Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-03-18 11:49:47 -04:00
Michael Ben-Ami 40858a61fe 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 <mzb@tailscale.com>
2026-03-06 09:42:44 -05:00
Michael Ben-AmiandClaude 811fe7d18e ipnext,ipnlocal,wgengine/filter: add extension hooks for custom filter matchers
Add PacketMatch hooks to the packet filter, allowing extensions to
customize filtering decisions:

- IngressAllowHooks: checked in RunIn after pre() but before the
  standard runIn4/runIn6 match rules. Hooks can accept packets to
  destinations outside the local IP set. First match wins; the
  returned why string is used for logging.

- LinkLocalAllowHooks: checked inside pre() for both ingress and
  egress, providing exceptions to the default policy of dropping
  link-local unicast packets. First match wins. The GCP DNS address
  (169.254.169.254) is always allowed regardless of hooks.

PacketMatch returns (match bool, why string) to provide a log reason
consistent with the existing filter functions.

Hooks are registered via the new FilterHooks struct in ipnext.Hooks
and wired through to filter.Filter in LocalBackend.updateFilterLocked.

Fixes tailscale/corp#35989
Fixes tailscale/corp#37207

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-02-24 10:54:56 -05:00
Michael Ben-Ami 0bac4223d1 tstun: add test for intercept ordering
Fixes tailscale/corp#36999

Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-02-11 16:48:19 -05:00
Michael Ben-Ami 5a5572e48a tstun,wgengine: add new datapath hooks for intercepting Connectors 2025
app connector packets

We introduce the Conn25PacketHooks interface to be used as a nil-able
field in userspaceEngine. The engine then plumbs through the functions
to the corresponding tstun.Wrapper intercepts.

The new intercepts run pre-filter when egressing toward WireGuard,
and post-filter when ingressing from WireGuard. This is preserve the
design invariant that the filter recognizes the traffic as interesting
app connector traffic.

This commit does not plumb through implementation of the interface, so
should be a functional no-op.

Fixes tailscale/corp#35985

Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-02-09 17:06:27 -05:00
Michael Ben-Ami 3f1851a6d9 types/dnstype, ipn/ipnlocal: allow other DNS resolvers with exit nodes
dnstype.Resolver adds a boolean UseWithExitNode that controls
whether the resolver should be used in tailscale exit node contexts
(not wireguard exit nodes). If UseWithExitNode resolvers are found,
they are installed as the global resolvers. If no UseWithExitNode resolvers
are found, the exit node resolver continues to be installed as the global
resolver. Split DNS Routes referencing UseWithExitNode resolvers are also
installed.

Updates #8237

Fixes tailscale/corp#30906
Fixes tailscale/corp#30907

Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2025-08-15 08:17:01 -04:00
mzbenami 5865d0a61a Makefile: 'generate' target (#16746)
Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2025-08-01 13:30:42 -04:00