policy: surface exit nodes via autogroup:internet (#3212)

compileFilterRules skipped autogroup:internet destinations to keep them
out of the wire-format PacketFilter, but those same compiled rules are
the source of pm.matchers — and Node.CanAccess relies on a matcher whose
DestsIsTheInternet covers the public internet to surface exit-node peers
to ACL sources. With the skip in place no such matcher existed, exit
nodes silently dropped out of the source's peer list, and the docs'
exit-node walkthrough stopped working: `tailscale exit-node list`
returned "no exit nodes found" and `tailscale set --exit-node=<ip>`
returned "no node found in netmap with IP".

Drop the compile-time skip so autogroup:internet flows through normal
matcher derivation, and teach ReduceFilterRules to keep the resulting
client packet-filter rule on exit-route advertisers — Tailscale SaaS
sends those rules to exit nodes so the kernel filter accepts traffic
forwarded by autogroup:internet sources.

Verified against a live tailnet on 2026-04-28 via tscap; the b17/b18
captures land under testdata/issue_3212/ as a regression guard. The
captures are isolated from testdata/routes_results/ because the broader
TestRoutesCompat machinery assumes a CIDR-prefix wire format that
differs from the IPSet-range form SaaS emits for autogroup:internet —
aligning that wire format is tracked separately.

Fixes #3212
This commit is contained in:
Kristoffer Dalby 2026-04-28 11:30:58 +00:00
parent a7d405a255
commit c7a0ca709f
8 changed files with 22985 additions and 11 deletions

View file

@ -951,11 +951,13 @@ func TestReduceNodesFromPolicy(t *testing.T) {
]
}`,
node: n(1, "100.64.0.1", "mobile", "mobile"),
// autogroup:internet does not generate packet filters - it's handled
// by exit node routing via AllowedIPs, not by packet filtering.
// Only server is visible through the mobile -> server:80 rule.
// autogroup:internet emits no client packet filter, but it
// must still produce a matcher: Node.CanAccess uses
// matcher.DestsIsTheInternet() + IsExitNode() to surface
// exit-node peers (juanfont/headscale#3212).
want: types.Nodes{
n(2, "100.64.0.2", "server", "server"),
n(3, "100.64.0.3", "exit", "server", "0.0.0.0/0", "::/0"),
},
wantMatchers: 1,
},