How to Integrate iroh with Other Applications: Endpoint and Builder API Guide
Integrate iroh into any Rust application by creating an Endpoint through the Builder API, which handles cryptographic identity, NAT traversal, and QUIC connections, then use the Router to dispatch incoming streams by ALPN protocol.
The n0-computer/iroh repository provides a Rust library that embeds a complete peer-to-peer networking stack into your program. To integrate iroh with other applications, you instantiate an Endpoint—the core abstraction that manages cryptographic keys, relay connections, and address publication—then bind it to handle inbound and outbound QUIC streams. This architecture allows any existing codebase to gain direct connectivity without managing low-level socket operations or NAT traversal logic manually.
Understanding the iroh Integration Architecture
The integration surface centers on a few high-level types that encapsulate the complexity of QUIC networking.
The Endpoint as the Core Gateway
The Endpoint struct, defined in [iroh/src/endpoint.rs](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs), serves as the public API for creating connections. It owns a cryptographic identity, handles NAT traversal automatically, communicates with relay servers when direct paths are unavailable, and exposes methods for dialing and accepting connections. When you bind an endpoint, it spawns the underlying QUIC server, contacts configured relays, and publishes your address to the chosen lookup services.
Builder Pattern for Configuration
Configuration occurs through the Builder, implemented in the same file starting around line 22. The builder accepts settings for ALPN protocols, relay modes, address-lookup services (such as Pkarr DNS), and custom transport layers. Key methods include alpns(), relay_mode(), and address_lookup(). Calling bind().await on the builder finalizes the configuration and returns a ready-to-use Endpoint.
Router for Protocol Dispatch
The optional Router utility, created via Router::builder(endpoint.clone()), dispatches inbound streams to protocol handlers based on the ALPN identifier. This decouples connection management from application logic, allowing you to register multiple protocol handlers on a single endpoint.
Step-by-Step Integration Workflow
Follow this sequence to embed iroh into your existing application:
- Add the dependency to your
Cargo.toml(iroh = "…"). - Create a builder using a preset like
presets::N0for sensible defaults. - Configure ALPN protocols, relay mode, and address-lookup services.
- Bind the endpoint with
builder.bind().awaitto initialize the networking stack. - Accept inbound connections manually via
endpoint.accept()or automatically via aRouter. - Dial remote endpoints using
endpoint.connect()orconnect_with_opts()for advanced scenarios.
All networking is end-to-end encrypted, requiring no external trust anchors beyond optional CA TLS configuration for relay or DNS services.
Code Examples for iroh Integration
Basic Echo Server and Client
This example demonstrates a complete server that accepts connections and a client that dials it, using the Router to handle protocol dispatch.
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()]) // Register the ALPN we will accept
.bind()
.await?; // Bind (creates sockets, contacts relay)
let router = Router::builder(server_ep.clone())
.accept(ALPN, Echo) // Route incoming streams to the handler
.spawn();
// Make the server reachable (optional, for NAT traversal)
server_ep.online().await;
// ---------- Client side ----------
let client_ep = Endpoint::builder(presets::N0).bind().await?;
let remote_addr = server_ep.addr(); // Publishable address of the server
// Dial the server using the same ALPN
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!");
// Clean shutdown
router.shutdown().await?;
client_ep.close().await;
server_ep.close().await;
Ok(())
}
// Simple echo protocol handler
#[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(())
}
}
Key points: presets::N0 pre-configures the Pkarr DNS lookup and a default relay set. The alpns method registers the protocol identifier, which must match on both sides. The Router::builder(...).accept(ALPN, Echo) call automatically spawns a task that invokes accept on each incoming stream.
Configuring Custom Relays and Addresses
To integrate with a private relay infrastructure or advertise specific addresses, use RelayMode::Custom and add_external_addr:
use iroh::{
Endpoint,
endpoint::{presets, RelayMode},
iroh_relay::RelayConfig,
iroh_base::RelayUrl,
};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Build a custom relay map (replace with real URLs)
let custom_relay = RelayUrl::parse("https://my-relay.example.com/")?;
let relay_cfg = RelayConfig::default(); // configure auth, etc. if needed
let ep = Endpoint::builder(presets::N0)
.relay_mode(RelayMode::Custom(vec![(custom_relay.clone(), relay_cfg)]))
.bind()
.await?;
// Optionally add a directly reachable address
ep.add_external_addr("203.0.113.42:4000".parse()?).await;
// Use the endpoint as usual
// …
Ok(())
}
RelayMode::Custom overrides the default relay set, directing the endpoint to your own relay cluster defined in iroh-base/src/relay_url.rs.
Implementing Custom Transport Layers
For advanced integration requiring custom packet transport, enable the unstable feature and implement the CustomTransport trait:
# Cargo.toml
iroh = { version = "…", features = ["unstable-custom-transports"] }
use std::{sync::Arc, net::SocketAddr};
use iroh::{
Endpoint,
endpoint::{presets, transports::CustomTransport},
socket::transports::TransportConfig,
};
struct MyTransport;
impl CustomTransport for MyTransport {
// Implement required methods (see iroh/src/socket/transports/custom.rs)
// – this is an advanced use-case.
}
let custom = Arc::new(MyTransport);
let ep = Endpoint::builder(presets::N0)
.add_custom_transport(custom) // Register the transport
.bind()
.await?;
This API is gated behind the unstable-custom-transports feature and allows you to replace or supplement the default UDP socket implementation.
Key Source Files in the n0-computer/iroh Repository
When integrating iroh, reference these specific source locations to understand the implementation details:
iroh/src/endpoint.rs– Contains theEndpointandBuilderdefinitions, connection logic, and address handling.iroh-base/src/relay_url.rs– DefinesRelayUrlandRelayMaptypes used for relay configuration.iroh/src/address_lookup.rs– Implements theAddressLookuptrait and default services (Pkarr, DNS) for publishing and discovering addresses.iroh/src/socket.rs– Manages low-level socket handling, transport configuration, and QUIC integration.iroh/src/presets.rs– Provides predefined configuration bundles likepresets::N0.iroh-relay/src/server.rs– Reference implementation for running your own relay server.iroh/examples/echo.rs– Full-featured echo example demonstrating best practices.
Summary
- The
Endpointis the primary interface for iroh integration, defined iniroh/src/endpoint.rs. - Use the
Builderpattern to configure ALPN protocols, relay modes, and address lookup services before binding. - The
Routerautomates inbound stream dispatch based on ALPN identifiers, decoupling protocol logic from connection management. - All connections are end-to-end encrypted using the endpoint's cryptographic identity, with optional trust anchors for external services.
- Custom relays can be injected via
RelayMode::Custom, and custom transports can be implemented via the unstableCustomTransporttrait.
Frequently Asked Questions
What is the minimum code needed to integrate iroh into an existing Rust application?
At minimum, create an Endpoint using Endpoint::builder(presets::N0).bind().await, then use endpoint.connect() to dial peers or endpoint.accept() to receive connections. This single call establishes the QUIC-based P2P stack with default relay and DNS configuration, allowing immediate peer-to-peer communication.
How does iroh handle NAT traversal when integrating with external applications?
The Endpoint automatically manages NAT traversal through relay servers configured via RelayMode. When direct UDP connectivity fails, traffic routes through relays defined in RelayMap (see iroh-base/src/relay_url.rs), while address_lookup services publish public addresses for discovery according to the implementation in iroh/src/address_lookup.rs.
Can I use iroh with custom application protocols?
Yes, register your protocol identifier (ALPN) with Builder::alpns() and handle streams via a Router or manual accept calls on the Connection. For transport-level customization, implement the CustomTransport trait (unstable feature) as defined in the socket transport modules, allowing you to integrate iroh with specialized network hardware or simulation environments.
Where is the Connection struct defined and how do I use it for stream handling?
Connection is defined in iroh/src/endpoint.rs and represents a QUIC connection to a remote peer. Obtain it via Endpoint::connect() or Endpoint::accept(), then open bidirectional or unidirectional streams using methods like open_bi() and open_uni(). These streams provide standard async I/O traits for reading and writing data.
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 →