How Does Iroh Interact with Existing Networks? P2P Overlay on IPv4/IPv6

Iroh builds a peer-to-peer overlay on top of existing IP networks by binding to local UDP sockets, publishing reachable addresses via pluggable lookup services, and negotiating encrypted QUIC streams that fall back to relay servers when direct connectivity is blocked by NATs.

Iroh is a Rust implementation of a modern peer-to-peer networking stack that operates over standard IPv4 and IPv6 infrastructure. Rather than requiring specialized hardware or network configurations, it discovers connectivity paths through existing internet protocols while providing applications with encrypted, authenticated QUIC connections. This article examines the specific mechanisms in n0-computer/iroh that enable seamless interaction with existing networks, from socket binding to NAT traversal.

The Three-Phase Network Architecture

Iroh interacts with existing networks through three distinct phases: local socket binding, address discovery, and NAT traversal with relay fallback. Each phase is implemented in specific source files and exposes configuration options through the Endpoint builder API.

Local Network Binding

The interaction begins when an Endpoint binds to the host’s network interfaces. In src/endpoint.rs, the Builder struct provides methods to configure socket binding before the endpoint becomes active.

You can bind specific addresses using Builder::bind_addr or Builder::bind_addr_with_opts, or rely on default "any-address" sockets (0.0.0.0:0 and [::]:0) that listen on all available interfaces. This initial binding creates the UDP sockets that underlie all QUIC communication.

use iroh::{Endpoint, endpoint::presets};

#[tokio::main]
async fn main() -> n0_error::Result<()> {
    // Use the "N0" preset – default relay map, portmapper, DNS resolver, etc.
    let ep = Endpoint::builder(presets::N0).bind().await?;
    
    println!("Our EndpointAddr: {}", ep.addr());
    ep.close().await;
    Ok(())
}

Address Discovery via Lookup Services

Once bound, the endpoint must publish its addressing information so other peers can locate it. Iroh implements a pluggable address discovery system in src/address_lookup.rs centered on the AddressLookup trait.

The system supports multiple providers that can be combined:

  • PKARR (HTTP-based signed packets) – Implemented in iroh-dns and iroh-dns-server, with resolution handled by PkarrResolver
  • DNS – Traditional DNS zone queries via DnsResolver in iroh-dns/src/dns.rs
  • In-process memory lookup – For testing or when the application already knows the peer’s address
  • Custom providers – Via the unstable unstable-custom-transports feature

When Endpoint::connect is called, iroh queries all configured lookup services concurrently through AddressLookupServices::resolve and merges the results, preferring direct IP addresses over relay URLs.

NAT Traversal and Relay Assistance

For endpoints behind firewalls or NATs, iroh attempts to open direct paths using the portmapper module in src/portmapper.rs. When the portmapper feature is enabled, PortmapperConfig::Enabled creates a portmapper::Client that automatically discovers external IPv4 addresses via UPnP, PCP, or NAT-PMP and publishes these via address-lookup services.

If direct connectivity fails, iroh falls back to relay servers configured via Builder::relay_mode and stored in a RelayMap (defined in iroh-relay/src/relay_map.rs). The relay performs the QUIC handshake on behalf of both peers, forwarding encrypted packets without exposing plaintext content. This enables connectivity even when both endpoints are behind symmetric NATs.

All traffic after the handshake uses TLS-based QUIC encryption (src/transport.rs and src/quic.rs), ensuring end-to-end confidentiality regardless of whether the path is direct or relayed.

Connection Flow: From Bind to QUIC Stream

Understanding how iroh interacts with existing networks requires following the complete connection lifecycle:

  1. Endpoint creationEndpoint::builder(presets::N0) creates a Builder pre-configured with a default relay map, enabled portmapper, and DNS resolver.

  2. Socket bindingbuilder.bind().await? binds default IPv4/IPv6 sockets. Additional sockets can be bound with bind_addr to restrict traffic to specific interfaces.

  3. Address publication – The endpoint calls address_lookup().publish(&data) whenever external addresses change (e.g., after a successful portmapper mapping). Published data includes the external IP, relay URL, and user-defined metadata.

  4. Peer resolution – When Endpoint::connect(remote, alpn) is called, iroh checks cached EndpointAddr for direct IPs. If none are viable, it queries all configured address-lookup services concurrently and merges results.

  5. Connection establishment – Iroh selects the first usable address (or home relay) and initiates a QUIC connection via noq_endpoint().connect_with. If direct, traffic flows peer-to-peer over UDP; otherwise, it routes through the selected relay.

