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:
iroh/src/endpoint.rs– Contains theEndpointstruct,Builderimplementation (starting around line 22), and connection logic.iroh-base/src/relay_url.rs– DefinesRelayUrlandRelayMaptypes for relay configuration.iroh/src/address_lookup.rs– ImplementsAddressLookupBuilderand the Pkarr DNS service.iroh/src/socket.rs– Low-level UDP socket handling and QUIC integration.iroh/examples/echo.rs– Full-featured echo example referenced in the repository README.
Summary
- The
Endpointis 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
alpnsmethod to register which protocols your application supports. - The
Routersimplifies 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →