Skip to content

fix(routing): never route multicast or broadcast off-box - #16

Open
wighawag wants to merge 1 commit into
tunnetio:mainfrom
wighawag:fix/multicast-exit-node-leak
Open

fix(routing): never route multicast or broadcast off-box#16
wighawag wants to merge 1 commit into
tunnetio:mainfrom
wighawag:fix/multicast-exit-node-leak

Conversation

@wighawag

@wighawag wighawag commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

is_mesh_or_link_local checks loopback, link-local, broadcast and unspecified, but not multicast. With an exit node configured, multicast therefore passed the internet catch-all in route_once and was tunneled to the exit peer. Broadcast leaked by a different path: an exit node contributes 0.0.0.0/0 to the subnet table, and that LPM entry matched before the catch-all was reached.

LAN discovery protocols (mDNS, SSDP, Ableton Link) beacon on every interface including the mesh interface, continuously and forever. On one host that was a steady 8 packets/s of local service announcements, which on any machine with an exit node would have been pushed through the tunnel and out of the exit node.

Reject both before the subnet lookup and before the catch-all. Placing the check earlier is what closes the broadcast path; adding is_multicast to is_mesh_or_link_local alone would not have.

The mDNS relay is unaffected: it republishes discovered services over its own mesh topic rather than tunneling raw multicast frames.

Both tests fail without the fix and pass with it, and an existing assertion that ordinary internet traffic still reaches the exit node is kept alongside them.

@wighawag

wighawag commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Found while investigating #18 (the CGNAT range collision), but independent of it: this fires on any host with an exit node configured, regardless of Tailscale.

Refs #18

`is_mesh_or_link_local` checks loopback, link-local, broadcast and
unspecified, but not multicast. With an exit node configured, multicast
therefore passes the internet catch-all in `lookup_ip` and is tunneled to
the exit peer. Broadcast leaks by a different path: an exit node
contributes `0.0.0.0/0` to the subnet table, and that entry matches before
the catch-all is reached.

LAN discovery protocols (mDNS, SSDP, Ableton Link) beacon on every
interface including the mesh interface, continuously. On one host that was
a steady 8 packets/s of local service announcements, which on any machine
with an exit node would be pushed through the tunnel and out of the exit.

Reject both before the subnet lookup and before the catch-all. Placing the
check earlier is what closes the broadcast path; adding `is_multicast` to
`is_mesh_or_link_local` alone would not have.

The mDNS relay is unaffected: it republishes discovered services over its
own mesh topic rather than tunneling raw multicast frames.

Both new tests fail without the fix and pass with it, and an existing
assertion that ordinary internet traffic still reaches the exit node is
kept alongside them.
@wighawag
wighawag force-pushed the fix/multicast-exit-node-leak branch from 2e6a426 to 61964ab Compare September 6, 2026 14:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant