Summary
The current UDP relay (crates/network/lib/udp_relay.rs) creates a separate host-side UDP socket per (guest_src, guest_dst) pair. This results in address-dependent mapping — different destinations see different source ports from the guest. This breaks UDP hole punching (STUN/TURN, WebRTC, Tailscale direct connections).
Current behavior
- Guest sends to
1.2.3.4:3478 → host socket A on port X
- Guest sends to
5.6.7.8:3478 → host socket B on port Y
Different destinations see different external source ports.
Desired behavior
Endpoint-independent mapping: the same guest source port always maps to the same external port regardless of destination.
- Guest sends to
1.2.3.4:3478 → host socket A on port X
- Guest sends to
5.6.7.8:3478 → same host socket A on port X
Implementation notes
The relay would need to:
- Key sessions on
guest_src only, not (guest_src, guest_dst)
- Use a single unconnected host socket per guest source port
- Use
send_to() / recv_from() instead of connect() + send() / recv()
- Filter responses by source address manually (currently handled by the connected socket)
Trade-off: connected sockets currently provide free source-address filtering and prevent spoofed datagrams. An unconnected design needs explicit filtering.
Context
Requested by a user trying to use Tailscale with direct peer-to-peer connectivity inside microsandbox.
Summary
The current UDP relay (
crates/network/lib/udp_relay.rs) creates a separate host-side UDP socket per(guest_src, guest_dst)pair. This results in address-dependent mapping — different destinations see different source ports from the guest. This breaks UDP hole punching (STUN/TURN, WebRTC, Tailscale direct connections).Current behavior
1.2.3.4:3478→ host socket A on port X5.6.7.8:3478→ host socket B on port YDifferent destinations see different external source ports.
Desired behavior
Endpoint-independent mapping: the same guest source port always maps to the same external port regardless of destination.
1.2.3.4:3478→ host socket A on port X5.6.7.8:3478→ same host socket A on port XImplementation notes
The relay would need to:
guest_srconly, not(guest_src, guest_dst)send_to()/recv_from()instead ofconnect()+send()/recv()Trade-off: connected sockets currently provide free source-address filtering and prevent spoofed datagrams. An unconnected design needs explicit filtering.
Context
Requested by a user trying to use Tailscale with direct peer-to-peer connectivity inside microsandbox.