Skip to content

Keep holepunching to every peer instead of settling for the relay - #140

Open
jmiettine wants to merge 3 commits into
mainfrom
feature/optimize_p2p_connections
Open

jmiettine wants to merge 3 commits into
mainfrom
feature/optimize_p2p_connections

Conversation

@jmiettine

@jmiettine jmiettine commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Why connections stayed on the relay

From iroh 1.4.1's source:

  • A holepunching round probes for about 4 s. After a failed round, iroh doesn't start another until an address changes or its 60 s check comes round.
  • Only the side holding a client connection to a peer starts rounds.
  • Guest-to-guest pairs had no connection until Start, so they only got the loading time before Go to punch.
  • Guests sent Nat { hard } right after joining, before the first net report, so it was nearly always false. That broke the "hard NAT dials" rule in to_dial.

Changes

  • New crates/net/src/punch.rs. Each peer holds a short-lived PUNCH_ALPN connection to every lobby member and the race roster. Opening one starts a round from our side. While the peer is still relayed, it is reopened every 6 s for a minute, then every 30 s. Once direct, it is just watched, and punching starts again if the path falls back to the relay. A direct path found on any connection to a peer is used by all of them, so the lobby connection and mesh links get it too. Disabled in the browser and in relay-only mode.
  • Nat is resent to the host when the net report changes the hard-NAT flag. Old hosts already accepted repeated Nat, so the wire format is unchanged.
  • Incoming handshakes are accepted in a task, not in the session loop. Waiting for them in the loop stalled it, and the pads, for a relayed round trip per connection, which would have been frequent with the punch connections.
  • The lobby ALPN is bumped to ctr-rust/lobby/14. Old builds would refuse the punch connections only partway through the handshake, so each attempt would stall an old peer's loop. The bump keeps the two builds apart, and the realm listing greys out the other build's lobbies.

Testing

New tests:

  • punch.rs unit tests, on two local endpoints found through an in-memory address lookup (no relay or DNS):
    • a_direct_peer_is_held_on_one_connection: one punch connection is opened and held past the first retry window, though the peer set is given again every 100 ms, as the session does. Ourselves and repeated peers are left out.
    • a_peer_left_out_of_the_set_is_let_go: removing a peer closes its punch connection.
    • without_sockets_of_our_own_nothing_is_punched: relay-only mode punches nothing.
  • loopback.rs: a_punch_connection_to_the_host_is_held_and_is_no_guest: the host keeps a bare endpoint's punch connection open, doesn't count it as a guest, and a real guest still joins.

The first and last of these were checked by breaking the code on purpose (reopening while direct; the host treating punch connections as guests). Each failed as it should.

Results:

  • cargo test -p net --lib: 30 passed. The held-connection test adds about 8 s.
  • cargo test -p net --test loopback: 16 passed on two runs in a row. One run before handshakes moved off the loop had a single timeout in a_guest_with_another_build_is_turned_away. It didn't recur.

Not covered by tests: resending Nat (needs iroh's net report to change its answer), relay→direct upgrades (local endpoints are direct from the start), and handshakes not blocking the loop (covered only by the existing suite passing).

Not yet tried on real NATs. Loopback involves no relay or NAT. To check: the relay flag in Event::Rtt / net_info(), and RUST_LOG=iroh::_events=debug (qnt::init lines, one per round).

Symmetric ↔ port-restricted and symmetric ↔ symmetric pairs mostly still can't be punched (iroh does no port prediction), so those stay on the relay.

🤖 Generated with Claude Code

@jmiettine
jmiettine requested a review from elipp November 3, 2026 23:17
jmiettine and others added 2 commits November 5, 2026 22:09
iroh starts a holepunching round when a connection comes up, when an
address changes, or once a minute, and only from the side holding a
client connection. A failed first round left a pair on the relay for up
to a minute, and guest-to-guest pairs had no connection until the race
started.

- punch: hold a PUNCH_ALPN connection to every lobby member (and the
  race roster). Reopening it while the peer is relayed starts a new
  round from our side: every 6 s for a minute, then every 30 s. A direct
  path found on any connection to a peer is used by all of them.
- Resend Nat to the host when the net report changes the hard-NAT flag:
  guests sent it before the first report, so it was nearly always false.
- Accept incoming handshakes off the session loop, so they no longer
  stall it (and the pads) for a relayed round trip.
- Bump the lobby ALPN to ctr-rust/lobby/14 so old and new builds do not
  share lobbies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- punch: a direct peer is held on one connection (no new ones on every
  `set`, ourselves and repeats left out); a peer left out of the set is
  let go; an endpoint without sockets punches nothing.
- loopback: the host holds a punch connection and does not take it for
  a guest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jmiettine
jmiettine force-pushed the feature/optimize_p2p_connections branch from a1bdfcb to 46aa090 Compare November 5, 2026 20:10
- punch: each peer added or dropped, each attempt and how it ended
  (direct and by which path, still relayed, unreachable), and a direct
  path that is lost.
- session: our NAT as iroh's net report says it (or that it is not known
  yet), what a guest told the host and when it told it again, and the
  roster's hard NATs at the start of a race.
- peers: which links each player dials, next to the roster's flags.

Shown with RUST_LOG=net=debug (with iroh::_events=debug for iroh's side).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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