Files
fips/src/transport
Johnathan Corgan ae9a574bc0 fix(transport/udp): let connected sockets join an adopted traversal socket's port
A NAT traversal binds its base socket with a plain port-zero bind, and a
successful punch adopts that socket as the peer's transport. Connected-UDP
activation then opens a per-peer connected socket on the transport's local
address. Linux admits that second bind only when the socket already holding
the port set a reuse flag, and the traversal socket set neither, so every
activation attempt failed with EADDRINUSE at bind. The rx-loop tick retried
it on every pass, and the peer never left the unconnected path.

Set SO_REUSEPORT and SO_REUSEADDR inside UdpRawSocket::adopt, after the
socket is already bound. The order matters. Flags set before a port-zero
bind let the kernel hand out a port another flagged socket already holds, so
two concurrent traversals could share a port and the kernel would silently
split one peer's datagrams across two transports. Flags set after the bind
cannot change which port the socket was given; they only let a later socket
join it. The listen socket in UdpRawSocket::open already sets its flags
after its bind for the same reason.

adopt has one caller chain, which ends at traversal adoption, so the STUN
probe and configured listeners are untouched. The flags are best-effort, as
in open: if setting them fails, adoption still succeeds and only the
connected fast path is refused, rather than a working punched peer being
torn down.

Two tests cover the change, and both fail without it. The first adopts a
socket bound the way the traversal path binds, checks that it arrives with
neither flag, and requires a connected socket to open on its port and both
flags to read back; without the change the open fails with EADDRINUSE at
bind. The second adopts a traversal socket into a node peered with a node on
a configured listener, runs activation on both, and requires a connected
socket on each side, the adopted side's on the adopted socket's own port;
without the change the configured side activates, the adopted side does
not, and the failure carries the bind error from a direct open.

Each part of the change was also removed in turn. Without SO_REUSEPORT the
first test fails on that readback while the connected open still succeeds,
because either flag on the holder admits the join; without SO_REUSEADDR it
fails on that readback the same way; without both, both tests fail at the
bind. Flagging the socket before adoption fails the first test's
precondition, and running the second with FIPS_CONNECTED_UDP=0 fails its
configured-listener assertion first, so neither can pass vacuously.

Not established: Darwin behaviour. adopt is shared by the Unix backends, so
macOS builds get the flags too, and no measurement covers SO_REUSEPORT set
after bind there. FIPS_MACOS_CONNECTED_UDP=0 or FIPS_CONNECTED_UDP=0 turns
the fast path off without a rebuild.
2026-09-14 15:09:12 +00:00
..