Skip to content

Changes related to holepunching - #8

Open
cowlicks wants to merge 2 commits into
datrs:mainfrom
cowlicks:per-datagram-ttl
Open

Changes related to holepunching#8
cowlicks wants to merge 2 commits into
datrs:mainfrom
cowlicks:per-datagram-ttl

Conversation

@cowlicks

@cowlicks cowlicks commented Aug 30, 2026

Copy link
Copy Markdown
Member

Holepunching requires being able to set the TTL/hoplimit on packets. When holepunching sometimes we want to open a lot ports on a NAT on our own (source) side, but without hitting the NAT on their (destination) side. To do this we send packets with a low TTL so they leave our NAT, creating a opening, then die before reaching the other (destination) NAT. We want to avoid hitting the destination with these packets because it would look like port scanning.

This PR also abstracts the network interface with a trait. We define the Transport trait, which by default wraps the UdpSocket. We do this so we use a simulated network for testing and experimenting with holepunching.

cowlicks and others added 2 commits August 30, 2026 02:08
Datagrams could only be sent at the socket's default TTL, so a caller had no
way to send one with a deliberately short hop budget.

Holepunching needs that. A puncher's first rounds go out at a low TTL (hyperdht
uses 5) so they cross the local NAT, creating the mapping that is the whole
point of punching, but expire in transit before reaching the peer. Delivering
them would be useless (the far NAT has no mapping yet, so they are dropped) and
harmful: a burst of unsolicited inbound packets looks like a port scan and gets
the source rate-limited or blocked right before the real connection attempt.

Transmit gains a `ttl`, emitted as an IP_TTL (v4) or IPV6_HOPLIMIT (v6) control
message in prepare_msg, which serves both the sendmmsg and macOS sendmsg paths.
CMSG_LEN goes 88 -> 112: 88 was exactly the sum of the three existing control
messages with nothing spare, so without the bump the TTL is silently dropped.
The init assertion now demands room for two c_int messages instead of one.

Stream traffic always uses UDX_DEFAULT_TTL; only the raw datagram path carries a
caller value. UdxSocket::send keeps its signature and delegates to the new
send_with_ttl, so no downstream source changes are needed (verified: the full
hyperswarm suite, JS interop included, passes against this unchanged).

The non-unix fallback has no control messages, so it sets TTL as a socket option
when the value changes. That path is cfg'd out on unix and is untested.
UdxSocketInner held a concrete udx_udp::UdpSocket built straight from
std::net::UdpSocket, so udx could only ever run on a kernel socket. Testing
holepunching needs otherwise: a simulated network with configurable NAT
behaviour and deterministic delivery.

Adds `trait Transport` (poll_send / poll_recv / local_addr / udp_state), stored
as `Box<dyn Transport>`. Erasure rather than a type parameter because UdxSocket
is already Arc<Mutex<UdxSocketInner>>, i.e. a type-erased handle behind a
pointer and a lock, whereas a generic would bubble through nine public types
across four crates (UdxSocket -> MessageDataStream -> IoHandler -> Rpc -> Dht ->
Server -> Swarm) and break three published crates for a test seam. Dispatch is
one virtual call per sendmmsg batch of up to 32 datagrams, behind a mutex and a
syscall.

`with_transport` takes `impl Transport` and boxes internally, so callers need no
turbofish or Box::new, and no existing signature changes. Verified: the whole
hyperswarm suite, JS interop included, passes against this with zero source
changes downstream.

Batching now follows the transport's own UdpState instead of the platform's, via
the new UdpState::with_max_gso_segments. Otherwise a non-kernel transport is
handed GSO transmits carrying several datagrams under one header, which it would
have to take apart again.

`mod udp` becomes public so an out-of-tree Transport can name Transmit and
RecvMeta through this crate, rather than via a separate udx-udp dependency that
could resolve to a second, incompatible copy of those types.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GxZBTwEoK9qRe48qSQ9E5V
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