Configuring Custom Transport Layers in iroh with unstable-custom-transports
The unstable-custom-transports feature flag in iroh enables a pluggable networking API that lets developers implement custom packet transport layers—such as Bluetooth, Tor overlays, or proprietary protocols—by implementing the CustomTransport trait, registering a unique transport ID, and injecting the transport into the Endpoint builder with a custom PathSelector to prioritize it over standard IPv4/IPv6 or relay paths.
The n0-computer/iroh repository provides a modular networking stack designed for distributed systems. By enabling the unstable-custom-transports Cargo feature, you can extend the default transport implementations with user-defined networking layers that integrate seamlessly with iroh's connection logic and path selection algorithms.
Enabling the unstable-custom-transports Feature
To access the custom transport APIs, enable the feature in your Cargo.toml:
[dependencies]
iroh = { version = "0.x", features = ["unstable-custom-transports"] }
When this feature is active, the iroh::endpoint module re-exports the low-level transport types required for implementation. Specifically, [src/endpoint.rs](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs#L29-L48) exposes the transports submodule, which includes CustomTransport, CustomEndpoint, and CustomSender at lines 41-46.
Architecture of Custom Transports
Custom transports in iroh are identified by a transport ID, which is a u16 value that distinguishes the networking protocol. The repository maintains a canonical registry of these IDs in [TRANSPORTS.md](https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md#L5-L9). For example, the built-in test transport reserves ID 0x20.
The architecture separates concerns across three layers:
- Transport Implementation: Defines how packets are sent and received
- Endpoint Builder: Configures which transports are available to a local endpoint
- Path Selector: Determines which transport to use for a given connection
Implementing a Custom Transport
The CustomTransport Trait
A custom transport must implement the CustomTransport trait, which defines two essential methods:
addr(): Returns aCustomAddrcontaining the transport ID and opaque address dataconnect(): Creates aCustomSendercapable of sending and receiving raw bytes
These types are defined in the socket layer and re-exported through the endpoint module.
Reference Implementation
The repository provides a reference implementation in [iroh/src/test_utils/test_transport.rs](https://github.com/n0-computer/iroh/blob/main/iroh/src/test_utils/test_transport.rs). The TestTransport struct demonstrates how to implement the trait for an in-memory network, using transport ID 0x20 and providing the required CustomEndpoint and CustomSender implementations.
Configuring Endpoints with Custom Transports
Adding Transports to the Builder
The Endpoint::builder() creates a Builder that accumulates transport configurations. To add a custom transport, use the .preset() method, which accepts any type implementing CustomTransport:
let transport = Arc::new(TestTransport::new());
let builder = Endpoint::builder(presets::N0)
.secret_key(secret_key)
.preset(transport);
To disable default transports, chain the builder methods .clear_ip_transports() and .clear_relay_transports(). This forces the endpoint to use only your custom implementation.
Implementing Path Selection
The PathSelector trait determines which available path to use for a connection. The example in [examples/custom-transport.rs](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs) (lines 37-69) demonstrates a PreferTestTransport selector that prioritizes paths using the custom transport ID:
impl PathSelector for PreferTestTransport {
fn select(&self, ctx: &PathSelectionContext<'_>) -> PathSelection {
let mut selection = PathSelection::none();
// Prefer the custom transport if available
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 path
ctx.select_lowest_rtt(&mut selection);
selection
}
}
Attach the selector to the builder using .path_selector(Arc::new(PreferTestTransport)).
Complete Configuration Example
The following example demonstrates building an endpoint that prefers the test custom transport while disabling standard IP transports:
use iroh::{
Endpoint, SecretKey,
endpoint::{Builder, PathSelector, PathSelectionContext},
endpoint::transports::Addr,
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_key: SecretKey,
transport: Arc<TestTransport>,
) -> anyhow::Result<Endpoint> {
Endpoint::builder(presets::N0)
.secret_key(secret_key)
.preset(transport)
.path_selector(Arc::new(PreferTestTransport))
.clear_ip_transports()
.clear_relay_transports()
.bind()
.await
}
Running this configuration ensures that all connections attempt to use your custom transport implementation before falling back to alternative paths.
Summary
- Enable the feature by adding
unstable-custom-transportsto your Cargo dependencies to access theCustomTransporttrait and related types. - Implement the trait by defining
addr()andconnect()methods, using a unique transport ID registered inTRANSPORTS.md. - Configure the builder using
.preset()to inject your transport, and optionally call.clear_ip_transports()to disable default networking. - Control selection by implementing
PathSelectorto prioritizeAddr::Customvariants with your transport ID. - Reference the test implementation in
iroh/src/test_utils/test_transport.rsand the working example iniroh/examples/custom-transport.rsfor complete integration patterns.
Frequently Asked Questions
What is the unstable-custom-transports feature flag in iroh?
The unstable-custom-transports feature flag is a Cargo configuration that unlocks iroh's experimental transport plugin API. When enabled, it exposes the CustomTransport, CustomEndpoint, and CustomSender types from the iroh::endpoint::transports module, allowing developers to implement alternative networking layers that can coexist with or replace the built-in IPv4, IPv6, and relay transports.
How do I register a new transport ID for my custom implementation?
Transport IDs are 16-bit unsigned integers (u16) tracked in the [TRANSPORTS.md](https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md) file at the repository root. To register a new transport, submit a pull request to the n0-computer/iroh repository that adds your transport ID to the registry table, along with a description of the protocol and its status (reserved, experimental, or stable). The test transport uses ID 0x20 as a reference implementation.
Can custom transports coexist with standard IP and relay transports?
Yes, custom transports can coexist with the default IPv4, IPv6, and relay transports. By default, adding a custom transport via .preset() makes it available alongside standard transports. You can selectively disable IP or relay transports using .clear_ip_transports() and .clear_relay_transports() on the Builder if you want to force connections through your custom implementation or create a specialized network configuration.
What methods must a custom transport implement?
A custom transport must implement the CustomTransport trait, which requires two primary methods: addr(), which returns a CustomAddr containing the transport ID and address data, and connect(), which establishes a connection and returns a CustomSender for transmitting and receiving raw packet data. Additionally, the transport must provide a way to create a CustomEndpoint that can accept incoming connections, typically through a constructor method that returns an Arc<dyn CustomTransport>.
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 →