# Iroh Advanced Usage Examples: Building Custom Protocols on QUIC

> Explore Iroh advanced usage examples for building custom protocols on QUIC. Discover how to create echo servers, blob transfers, and gossip overlays with APIs.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: deep-dive
- Published: 2026-07-06

---

**Iroh advanced usage examples demonstrate how to implement custom networking protocols using the `Endpoint` and `Router` APIs, including echo servers, content-addressed blob transfers, and scalable gossip overlays on top of QUIC connections.**

The n0-computer/iroh repository provides a Rust-first library for building peer-to-peer applications that dial peers by public key while automatically selecting the fastest path—direct, hole-punched, or relayed. These Iroh advanced usage examples cover production-ready patterns from the source code, showing how to compose protocol handlers, manage content-addressed storage, and deploy private relay infrastructure.

## Implementing a Custom Echo Protocol

The echo protocol demonstrates the fundamental pattern for all Iroh networking: bind an endpoint, negotiate connections via Application-Layer Protocol Negotiation (ALPN), and handle bidirectional streams. This pattern is implemented in [`iroh/src/lib.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/lib.rs) where the `Endpoint` type manages the underlying QUIC engine.

### Establishing Client Connections

The client side creates an `Endpoint`, connects to a remote address, and opens a bidirectional stream. Connections automatically handle hole-punching and relay fallback according to the implementation in [`iroh/src/lib.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/lib.rs).

```rust
use iroh::Endpoint;
use anyhow::Result;
use tokio::io::{AsyncWriteExt, AsyncReadExt};

const ALPN: &[u8] = b"iroh-example/echo/0";

#[tokio::main]
async fn main() -> Result<()> {
    // Bind a local endpoint that manages QUIC connections
    let endpoint = Endpoint::bind().await?;
    
    // Connect to a remote peer by address (e.g., "iroh://<public-key>@host:port")
    let addr = "...remote endpoint address...".parse()?;
    let conn = endpoint.connect(addr, ALPN).await?;
    
    // Open a bidirectional QUIC stream
    let (mut send, mut recv) = conn.open_bi().await?;
    
    // Send payload and read echoed response
    send.write_all(b"Hello, iroh!").await?;
    send.finish()?;  // Signal end-of-write
    let mut response = Vec::new();
    recv.read_to_end(&mut response).await?;
    
    // Cleanup
    conn.close(0u32.into(), b"bye!");
    endpoint.close().await;
    Ok(())
}

```

### Registering Protocol Handlers on the Server

