# How Iroh Handles Network Transitions: WiFi to Cellular Handoff Explained

> Discover how Iroh seamlessly handles WiFi to cellular transitions using platform-native watchers and asynchronous network reports for uninterrupted connectivity.

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

---

**Iroh automatically adapts to network interface changes by monitoring system state with a platform-native watcher, triggering asynchronous network reports to assess the new path, and atomically updating direct address sets while maintaining relay connectivity as a fallback.**

Iroh is a Rust-based networking stack designed for resilient peer-to-peer connectivity across volatile network conditions. When your device switches from WiFi to cellular—or any interface change occurs—the library seamlessly migrates active QUIC connections without application intervention. This article examines the internal architecture that enables these automatic network transitions based on the n0-computer/iroh source code.

## The Network Transition Pipeline

When the operating system reports a new local IP address or a removed interface, iroh executes a coordinated multi-stage pipeline to reassess connectivity paths.

### Monitoring Interface State with netwatch

The foundation of network transition handling starts with the **netwatch** subsystem. A `netwatch::Monitor` instance creates a `Watcher` that emits a fresh `netwatch::State` whenever an interface appears, disappears, or its IP addresses change. According to the source in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs) at lines 1469-1470, this watcher is stored in `Socket::local_interfaces_watcher`, ensuring the socket actor maintains a live view of the host's network topology.

### Propagating Changes via ActorMessage

Interface state changes drive an internal message passing system. The watcher emits events that translate into `ActorMessage::NetworkChange` messages, defined in the `enum ActorMessage` at lines 1319-1322 of [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs). The main `Actor::run` loop receives these messages and immediately calls `self.direct_addr_update_state.schedule_run(UpdateReason::LinkChangeMajor, if_state)`. This method schedules a fresh network report with high priority, indicating a major link change has occurred.

### Running the Network Report

The `DirectAddrUpdateState::run` method creates a `net_report::Client` and issues a `get_report` request. This probe discovers the current NAT type, identifies reachable relay servers, and determines viable direct IP routes. As implemented in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs) at lines 892-905, the report runs asynchronously and is automatically cancelled if the endpoint begins shutdown. This non-blocking design prevents network transitions from stalling the socket actor's event loop.

### Updating Direct Addresses and Remote Maps

When the net-report completes, the socket updates its `direct_addrs` via `store_direct_addresses`. If the set of usable IP addresses has changed, the socket publishes the new addresses to the address-lookup service using `publish_my_addr` and informs the `RemoteMap`. As shown in lines 511-525 of [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs), this process may open new QUIC paths to peers or close stale ones derived from the previous network interface.

## Maintaining Connectivity During Transitions

Iroh preserves application-level connectivity even when direct paths temporarily vanish during network handoffs.

### Relay Path Persistence

Even if the direct path disappears—such as when switching to cellular without an IPv4 address—the relay transport remains active. The `RelayActor`, located in [`iroh/src/socket/transports/relay/actor.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/relay/actor.rs) (lines 21-27), watches the same `local_interfaces_watcher` and automatically reconnects the WebSocket after the interface recovers. This ensures NAT traversal and peer discovery continue uninterrupted during the transition window.

### Intelligent Path Selection

The **PathSelector** (default implementation: `biased_rtt_path_selector`) receives updated RTT and availability metrics from the net-report. Based on this data, it may switch the active data path from a WiFi-derived direct address to a cellular-derived one, or fall back to the relay if the new interface cannot reach the remote directly. This logic is orchestrated in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs) at lines 1338-1341.

## Explicit Control and Testing

While iroh handles transitions automatically, the public API exposes `Endpoint::network_change()` for manual triggering. This method forces an immediate re-evaluation of network conditions, useful for testing or responding to explicit user signals. The implementation resides in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs) at lines 888-894.

## Practical Implementation

The following example demonstrates creating an endpoint that automatically handles network transitions:

```rust
use iroh::Endpoint;

// Create an endpoint (bind will start the network-watcher automatically)
let endpoint = Endpoint::builder()
    .secret_key(my_secret_key)
    .transports(vec![/* … */])
    .bind()
    .await?;

// The endpoint will automatically adapt when the OS switches from Wi-Fi to cellular.
// If you want to trigger an immediate check (e.g. in a test) you can call:
endpoint.network_change().await;

```

To observe relay changes during network transitions:

```rust
// Subscribe to the socket's home-relay watcher to know when a new relay is selected
let mut relay_watcher = endpoint.socket().home_relay();
while let Some(relays) = relay_watcher.next().await {
    println!("Current relays: {:?}", relays);
}

```

## Summary

- **Automatic Detection**: The `netwatch::Monitor` Watcher in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs) tracks interface state changes in real-time.
- **Asynchronous Probing**: Network reports run via `net_report::Client` to assess NAT type and path viability without blocking the actor.
- **Atomic Updates**: Direct addresses are refreshed through `store_direct_addresses`, with changes propagated to the `RemoteMap` and address-lookup service.
- **Continuous Connectivity**: The `RelayActor` maintains fallback paths during interface outages, while the `PathSelector` optimizes for the lowest-latency route.
- **Manual Override**: The `Endpoint::network_change()` method allows explicit re-evaluation for testing scenarios.

## Frequently Asked Questions

### Does iroh drop connections when switching from WiFi to cellular?

No, iroh maintains the logical connection. While individual QUIC paths may close when their underlying IP addresses become invalid, the `PathSelector` immediately attempts to establish new paths using the updated direct addresses. The relay connection remains active as a fallback, ensuring the session survives the transition.

### How long does a network transition take?

The transition latency depends on the net-report duration, which typically completes within milliseconds to a few seconds depending on network conditions. The `DirectAddrUpdateState` runs the report asynchronously, so application traffic experiences minimal interruption while the new path is being established.

### Can I disable automatic network adaptation?

Iroh does not provide a configuration flag to disable interface monitoring because maintaining accurate address sets is critical for QUIC path migration. However, you can control the behavior by selecting specific transports or manually triggering evaluations via `Endpoint::network_change()` if you need to synchronize transitions with application logic.

### What happens if the new network has no public IP address?

When the cellular network only provides CGNAT or IPv6 without public IPv4, the net-report will determine that direct connectivity is limited. The `PathSelector` will continue using the relay path for NAT traversal, and the `RelayActor` ensures the WebSocket connection to the relay server is re-established over the new interface.