Understanding the iroh Network Stack Architecture: A Layer-by-Layer Breakdown

The iroh network stack is a full-stack, peer-to-peer networking layer built on QUIC that provides public-key-based dialing, hole-punching, and relay fallback through modular components including transport abstraction, path selection, and pluggable discovery.

The n0-computer/iroh repository implements a production-ready peer-to-peer networking library designed for robust connectivity across diverse network conditions. This article examines the iroh network stack architecture, detailing how its layered design—from public API to QUIC implementation—enables seamless NAT traversal and public-key-based endpoint identification.

Architectural Layers of the iroh Network Stack

Public API and Endpoint Construction

The user-facing entry point resides in iroh/src/endpoint.rs, exposing the Endpoint and Builder types. The Builder struct (lines 22-48) allows configuration of ALPNs, transport stacks, and path selectors before calling bind() to initialize the stack. Presets such as N0 provide sensible defaults for immediate connectivity, handling TLS configuration, transport initialization, and address lookup services automatically.

Transport Abstraction Layer

Located in iroh/src/socket/transports.rs, this layer defines the Transport trait and concrete implementations including Ip, Relay, and custom transports. It handles socket binding, network-change notifications, and path creation while remaining agnostic to the underlying wire protocol. This abstraction enables the stack to treat direct IP sockets, relay connections, and custom network implementations uniformly.

QUIC Implementation via noq

The actual encrypted transport relies on the noq crate, configured in iroh/src/protocol.rs. This layer supplies noq::Endpoint and stream primitives, handling connection establishment, cryptography, and multiplexing without exposing implementation details to higher layers. The QuicTransportConfig struct bridges iroh's configuration with the underlying QUIC implementation.

Remote State and Path Management

iroh/src/socket/remote_map.rs maintains per-remote state through RemoteMap, tracking all available network paths for each peer. The BiasedRttPathSelector in iroh/src/socket/biased_rtt_path_selector.rs implements the default path-selection policy, preferring low-RTT IPv6 paths, falling back to IPv4, then to relays. This component maintains stickiness to prevent path flapping while continuously evaluating path health.

Address Discovery and Resolution

The address_lookup module in iroh/src/address_lookup.rs resolves EndpointId values to reachable addresses. Built-in implementations include PkarrResolver for distributed DNS and DnsResolver for traditional lookups, allowing custom discovery mechanisms via the AddressLookup trait. This pluggable architecture supports resolution workflows ranging from centralized DNS to decentralized pkarr records.

Relay Subsystem for NAT Traversal

When direct connections fail, the relay subsystem takes over. Defined in iroh-relay/src/relay_map.rs with client and server implementations in iroh-relay/src/client.rs and iroh-relay/src/server.rs, this component provides fallback connectivity through publicly accessible relay servers. The RelayMap tracks available relay endpoints, while the Client implementation handles the protocol details of proxying traffic through these servers.

Supporting Infrastructure

Additional components include TLS configuration (iroh/src/tls.rs) generating TlsConfig and CaTlsConfig from permanent secret keys, port mapping via UPnP/PCP/NAT-PMP (iroh/src/portmapper.rs), continuous connectivity probing (iroh/src/net_report.rs), and observability (iroh/src/metrics.rs exposing EndpointMetrics).

Data Flow Through the Stack

Construction: Users instantiate a Builder, configure transports and lookup services, then call bind() to create an Endpoint backed by socket::EndpointInner holding static configuration and a running QUIC endpoint.

Discovery: Upon calling Endpoint::connect(), the provided EndpointAddr passes through address-lookup services to resolve public keys into concrete IP addresses or relay URLs.

Path Selection: The RemoteMap registers all viable paths, and the PathSelector evaluates them using the biased RTT algorithm, selecting the optimal route.

Connection: The selected path's configuration and TLS credentials are passed to the noq endpoint to establish a QUIC Connection using the configured ALPN.

Data Transport: Bidirectional and unidirectional streams are multiplexed over QUIC, with the socket layer demultiplexing incoming packets to the appropriate connection handler.

Adaptation: Network-change events trigger path re-evaluation and health checks, with NetReport updating the endpoint's advertised capabilities and the RemoteMap potentially selecting alternative paths.