Servers implement the `ProtocolHandler` trait and register handlers with a `Router`. The router dispatches incoming connections based on ALPN identifiers, as defined in [`iroh/src/lib.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/lib.rs).

```rust
use iroh::{Endpoint, Router};
use iroh::protocol::ProtocolHandler;
use iroh::connection::Connection;
use anyhow::Result;
use std::sync::Arc;
use tokio::io;

const ALPN: &[u8] = b"iroh-example/echo/0";

#[derive(Debug, Clone)]
struct Echo;

#[async_trait::async_trait]
impl ProtocolHandler for Echo {
    async fn accept(&self, conn: Connection) -> Result<()> {
        let (mut send, mut recv) = conn.accept_bi().await?;
        io::copy(&mut recv, &mut send).await?;
        send.finish()?;
        conn.closed().await;
        Ok(())
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    let endpoint = Endpoint::bind().await?;
    let router = Router::builder(endpoint)
        .accept(ALPN.to_vec(), Arc::new(Echo))
        .spawn()
        .await?;
    // Router runs forever processing connections
    Ok(())
}

```

## Content-Addressed Blob Transfers with iroh-blobs

The `iroh-blobs` crate provides BLAKE3-hashed content addressing for large file transfers. The implementation in [`iroh-blobs/src/lib.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-blobs/src/lib.rs) exposes a `BlobStore` type that integrates with the same `Endpoint` API used for raw protocols.

```rust
use iroh::Endpoint;
use iroh::Router;
use iroh_blobs::BlobStore;
use anyhow::Result;
use std::path::PathBuf;
use std::sync::Arc;

const ALPN: &[u8] = b"iroh-blobs/transfer/0";

#[tokio::main]
async fn main() -> Result<()> {
    // Initialize endpoint and persistent blob store
    let endpoint = Endpoint::bind().await?;
    let store = BlobStore::open(PathBuf::from("./my_blobs")).await?;
    
    // Sender: Add file to store and push to peer
    let cid = store.add_file("large_file.dat").await?;
    let conn = endpoint.connect(remote_addr, ALPN).await?;
    store.send(&conn, cid).await?;
    
    // Receiver: Accept connections and automatically persist blobs
    let router = Router::builder(endpoint)
        .accept(ALPN.to_vec(), Arc::new(store.clone()))
        .spawn()
        .await?;
    Ok(())
}

```

The blob store handles streaming verification during transfer, ensuring data integrity without requiring the entire file to reside in memory. For a complete runnable demonstration, see [`iroh-blobs/examples/transfer.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-blobs/examples/transfer.rs) in the repository.

## Building Scalable Gossip Networks

`iroh-gossip` implements a scalable publish-subscribe overlay network on top of Iroh's connection layer. The high-level API in [`iroh-gossip/src/lib.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-gossip/src/lib.rs) abstracts peer discovery and mesh maintenance while exposing simple `publish` and `subscribe` methods.

### Publishing Messages to a Topic

Topics are derived from byte strings and act as broadcast channels across the network:

```rust
use iroh::Endpoint;
use iroh_gossip::{Gossip, Topic};
use anyhow::Result;

#[tokio::main]
async fn main() -> Result<()> {
    let endpoint = Endpoint::bind().await?;
    let gossip = Gossip::new(endpoint.clone()).await?;
    
    // Create topic from arbitrary byte string
    let topic = Topic::from(b"chat-room-1".as_ref());
    
    // Publish messages at regular intervals
    let mut interval = tokio::time::interval(std::time::Duration::from_secs(1));
    loop {
        interval.tick().await;
        let msg = format!("ping at {}", chrono::Utc::now());
        gossip.publish(&topic, msg.into_bytes()).await?;
    }
}

```

### Subscribing to Topic Updates

Subscribers register callbacks that process incoming messages asynchronously:

```rust
use iroh::Endpoint;
use iroh_gossip::{Gossip, Topic, Message};
use anyhow::Result;

#[tokio::main]
async fn main() -> Result<()> {
    let endpoint = Endpoint::bind().await?;
    let gossip = Gossip::new(endpoint.clone()).await?;
    let topic = Topic::from(b"chat-room-1".as_ref());
    
    // Register handler for specific topic
    gossip.subscribe(&topic, |msg: Message| async move {
        println!("Got: {}", String::from_utf8_lossy(&msg.payload));
    }).await?;
    
    // Keep application alive
    futures::future::pending::<()>().await;
    Ok(())
}

```

The gossip protocol handles peer discovery and mesh optimization automatically. Reference the full implementation in [`iroh-gossip/examples/pubsub.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-gossip/examples/pubsub.rs).

## Deploying Private Relay Infrastructure

For testing or private deployments that require NAT traversal assistance, the `iroh-relay` crate provides a standalone relay server. The entry point in [`iroh-relay/src/main.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/main.rs) supports TLS configuration and custom listening addresses.

```bash
cargo run -p iroh-relay -- --listen 0.0.0.0:2345 --cert ./cert.pem --key ./key.pem

```

The relay server handles client authentication and connection pooling as implemented in [`iroh-relay/src/lib.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/lib.rs). Endpoints automatically fall back to configured relays when direct connections fail, requiring no code changes to the protocol handlers.

## Summary

- **Custom Protocols** implement the `ProtocolHandler` trait and register with `Router::builder` to handle ALPN-tagged connections via `accept_bi()` and `open_bi()` streams.
- **Blob Transfer** uses `BlobStore` from `iroh-blobs` for BLAKE3-verified content addressing, integrating seamlessly with the `Endpoint` connection API.
- **Gossip Networks** provide scalable publish-subscribe through `iroh-gossip`, abstracting peer discovery and mesh maintenance behind `Topic` handles.
- **Relay Infrastructure** can be self-hosted using `iroh-relay` for environments requiring NAT traversal assistance, with automatic fallback handled by the `Endpoint` in [`iroh/src/lib.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/lib.rs).

## Frequently Asked Questions

### What is the difference between Endpoint and Router in Iroh?

The **Endpoint** ([`iroh/src/lib.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/lib.rs)) manages the underlying QUIC engine, handles hole-punching, and establishes connections to remote peers. The **Router** registers protocol handlers against specific ALPN identifiers and dispatches incoming connections to the appropriate handler. You bind one Endpoint per application but may register multiple protocol handlers with a single Router.

### How does Iroh handle NAT traversal without a relay?

Iroh attempts direct connection first, then uses **hole punching** via STUN and ICE protocols to establish paths through NATs. If these methods fail, the Endpoint automatically falls back to a configured relay server (such as the public relays or a private `iroh-relay` instance) to proxy traffic. This logic is implemented in the connection establishment code within [`iroh/src/lib.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/lib.rs).

### What is ALPN and why is it required for Iroh protocols?

**Application-Layer Protocol Negotiation (ALPN)** is a TLS extension that allows peers to negotiate which protocol to use over a connection. In Iroh, ALPN identifiers (byte strings like `b"iroh-example/echo/0"`) let the Router dispatch incoming connections to the correct `ProtocolHandler` implementation. Each custom protocol must define a unique ALPN constant to avoid conflicts with other handlers.

### Can Iroh protocols compose multiple crates like blobs and gossip?

Yes. Since both `iroh-blobs` and `iroh-gossip` use the same `Endpoint` type from `iroh-base`, you can initialize a single Endpoint and share it across multiple protocol handlers. Register both the blob store and gossip instance with the same `Router::builder` to run content-addressed storage and publish-subscribe messaging simultaneously over a single QUIC connection backbone.