# How to Implement Custom Transports in iroh: A Complete Guide

> Learn how to implement custom transports in iroh with this guide. Implement the CustomTransport trait and register it using Endpoint::builder().with_custom_transport() to extend iroh's capabilities.

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

---

**Implement custom transports in iroh by implementing the `CustomTransport` trait and registering it via `Endpoint::builder().with_custom_transport()`, while using `QuicTransportConfig` to tune QUIC-level parameters.**

iroh's networking layer is built around a flexible transport abstraction that decouples the protocol implementation from the underlying connectivity. Whether you need to tunnel traffic through a specialized network, implement a testing mock, or support a non-standard protocol, you can plug custom transports directly into the `Endpoint` builder. This guide covers the exact mechanisms for implementing custom transports in iroh using the source code from the `n0-computer/iroh` repository.

## Understanding iroh's Transport Architecture

The transport system centers on two core abstractions defined in [`iroh/src/socket/transports.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports.rs): the `TransportConfig` enum and the `CustomTransport` trait. These types allow the `EndpointBuilder` in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) to compose multiple transport backends while maintaining a unified connection interface.

### The TransportConfig Enum

The `TransportConfig` enum defines the set of transport types available to an iroh node. According to the source in [`iroh/src/socket/transports.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports.rs), the variants include:

- **`Ip { bind: SocketAddr, ... }`** — Standard UDP/QUIC sockets for IPv4/IPv6 connectivity.
- **`Relay { ... }`** — Relayed connections through iroh relay servers for NAT traversal.
- **`Custom(Arc<dyn CustomTransport>)`** — A placeholder for user-provided transport implementations.

When building an endpoint, you construct a `Vec<TransportConfig>` that determines which protocols the node will attempt to use for dialing and listening.

### The CustomTransport Trait

To implement custom transports in iroh, you must satisfy the `CustomTransport` trait. As defined in [`iroh/src/socket/transports.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports.rs), implementors must provide three asynchronous methods:

```rust
use iroh::socket::{TransportAddr, Connection, Listener};

#[async_trait::async_trait]
pub trait CustomTransport: Send + Sync {
    async fn dial(&self, addr: TransportAddr) -> anyhow::Result<Connection>;
    async fn listen(&self, bind: TransportAddr) -> anyhow::Result<Listener>;
    fn local_addr(&self) -> TransportAddr;
}

```

The **dial** method initiates outgoing connections, **listen** creates a listener for incoming connections, and **local_addr** returns the transport address this implementation advertises to other nodes.

## Configuring QUIC Transport Settings

Before adding custom transports, you often need to tune QUIC-level parameters that affect all connections. The `QuicTransportConfig` builder in [`iroh/src/endpoint/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/quic.rs) exposes knobs like `keep_alive_interval`, `max_idle_timeout`, and congestion control settings.

```rust
use iroh::endpoint::QuicTransportConfig;
use std::time::Duration;

// Configure QUIC with aggressive keep-alives and a short idle timeout
let quic_cfg = QuicTransportConfig::builder()
    .keep_alive_interval(Duration::from_secs(15))
    .max_idle_timeout(Duration::from_secs(30))
    .build();

```

The `builder()` method returns a `QuicTransportConfigBuilder` that validates parameters before construction. These settings apply to the underlying `noq::TransportConfig` used by the endpoint's QUIC implementation.

## Implementing a Custom Transport