Key Design Principles

  • Key-based addressing: The EndpointId (public key) serves as the stable identifier, eliminating dependence on mutable IP addresses or DNS names.
  • Transport-agnostic core: The Transport trait allows seamless integration of new network technologies beyond IP and relay without modifying higher-level code.
  • Fine-grained path control: Multiple simultaneous paths per remote enable smart selection and seamless failover without connection interruption.
  • Pluggable discovery: The AddressLookup interface supports custom resolution mechanisms beyond pkarr and DNS through a simple trait implementation.
  • Graceful fallback: Automatic degradation from direct IPv6 → IPv4 → relay ensures connectivity in restrictive NAT environments.

Implementation Examples

The following examples demonstrate common patterns for constructing and using the iroh network stack:

// 1️⃣ Create a default endpoint (preset “N0”) and bind it.
//    See Builder::new() → Builder::bind().
let ep = iroh::Endpoint::builder(iroh::endpoint::presets::N0)
    .alpns(vec![b"my-proto".to_vec()])   // ALPN that we will accept
    .bind()
    .await?;                             // → https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs#L24-L30
// 2️⃣ Connect to a remote endpoint identified by its public key.
//    `EndpointAddr::from_id` creates an address containing only the key.
let remote_id = iroh_base::EndpointId::from_hex("...")?;
let addr = iroh::EndpointAddr::from_id(remote_id);
let conn = ep.connect(addr, b"my-proto").await?; // → https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs#L50-L66
// 3️⃣ Open a bidirectional QUIC stream and exchange data.
//    Under the hood this uses `noq::Connection::open_bi`.
let (mut send, mut recv) = conn.open_bi().await?; // → https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs#L101-L106
send.write_all(b"hello").await?;
send.finish()?;
let mut buf = Vec::new();
recv.read_to_end(&mut buf).await?;
println!("got: {}", String::from_utf8_lossy(&buf));
// 4️⃣ Customize the transport stack – replace the default IPv4/IPv6 sockets
//    with a single relay transport.
let ep = iroh::Endpoint::builder(iroh::endpoint::presets::N0)
    .clear_ip_transports()                // remove IP transports
    .relay_mode(iroh::RelayMode::Default) // add the default relay transport
    .bind()
    .await?;                              // → https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs#L94-L106
// 5️⃣ Install a custom path selector (unstable feature) that always prefers a
//    user‑provided transport.
#[cfg(feature = "unstable-custom-transports")]
let selector = std::sync::Arc::new(my_custom_selector);
let ep = iroh::Endpoint::builder(iroh::endpoint::presets::N0)
    .path_selector(selector)               // → https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs#L138-L144
    .bind()
    .await?;

Summary

  • The iroh network stack architecture layers QUIC-based transport with modular components for path selection, discovery, and relay fallback.
  • The Endpoint and Builder in iroh/src/endpoint.rs provide the public API for constructing and configuring the stack.
  • Transport abstraction in iroh/src/socket/transports.rs enables IP, relay, and custom transport implementations through a unified trait interface.
  • Path selection via BiasedRttPathSelector optimizes connectivity by evaluating RTT across IPv6, IPv4, and relay paths while maintaining connection stability.
  • Address lookup services in iroh/src/address_lookup.rs provide extensible endpoint discovery via pkarr and DNS implementations.
  • The relay subsystem ensures connectivity when direct paths fail, implemented across iroh-relay/src/client.rs and iroh-relay/src/server.rs.

Frequently Asked Questions

What transport protocol does iroh use?

The iroh network stack uses QUIC as its primary transport protocol, implemented through the noq crate. This provides encrypted, multiplexed connections with built-in congestion control and stream isolation, enabling multiple simultaneous streams over a single connection without head-of-line blocking.

How does iroh handle NAT traversal?

iroh implements multiple NAT traversal strategies including UPnP/PCP/NAT-PMP port mapping via the Portmapper component in iroh/src/portmapper.rs, STUN-based address discovery through the NetReport system, and a relay fallback system (iroh-relay) that proxies traffic when direct connections fail. The stack automatically attempts direct connection first, then falls back to relay servers only when necessary.

What is the role of the PathSelector in iroh?

The PathSelector determines which network path to use when multiple options exist (direct IPv6, IPv4, or relay). The default BiasedRttPathSelector in iroh/src/socket/biased_rtt_path_selector.rs prioritizes low-latency paths while maintaining stickiness to prevent flapping between equivalent routes. This component can be customized at runtime for specific network requirements.

How does iroh discover endpoint addresses?

Address discovery occurs through pluggable AddressLookup implementations in iroh/src/address_lookup.rs. The library includes resolvers for pkarr (distributed DNS) and traditional DNS, while allowing custom discovery mechanisms for specific deployment scenarios. When Endpoint::connect() is called, the provided EndpointId is enriched by these services to produce reachable 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 →