How Iroh Uses QUIC for Peer-to-Peer Connections: Direct Paths Through NATs

Iroh establishes direct peer-to-peer connections using QUIC by combining relay-assisted address discovery with public-key authentication, enabling nodes behind NATs to negotiate direct UDP paths without X.509 certificates.

The n0-computer/iroh repository implements a peer-to-peer networking stack that leverages QUIC for secure, low-latency communication. Unlike traditional client-server QUIC implementations, Iroh uses QUIC for peer-to-peer connections by embedding custom NAT traversal logic directly into the connection lifecycle. This article examines how Iroh's Endpoint API orchestrates relay registration, QUIC address discovery, and direct path establishment to create resilient P2P links.

Endpoint Creation and QUIC Transport Configuration

Iroh's QUIC implementation begins with the Endpoint builder pattern defined in iroh/src/endpoint.rs. When calling Endpoint::builder(...).bind(), the system creates a UDP socket, generates a secret key for the node, and constructs a QuicTransportConfig that tunes the underlying noq QUIC stack for peer-to-peer scenarios.

The configuration logic resides in iroh/src/endpoint/quic.rs, where default transport settings are optimized for hole-punching and NAT traversal. This file re-exports core types from the noq crate, including Connection, OpenBi, and RecvStream, which the high-level API uses to manage QUIC streams.

// Create a default endpoint that can talk to relays and perform QAD.
let ep = iroh::Endpoint::bind(iroh::endpoint::presets::N0).await?;

Relay Registration and QUIC Address Discovery

Before establishing direct paths, each endpoint must register with a Relay server to ensure reachability. The registration process, handled in iroh/src/socket/transports/relay/actor.rs, establishes an HTTP/1.1 connection that upgrades to a custom binary protocol. The endpoint registers its EndpointId (derived from its public key), allowing the relay to forward encrypted datagrams when direct communication is not yet possible.

The relay facilitates QUIC Address Discovery (QAD) through iroh-relay/src/quic.rs, which defines a dedicated ALPN (/iroh-qad/0) for address discovery services. The relay server observes each node's external IP and port, publishing this information within QUIC-encrypted frames. Endpoints trigger QAD probes via iroh/src/net_report/probes.rs to learn their public-facing UDP addresses and discover the direct socket addresses of remote peers.

Direct QUIC Handshake and Public Key Authentication

Armed with the remote peer's external address, Iroh attempts to bypass the relay entirely. The endpoint sends a QUIC Initial packet directly to the discovered UDP socket, initiating a handshake through the noq stack.

Crucially, this handshake uses TLS 1.3 with public keys rather than traditional X.509 certificates. Peers authenticate each other using their EndpointId (public keys), eliminating the need for certificate authorities in pure peer-to-peer scenarios. The Endpoint::connect method in iroh/src/lib.rs orchestrates this process, returning a Connection object that represents a direct QUIC channel once the handshake succeeds.

// Connect to a remote peer (EndpointId is a public key) using an ALPN label.
let conn = ep.connect(remote_endpoint_id, b"my-alpn").await?;

Path Selection and Relay Fallback

Iroh maintains redundant connectivity paths to ensure session resilience. The system tracks both direct UDP paths and relay paths simultaneously in iroh/src/socket/remote_map/remote_state.rs. When a direct QUIC connection drops—such as when a NAT mapping expires—the endpoint can instantly fall back to the relay path without terminating higher-level streams.

The remote_state.rs module marks connections as "direct" when valid IP paths are confirmed, while iroh/src/socket/metrics.rs exposes counters (paths_direct) that reflect the current state of direct connectivity. This dual-path approach ensures that applications experience seamless connectivity even as network conditions change.

Working with QUIC Streams

Once established, direct QUIC connections support cheap, multiplexed streams. Iroh provides bidirectional (open_bi) and unidirectional (open_uni) streams that share the underlying connection, allowing multiple simultaneous conversations without head-of-line blocking.

The iroh/examples/listen.rs file demonstrates accepting incoming connections and reading from streams, while the library documentation in iroh/src/lib.rs shows how to open bidirectional streams for request-response patterns.

// Open a bidirectional stream, send a message and receive the reply.
let (mut send, mut recv) = conn.open_bi().await?;
send.write_all(b"hello").await?;
send.finish().await?;
let reply = recv.read_to_end(1024).await?;
println!("got: {}", String::from_utf8_lossy(&reply));

Summary

Iroh implements QUIC for peer-to-peer connections through a sophisticated multi-stage process:

Frequently Asked Questions

How does Iroh handle NAT traversal without traditional STUN/TURN servers?

Iroh uses a relay-assisted approach combined with QUIC Address Discovery (QAD). Nodes register with Iroh relay servers that observe their external UDP addresses and share them with peers. This allows endpoints to attempt direct UDP hole punching using the discovered addresses, falling back to the relay only when necessary. According to the source code in iroh-relay/src/quic.rs, the relay runs a dedicated QUIC server for address discovery that publishes observed IPs within encrypted QUIC frames.

What authentication mechanism does Iroh use for QUIC connections?

Iroh authenticates peers using public keys embedded in the TLS 1.3 handshake rather than X.509 certificates. Each node's EndpointId is derived from its public key, allowing mutual authentication without certificate authorities. This is implemented in the noq QUIC stack, which is re-exported in iroh/src/endpoint/quic.rs. When Endpoint::connect is called, the handshake validates that the remote peer possesses the private key corresponding to the expected public key.

How does Iroh maintain connectivity when direct paths fail?

The system implements seamless path migration by maintaining both direct and relay paths simultaneously. As implemented in iroh/src/socket/remote_map/remote_state.rs, the endpoint tracks whether a connection has established a direct IP path. If the direct QUIC connection drops due to NAT timeout or firewall changes, the endpoint instantly falls back to the relay path without breaking application-level streams. Metrics in iroh/src/socket/metrics.rs track these transitions via the paths_direct counter.

Can Iroh endpoints communicate without any relay infrastructure?

While Iroh can establish direct connections between nodes with public IP addresses or within the same network, the relay infrastructure is required for the initial QUIC Address Discovery process when nodes are behind NATs. The code in iroh/src/net_report/probes.rs triggers QAD probes that rely on the relay to observe and report external addresses. However, once direct paths are established and confirmed in remote_state.rs, communication can occur entirely over direct UDP/QUIC without relay involvement.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →