How to Integrate iroh into an Existing Application: A Complete Guide

You integrate iroh into an existing application by creating an Endpoint using the Builder API, configuring ALPN protocols and relay settings, and binding to spawn the QUIC networking stack that handles NAT traversal and peer-to-peer connections automatically.

The iroh crate from the n0-computer/iroh repository provides a Rust library that embeds a full-featured QUIC-based peer-to-peer networking stack into any application. By leveraging the Endpoint abstraction, you can add decentralized connectivity, NAT traversal, and end-to-end encryption without managing complex network internals.

Core Architecture of iroh

Understanding the primary components in iroh/src/endpoint.rs helps you integrate the library effectively:

  • Endpoint – The public API that owns your cryptographic identity, manages UDP sockets, and exposes methods for dialing and accepting connections.
  • Builder – Configures the endpoint before binding, handling secret keys, ALPN protocols, transport settings, and relay maps.
  • RelayMap – Stores relay server URLs that facilitate NAT traversal when direct connections fail.
  • Address Lookup – Services like the built-in Pkarr DNS that publish and resolve public addresses.
  • Connection – Represents a QUIC connection to a remote peer, providing bidirectional and unidirectional streams.
  • Router – Optional dispatcher that routes inbound streams to protocol handlers based on ALPN identifiers.

Step-by-Step Integration Process

Add the Dependency

Include iroh in your Cargo.toml:

[dependencies]
iroh = "0.x"
tokio = { version = "1", features = ["full"] }

Configure the Endpoint Builder

Create a Builder using a preset configuration, then customize it with your application's protocols:

use iroh::Endpoint;

let endpoint = Endpoint::builder(presets::N0)  // Use N0 preset for DNS and relay defaults
    .alpns(vec![b"my-app/1.0".to_vec()])       // Register your ALPN protocol
    .relay_mode(RelayMode::Default)            // Use default relay servers
    .bind()
    .await?;

The presets::N0 module (defined in iroh/src/presets.rs) bundles sensible defaults including Pkarr address lookup and the number 0 relay cluster.

Bind and Initialize

Calling bind().await spawns the QUIC server, creates UDP sockets, contacts relay servers, and publishes your address. After binding, call online().await to ensure NAT traversal is ready:

endpoint.online().await?;
let my_addr = endpoint.addr();  // Share this with peers

Accept and Dial Connections

Use the Router to handle inbound protocols automatically, or manage connections directly:

// Accept connections manually
while let Some(conn) = endpoint.accept().await {
    let connection = conn.await?;
    handle_connection(connection).await;
}

// Or dial a remote peer
let conn = endpoint.connect(remote_addr, b"my-app/1.0").await?;

Practical Implementation Examples

Basic Echo Server and Client

This complete example demonstrates a server that accepts connections and echoes data back, plus a client that connects and sends a message:

use iroh::{
    Endpoint,
    endpoint::{presets, Connection, Router},
    protocol::{AcceptError, ProtocolHandler},
};

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

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Server side
    let server_ep = Endpoint::builder(presets::N0)
        .alpns(vec![ALPN.to_vec()])
        .bind()
        .await?;

    let router = Router::builder(server_ep.clone())
        .accept(ALPN, Echo)
        .spawn();

    server_ep.online().await;

    // Client side
    let client_ep = Endpoint::builder(presets::N0).bind().await?;
    let remote_addr = server_ep.addr();
    
    let conn = client_ep.connect(remote_addr, ALPN).await?;
    let (mut send, mut recv) = conn.open_bi().await?;
    send.write_all(b"Hello, iroh!").await?;
    send.finish()?;
    
    let reply = recv.read_to_end(1024).await?;
    assert_eq!(&reply, b"Hello, iroh!");

    // Cleanup
    router.shutdown().await?;
    client_ep.close().await;
    server_ep.close().await;
    Ok(())
}

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

Configuring Custom Relays

To use your own relay infrastructure instead of the defaults, configure a custom RelayMap as implemented in iroh-base/src/relay_url.rs:

use iroh::{
    Endpoint,
    endpoint::{presets, RelayMode},
    iroh_relay::RelayConfig,
    iroh_base::RelayUrl,
};

let custom_relay = RelayUrl::parse("https://my-relay.example.com/")?;
let relay_cfg = RelayConfig::default();

let ep = Endpoint::builder(presets::N0)
    .relay_mode(RelayMode::Custom(vec![(custom_relay, relay_cfg)]))
    .bind()
    .await?;

Implementing Custom Transports

For advanced use cases requiring custom network transports, enable the unstable feature and implement the CustomTransport trait:

[dependencies]
iroh = { version = "0.x", features = ["unstable-custom-transports"] }
use std::sync::Arc;
use iroh::{
    Endpoint,
    endpoint::{presets, transports::CustomTransport},
};

struct MyTransport;
impl CustomTransport for MyTransport {
    // Implement required methods per iroh/src/socket/transports/custom.rs
}

let ep = Endpoint::builder(presets::N0)
    .add_custom_transport(Arc::new(MyTransport))
    .bind()
    .await?;

Key Source Files Reference

Referencing these specific files in the n0-computer/iroh repository helps you understand implementation details:

Summary

  • The Endpoint is the primary abstraction for managing peer-to-peer connections in iroh, handling cryptographic identities and NAT traversal automatically.
  • Use Endpoint::builder(presets::N0) to quickly configure a new endpoint with sensible defaults for relays and address lookup.
  • Configure ALPN protocols via the alpns method to register which protocols your application supports.
  • The Router simplifies inbound connection handling by dispatching streams to protocol handlers based on ALPN identifiers.
  • All connections are end-to-end encrypted using QUIC, requiring no additional TLS configuration for peer-to-peer security.

Frequently Asked Questions

Can iroh integrate with existing async Rust applications?

Yes, iroh is built on Tokio and integrates seamlessly with existing async Rust code. The Endpoint methods like bind(), connect(), and accept() are async and can be used within your existing runtime without blocking.

Does iroh require a specific relay server to function?

No, while iroh provides default relay servers through the N0 preset, you can configure custom relays using RelayMode::Custom with your own RelayUrl endpoints. The system will attempt direct connections first, falling back to relays only when necessary for NAT traversal.

How do I handle protocol versioning in iroh?

Implement protocol versioning through ALPN strings. Register multiple versions using the alpns method on the Builder, or use the Router to dispatch different versions to different ProtocolHandler implementations based on the ALPN identifier received during connection establishment.

Is the custom transport API stable for production use?

No, the custom transport API is currently unstable and requires the unstable-custom-transports feature flag. For production applications, use the standard UDP transport configured automatically by the Builder until the custom transport interface stabilizes in future releases.

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 →