# How to Establish a Peer-to-Peer QUIC Connection with Iroh

> Learn how to establish a peer-to-peer QUIC connection with Iroh. Discover direct connections via relay servers, QAD, and authenticated UDP handshakes.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: how-to-guide
- Published: 2026-06-25

---

**Iroh establishes direct peer-to-peer QUIC connections by registering with a relay server, discovering external addresses via QUIC Address Discovery (QAD), and performing authenticated handshakes over UDP while maintaining a relay fallback path.**

The **n0-computer/iroh** library provides a high-level API for building direct peer-to-peer connections over QUIC, abstracting complex NAT traversal, hole punching, and path management. This guide explains the internal machinery that enables direct QUIC connectivity between peers, even when both lie behind firewalls or NATs.

## Overview of the Iroh Connection Flow

Iroh does not simply open a UDP socket. Instead, it orchestrates a multi-step process that combines relay-assisted discovery with direct QUIC handshakes. The flow involves six distinct phases: endpoint initialization, relay registration, address discovery, direct handshake, path management, and stream multiplexing.

According to the source code in [`iroh/src/lib.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/lib.rs), the `Endpoint` type serves as the primary interface, while lower-level modules handle the transport specifics. The implementation relies on the `noq` QUIC stack for crypto handshakes and stream management.

## Step-by-Step Connection Establishment

### 1. Endpoint Creation and QUIC Configuration

The process begins with `Endpoint::builder(...).bind()`, which creates a UDP socket, generates a **secret key** for the node, and constructs a `QuicTransportConfig`. This configuration is stored in the endpoint and passed to the underlying `noq` QUIC stack.

In [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) (lines 58–87), the builder assembles transport settings, while [`iroh/src/endpoint/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/quic.rs) (lines 90–120) defines the default transport parameters tuned for hole punching. The [`iroh/src/lib.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/lib.rs) file exposes the high-level API that applications use to initiate this process.

### 2. Home-Relay Registration

Once the endpoint exists, it contacts the closest **Relay server** using an HTTP/1.1 connection upgraded to a custom binary protocol. The endpoint registers its **EndpointId** (derived from its public key), allowing the relay to forward encrypted datagrams to the node.

The relay actor maintains this persistent connection in [`iroh/src/socket/transports/relay/actor.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/relay/actor.rs) (lines 64–84). The client-side implementation of the relay protocol resides in [`iroh-relay/src/client.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/client.rs), handling the protocol upgrade and registration handshake.

### 3. QUIC Address Discovery (QAD)

Both peers utilize the relay for **QUIC Address Discovery**. The relay server runs a small QUIC endpoint defined in [`iroh-relay/src/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/quic.rs) (lines 1–15) that publishes each node's observed external IP and port inside QUIC-encrypted frames. 

The endpoint triggers QAD probes via the network reporting module in [`iroh/src/net_report/probes.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/net_report/probes.rs) (lines 28–33). This data allows each peer to learn the direct UDP socket address of the remote peer before attempting a connection.

### 4. Direct QUIC Handshake and Authentication

Armed with the remote's external address, the endpoint sends a **QUIC Initial** packet directly to the peer, bypassing the relay. The `noq` stack performs a TLS 1.3 handshake where peers authenticate each other using their **public keys**—no X.509 certificates are required.

The connection logic is invoked through `Endpoint::connect` in [`iroh/src/lib.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/lib.rs) (lines 64–72). Types such as `Connection`, `OpenBi`, and `RecvStream` are re-exported in [`iroh/src/endpoint/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/quic.rs) (lines 13–41), providing the interface for direct QUIC communication.

### 5. Path Selection and Relay Fallback

The endpoint maintains both a **direct** path and a **relay** path simultaneously. If the direct QUIC connection drops (for example, due to NAT mapping expiration), the endpoint instantly falls back to the relay without breaking higher-level streams.

The `socket::remote_map` module tracks the state of each path. 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) (lines 1245–1252), the system marks connections as "direct" when a path is established. Metrics exposing these states are defined in [`iroh/src/socket/metrics.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/metrics.rs) (lines 49–65), including counters for `paths_direct`.

## Working with QUIC Streams

Once the direct QUIC connection is established, the API offers **bidirectional** (`open_bi`) and **unidirectional** (`open_uni`) streams. Streams are cheap to create and multiplex over the same QUIC connection, allowing many simultaneous messages.

The [`iroh/examples/listen.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/listen.rs) file demonstrates accepting incoming connections and reading from streams, while [`iroh/examples/screening-connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/screening-connection.rs) shows opening unidirectional streams after establishing direct connectivity.

## Complete Code Examples

The following examples demonstrate the high-level API that automatically triggers the direct-QUIC flow:

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

```

```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 hide the complexity of NAT traversal, QUIC configuration, and path management behind simple `bind` and `connect` calls.

## Summary

- **Use `Endpoint::bind`** with presets to initialize the QUIC stack, UDP socket, and secret key generation.
- **Register with a home relay** automatically to ensure reachability behind NATs and firewalls.
- **Rely on QUIC Address Discovery (QAD)** to learn peer external addresses before attempting direct connections.
- **Authenticate via TLS 1.3** using public keys (EndpointId) instead of X.509 certificates.
- **Maintain parallel paths** with automatic fallback from direct UDP to relay if connectivity changes.

## Frequently Asked Questions

### What is QUIC Address Discovery (QAD) in Iroh?

QUIC Address Discovery is a protocol where the relay server publishes observed external IP and port information inside QUIC-encrypted frames. Peers query this service via the relay to learn each other's direct UDP addresses before initiating a connection, enabling successful hole punching through NATs.

### How does Iroh handle NAT traversal without manual configuration?

Iroh combines relay-assisted connectivity with automatic hole punching. The endpoint registers with a relay server for initial reachability, discovers its external address via QAD, and attempts direct UDP connections. If the direct path fails, it falls back to the relay transparently, all without requiring manual port forwarding or STUN server configuration.

### What is the role of the relay server in Iroh connections?

The relay server serves two critical functions: it provides a fallback path for encrypted traffic when direct UDP connectivity is impossible, and it hosts the QUIC Address Discovery endpoint that helps peers find each other's public addresses. The relay never sees plaintext data, as all traffic remains encrypted end-to-end.

### How does Iroh authenticate peers without X.509 certificates?

Iroh uses the `noq` QUIC stack to perform a TLS 1.3 handshake where authentication is based on the peers' public keys (EndpointId) rather than traditional X.509 certificates. This eliminates certificate management overhead while providing cryptographic verification of peer identity.