# How iroh ALPN Protocol Negotiation Works: Endpoint Configuration to Protocol Dispatch

> Discover how iroh's ALPN protocol negotiation works for TLS QUIC connections. Learn endpoint configuration, protocol advertising, and connection dispatch for seamless application integration.

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

---

**iroh uses TLS/QUIC Application-Layer Protocol Negotiation (ALPN) to select the application-level protocol during connection establishment, advertising supported protocols during endpoint configuration and dispatching connections to registered handlers based on the first matching ALPN identifier.**

The n0-computer/iroh library implements ALPN negotiation over QUIC to determine which application protocol runs on a new connection. Understanding this flow is essential for building compatible networked applications that can handle protocol versioning and multiplex different services on a single endpoint.

## Endpoint Configuration: Advertising ALPN Identifiers

Configuration begins with the **EndpointBuilder** API in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs). You define which protocols your endpoint accepts using two complementary mechanisms.

**Primary ALPN** for outbound connections is specified via `Endpoint::connect_with_opts` (lines 1786‑1799). This identifier represents the protocol you intend to speak when actively connecting to a remote peer.

**Inbound ALPN advertisement** is configured through `EndpointBuilder::alpns` (lines 529‑535), which accepts a vector of byte strings representing all protocols the endpoint understands:

```rust
let mut builder = Endpoint::builder();
builder.alpns(vec![b"/iroh/echo/1".to_vec()]); // Accepted ALPNs
let endpoint = builder.bind(addr).await?;

```

For backwards compatibility, you can advertise **additional ALPNs** beyond your primary protocol. The endpoint will accept connections from clients speaking any advertised identifier, enabling seamless protocol upgrades while supporting legacy clients.

## TLS/QUIC Handshake and Protocol Advertisement

During the QUIC handshake, iroh injects the configured ALPN vectors into the underlying TLS configuration. The library constructs `noq_proto::ClientConfig` and `ServerConfig` instances that carry these protocol lists via `set_alpn_protocols`.

Server-side configuration creation occurs in `socket::static_config.create_server_config` within [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs) (lines 2580‑2582). Here, the server’s accepted ALPN list is embedded into the TLS parameters.

On the client side, the specific ALPN for this connection is set through `quic_client_config.set_alpn_protocols` (lines 2652‑2654 in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs)). This transmits the client's supported protocols to the peer during the cryptographic handshake.

## ALPN Selection and Negotiation Result

The QUIC library automatically selects the **first common** ALPN string present in both the client’s and server’s advertised vectors. This selection happens during the cryptographic handshake before any application data flows.

After handshake completion, iroh extracts the negotiated identifier using the `extract_alpn` function in [`iroh/src/endpoint/connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/connection.rs) (lines 270‑304). The resulting protocol string is exposed through `ConnectionInfo::alpn` and accessible on the connection object:

```rust
let conn = endpoint.connect(remote_id, b"/iroh/echo/1").await?;
let negotiated = conn.alpn(); // Returns the agreed protocol identifier

```

If no common protocol exists between the peers, iroh rejects the connection with an `"unsupported ALPN protocol"` error, as implemented in [`iroh/src/protocol.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/protocol.rs) (lines 643‑648).

## Protocol Dispatch to Handlers

Once the ALPN is negotiated, iroh routes the connection to the appropriate application handler. This dispatch mechanism centers on the **Router** API in [`iroh/src/protocol.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/protocol.rs).

Protocol handlers are registered using `Router::accept(alpn, handler)` (lines 86‑92), which populates an internal `ProtocolMap`. When a new connection arrives, iroh queries this map using the negotiated ALPN (`protocol_map.get(alpn)`) to retrieve the corresponding handler:

```rust
let router = Router::builder(endpoint.clone())
    .accept(b"/iroh/echo/1", EchoHandler)
    .spawn();

```

The handler then drives the connection logic, processing streams according to the specific protocol semantics.

## Supporting Multiple Protocol Versions

Iroh endpoints can simultaneously support multiple protocol versions by advertising several ALPN identifiers. Configure this using `Endpoint::set_additional_alpns` or by passing a vector to `EndpointBuilder::alpns`:

```rust
builder.alpns(vec![
    b"/iroh/echo/1".to_vec(),
    b"/iroh/old-echo/0".to_vec(),
]);

```

During negotiation, the remote peer selects their preferred matching protocol from the advertised set. Iroh automatically dispatches to the correct handler based on whichever identifier the peer selects, allowing graceful protocol evolution without breaking existing clients.

## Summary

- **ALPN Configuration**: Set accepted protocols via `EndpointBuilder::alpns` in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) and specify primary protocols via `Endpoint::connect_with_opts`.
- **TLS Integration**: iroh embeds ALPN vectors into QUIC TLS configurations in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs) using `set_alpn_protocols` for both client and server handshakes.
- **Negotiation Logic**: The first common ALPN identifier is selected automatically by the QUIC library and extracted via `extract_alpn` in [`iroh/src/endpoint/connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/connection.rs).
- **Connection Routing**: The Router API in [`iroh/src/protocol.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/protocol.rs) dispatches connections to registered handlers based on the negotiated ALPN string retrieved from `ConnectionInfo::alpn`.
- **Versioning Support**: Endpoints can advertise multiple ALPNs to support protocol upgrades and maintain backwards compatibility with legacy clients.

## Frequently Asked Questions

### What happens if a client and server have no common ALPN?

The QUIC handshake fails and iroh returns an error with the message `"unsupported ALPN protocol"` from the protocol dispatch logic in [`iroh/src/protocol.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/protocol.rs) (lines 643‑648). The connection is rejected before any application data is exchanged, preventing protocol mismatches at the application layer.

### Can an iroh endpoint handle multiple protocols simultaneously?

Yes. Configure multiple accepted ALPNs using `EndpointBuilder::alpns` with a vector of protocol identifiers, or use `Endpoint::set_additional_alpns`. Register distinct handlers for each ALPN using `Router::accept`. The endpoint will negotiate the appropriate protocol per-connection and dispatch to the matching handler automatically.

### How do I access the negotiated ALPN after a connection is established?

Call the `.alpn()` method on the connection object, which returns the negotiated protocol identifier as a byte slice. This value is extracted during the handshake by the `extract_alpn` function in [`iroh/src/endpoint/connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/connection.rs) and stored in the connection's metadata.

### What is the difference between primary and additional ALPNs in iroh?

The **primary ALPN** is the specific protocol identifier used when initiating an outbound connection via `Endpoint::connect_with_opts`. **Additional ALPNs** are extra protocol identifiers the endpoint advertises as supported for inbound connections. This distinction allows an endpoint to prefer modern protocols when connecting outward while still accepting connections from peers using older protocol versions.