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-transports feature flag to access the transport API in iroh/src/endpoint.rs.
  • Implement CustomTransport and register a unique transport ID in TRANSPORTS.md.
  • Use Builder::preset() to inject your transport and clear_ip_transports()/clear_relay_transports() to exclude defaults.
  • Implement PathSelector to prioritize your custom transport over built-in paths.
  • Reference the full example in iroh/examples/custom-transport.rs for 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →