How Iroh Uses ALPN for Protocol Routing in QUIC Connections
Iroh leverages Application-Layer Protocol Negotiation (ALPN) during the QUIC/TLS handshake to route connections to the correct protocol handler, using the negotiated ALPN identifier as a lookup key in the router's handler registry.
Iroh, the open-source distributed systems toolkit by n0-computer, implements ALPN-based protocol routing to decouple transport layer concerns from application logic. By encoding protocol selection in the TLS handshake itself, Iroh eliminates the need for manual protocol negotiation after connection establishment. This architecture enables a single endpoint to multiplex multiple protocols—from built-in diagnostics to custom application protocols—while maintaining clean separation between transport and application code.
Understanding ALPN in Iroh's Architecture
Application-Layer Protocol Negotiation (ALPN) is a TLS extension that allows peers to agree on an application protocol during the initial handshake. In Iroh's implementation, built on QUIC, the ALPN identifier functions as a routing key that determines which ProtocolHandler will process the connection. When a client connects, it advertises one or more ALPN identifiers as a Vec<u8>, and the server uses this value to dispatch the connection to the appropriate handler.
How Iroh Implements ALPN Routing
Extracting ALPN from the TLS Handshake
The routing process begins when the server accepts a QUIC connection. In iroh/src/endpoint/connection.rs, the extract_alpn function retrieves the negotiated ALPN identifier from the TLS handshake metadata. This byte slice represents the protocol that both parties have agreed upon.
If the extracted ALPN does not match any registered handler, the connection is immediately rejected with a warning log: warn!("Ignoring connection: unsupported ALPN protocol"). This early rejection prevents resource allocation for unsupported protocols before any application data is exchanged.
Registering Protocol Handlers
The router system, defined in iroh/src/protocol.rs, maintains a mapping between ALPN identifiers and protocol implementations. You construct a router using Router::builder(endpoint) and register handlers via the accept method, which associates an ALPN byte slice with a concrete type implementing the ProtocolHandler trait.
const ECHO_ALPN: &[u8] = b"/iroh/echo/1";
let router = Router::builder(ep.clone())
.accept(ECHO_ALPN, Echo) // Registers Echo handler for this ALPN
.spawn();
The accept method stores this mapping in the router's internal registry. When a connection arrives, the router compares the negotiated ALPN against its registry and dispatches the connection to the matching handler.
Configuring ALPN on the Endpoint
The underlying endpoint configuration in iroh/src/endpoint.rs provides methods to set advertised ALPNs. You can configure supported protocols via Builder::alpns during endpoint construction or dynamically using Endpoint::set_alpns. These methods update the QUIC configuration that iroh/src/socket.rs uses when creating TLS client and server configs.
The server configuration specifically uses static_config.create_server_config(vec![ALPN.to_vec()]) to establish the list of acceptable protocols for the TLS handshake.
Practical ALPN Routing Implementation
Client-Side Connection Setup
When initiating a connection, the client specifies which ALPN it expects the server to support. The Endpoint::connect_with_opts method creates a QUIC client configuration that includes the target ALPN in the handshake.
// Define a custom protocol identifier
const MY_PROTO_ALPN: &[u8] = b"/iroh/myproto/1";
// Build an endpoint that advertises this ALPN when connecting
let endpoint = Endpoint::builder()
.alpns(vec![MY_PROTO_ALPN.to_vec()]) // Advertised on client side
.bind(([0, 0, 0, 0], 0))?; // Listen on an OS-chosen port
// Register a handler for incoming connections using the same ALPN
let router = Router::builder(endpoint.clone())
.accept(MY_PROTO_ALPN, MyProtocolHandler)
.spawn();
// Client connects, specifying the ALPN it expects the server to support
let conn = endpoint.connect(server_addr, MY_PROTO_ALPN).await?;
Implementing a Protocol Handler
Protocol handlers implement the ProtocolHandler trait and process connections that match their registered ALPN. Inside the handler, you can inspect the negotiated ALPN if needed, though the router has already validated the match.
impl ProtocolHandler for MyProtocolHandler {
async fn handle(&self, mut conn: Connection) -> anyhow::Result<()> {
// The connection's negotiated ALPN can be inspected if needed
let alpn = conn.alpn();
println!("Negotiated ALPN: {}", String::from_utf8_lossy(&alpn));
// Handle protocol-specific messages
Ok(())
}
}
Handling Multiple ALPNs and Fallback Behavior
Iroh supports connections that advertise multiple ALPN identifiers simultaneously. The router accepts the connection if any of the advertised identifiers matches a registered handler. This design enables graceful protocol upgrades and backward compatibility—you can register handlers for both legacy and new protocol versions, and clients can advertise support for multiple versions during the handshake.
The implementation in iroh/tests/integration.rs demonstrates this behavior with the Echo protocol test suite, verifying that connections succeed when the server has a matching handler registered and fail appropriately when the ALPN is not accepted.
Summary
- ALPN as routing key: Iroh uses the ALPN identifier negotiated during the QUIC/TLS handshake to determine which protocol handler should process a connection.
- Early rejection: The
extract_alpnfunction iniroh/src/endpoint/connection.rsenables immediate connection rejection if no handler matches the negotiated protocol. - Handler registration: The
Router::builderpattern iniroh/src/protocol.rsallows associating ALPN byte slices with specificProtocolHandlerimplementations via theacceptmethod. - Client configuration: Endpoints advertise supported ALPNs through
Builder::alpnsorEndpoint::set_alpns, ensuring the TLS handshake includes the correct protocol identifiers. - Multi-ALPN support: Clients can advertise multiple protocols, and servers accept connections if any advertised ALPN matches a registered handler, facilitating protocol versioning.
Frequently Asked Questions
What happens if a client connects with an unsupported ALPN?
If the ALPN extracted from the TLS handshake does not match any registered handler in the router, Iroh logs a warning ("Ignoring connection: unsupported ALPN protocol") and rejects the connection. This occurs before any application data is processed, preventing resource exhaustion from unsupported protocol requests.
Can a single Iroh endpoint support multiple protocols simultaneously?
Yes. A single endpoint can support multiple protocols by registering multiple handlers with distinct ALPN identifiers using Router::builder. The endpoint advertising these ALPNs via the configuration in iroh/src/endpoint.rs will route incoming connections to the appropriate handler based on the negotiated ALPN.
How does ALPN negotiation differ from manual protocol switching?
ALPN negotiation happens during the TLS handshake in iroh/src/socket.rs, before the connection is fully established. This eliminates the need for application-layer protocol detection or version negotiation after connection setup, reducing latency and complexity compared to manual switching mechanisms that require initial bytes to be exchanged.
Where is the ALPN identifier stored after the handshake completes?
The negotiated ALPN identifier is stored within the QUIC connection metadata and can be accessed via the Connection::alpn() method. This value is extracted in iroh/src/endpoint/connection.rs and remains available throughout the connection lifetime for inspection by protocol handlers if needed.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →