How to Use Custom Transports in iroh with the unstable-custom-transports Feature
Enable the unstable-custom-transports Cargo feature to plug user-defined networking layers into iroh's connection stack, allowing you to implement the CustomTransport trait and prioritize custom paths via a PathSelector when building an Endpoint.
The iroh networking library provides a pluggable transport architecture that extends its default IPv4/IPv6 and relay capabilities with alternative packet movers. By activating the unstable-custom-transports feature in the n0-computer/iroh repository, you gain access to the low-level APIs necessary to inject Bluetooth, Tor overlays, or in-memory test networks directly into the connection lifecycle.
Enabling the Feature Flag
To access the custom transport APIs, add the feature to your Cargo.toml:
[dependencies]
iroh = { version = "0.x", features = ["unstable-custom-transports"] }
When enabled, the transports submodule becomes available through iroh::endpoint, exposing the traits required to define new transport layers.
Core Architecture and Types
The custom transport system centers on types re-exported in iroh/src/endpoint.rs. According to the source code (lines 41-46), the module publicly exposes:
CustomTransport: The trait your implementation must satisfyCustomEndpoint: Represents the local endpoint of your custom transportCustomSender: Handles raw byte transmission for your protocol
Each transport is identified by a unique u16 transport ID. The repository maintains a canonical registry in [TRANSPORTS.md](https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md) where the internal test transport occupies ID 0x20 (lines 5-9). Before deploying a production transport, allocate a new ID by submitting a PR to this registry.
Implementing the CustomTransport Trait
A functional implementation requires defining address formatting and connection logic. The reference TestTransport in iroh/src/test_utils/test_transport.rs demonstrates the interface:
addr(): Returns aCustomAddrcontaining your transport ID and opaque address dataconnect(): Establishes a stream and returns aCustomSendercapable of sending and receiving raw packets
Your implementation handles the wire protocol—whether Bluetooth L2CAP frames, QUIC overlays, or encapsulated UDP—while iroh manages higher-level connection state.
Configuring the Endpoint Builder
To activate your transport, use the Endpoint::builder() pattern. The implementation in iroh/src/endpoint.rs exposes methods to inject transports and control path selection:
- Inject the transport using
.preset()as shown in the custom-transport example (lines 71-87) - Optionally disable defaults with
.clear_ip_transports()and.clear_relay_transports()to force traffic through your layer - Set a path selector via
.path_selector()to prioritize your transport over built-in options
The example configuration follows this pattern:
let mut builder = Endpoint::builder(presets::N0)
.secret_key(secret_key)
.preset(transport) // Inject the custom transport
.path_selector(Arc::new(PreferTestTransport));
if !self.keep_ip {
builder = builder.clear_ip_transports();
}
if !self.keep_relay {
builder = builder.clear_relay_transports();
}
Writing a PathSelector
The PathSelector trait determines which network path iroh uses for a connection. To prefer your custom transport, implement the trait to check for Addr::Custom variants. The PreferTestTransport implementation in the example (lines 37-69) shows the selection logic:
impl PathSelector for PreferTestTransport {
fn select(&self, ctx: &PathSelectionContext<'_>) -> PathSelection {
let mut selection = PathSelection::none();
// Prioritize paths using the custom transport ID
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;
}
// Fall back to lowest-RTT path otherwise
// ...
selection
}
}
Complete Usage Example
Below is a practical example binding an endpoint with a test transport and forcing it to win path selection:
use iroh::{Endpoint, SecretKey};
use iroh::endpoint::{Builder, PathSelector, PathSelectionContext, PathSelection, Addr};
use std::sync::Arc;
// From iroh/src/test_utils/test_transport.rs
use iroh::test_utils::test_transport::{TestTransport, TEST_TRANSPORT_ID};
struct PreferCustomTransport;
impl PathSelector for PreferCustomTransport {
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
}
}
fn build_endpoint(secret: SecretKey, transport: Arc<TestTransport>) -> Builder {
Endpoint::builder(presets::N0)
.secret_key(secret)
.preset(transport) // Inject custom transport
.path_selector(Arc::new(PreferCustomTransport))
.clear_ip_transports() // Optional: disable IPv4/IPv6
.clear_relay_transports() // Optional: disable relay
}
When executed, the connection resolves through the custom transport layer, verified by checking the selected path type (lines 66-74 in the example).
Summary
- Enable
unstable-custom-transportsinCargo.tomlto unlock the custom transport API - Implement
CustomTransportiniroh/src/test_utils/test_transport.rsto define packet movement and addressing - Register your transport with a unique 16-bit ID documented in
TRANSPORTS.md - Use
Endpoint::builder()with.preset()to inject your implementation - Implement
PathSelectorto prioritizeAddr::Custompaths over standard IP or relay connections - Reference the custom-transport example for working integration patterns
Frequently Asked Questions
What version of iroh supports unstable-custom-transports?
The feature is available in recent mainline builds and 0.x releases, though marked unstable because the API may evolve. Check the repository's Cargo.toml for the exact feature flag definition and ensure you're tracking a compatible git revision.
Can I use multiple custom transports simultaneously?
Yes. The Builder accumulates transports via multiple .preset() calls, and the PathSelector receives all available paths including multiple custom types. Your selector logic can prioritize between them based on transport ID or other path quality metrics.
How do I allocate a new transport ID for my protocol?
Submit a pull request adding your transport ID and description to TRANSPORTS.md. IDs are u16 values where the range is split between reserved internal transports, experimental user transports, and stable allocations. Using an unregistered ID risks collisions with future iroh updates.
Does enabling custom transports affect existing IPv4/IPv6 connections?
No, built-in transports remain active unless explicitly cleared. Call .clear_ip_transports() and .clear_relay_transports() on the builder to restrict the endpoint to only your custom implementation, or leave them enabled to allow fallback when your transport is unavailable.
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 →