Configuring Network Interaction

The following examples demonstrate how to customize iroh's interaction with existing networks using the builder API.

Custom Relay with Disabled Portmapper

To use a specific relay server while disabling automatic NAT traversal:

use iroh::{Endpoint, endpoint::presets, RelayUrl, PortmapperConfig};

#[tokio::main]
async fn main() -> n0_error::Result<()> {
    let custom_relay: RelayUrl = "https://my-relay.example.com".parse()?;
    let ep = Endpoint::builder(presets::Minimal)
        .relay_mode(custom_relay.into())
        .portmapper_config(PortmapperConfig::Disabled)
        .bind()
        .await?;

    println!("Endpoint with custom relay: {}", ep.addr());
    Ok(())
}

Manual External Address Publication

For scenarios where the external address is known in advance (e.g., in containerized environments with explicit port mappings):

use std::net::SocketAddr;
use iroh::{Endpoint, endpoint::presets};

#[tokio::main]
async fn main() -> n0_error::Result<()> {
    let ep = Endpoint::builder(presets::Minimal).bind().await?;
    let external: SocketAddr = "203.0.113.42:34567".parse()?;
    ep.add_external_addr(external).await;
    Ok(())
}

Monitoring Address Changes

Subscribe to address updates to detect when the portmapper successfully creates a mapping or when relay URLs change:

use iroh::{Endpoint, endpoint::presets};

#[tokio::main]
async fn main() -> n0_error::Result<()> {
    let ep = Endpoint::builder(presets::N0).bind().await?;
    let mut watcher = ep.watch_addr().stream();

    while let Some(addr) = watcher.next().await {
        println!("New advertised address: {}", addr);
    }
    Ok(())
}

Connecting by Endpoint ID Only

Iroh can connect to peers using only their EndpointId (public key), automatically resolving addresses through configured lookup services:

use iroh::{Endpoint, endpoint::presets};

#[tokio::main]
async fn main() -> n0_error::Result<()> {
    let ep_a = Endpoint::builder(presets::Minimal).bind().await?;
    let ep_b = Endpoint::builder(presets::Minimal).bind().await?;

    let a_id = ep_a.id();
    let conn = ep_b.connect(a_id, b"my-alpn").await?;
    
    println!("Connected! Remote ID = {}", conn.remote_id());
    Ok(())
}

Summary

  • Iroh binds to existing IP networks through standard UDP sockets via Builder::bind, supporting specific interface binding or any-address defaults.
  • Address discovery is pluggable through the AddressLookup trait in src/address_lookup.rs, supporting PKARR, DNS, and custom providers.
  • NAT traversal is automatic via the portmapper module (src/portmapper.rs) which attempts UPnP/PCP/NAT-PMP mappings before falling back to relay servers.
  • Relays enable universal connectivity by forwarding encrypted QUIC packets when direct paths are blocked, configured through RelayMap in iroh-relay/src/relay_map.rs.
  • All traffic is encrypted end-to-end using TLS-based QUIC, ensuring that relay servers cannot inspect connection contents.

Frequently Asked Questions

Does iroh require public IP addresses to function?

No. Iroh functions across NATs and private networks by using relay servers as a fallback when direct connectivity fails. The PortmapperConfig enables automatic discovery of external addresses when UPnP or NAT-PMP is available, but iroh remains functional even when endpoints are behind symmetric NATs with no direct path, routing encrypted traffic through configured relays.

How does iroh discover peer addresses without a central server?

Iroh uses the AddressLookup trait system in src/address_lookup.rs to query multiple sources concurrently. By default, it checks PKARR (signed DNS packets over HTTP), traditional DNS zones, and in-memory caches. Application developers can also implement custom lookup providers via the unstable-custom-transports feature, allowing integration with existing discovery mechanisms like DHTs or centralized registries.

What protocols does iroh use for the underlying network transport?

Iroh uses UDP for the underlying transport, implementing QUIC (HTTP/3) for the actual connection layer. All packets are encrypted using TLS 1.3, and the protocol supports 0-RTT connection resumption for subsequent connections between known endpoints. This design ensures compatibility with existing internet infrastructure while providing modern security and performance characteristics.

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 →