# How iroh Handles Network Path Migration Between Relay and Direct Connections

> Discover how iroh masters network path migration between relay and direct connections. Learn about its QUIC address migration and latency-biased algorithm for seamless, optimal routing.

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

---

**iroh leverages QUIC native address migration to seamlessly switch connections between relayed and direct paths without dropping the logical connection, using a latency-biased algorithm to automatically select the optimal route.**

The `n0-computer/iroh` networking layer enables robust peer-to-peer connectivity by dynamically migrating active connections between relay servers and direct UDP paths. Built on QUIC via the **noq** crate, the implementation allows a single logical connection to survive underlying network path changes without application-level reconnection logic.

## Enabling QUIC Address Migration

At the transport layer, iroh explicitly enables QUIC address migration to allow source address changes after the initial handshake. In [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs), the transport configuration sets `server_handshake_migration(true)`:

```rust
// iroh/src/socket.rs – enabling migration for the client side
transport_config.server_handshake_migration(true);

```

This configuration appears around line 2620 and instructs the QUIC stack to accept packets from new source addresses belonging to the same connection. Without this flag, the connection would terminate when the underlying path changes, forcing a full reconnection.

## Biased RTT Path Selection Algorithm

iroh uses the `BiasedRttPathSelector` to continuously evaluate available network paths. Located in [`iroh/src/socket/biased_rtt_path_selector.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/biased_rtt_path_selector.rs), this selector measures round-trip time (RTT) across all candidate paths and prefers the route with the lowest latency.

The selector is instantiated in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs) around line 998:

```rust
// iroh/src/socket.rs – default path selector
path_selector: Arc::new(BiasedRttPathSelector::default()),

```

The algorithm maintains fallback paths while actively using the lowest-latency option. If the current direct path exhibits high RTT or packet loss, the selector triggers a migration to an alternate address—such as a relay stream—without interrupting the application-level connection.

## Managing Multiple Network Paths in RemoteMap

The `RemoteMap` structure, defined in [`iroh/src/socket/remote_map.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map.rs), tracks every reachable address for a peer. This includes direct IP addresses discovered through NAT hole-punching and relay-server addresses. The endpoint aggregates these into `MappedAddrs`, exposing the active path through methods like `active_addr()`.

When multiple paths exist, iroh keeps them warm as potential migration targets. The connection maintains state across all viable paths, allowing instantaneous switching when the path selector determines a better route is available.

## Automatic Failover and Path Recovery

When network conditions degrade, iroh automatically fails over to alternate paths. If packets stop arriving on the current path or RTT increases significantly, the `BiasedRttPathSelector` removes that path from the active set. The QUIC stack then initiates migration to the next best address, re-binding the connection to a new socket without tearing down the logical connection.

Conversely, when a direct path becomes viable—for example, after successful NAT traversal—the selector detects the improved RTT and migrates the connection back from the relay to the direct address. This bi-directional migration ensures optimal performance while maintaining connectivity.

## Implementation Examples

Configure an endpoint with migration enabled and the biased RTT selector:

```rust
use iroh::endpoint::{Endpoint, Options};
use iroh::socket::BiasedRttPathSelector;
use std::sync::Arc;

// Build the endpoint with migration enabled
let mut transport = iroh::socket::TransportConfig::default();
transport.server_handshake_migration(true); // Allow address migration

let opts = Options {
    transports: vec![transport],
    path_selector: Arc::new(BiasedRttPathSelector::default()), // Select best path
    ..Default::default()
};

let endpoint = Endpoint::bind(opts).await?;

```

Inspect the currently active path to determine if traffic flows directly or through a relay:

```rust
let conn = endpoint.connect(peer_id, None).await?;
// MappedAddrs trait exposes the active transport address
println!("Active address: {}", conn.active_addr());

```

For testing purposes, you can force a network change to trigger migration logic:

```rust
// Simulate network change to trigger QUIC migration to next best path
conn.force_network_change(true).await;

```

## Summary

- **QUIC address migration** is enabled via `server_handshake_migration(true)` in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs), allowing connections to survive IP address changes after the handshake.
- **Biased RTT Path Selection** continuously measures latency across available routes via `BiasedRttPathSelector`, preferring direct connections when viable while maintaining relay fallbacks.
- **RemoteMap** in [`iroh/src/socket/remote_map.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map.rs) stores all discovered addresses for a peer, enabling rapid switching between direct and relayed paths.
- **Automatic failover** occurs when path quality degrades, seamlessly migrating to alternative routes without application interruption.
- The implementation spans [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs), [`iroh/src/socket/biased_rtt_path_selector.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/biased_rtt_path_selector.rs), and [`iroh/src/socket/transports.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports.rs).

## Frequently Asked Questions

### How does iroh decide when to switch from relay to direct connections?

The `BiasedRttPathSelector` continuously monitors round-trip time on all available paths. When a direct path demonstrates lower latency than the current relay connection, the selector triggers a QUIC migration to the direct address. This typically occurs after successful NAT hole-punching establishes a viable direct route.

### Can iroh maintain connections when switching between Wi-Fi and cellular networks?

Yes. Because iroh enables QUIC address migration via `server_handshake_migration(true)`, the connection can re-bind to new local addresses—such as when switching from Wi-Fi to cellular—without terminating the logical connection. The path selector treats these as standard path migrations and selects the optimal route based on RTT metrics.

### What happens if the direct path fails after migration?

If the active direct path fails or RTT degrades significantly, the `BiasedRttPathSelector` automatically removes that path from the active set and migrates the connection back to a relayed path or alternative direct address. This failover occurs at the QUIC layer without requiring application-level reconnection logic, as maintained in [`iroh/src/socket/remote_map.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map.rs).

### Where is the path selection logic implemented in the source code?

The core path selection algorithm resides in [`iroh/src/socket/biased_rtt_path_selector.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/biased_rtt_path_selector.rs). The integration with the socket layer occurs in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs) around line 998, where the selector is instantiated. Transport configuration enabling migration appears in [`iroh/src/socket/transports.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports.rs) and is applied in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs) near line 2620.