How to Get Started with iroh Development: Building Peer-to-Peer Applications in Rust

To get started with iroh development, add the iroh crate to your Rust project, bind an Endpoint using the presets::N0 configuration, and implement the ProtocolHandler trait to handle authenticated QUIC connections.

iroh is a Rust library maintained by n0-computer that enables developers to build peer-to-peer applications using hole-punched, encrypted QUIC connections. It abstracts complex networking concerns like NAT traversal, fallback relays, and address lookup through a high-level API centered on the Endpoint type. This guide covers the essential steps to get started with iroh development, from initial setup to running your first node.

Prerequisites and Installation

Ensure you have Rust installed via the official installer. Add the iroh crate to your Cargo.toml:

[dependencies]
iroh = "0.12"
tokio = { version = "1", features = ["full"] }

Creating Your First iroh Application

The Endpoint struct defined in iroh/src/lib.rs serves as the primary interface for both dialing outbound connections and accepting inbound peers.

Binding an Endpoint

Use Endpoint::bind() with the N0 preset to automatically configure your node for the public "number 0" relay fleet:

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

#[tokio::main]
async fn main() -> Result<()> {
    let ep = Endpoint::bind(presets::N0).await?;
    println!("Node ID: {}", ep.node_id());
    Ok(())
}

This configuration handles TLS encryption, address registration via DNS/Pkarr (as implemented in iroh-dns-server/src/main.rs), and automatic relay fallback when direct paths fail.

Implementing a Protocol Handler

Create a struct that implements ProtocolHandler and define the accept method to handle incoming connections. The following echo server implementation is defined in src/main.rs:

use iroh::{ProtocolHandler, Connection};
use n0_error::Result;

struct Echo;

#[iroh::async_trait]
impl ProtocolHandler for Echo {
    async fn accept(&self, conn: Connection) -> Result<()> {
        let (mut send, mut recv) = conn.accept_bi().await?;
        // Echo the inbound bytes back to the sender
        tokio::io::copy(&mut recv, &mut send).await?;
        send.finish()?;
        Ok(())
    }
}

Building the Router

Use Router::builder() to register your protocol handler with an ALPN identifier:

use std::sync::Arc;
use n0_error::Result;

#[tokio::main]
async fn main() -> Result<()> {
    let ep = Endpoint::bind(presets::N0).await?;
    
    let router = iroh::Router::builder(ep)
        .accept(b"iroh-demo/1".to_vec(), Arc::new(Echo))
        .spawn()
        .await?;
    
    // Keep the process alive
    router.wait_until_shutdown().await;
    Ok(())
}

Connecting to Peers

Clients use Endpoint::connect() with an EndpointAddr and matching ALPN identifier:

use n0_error::Result;

#[tokio::main]
async fn main() -> Result<()> {
    let ep = Endpoint::bind(presets::N0).await?;
    let alpn = b"iroh-demo/1";
    
    // Parse an address in the format: iroh://<peer-id>@<relay-url>
    let addr = "iroh://example-id@relay.iroh.link".parse()?;
    let conn = ep.connect(addr, alpn).await?;
    
    let (mut send, mut recv) = conn.open_bi().await?;
    send.write_all(b"ping").await?;
    send.finish()?;
    
    let resp = recv.read_to_end(256).await?;
    println!("Received: {:?}", resp);
    Ok(())
}

Core Architecture and Source Files

Understanding the codebase structure helps when debugging or extending functionality.

Key Components

  • Endpoint: Defined in iroh/src/lib.rs, manages node identity, connection state, and the authentication layer using SecretKey and PublicKey types.
  • Relay Server: Implemented in iroh-relay/src/server/http_server.rs, forwards encrypted traffic when direct peer-to-peer paths are blocked by NAT or firewalls.
  • Address Lookup: Located in iroh/src/address_lookup.rs, resolves RelayUrl and direct addresses via DNS/Pkarr.
  • Base Types: Found in iroh-base/src/lib.rs, includes EndpointId, PublicKey, SecretKey, and RelayUrl used throughout the ecosystem.

Running a Custom Relay Server

To host your own relay instead of using the public fleet, clone the repository and execute the relay server binary:

git clone https://github.com/n0-computer/iroh.git
cd iroh/iroh-relay
cargo run --release -- --listen 0.0.0.0:443

The server defined in iroh-relay/src/server/http_server.rs accepts encrypted iroh traffic on port 443 and forwards it to destination nodes based on their EndpointId. See the iroh-relay README for advanced configuration options including TLS certificates and authentication.

Working with Built-in Protocols

Content-Addressed Storage with iroh-blobs

Add iroh-blobs to your dependencies for BLAKE3-based content addressing:

use iroh_blobs::StoreBuilder;
use std::path::PathBuf;
use anyhow::Result;

#[tokio::main]
async fn main() -> Result<()> {
    // Store data under ./blobdir
    let store = StoreBuilder::default()
        .root(PathBuf::from("./blobdir"))
        .build()
        .await?;
    
    // Add a file to the store
    let hash = store.put_path("./Cargo.toml").await?;
    println!("Stored as: {}", hash);
    
    // Retrieve the data
    let data = store.get_bytes(&hash).await?;
    println!("Retrieved {} bytes", data.len());
    Ok(())
}

The iroh-blobs crate provides content-addressed storage where data is identified by its BLAKE3 hash, enabling efficient deduplication and verification.

Summary

  • Install the iroh crate from crates.io to begin development
  • Bind an Endpoint using presets::N0 in iroh/src/lib.rs for automatic relay configuration and NAT traversal
  • Implement ProtocolHandler to define how your node responds to incoming ALPN-identified connections
  • Use the Router to register multiple protocol handlers and manage the application lifecycle with wait_until_shutdown()
  • Reference source files including iroh-relay/src/server/http_server.rs for relay functionality and iroh-base/src/lib.rs for cryptographic types
  • Run a custom relay by executing the binary in the iroh-relay directory with your desired listen address

Frequently Asked Questions

What is the difference between an Endpoint and a Relay in iroh?

An Endpoint represents your local node in iroh/src/lib.rs, managing both outgoing connections via connect() and incoming listeners. A Relay is a publicly reachable server defined in iroh-relay/src/server/http_server.rs that forwards encrypted traffic when direct peer-to-peer paths are blocked by firewalls or NAT. Endpoints automatically use relays as fallback when direct connections fail, with address lookup handled by the DNS service in iroh-dns-server/src/main.rs.

How do I configure TLS certificates for a custom relay server?

The relay server in iroh-relay/src/server/http_server.rs accepts command-line arguments for TLS configuration. When running the server binary with cargo run --release, specify certificate paths and private keys using the appropriate flags. The server handles TLS termination for encrypted iroh traffic before forwarding it to destination nodes based on their EndpointId.

What is the purpose of the ALPN identifier in iroh connections?

The Application-Layer Protocol Negotiation (ALPN) identifier is a byte string that both client and server must agree upon before establishing a connection. It functions similarly to port numbers in TCP, allowing a single Endpoint to host multiple protocols simultaneously. The Router defined in iroh/src/lib.rs uses ALPN values to dispatch incoming connections to the correct ProtocolHandler implementation via the accept() method.

Where are the base types like EndpointId and SecretKey defined?

Core identifiers including EndpointId, PublicKey, SecretKey, and RelayUrl are defined in iroh-base/src/lib.rs. These types provide the cryptographic foundation for node identity and address resolution throughout the iroh ecosystem, and are re-exported through the main iroh crate for convenience.

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 →