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 a CustomAddr containing the transport ID and opaque address data
  • connect(): Creates a CustomSender capable 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-transports to your Cargo dependencies to access the CustomTransport trait and related types.
  • Implement the trait by defining addr() and connect() methods, using a unique transport ID registered in TRANSPORTS.md.
  • Configure the builder using .preset() to inject your transport, and optionally call .clear_ip_transports() to disable default networking.
  • Control selection by implementing PathSelector to prioritize Addr::Custom variants with your transport ID.
  • Reference the test implementation in iroh/src/test_utils/test_transport.rs and the working example in iroh/examples/custom-transport.rs for 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:

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 →