What is the Architecture of the iroh Node? A Deep Dive into the n0-computer/iroh Stack

The iroh node architecture comprises four specialized Rust crates—iroh, iroh-relay, iroh-base, and iroh-dns-server—that together provide a unified peer-to-peer QUIC endpoint with automatic NAT traversal, relay fallback, and DNS-based address resolution.

The n0-computer/iroh repository implements a modern peer-to-peer networking stack written in Rust. Its architecture centers on the Endpoint abstraction, which combines QUIC transport, UDP hole-punching, and relay-based connectivity into a single cohesive node. Understanding this architecture reveals how iroh achieves seamless direct connections while gracefully handling complex network conditions like NATs and firewalls.

Core Architectural Components

The iroh workspace is organized as a multi-crate Rust project where each crate handles a specific layer of the networking stack.

iroh: The Core Endpoint Library

The iroh crate serves as the primary interface for applications. Located in iroh/src/endpoint.rs, it implements the Endpoint type along with its Builder and Connection handles. This crate manages the full connection lifecycle, including hole-punching logic for NAT traversal, QUIC handshake coordination, and the high-level API that developers interact with. The Endpoint holds an Arc<EndpointInner> that owns the underlying QUIC implementation (via noq) and a RelayMap for relay configuration.

iroh-relay: Relay Infrastructure

The iroh-relay crate provides both client and server implementations for scenarios where direct connectivity fails. The server component, found in iroh-relay/src/server/http_server.rs, runs the public relay infrastructure that forwards encrypted traffic between peers. Key types include RelayServer, RelayClient, and RelayMap. When embedded in an endpoint, the relay client opens QUIC-tunneled streams to remote relay servers, enabling communication even when both peers are behind restrictive NATs.

iroh-base: Shared Primitives

The iroh-base crate contains the foundational types used across the entire workspace. Defined in iroh-base/src/endpoint_addr.rs, this crate provides EndpointId (public key-based identifiers), RelayUrl (relay server addresses), and EndpointAddr (composite addresses that may contain both direct socket addresses and relay URLs). These primitives ensure type safety and consistency between the core endpoint, relay client, and DNS components.

iroh-dns-server: Address Resolution

The iroh-dns-server crate implements the DNS infrastructure that resolves Pkarr (public-key-address) records to endpoint IDs. Located in iroh-dns-server/src/server.rs, this server enables ".dns.iroh.link" lookups, allowing peers to discover each other's current network addresses without hardcoding IP addresses. The DnsServer and EndpointInfo types manage this resolution service.

How the iroh Node Works: Connection Lifecycle

The architecture follows a six-phase lifecycle for establishing and maintaining peer-to-peer connections:

  1. Endpoint Creation – Applications initialize an Endpoint via Endpoint::builder() or the convenience Endpoint::bind() method. The builder configures TLS certificates, QUIC parameters, address lookup services, and populates the initial RelayMap with default or custom relay URLs.

  2. Address Resolution – When dialing a peer by its public key (EndpointId), the endpoint queries configured AddressLookup services. The DNS server resolves Pkarr records to EndpointAddr structures, which may contain direct socket addresses, relay URLs, or both.

  3. Connection Establishment – The endpoint attempts connectivity in priority order:

    • Direct Path: If the EndpointAddr contains reachable socket addresses, the endpoint initiates a direct QUIC handshake using the underlying noq transport via noq_endpoint().connect.
    • Relay Fallback: If direct addresses are unavailable or unreachable, the endpoint selects a RelayUrl from its RelayMap and establishes a relayed connection through the iroh-relay infrastructure.
  4. Hole-Punching – When both peers are behind NATs, the core iroh crate initiates a UDP hole-punching procedure before falling back to relays. This process leverages QUIC datagram transport to discover mutually routable paths, potentially upgrading a relayed connection to a direct one.

  5. Data Exchange – Once established, the Connection type provides methods like open_bi() and open_uni() to create bidirectional or unidirectional streams. These streams inherit QUIC's features including congestion control, stream multiplexing, and ordered delivery.

  6. Metrics and Monitoring – The iroh crate exposes EndpointMetrics for connection statistics, while the relay server provides HTTP-based metrics endpoints defined in iroh-relay/src/server/metrics.rs.

Key Source Files in the iroh Architecture

The implementation spans several critical files that define the system's behavior:

Building an iroh Node: Code Example

The following example demonstrates how the architectural components work together in practice:

use iroh::Endpoint;
use std::sync::Arc;

// 1. Build a default endpoint (binds to a random UDP port).
let endpoint = Endpoint::builder(Default::default())
    .bind()
    .await
    .expect("failed to bind");

// 2. Insert a public relay (optional – the default config already contains the public relays).
use iroh_base::RelayUrl;
use iroh::relay::RelayConfig;

let relay = RelayUrl::parse("https://relay.iroh.link")
    .expect("invalid relay URL");
endpoint.insert_relay(relay, Arc::new(RelayConfig::default()))
    .await;

// 3. Connect to a remote peer by its public key (represented as an EndpointId).
use iroh_base::EndpointId;

let remote_id = EndpointId::from_hex("deadbeef…").unwrap();
let alpn = b"my-protocol/1";
let conn = endpoint
    .connect(remote_id, alpn)
    .await
    .expect("connection failed");

// 4. Send and receive data over a bidirectional QUIC stream.
let (mut send, mut recv) = conn.open_bi().await?;
send.write_all(b"hello iroh!").await?;
send.finish()?;
let response = recv.read_to_end(1024).await?;
println!("peer replied: {}", String::from_utf8_lossy(&response));

Summary

The iroh node architecture delivers a complete peer-to-peer networking solution through:

  • Four specialized crates (iroh, iroh-relay, iroh-base, iroh-dns-server) that separate concerns between transport, relaying, primitives, and discovery.
  • Unified Endpoint abstraction that handles direct QUIC connections, NAT hole-punching, and automatic relay fallback.
  • DNS-based address resolution via Pkarr records, enabling persistent peer identifiers independent of network topology.
  • Built-in observability through metrics exposed by both the endpoint and relay server components.

Frequently Asked Questions

How does the iroh node handle NAT traversal?

The iroh node implements UDP hole-punching in the core iroh crate, which attempts to discover directly routable paths between peers behind NATs before falling back to relay servers. This process uses QUIC datagram transport to coordinate NAT mapping discovery between endpoints.

What is the relationship between the iroh endpoint and QUIC?

The Endpoint type in iroh/src/endpoint.rs wraps a noq-based QUIC implementation, exposing high-level methods like connect() and accept() while managing the underlying TLS configuration, ALPN negotiation, and connection state. Applications interact with QUIC streams through the Connection type without managing low-level QUIC details.

When does the iroh node use relay servers?

The node attempts direct connection establishment first using addresses from EndpointAddr. If the direct path fails or no direct addresses are available, the endpoint consults its RelayMap to select a RelayUrl and establishes a tunneled connection through the iroh-relay infrastructure, as implemented in iroh-relay/src/server/http_server.rs.

How does address resolution work in the iroh architecture?

The iroh-dns-server resolves Pkarr (public-key-address) records to endpoint IDs, publishing these records at dns.iroh.link. When an application calls Endpoint::connect() with a remote peer's public key, the endpoint queries this DNS infrastructure to retrieve current EndpointAddr information containing both direct and relay addresses.

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 →