How to Implement Custom Transport Configurations in iroh

You configure custom transports in iroh by implementing the CustomTransport trait and injecting it via Endpoint::builder(), while QUIC-specific settings are controlled through the QuicTransportConfig builder pattern.

The iroh networking stack provides a flexible transport abstraction that allows developers to customize both the underlying QUIC protocol parameters and inject entirely custom transport implementations. Whether you need to tune idle timeouts and keep-alive intervals or implement a specialized transport for testing or edge deployments, the configuration API in n0-computer/iroh exposes these capabilities through the EndpointBuilder and transport-specific configuration structs.

Core Transport Components

The transport system in iroh centers on three primary types defined in the source code.

TransportConfig Enum

Located in iroh/src/socket/transports.rs, the TransportConfig enum defines the available transport types:

  • Ip: Standard UDP/QUIC sockets binding to specific addresses
  • Relay: Transport through relay servers for NAT traversal
  • Custom: User-provided implementations via Arc<dyn CustomTransport>

QuicTransportConfig Builder

The QuicTransportConfig struct in iroh/src/endpoint/quic.rs provides a builder pattern for tuning QUIC-specific parameters. This wrapper around noq::TransportConfig exposes methods like keep_alive_interval(), max_idle_timeout(), and mtu_discovery_config() to control connection lifecycle and performance characteristics.

CustomTransport Trait

Also defined in iroh/src/socket/transports.rs, the CustomTransport trait requires implementors to provide three asynchronous methods. According to the source, you must implement:

async fn dial(&self, addr: TransportAddr) -> Result<Connection>;
async fn listen(&self, bind: TransportAddr) -> Result<Listener>;
fn local_addr(&self) -> TransportAddr;

These methods are called by the endpoint when it needs to open new paths or accept incoming connections.

Implementing Custom QUIC Transport Settings

To customize QUIC-level parameters such as keep-alive intervals and idle timeouts, use the QuicTransportConfig::builder() method from iroh/src/endpoint/quic.rs.

use iroh::endpoint::{QuicTransportConfig, Endpoint};
use std::time::Duration;

// Build a QUIC config with a 15-second keep-alive and a 30-second idle timeout
let quic_cfg = QuicTransportConfig::builder()
    .keep_alive_interval(Duration::from_secs(15))
    .max_idle_timeout(Duration::from_secs(30))
    .build();

// Build the endpoint, injecting the custom QUIC config
let secret_key = iroh::SecretKey::generate();
let endpoint = Endpoint::builder(secret_key)
    .with_transport_config(quic_cfg) // custom QUIC settings
    .build()
    .await?;

The with_transport_config() method on EndpointBuilder (defined in iroh/src/endpoint.rs) accepts this configuration and applies it to all QUIC connections created by the endpoint.

Creating a Custom Transport Implementation

To implement a completely custom transport, you must satisfy the CustomTransport trait and pass it to the builder using with_custom_transport(). A complete working example is available in iroh/examples/custom-transport.rs.

use std::{sync::Arc, net::SocketAddr};
use iroh::{
    endpoint::{Endpoint, TransportConfig},
    socket::{CustomTransport, TransportAddr, Connection, Listener},
    SecretKey,
};

#[derive(Clone)]
struct MyTransport;

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

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

    fn local_addr(&self) -> TransportAddr {
        // Return the address this transport advertises
        TransportAddr::Custom(iroh::socket::CustomAddr::new(0xdeadbeef, b"myaddr"))
    }
}

// Build the endpoint with the custom transport
let secret_key = SecretKey::generate();
let endpoint = Endpoint::builder(secret_key)
    .with_custom_transport(Arc::new(MyTransport))
    .build()
    .await?;

The TransportConfig::Custom variant wraps your implementation in an Arc<dyn CustomTransport>, allowing the endpoint to manage path selection across standard and custom transports simultaneously.

Combining QUIC Settings and Custom Transports

You can combine custom QUIC configurations with custom transport implementations by calling both builder methods. This approach is useful when your custom transport still utilizes QUIC semantics but requires specialized dialing logic.

use std::{sync::Arc, time::Duration};
use iroh::{
    endpoint::{Endpoint, QuicTransportConfig},
    socket::{CustomTransport, TransportConfig},
    SecretKey,
};

// Configure QUIC parameters
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(MyTransport)) // custom transport
    .build()
    .await?;

The endpoint combines these configurations in iroh/src/endpoint.rs, creating sockets with the specified QUIC parameters while maintaining your custom transport in the available path list.

Selecting Only Custom Transports

To disable the default IP transports (IPv4/IPv6) and use only your custom implementation, call clear_ip_transports() before building.

use iroh::{
    endpoint::{Endpoint, QuicTransportConfig},
    socket::TransportConfig,
    SecretKey,
};
use std::sync::Arc;

let secret_key = SecretKey::generate();

// Disable default IP transports, keep only the custom one
let endpoint = Endpoint::builder(secret_key)
    .clear_ip_transports()                     // removes IPv4/IPv6 transports
    .with_custom_transport(Arc::new(MyTransport))
    .build()
    .await?;

This method, defined in iroh/src/endpoint.rs, empties the default Vec<TransportConfig> that normally contains the IP bindings, allowing your custom transport to serve as the sole networking path.

Summary

  • TransportConfig in iroh/src/socket/transports.rs defines the three transport types: Ip, Relay, and Custom.
  • QuicTransportConfig provides a builder pattern in iroh/src/endpoint/quic.rs for tuning QUIC-specific parameters like timeouts and keep-alive intervals.
  • CustomTransport trait requires dial(), listen(), and local_addr() implementations to create user-defined transports.
  • Use Endpoint::builder(secret_key).with_transport_config() for QUIC settings and with_custom_transport() for custom implementations.
  • Call clear_ip_transports() to remove default IP bindings when using exclusively custom transports.
  • Reference iroh/examples/custom-transport.rs for a complete working implementation.

Frequently Asked Questions

What is the difference between QuicTransportConfig and CustomTransport?

QuicTransportConfig tunes the QUIC protocol parameters (timeouts, MTU, congestion control) for standard UDP sockets, while CustomTransport is a trait that lets you implement entirely new transport mechanisms with custom dialing and listening logic. You can use QuicTransportConfig with the default IP transport or alongside custom transports, but CustomTransport requires implementing the full connection lifecycle yourself.

How do I disable default IP transports when using a custom transport?

Call the clear_ip_transports() method on the EndpointBuilder before invoking with_custom_transport(). This removes the default IPv4 and IPv6 bindings from the internal Vec<TransportConfig>, ensuring only your custom transport handles network traffic. The method is defined in iroh/src/endpoint.rs and takes no arguments.

Can I combine multiple custom transports with standard QUIC?

Yes. The endpoint accepts a Vec<TransportConfig> that can contain multiple entries. You can call with_custom_transport() multiple times with different implementations, and the endpoint will treat them as distinct path options alongside standard IP and Relay transports. The TransportConfig::Custom variant wraps each implementation in an Arc<dyn CustomTransport> for efficient sharing across connections.

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

The repository includes a full implementation in iroh/examples/custom-transport.rs. This example demonstrates proper trait implementation for CustomTransport, including error handling with anyhow::Result and the correct TransportAddr construction. The test suite in iroh/tests/patchbay/util.rs also shows real-world usage of QuicTransportConfig::builder() around line 464.

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 →