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 usingSecretKeyandPublicKeytypes. - 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, resolvesRelayUrland direct addresses via DNS/Pkarr. - Base Types: Found in
iroh-base/src/lib.rs, includesEndpointId,PublicKey,SecretKey, andRelayUrlused 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
irohcrate from crates.io to begin development - Bind an Endpoint using
presets::N0iniroh/src/lib.rsfor 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.rsfor relay functionality andiroh-base/src/lib.rsfor cryptographic types - Run a custom relay by executing the binary in the
iroh-relaydirectory 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →