# How iroh Handles Connection Migration from Relay to Direct Connections

> Discover how iroh enables seamless connection migration from relay to direct paths using QUIC's address migration and latency-biased selection, ensuring uninterrupted data flow.

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

---

**iroh leverages QUIC's native address migration alongside a latency-biased path selector to seamlessly transition connections between relay servers and direct peer-to-peer paths without interrupting the logical connection or dropping application data.**

iroh is a peer-to-peer networking library built on QUIC (via the **quinn** crate) that enables resilient connections across NATs and firewalls. Understanding how iroh handles connection migration from relay to direct connections is essential for building distributed applications that automatically adapt to changing network topologies and optimize for latency.

## Enabling QUIC Address Migration

At the transport layer, iroh explicitly enables QUIC's connection migration feature to allow source addresses to change after the initial handshake. This is configured in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs) by setting `server_handshake_migration(true)` on the transport configuration.

```rust
// iroh/src/socket.rs – enabling migration for the endpoint
let mut transport_config = TransportConfig::default();
transport_config.server_handshake_migration(true);  // Allow address changes post-handshake

```

This single configuration change tells the underlying QUIC stack to accept packets from new source addresses for existing connection IDs, preventing connection teardown when the network path changes.

## Multi-Path Management with RemoteMap

iroh maintains awareness of all potential network paths through 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)). This component tracks every reachable address for a peer, including:

- Direct UDP addresses discovered via NAT hole-punching
- Relay server addresses allocated for the connection
- Historic addresses from previous successful connections

The `RemoteMap` exposes these addresses through the `MappedAddrs` trait, allowing the system to simultaneously maintain multiple candidate paths for a single logical connection.

## Latency-Biased Path Selection

Path selection is handled by `BiasedRttPathSelector` (implemented 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)), which continuously measures round-trip time (RTT) across all available paths. The selector prefers the path with the lowest RTT while keeping alternative paths as hot standbys.

```rust
// iroh/src/socket.rs – configuring the default path selector
use std::sync::Arc;
use iroh::socket::BiasedRttPathSelector;

let path_selector = Arc::new(BiasedRttPathSelector::default());

```

When `BiasedRttPathSelector` detects that the current direct path has significantly lower latency than the relay path (typically after successful NAT hole-punching), it signals the QUIC stack to migrate the connection. Conversely, if a direct path degrades or becomes unreachable, the selector triggers an automatic fallback to the relay address.

## Automatic Failover and Recovery

The migration process operates transparently to the application layer:

1. **Initial connection** often establishes over a relay server when direct connectivity is blocked by NAT or firewall rules
2. **Path probing** occurs continuously in the background, measuring RTT on direct addresses discovered via hole-punching
3. **Migration trigger** fires when a direct path's RTT drops below the relay path threshold for a sustained period
4. **Seamless switch** moves the QUIC connection to the direct path without dropping streams or requiring re-authentication
5. **Failover protection** ensures that if the direct path later fails, the connection automatically migrates back to the relay path

This mechanism ensures that iroh connections are resilient to network changes, temporarily switching to relays when direct paths fail, and returning to optimal direct paths when they become available.

## Implementing Connection Migration

To leverage connection migration in your application, configure the endpoint with migration enabled and the biased RTT selector:

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

// Configure transport to allow address migration
let mut transport = TransportConfig::default();
transport.server_handshake_migration(true);

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

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

```

You can inspect which path type is currently active using the `MappedAddrs` trait:

```rust
let conn = endpoint.connect(peer_id, None).await?;
println!("Active address: {}", conn.active_addr());
// Outputs either a direct IP or relay server address depending on current path

```

For testing purposes, you can simulate network changes to force migration between paths, though in production this happens automatically based on the selector's RTT measurements.

## Summary

- **QUIC 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 source address changes without connection teardown
- **Path tracking** occurs through `RemoteMap` in [`iroh/src/socket/remote_map.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map.rs), which maintains all direct and relay addresses for each peer
- **Smart selection** is performed by `BiasedRttPathSelector`, which continuously monitors RTT and migrates to the lowest-latency available path
- **Zero-downtime failover** ensures connections survive network transitions, automatically moving between relay and direct paths as network conditions change
- **Application transparency** means the migration happens at the transport layer; streams and application state persist through path changes

## Frequently Asked Questions

### Does connection migration drop in-flight packets?

No. QUIC's address migration includes path validation and packet number spaces that ensure in-flight packets are not lost during the transition. The `BiasedRttPathSelector` only migrates after confirming path viability through active probing, ensuring seamless continuity.

### How quickly does iroh switch from relay to direct connections?

The switch typically occurs within seconds of the direct path becoming viable. The `BiasedRttPathSelector` measures RTT continuously and triggers migration once the direct path demonstrates consistently lower latency than the relay path, usually after successful NAT hole-punching completes.

### Can I disable automatic migration in iroh?

Yes. While migration is enabled by default in the transport configuration, you can create a custom `TransportConfig` without `server_handshake_migration(true)` or implement a custom path selector that pins connections to specific addresses regardless of RTT measurements.

### Does this work with symmetric NATs and strict firewalls?

Yes. iroh's architecture handles symmetric NATs by maintaining the relay path as a persistent fallback. Even when direct hole-punching fails or is blocked by firewall rules, the connection remains functional over the relay. Migration only occurs to direct paths when they are actually reachable, ensuring the connection never breaks due to failed migration attempts.