How to Use Custom Transports in iroh with the unstable-custom-transport Feature
Enable the unstable-custom-transports feature flag to inject user-defined networking layers into iroh's Endpoint, allowing you to route packets over bespoke channels like Bluetooth or Tor while retaining higher-level QUIC and NAT traversal logic.
Iroh is a peer-to-peer networking stack that ships with built-in IPv4, IPv6, and relay transports, but also exposes a pluggable transport API for custom implementations. By activating the unstable-custom-transports feature, developers can register their own transport layers—identified by unique transport IDs—and prioritize them using custom path selectors.
Architecture of Custom Transports in iroh
The Transport Module and Feature Gating
In iroh/src/endpoint.rs, the transports submodule is conditionally compiled behind the unstable-custom-transports flag (lines 29-48). When enabled, it re-exports the low-level transport primitives from the socket layer, including CustomTransport, CustomEndpoint, and CustomSender (lines 41-46).
Transport Identification
Each custom transport must register a unique transport ID (a u16) in the repository's TRANSPORTS.md file. The test transport used in examples reserves ID 0x20 (see lines 5-9).
Implementing the CustomTransport Trait
Core Interface
A custom transport implements the CustomTransport trait defined in the socket layer. The reference implementation, TestTransport, lives in iroh/src/test_utils/test_transport.rs and demonstrates the required interface.
The transport must return a CustomAddr containing the transport ID and opaque address data via the addr() method. Connections are established through connect(), which returns a CustomSender capable of sending and receiving raw bytes.
Configuring the Endpoint Builder
Enabling the Feature
Add the feature to your Cargo.toml:
[dependencies]
iroh = { git = "https://github.com/n0-computer/iroh", features = ["unstable-custom-transports"] }
Builder Pattern
The Builder struct in iroh/src/endpoint.rs provides methods to inject custom transports and clear defaults. The example in iroh/examples/custom-transport.rs (lines 71-87) shows the configuration pattern:
let mut builder = Endpoint::builder(presets::N0)
.secret_key(secret_key)
.preset(transport) // Inject the custom transport
.path_selector(Arc::new(PreferTestTransport));
if !keep_ip { builder = builder.clear_ip_transports(); }
if !keep_relay { builder = builder.clear_relay_transports(); }
Selecting Custom Transport Paths
Implementing PathSelector
To prioritize your custom transport, implement the PathSelector trait. The example's PreferTestTransport (lines 37-69) demonstrates selecting paths where the remote address matches your transport ID:
impl PathSelector for PreferTestTransport {
fn select(&self, ctx: &PathSelectionContext<'_>) -> PathSelection {
let mut selection = PathSelection::none();
if let Some(p) = ctx.paths().find(
|p| matches!(p.network_path().remote(), Addr::Custom(c) if c.id() == TEST_TRANSPORT_ID)
) {
selection.set(&p);
return selection;
}
// Fallback to lowest RTT if custom path unavailable
ctx.select_lowest_rtt(selection)
}
}
Complete Integration Example
End-to-End Setup
The following excerpt combines the builder configuration with path selection, mirroring the test harness in iroh/examples/custom-transport.rs:
use iroh::{
Endpoint, SecretKey, endpoint::{Builder, transports::Addr, PathSelector, PathSelectionContext},
test_utils::test_transport::{TEST_TRANSPORT_ID, TestTransport},
};
use std::sync::Arc;
struct PreferTestTransport;
impl PathSelector for PreferTestTransport {
fn select(&self, ctx: &PathSelectionContext<'_>) -> PathSelection {
let mut sel = PathSelection::none();
if let Some(p) = ctx.paths().find(|p| {
matches!(p.network_path().remote(), Addr::Custom(c) if c.id() == TEST_TRANSPORT_ID)
}) {
sel.set(&p);
}
sel
}
}
async fn create_custom_endpoint(
secret: SecretKey,
transport: Arc<TestTransport>
) -> anyhow::Result<Endpoint> {
Endpoint::builder(presets::N0)
.secret_key(secret)
.preset(transport)
.path_selector(Arc::new(PreferTestTransport))
.clear_ip_transports()
.clear_relay_transports()
.bind()
.await
}
Summary
- Enable the
unstable-custom-transportsfeature flag to access the transport API iniroh/src/endpoint.rs. - Implement
CustomTransportand register a unique transport ID inTRANSPORTS.md. - Use
Builder::preset()to inject your transport andclear_ip_transports()/clear_relay_transports()to exclude defaults. - Implement
PathSelectorto prioritize your custom transport over built-in paths. - Reference the full example in
iroh/examples/custom-transport.rsfor a working implementation.
Frequently Asked Questions
What is the unstable-custom-transport feature in iroh?
The unstable-custom-transports feature flag gates access to iroh's pluggable transport API, allowing developers to inject custom networking implementations alongside or in place of the built-in IPv4, IPv6, and relay transports. When enabled, it exposes the CustomTransport trait and related types through the endpoint::transports module.
How do I assign a transport ID for my custom implementation?
Transport IDs are reserved in the TRANSPORTS.md file at the repository root. Submit a pull request to n0-computer/iroh to register a new u16 ID for your transport to avoid collisions with other implementations. The test transport uses ID 0x20 as a reference.
Can I use custom transports alongside standard IP networking?
Yes. The Builder API allows you to add custom transports via .preset() while retaining IP transports, or explicitly remove them using .clear_ip_transports() and .clear_relay_transports() if you want to force traffic through your custom layer only. The example demonstrates verification that the custom path was selected (lines 66-74).
Where can I find a reference implementation of a custom transport?
The TestTransport struct in iroh/src/test_utils/test_transport.rs provides a complete reference implementation, and iroh/examples/custom-transport.rs demonstrates how to configure and select it within an application. Additionally, the low-level transport abstractions are defined in iroh/src/socket/transports.rs.
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 →