# How to Implement Custom Transport Layers in iroh Using the Unstable Custom Transports Feature

> Learn to implement custom transport layers in iroh using the unstable custom transports feature. Extend iroh's networking by implementing the CustomTransport trait and registering your transport.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: how-to-guide
- Published: 2026-06-26

---

**Enable the `unstable-custom-transports` Cargo feature to extend iroh's networking stack with custom transports by implementing the `CustomTransport` trait, registering a unique transport ID, and using a custom `PathSelector` to prioritize your transport during path selection.**

Iroh provides a pluggable networking architecture that allows developers to integrate alternative packet transport mechanisms alongside built-in IPv4/IPv6 and relay protocols. By enabling the `unstable-custom-transports` feature, you can implement custom transport layers that work within iroh's connection establishment and path selection logic, supporting use cases from Bluetooth overlays to Tor tunnels.

## Enabling the Unstable Feature

Custom transports are gated behind the `unstable-custom-transports` Cargo feature flag. Add the following to your [`Cargo.toml`](https://github.com/n0-computer/iroh/blob/main/Cargo.toml) to access the transport extension APIs:

```toml
[dependencies]
iroh = { git = "https://github.com/n0-computer/iroh", features = ["unstable-custom-transports"] }

```

When enabled, the `Endpoint` module re-exports the necessary types from the internal socket layer. In [[`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs)](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs#L29-L48), the `transports` sub-module exposes `CustomTransport`, `CustomEndpoint`, and `CustomSender` ([lines 41-46](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs#L41-L46)).

## Understanding the Transport Architecture

A custom transport in iroh is identified by a **transport ID** (a `u16` value). The project maintains a canonical registry in [[`TRANSPORTS.md`](https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md)](https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md#L5-L9), where `0x20` is reserved for the internal test transport.

The architecture revolves around three core abstractions defined in the socket layer:

- **`CustomTransport`** – The trait you implement to define packet sending/receiving logic
- **`CustomAddr`** – A structured address containing the transport ID and opaque endpoint-specific data
- **`PathSelector`** – The mechanism that chooses which transport path to use for a given connection

## Implementing the CustomTransport Trait

Your transport implementation must satisfy the `CustomTransport` interface expected by the socket layer. While the internal trait definition resides in the socket transports module, you can reference the test implementation in [[`iroh/src/test_utils/test_transport.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/test_utils/test_transport.rs)](https://github.com/n0-computer/iroh/blob/main/iroh/src/test_utils/test_transport.rs) as a working template.

A minimal implementation requires:

1. **Address generation** – Construct a `CustomAddr` containing your transport ID and endpoint-specific metadata
2. **Connection establishment** – Return a `CustomSender` capable of sending and receiving raw bytes
3. **Transport identification** – Return your allocated transport ID when queried

## Configuring the Endpoint Builder

To activate your custom transport, inject it during `Endpoint` construction using the builder API. The example in [[`iroh/examples/custom-transport.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs)](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs) demonstrates this pattern in the `configure` method ([lines 71-87](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs#L71-L87)):

```rust
use iroh::{Endpoint, endpoint::Builder, SecretKey};
use std::sync::Arc;

fn builder_with_custom(
    secret_key: SecretKey,
    transport: Arc<dyn CustomTransport>,
) -> Builder {
    Endpoint::builder(presets::N0)
        .secret_key(secret_key)
        .preset(transport)  // Inject the custom transport
        .clear_ip_transports()      // Optional: disable IPv4/IPv6
        .clear_relay_transports()   // Optional: disable relay fallback
}

```

The `.preset()` method adds your transport to the internal `Vec<TransportConfig>` maintained by the builder, while `clear_ip_transports()` and `clear_relay_transports()` allow you to force exclusive use of your custom implementation.

## Creating a Custom Path Selector

By default, iroh uses RTT-based path selection. To prioritize your custom transport, implement the `PathSelector` trait and inspect the `PathSelectionContext` for custom addresses. The reference implementation in [[`iroh/examples/custom-transport.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs)](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs#L37-L69) shows a selector that prefers the test transport:

```rust
use iroh::endpoint::{PathSelector, PathSelection, PathSelectionContext, transports::Addr};

const TEST_TRANSPORT_ID: u16 = 0x20;

struct PreferTestTransport;

impl PathSelector for PreferTestTransport {
    fn select(&self, ctx: &PathSelectionContext<'_>) -> PathSelection {
        let mut selection = PathSelection::none();
        
        // Prioritize paths using our custom transport
        if let Some(path) = ctx.paths().find(|p| {
            matches!(p.network_path().remote(), Addr::Custom(c) if c.id() == TEST_TRANSPORT_ID)
        }) {
            selection.set(&path);
            return selection;
        }
        
        // Fallback to lowest RTT path
        for path in ctx.paths() {
            if selection.is_none() || path.rtt() < selection.rtt() {
                selection.set(&path);
            }
        }
        selection
    }
}

```

Attach the selector to your builder using `.path_selector(Arc::new(PreferTestTransport))`.

## Complete Working Example

Putting the components together, here is a minimal example that establishes a connection using only the custom test transport:

```rust
use iroh::{Endpoint, SecretKey, endpoint::PathSelectionContext};
use iroh::endpoint::{PathSelector, PathSelection, transports::Addr};
use std::sync::Arc;

// Assume TestTransport implements CustomTransport
use iroh::test_utils::test_transport::{TestTransport, TEST_TRANSPORT_ID};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let secret = SecretKey::generate();
    let transport = Arc::new(TestTransport::new());
    
    // Build endpoint with custom transport
    let endpoint = Endpoint::builder(presets::N0)
        .secret_key(secret)
        .preset(transport)
        .path_selector(Arc::new(PreferTestTransport))
        .clear_ip_transports()
        .clear_relay_transports()
        .bind()
        .await?;
    
    // Connect using the custom transport path
    let conn = endpoint.connect(node_id, ALPN).await?;
    Ok(())
}

```

This configuration forces the endpoint to use only your custom transport implementation, bypassing standard networking paths entirely.

## Key Source Files

Understanding the following source locations is essential for implementing custom transports:

- **[[`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs)](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs)** – Public API surface (`Endpoint`, `Builder`) and re-exports of `CustomTransport` types ([lines 29-48](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs#L29-L48))
- **[[`iroh/src/socket/transports.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports.rs)](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports.rs)** – Low-level transport abstractions including `TransportConfig`, `Addr`, and `CustomAddr`
- **[[`iroh/src/test_utils/test_transport.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/test_utils/test_transport.rs)](https://github.com/n0-computer/iroh/blob/main/iroh/src/test_utils/test_transport.rs)** – Reference implementation showing the `CustomTransport` trait contract
- **[[`iroh/examples/custom-transport.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs)](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs)** – End-to-end CLI example demonstrating transport selection and configuration ([lines 37-69](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs#L37-L69) for selector, [lines 71-87](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs#L71-L87) for builder)
- **[[`TRANSPORTS.md`](https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md)](https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md)** – Official registry of transport IDs and reserved ranges

## Summary

- **Enable the feature flag** `unstable-custom-transports` in your [`Cargo.toml`](https://github.com/n0-computer/iroh/blob/main/Cargo.toml) to access extension APIs
- **Implement `CustomTransport`** to define packet transport logic and address formats
- **Register a transport ID** in [`TRANSPORTS.md`](https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md) to avoid collisions with other implementations
- **Use the builder API** to inject your transport via `.preset()` and optionally disable built-in transports with `.clear_ip_transports()` and `.clear_relay_transports()`
- **Implement `PathSelector`** to prioritize your custom transport over IPv4/IPv6 paths during connection establishment
- **Reference the test implementation** in [`test_transport.rs`](https://github.com/n0-computer/iroh/blob/main/test_transport.rs) and the working example in [`custom-transport.rs`](https://github.com/n0-computer/iroh/blob/main/custom-transport.rs) for concrete trait implementations

## Frequently Asked Questions

### What does the `unstable-custom-transports` feature enable?

The feature unlocks the `CustomTransport` trait and associated types in the public API, allowing you to inject arbitrary packet transport mechanisms into iroh's networking stack. Without this flag, the transport extension points are hidden and the necessary builder methods are unavailable.

### How do I reserve a transport ID for my implementation?

Transport IDs are 16-bit unsigned integers (`u16`) tracked in the [[`TRANSPORTS.md`](https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md)](https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md) file at the repository root. To avoid collisions, submit a pull request that registers your ID with a description of your transport mechanism. The range `0x0001-0x00FF` is reserved for experimental use, while `0x20` is allocated for the internal test transport.

### Can I use multiple custom transports simultaneously?

Yes. The `Endpoint` builder accepts multiple transports via repeated calls to `.preset()`. Each transport must have a unique ID. The `PathSelector` implementation determines which transport wins for a given connection by inspecting the `Addr::Custom` variant in the path selection context and choosing based on your priority logic.

### What is the difference between `CustomEndpoint` and `CustomSender`?

`CustomEndpoint` represents the local binding of your transport (analogous to a socket), while `CustomSender` is the per-connection handle returned by your transport's `connect` method that actually transmits and receives byte streams. The endpoint manages the local address and incoming connection state, whereas the sender handles the active data flow for established connections.