To create a working custom transport, define a struct and implement the `CustomTransport` trait using `async_trait::async_trait`. The reference implementation in [`iroh/examples/custom-transport.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs) demonstrates the required boilerplate.

```rust
use std::sync::Arc;
use iroh::{
    socket::{CustomTransport, TransportAddr, Connection, Listener},
    SecretKey,
};

#[derive(Clone)]
struct MyCustomTransport;

#[async_trait::async_trait]
impl CustomTransport for MyCustomTransport {
    async fn dial(&self, addr: TransportAddr) -> anyhow::Result<Connection> {
        // Implement your dialing logic here
        unimplemented!("Dial to {:?}", addr)
    }

    async fn listen(&self, bind: TransportAddr) -> anyhow::Result<Listener> {
        // Implement your listening logic here
        unimplemented!("Listen on {:?}", bind)
    }

    fn local_addr(&self) -> TransportAddr {
        // Return a custom address identifier
        TransportAddr::Custom(iroh::socket::CustomAddr::new(0xdeadbeef, b"mytransport"))
    }
}

```

Your implementation must be `Send + Sync` because the endpoint holds the transport as an `Arc<dyn CustomTransport>` and uses it across asynchronous tasks.

## Registering Custom Transports with the Endpoint

Once implemented, register your transport with the `EndpointBuilder` using `with_custom_transport()`. This method accepts an `Arc<dyn CustomTransport>` and appends it to the transport list.

```rust
use iroh::endpoint::Endpoint;
use std::sync::Arc;

let secret_key = SecretKey::generate();
let endpoint = Endpoint::builder(secret_key)
    .with_custom_transport(Arc::new(MyCustomTransport))
    .build()
    .await?;

```

### Combining QUIC Configuration and Custom Transports

You can simultaneously inject custom QUIC settings and custom transport implementations. The endpoint uses the `QuicTransportConfig` for protocol parameters while your custom transport provides the actual socket operations.

```rust
use iroh::endpoint::{Endpoint, QuicTransportConfig};

let quic_cfg = QuicTransportConfig::builder()
    .keep_alive_interval(Duration::from_secs(10))
    .max_idle_timeout(Duration::from_secs(20))
    .build();

let secret_key = SecretKey::generate();
let endpoint = Endpoint::builder(secret_key)
    .with_transport_config(quic_cfg)                // Custom QUIC knobs
    .with_custom_transport(Arc::new(MyCustomTransport)) // Custom transport
    .build()
    .await?;

```

The `with_transport_config()` method is defined in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) and accepts a `QuicTransportConfig` constructed via the builder pattern shown above.

### Using Only Custom Transports

To disable default IP transports and run exclusively with your custom implementation, call `clear_ip_transports()` before building. This removes the automatic IPv4 and IPv6 socket bindings.

```rust
let secret_key = SecretKey::generate();
let endpoint = Endpoint::builder(secret_key)
    .clear_ip_transports()                     // Removes IPv4/IPv6 from the transport list
    .with_custom_transport(Arc::new(MyCustomTransport))
    .build()
    .await?;

```

This configuration is useful when running iroh in restricted environments where UDP sockets are unavailable or when implementing proxy transports that handle all connectivity internally.

## Summary

- **`CustomTransport` trait** — Implement `dial()`, `listen()`, and `local_addr()` in [`iroh/src/socket/transports.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports.rs) to define custom transport behavior.
- **`QuicTransportConfig` builder** — Use [`iroh/src/endpoint/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/quic.rs) to tune keep-alive intervals, idle timeouts, and MTU discovery before injecting transports.
- **Endpoint registration** — Pass custom transports to `Endpoint::builder().with_custom_transport()` and combine with `with_transport_config()` for full control.
- **Selective transport chains** — Use `clear_ip_transports()` to disable default UDP sockets when running purely on custom transports.
- **Reference implementation** — Study [`iroh/examples/custom-transport.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs) for a complete working example of the trait implementation.

## Frequently Asked Questions

### What methods must I implement for the CustomTransport trait?

You must implement three methods defined in [`iroh/src/socket/transports.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports.rs): `async fn dial()` for initiating connections, `async fn listen()` for accepting them, and `fn local_addr()` to advertise your transport's address. All methods must be thread-safe because the endpoint shares the transport across asynchronous tasks using an `Arc` wrapper.

### Can I use multiple custom transports simultaneously?

Yes. The `EndpointBuilder` stores transports in a `Vec<TransportConfig>`, and you can call `with_custom_transport()` multiple times with different implementations. The endpoint will attempt to use all registered transports when establishing connections, falling back through the list based on path availability.

### How do I configure QUIC keep-alive intervals when using custom transports?

Use `QuicTransportConfig::builder()` from [`iroh/src/endpoint/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/quic.rs) to set `keep_alive_interval()` and `max_idle_timeout()`, then pass the resulting config to `Endpoint::builder().with_transport_config()`. These settings apply to the QUIC protocol layer regardless of whether you use built-in IP transports or custom implementations.

### Where can I find a complete working example of a custom transport?

The official [`iroh/examples/custom-transport.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs) file in the `n0-computer/iroh` repository provides a full reference implementation. Additionally, [`iroh/tests/patchbay/util.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/tests/patchbay/util.rs) (around line 464) demonstrates real-world usage of `QuicTransportConfig::builder()` inside the test suite, showing how custom transports integrate with the broader endpoint system.