How iroh Handles Direct Connection Failures: Automatic Relay Fallback Explained

When direct peer-to-peer connections fail in iroh, the Endpoint automatically falls back to relay servers after a 10-second timeout, maintaining encrypted end-to-end connections through intermediary relays without requiring manual configuration.

The n0-computer/iroh library implements robust NAT traversal by automatically switching to relay infrastructure when direct paths are unavailable. This mechanism ensures that applications remain connected even when peers are behind symmetric NATs, firewalled networks, or when address information has become stale.

How Direct Connections Fail in iroh

When an iroh Endpoint initiates a connection to a remote peer, it first attempts to establish a direct path using known IP addresses. These direct attempts can fail when:

  • Symmetric NATs prevent direct hole punching
  • Firewall rules block incoming UDP packets
  • Stale address information points to outdated IP addresses
  • Network partitions temporarily isolate the peer

The failure detection happens transparently inside the connection logic, with the implementation attempting all available direct addresses before considering fallback options.

The Automatic Relay Fallback Mechanism

The fallback process is handled entirely within the Endpoint implementation, following a specific resolution and timeout sequence.

Address Resolution and the 10-Second Timeout

Connection attempts begin in Endpoint::connect_with_opts, which calls self.inner.resolve_remote(endpoint_addr).await to determine available paths. According to the source code in iroh/src/endpoint.rs (lines 1127-1135), the resolver examines the EndpointAddr and implements the following logic:

  • If the address contains direct IPs, they are tried first
  • If the direct IP list is empty or all attempts time out, the resolver queries configured Address Lookup services (DNS, PKARR) for additional addresses

The direct connection attempt is subject to a 10-second timeout. As noted in the source comments within iroh/src/endpoint.rs: "Start connecting via noq. This will time out after 10 seconds if no reachable address is available." When this timeout expires without establishing a reachable direct address, the fallback logic immediately proceeds to the next available address type.

MappedAddr and Path Selection

The resolver returns a MappedAddr structure, defined in iroh/src/socket/remote_map.rs, which contains the chosen private socket address via private_socket_addr(). This abstraction is critical because:

  • A MappedAddr can represent either a direct IP address or a relay URL
  • The consuming code does not need to differentiate between direct and relayed paths
  • The selection happens automatically based on availability

NOQ Integration and QUIC Streaming

Once the MappedAddr is determined, the endpoint builds a QUIC client configuration and passes the destination address to NOQ (the internal QUIC implementation). If the MappedAddr resolves to a relay address, NOQ automatically opens a QUIC stream to the relay server rather than attempting a direct peer-to-peer socket connection.

Configuring Relay Maps and Modes

The set of available relays is controlled by a RelayMap, which determines which relay infrastructure the endpoint may use when direct connections fail.

Default Public Relays

By default, the endpoint builder configures the "number 0" public relays using RelayMode::Default. This provides immediate connectivity without configuration:

let ep = iroh::Endpoint::builder(iroh::endpoint::presets::N0)
    .relay_mode(iroh::endpoint::RelayMode::Default)
    .bind()
    .await?;

Custom Relay Deployments

For private deployments or testing scenarios, you can replace the default map with custom relay URLs via Builder::relay_mode (implemented in iroh/src/endpoint.rs at lines 558-571):

let custom_map = {
    let url: iroh::RelayUrl = "https://my-relay.example".parse()?;
    iroh::RelayConfig::new(url, iroh::QuicTransportConfig::default()).into()
};

let ep = iroh::Endpoint::builder(presets::N0)
    .relay_mode(iroh::RelayMode::Custom(custom_map))
    .bind()
    .await?;

When the remote's direct IPs are unreachable, the connection automatically uses the relay defined in your custom map.

Detecting Relayed Connections

After establishing a connection, you can inspect whether the traffic is being relayed. The Connection type exposes an is_relay() method that returns true when packets are flowing through a relay server:

let conn = ep.connect(remote_addr, b"my-alpn").await?;
println!("Is relayed? {}", conn.is_relay());

For testing purposes, the iroh::tests::util::is_relayed helper provides additional verification capabilities. The test suite in iroh/tests/patchbay.rs (around line 188) demonstrates how connections can start relayed and potentially recover to direct mode, or remain relayed depending on network conditions.

Summary

  • Automatic fallback: When direct IP addresses are unreachable, iroh automatically switches to relay paths after a 10-second timeout
  • Transparent abstraction: The MappedAddr type handles both direct and relay addresses uniformly, requiring no code changes to support fallback
  • End-to-end encryption: Relayed connections remain encrypted; relay servers only forward packets without accessing content
  • Configurable infrastructure: Use RelayMode::Default for public relays or RelayMode::Custom for private relay deployments
  • Runtime detection: Query conn.is_relay() to determine if an active connection is using relay infrastructure

Frequently Asked Questions

How long does iroh wait before falling back to a relay?

Iroh implements a 10-second timeout for direct connection attempts. This duration is hardcoded in the connection logic within iroh/src/endpoint.rs. If no reachable direct address is available within this window, the endpoint immediately attempts the next available address, which will be a relay URL from the active RelayMap.

Is traffic still encrypted when using iroh relay servers?

Yes. When an iroh connection uses relay fallback, the traffic remains end-to-end encrypted between the peers. The relay server acts as a packet forwarder and cannot decrypt the content. The encryption is handled by the QUIC layer within NOQ, ensuring that relay operators cannot inspect connection data.

Can I use custom relay servers instead of the default public ones?

Yes. You can configure custom relays using the Builder::relay_mode method with RelayMode::Custom. Pass a RelayMap containing your custom relay URLs to override the default public relay infrastructure. This is essential for private deployments or when compliance requirements prohibit using public relay services.

How can I check if an active connection is relayed?

Use the is_relay() method on the Connection object. This returns a boolean indicating whether the connection is currently traversing a relay server. For example: if conn.is_relay() { println!("Using relay"); }. Additionally, the test utilities in iroh::tests::util provide helper functions for verifying relayed status in test scenarios.

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 →