How iroh ALPN Protocol Negotiation Works: Endpoint Configuration to Protocol Dispatch
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. 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:
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 (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). 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 (lines 270‑304). The resulting protocol string is exposed through ConnectionInfo::alpn and accessible on the connection object:
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 (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.
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:
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:
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::alpnsiniroh/src/endpoint.rsand specify primary protocols viaEndpoint::connect_with_opts. - TLS Integration: iroh embeds ALPN vectors into QUIC TLS configurations in
iroh/src/socket.rsusingset_alpn_protocolsfor both client and server handshakes. - Negotiation Logic: The first common ALPN identifier is selected automatically by the QUIC library and extracted via
extract_alpniniroh/src/endpoint/connection.rs. - Connection Routing: The Router API in
iroh/src/protocol.rsdispatches connections to registered handlers based on the negotiated ALPN string retrieved fromConnectionInfo::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 (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 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.
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 →