# How to Implement Custom Protocol Handlers Using ALPN in iroh

> Learn to implement custom protocol handlers in iroh using ALPN. Register byte-string ALPN identifiers with RouterBuilder to map QUIC connections to ProtocolHandler implementations.

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

---

**To implement custom protocol handlers using ALPN in iroh, you register byte-string ALPN identifiers with a `Router` using `RouterBuilder::accept`, which maps incoming QUIC connections to trait-based handlers that implement `ProtocolHandler::accept`.**

iroh uses QUIC/TLS Application-Layer Protocol Negotiation (ALPN) to route incoming connections to the correct application-level protocol handler. When you implement custom protocol handlers using ALPN in iroh, you create secure, peer-to-peer endpoints that automatically negotiate and dispatch connections without managing low-level TLS or QUIC state manually. This architecture separates transport concerns from application logic, allowing you to define protocols as simple Rust traits.

## Understanding ALPN Protocol Routing in iroh

iroh leverages QUIC/TLS ALPN to advertise supported protocols during the TLS handshake phase. The `Endpoint` builder in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) configures the underlying TLS stack via `Builder::bind`, which internally calls `static_config.create_server_config(self.alpn_protocols)` to register the protocols this node supports.

When a remote peer connects, the accept loop in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs) extracts the negotiated ALPN identifier using `incoming.alpn().await`, looks up the corresponding handler in the `ProtocolMap`, and dispatches the connection to your custom implementation.

## Implementing the ProtocolHandler Trait

Custom protocols implement the `ProtocolHandler` trait defined in [`iroh/src/protocol.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/protocol.rs). This trait requires a single asynchronous method, `accept`, which receives an established `Connection` and returns a `Result<(), AcceptError>`.

### Defining the Handler Structure

Create a Rust struct that derives `Debug` and `Clone`, then implement `ProtocolHandler` for it. Here is a complete echo protocol handler that mirrors the reference implementation in [`iroh/examples/echo.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/echo.rs):

```rust
use iroh::{
    endpoint::{Connection, AcceptError},
    protocol::ProtocolHandler,
};

#[derive(Debug, Clone)]
struct Echo;

impl ProtocolHandler for Echo {
    async fn accept(&self, connection: Connection) -> Result<(), AcceptError> {
        // Split the QUIC connection into a send and receive side.
        let (mut send, mut recv) = connection.accept_bi().await?;
        
        // Echo everything we receive back to the peer.
        tokio::io::copy(&mut recv, &mut send).await?;
        
        // Gracefully finish the send side and wait for the peer to close.
        send.finish()?;
        connection.closed().await;
        Ok(())
    }
}

```

### Handling Bidirectional Streams

Inside the `accept` method, the `Connection` object represents the established QUIC stream pair. Use `accept_bi()` to open bidirectional streams, then process data using standard async I/O operations. The handler runs on a dedicated Tokio task, allowing concurrent processing of multiple connections.

## Registering Handlers with the Router

The `Router` acts as a dispatcher, mapping ALPN byte strings to your handlers via a `ProtocolMap` stored in the `RouterBuilder`.

### Building the Endpoint

Configure your endpoint using presets like `presets::N0` or `presets::Minimal`, then bind to an address:

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

let endpoint = Endpoint::builder(presets::N0).bind().await?;

```

### Mapping ALPN to Handlers

Use `Router::builder(endpoint)` to obtain a `RouterBuilder`, then call `.accept(alpn, handler)` where `alpn` is any `&[u8]` slice (commonly a static byte string such as `b"/iroh/echo/1"`). Finally, spawn the router to begin accepting connections:

```rust
use iroh::protocol::Router;

const ECHO_ALPN: &[u8] = b"/iroh/echo/1";

let router = Router::builder(endpoint)
    .accept(ECHO_ALPN, Echo)
    .spawn();

// The router now accepts connections that negotiate the echo ALPN.
// It runs until the process exits or `router.shutdown().await` is called.

