# How Iroh Establishes Direct Peer-to-Peer Connections Using QUIC: A Complete Technical Breakdown

> Discover how Iroh uses QUIC for direct P2P connections via relay-assisted discovery, NAT hole-punching, and public-key auth. Explore the technical breakdown.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: deep-dive
- Published: 2026-06-18

---

**Iroh establishes direct peer-to-peer connections using QUIC by orchestrating relay-assisted address discovery, NAT hole-punching, and public-key authentication, allowing nodes to communicate directly while maintaining fallback paths through relay servers.**

Iroh is a peer-to-peer networking library that builds robust direct connections on top of the QUIC protocol. Unlike traditional client-server models, Iroh implements a sophisticated NAT traversal strategy involving relay servers, QUIC address-discovery (QAD), and the `noq` QUIC stack to establish encrypted connections between nodes behind firewalls. This article examines the complete connection lifecycle as implemented in the `n0-computer/iroh` repository, from endpoint initialization to direct stream multiplexing.

## Endpoint Initialization and QUIC Configuration

Every Iroh connection begins with the **Endpoint** abstraction, which serves as the central configuration and connection factory. In [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs), the `Endpoint::builder` constructs a UDP socket and generates a cryptographic **secret key** that uniquely identifies the node. The builder assembles a `QuicTransportConfig` (defined in [`iroh/src/endpoint/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/quic.rs)) containing default parameters tuned specifically for hole-punching scenarios, such as aggressive retransmission timers and UDP socket options that maximize NAT compatibility.

The configuration is stored internally and passed to the underlying `noq` QUIC stack. When `bind()` is called, the endpoint becomes capable of accepting direct UDP traffic while simultaneously preparing for relay-assisted discovery. This dual-mode capability ensures that the node can receive both direct incoming connections and relayed datagrams without restarting.

## Relay Registration for Peer-to-Peer Reachability

Before direct connection is possible, each endpoint must establish a persistent presence on a **Relay server**. According to [`iroh/src/socket/transports/relay/actor.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/relay/actor.rs), the endpoint opens an HTTP/1.1 connection to the nearest relay, upgrades it to a custom binary protocol, and registers its **EndpointId** (derived from the public key). The relay actor maintains this connection as a long-lived background task, ensuring the node remains reachable even when hidden behind symmetric NATs.

The relay client implementation in [`iroh-relay/src/client.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/client.rs) handles the protocol specifics, including heartbeat messages and connection keepalives. Once registered, the relay knows how to forward encrypted datagrams to the node, providing a reliable fallback path while the system attempts to establish a direct route.

## QUIC Address Discovery (QAD) for Direct Connectivity

To enable **NAT traversal**, Iroh uses **QUIC Address Discovery (QAD)** to learn the public-facing UDP socket of remote peers. As implemented in [`iroh-relay/src/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/quic.rs), the relay runs a dedicated QAD server that publishes observed external IP addresses inside QUIC-encrypted frames. When a peer initiates a connection, the endpoint triggers QAD probes (managed in [`iroh/src/net_report/probes.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/net_report/probes.rs)) to request the remote node's external address from the relay.

The QAD protocol uses a specific ALPN identifier (`/iroh-qad/0`) to distinguish these discovery packets from application traffic. By leveraging the relay's authoritative view of the network, peers obtain the necessary addressing information to attempt direct UDP communication without relying on static IP configurations.

## Direct QUIC Handshake and Public-Key Authentication

Armed with the remote peer's external address, the endpoint initiates a **direct QUIC handshake** by sending a QUIC Initial packet straight to the discovered UDP socket, bypassing the relay entirely. In [`iroh/src/lib.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/lib.rs), the `Endpoint::connect` method coordinates this attempt using the `noq` QUIC stack.

The handshake performs TLS 1.3 authentication where peers verify each other using their **public keys** (EndpointIds) rather than traditional X.509 certificates. This eliminates the need for certificate authorities while providing cryptographic identity verification. If the UDP packets successfully traverse the NAT (a process known as hole-punching), the `noq` stack completes the handshake and creates a `Connection` object representing the direct channel.

## Path Selection and Fallback for P2P Connections

Iroh maintains simultaneous **direct** and **relay** paths for every connection. The `socket::remote_map` module (specifically [`iroh/src/socket/remote_map/remote_state.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map/remote_state.rs)) tracks the state of each path, incrementing the `paths_direct` metric when a direct IP route is confirmed. If the direct QUIC connection drops due to NAT mapping expiration or firewall changes, the endpoint instantly falls back to the relay path without breaking higher-level streams.

This path redundancy is transparent to applications. Metrics exposed in [`iroh/src/socket/metrics.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/metrics.rs) allow operators to monitor which connections currently utilize direct paths versus relayed paths, providing visibility into network topology and traversal success rates.

## Stream Multiplexing Over Direct Connections

Once a direct QUIC connection is established, the API exposes cheap, multiplexed streams. Unlike TCP, where each connection requires a new socket, QUIC streams share the same underlying UDP association. The endpoint provides `open_bi()` for bidirectional streams and `open_uni()` for unidirectional streams, as demonstrated in the library examples.

These streams handle the backpressure and flow control internally within the `noq` stack, allowing applications to spawn thousands of concurrent conversations over a single direct connection without blocking.

## Practical Implementation: Client and Server Examples

The following examples demonstrate the complete client and server flow that automatically triggers the direct-QUIC establishment process described above.

### Client Connection

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

// 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?;

// 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));

```

### Server Acceptance

```rust
// Server side: accept incoming connections and read from a stream.
let ep = iroh::Endpoint::builder(iroh::endpoint::presets::N0)
    .alpns(vec![b"my-alpn".to_vec()])
    .bind()
    .await?;

let incoming = ep.accept().await?.await?;          // a `Connection`
let (mut send, mut recv) = incoming.accept_bi().await?;
let data = recv.read_to_end(1024).await?;
println!("peer sent: {}", String::from_utf8_lossy(&data));
send.write_all(b"world").await?;
send.finish().await?;

```

These snippets invoke `Endpoint::bind` and `Endpoint::connect` (or `accept`), which internally handle NAT traversal, QAD probing, and direct path negotiation.

## Summary

- **Endpoint initialization** in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) configures the `noq` QUIC stack with secret keys and hole-punching optimized parameters.
- **Relay registration** via [`iroh/src/socket/transports/relay/actor.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/relay/actor.rs) ensures nodes remain reachable behind NATs using HTTP/1.1 upgraded connections.
- **QUIC Address Discovery** in [`iroh-relay/src/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/quic.rs) provides external IP information necessary for direct UDP communication.
- **Direct handshake** uses public-key authentication (EndpointIds) instead of X.509 certificates, implemented in [`iroh/src/lib.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/lib.rs).
- **Path management** tracks direct and relay routes simultaneously, falling back instantly if direct paths fail, as monitored in [`iroh/src/socket/remote_map/remote_state.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map/remote_state.rs).
- **Stream multiplexing** provides efficient bidirectional and unidirectional channels over the single direct QUIC connection.

## Frequently Asked Questions

### How does Iroh handle NAT traversal without manual port forwarding?

Iroh uses **QUIC Address Discovery (QAD)** combined with relay-assisted hole-punching. The relay server (implemented in [`iroh-relay/src/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/quic.rs)) observes the external IP and port of each endpoint and shares this information with peers. Both nodes then attempt to send UDP packets directly to these discovered addresses, creating NAT mappings that allow incoming traffic to reach the otherwise hidden internal sockets.

### What authentication mechanism secures direct QUIC connections in Iroh?

Direct connections authenticate using **TLS 1.3 with public keys** rather than traditional X.509 certificates. Each endpoint generates a secret key during initialization ([`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs)), and the corresponding public key serves as the **EndpointId**. During the QUIC handshake managed by the `noq` stack, peers cryptographically verify these identities, ensuring encrypted connections without requiring certificate authorities.

### Can Iroh maintain connections if the direct path fails?

Yes. Iroh maintains simultaneous **direct** and **relay** paths for every connection. The `socket::remote_map` module tracks connection states in [`iroh/src/socket/remote_map/remote_state.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map/remote_state.rs), and if the direct QUIC path becomes unavailable (due to NAT timeout or firewall changes), traffic automatically falls back to the relay path without application-level interruption. Metrics in [`iroh/src/socket/metrics.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/metrics.rs) expose which path is currently active.

### What is the role of the ALPN identifier in Iroh's QUIC implementation?

The **Application-Layer Protocol Negotiation (ALPN)** identifier distinguishes different protocols running over the same QUIC connection. Iroh uses specific ALPN strings such as `/iroh-qad/0` for QUIC address-discovery traffic (defined in [`iroh-relay/src/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/quic.rs)) and user-defined ALPNs (like `b"my-alpn"` in the examples) for application traffic. This allows the endpoint to route incoming streams to the appropriate handlers based on the negotiated protocol.