```

The `RouterBuilder::accept` method stores the handler in a `ProtocolMap` and automatically updates the endpoint's ALPN list via `endpoint.set_alpns(alpns)` when `spawn()` is called.

## Connecting from Clients

Clients connect by specifying the same ALPN byte string used during registration. The client endpoint does not need to register ALPNs for outgoing connections:

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

let client = Endpoint::builder(presets::Minimal).bind().await?;
let server_addr = /* EndpointAddr obtained from router.endpoint().addr() */;

// Connect using the same ALPN identifier.
let conn = client.connect(server_addr, b"/iroh/echo/1").await?;

// Use the connection (e.g., open a bidirectional stream, send data, read echo).
let (mut send, mut recv) = conn.open_bi().await?;
// ...

```

## Key Source Files and Implementation Details

The routing logic spans several critical files in the iroh repository:

- **[`iroh/src/protocol.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/protocol.rs)** — Defines the `ProtocolHandler` trait, `DynProtocolHandler`, and the `ProtocolMap` struct. Contains the `RouterBuilder::accept` registration API.
- **[`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs)** — Implements `Builder::alpns` and `Builder::bind`, which configures the TLS server with `static_config.create_server_config(self.alpn_protocols)`.
- **[`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs)** — Contains the low-level QUIC accept loop in `handle_connection` that extracts the negotiated ALPN via `incoming.alpn().await` and dispatches to registered handlers.
- **[`iroh/examples/echo.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/echo.rs)** — A complete, runnable example demonstrating a custom echo protocol handler wired with a router.
- **[`iroh/tests/patchbay/util.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/tests/patchbay/util.rs)** — Test utilities including `TEST_ALPN` constants and example usage patterns.

## Summary

- **Implement `ProtocolHandler`** for your custom logic, defining the `accept` method to handle `Connection` objects and manage bidirectional streams.
- **Register handlers** with `RouterBuilder::accept` using unique ALPN byte strings like `b"/myapp/1"` to map incoming connections to your implementation.
- **Configure the endpoint** using `Endpoint::builder` with presets, then bind to create the underlying QUIC/TLS configuration.
- **Automatic routing** occurs when the accept loop extracts the negotiated ALPN and dispatches to your handler via the `ProtocolMap`.
- **Reference implementations** are available in [`iroh/examples/echo.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/echo.rs) and test utilities in [`iroh/tests/patchbay/util.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/tests/patchbay/util.rs).

## Frequently Asked Questions

### What is ALPN and why does iroh use it?

Application-Layer Protocol Negotiation (ALPN) is a TLS extension that allows peers to negotiate application protocols during the handshake phase. iroh uses ALPN to route incoming QUIC connections to the correct handler without requiring additional round-trips or custom framing, enabling efficient multiplexing of different protocols over a single endpoint.

### How do I choose an ALPN identifier for my custom protocol?

Choose a unique byte string that follows the convention `b"/vendor/protocol/major_version"`, such as `b"/mycompany/filesync/1"`. The identifier must be unique within your application to prevent collisions. Static byte strings like `b"/iroh/echo/1"` are commonly used in iroh examples and test utilities found in [`iroh/tests/patchbay/util.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/tests/patchbay/util.rs).

### Can a single iroh endpoint handle multiple protocols simultaneously?

Yes. The `Router` supports multiple handlers through repeated calls to `RouterBuilder::accept` with different ALPN identifiers. Each incoming connection is routed to the appropriate handler based on the ALPN negotiated during the TLS handshake, allowing a single node to serve file transfers, control commands, and metrics endpoints concurrently.

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

The iroh repository includes a full echo client and server implementation in [`iroh/examples/echo.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/echo.rs). This file demonstrates both the server-side `ProtocolHandler` implementation and the client-side connection logic, providing a complete reference for implementing custom protocol handlers using ALPN in iroh.