# How Does Iroh Handle NAT Traversal: Hole-Punching, Relay Fallback, and the RemoteState Machine

> Discover how Iroh achieves NAT traversal through hole-punching and relay fallback. Learn about the RemoteState actor enabling direct peer-to-peer connections.

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

---

**Iroh handles NAT traversal by coordinating address discovery, UDP/QUIC hole-punching attempts, and automatic relay fallback through the RemoteState actor, enabling direct peer-to-peer connections even when both nodes are behind symmetric NATs.**

NAT traversal is a fundamental challenge in peer-to-peer networking that prevents direct communication between nodes behind routers. The n0-computer/iroh repository solves this through a sophisticated networking stack that automatically discovers paths, attempts to punch holes through NATs, and falls back to relay servers when necessary. Understanding how iroh handles NAT traversal reveals the architectural decisions that allow applications to ignore network complexity entirely.

## The Three-Stage NAT Traversal Architecture

Iroh's networking stack implements a coordinated three-phase approach to establishing connectivity across network boundaries.

### Address Discovery and Candidate Collection

Before attempting traversal, each endpoint gathers a list of potential addresses. These candidates include local interface addresses, public addresses discovered via STUN, and relay URLs obtained from the configuration in [[`iroh-base/src/relay_url.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-base/src/relay_url.rs)](https://github.com/n0-computer/iroh/blob/main/iroh-base/src/relay_url.rs). The endpoint advertises these candidates to remote peers, forming the initial set of paths to attempt.

### Direct Path Negotiation via Hole-Punching

When a peer wants to connect, the system attempts to establish a direct UDP/QUIC path. This process is orchestrated by the `RemoteState` struct in [[`iroh/src/socket/remote_map/remote_state.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map/remote_state.rs)](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map/remote_state.rs), which maintains:

```rust
/// Information about the last holepunching attempt.
last_holepunch: Option<HolepunchAttempt>,
/// When the next holepunch should be tried.
scheduled_holepunch: Option<Instant>,

```

The `trigger_holepunching()` method determines whether to initiate a new hole-punch attempt based on whether candidates have changed or sufficient time has passed since the last attempt. When triggered, the system sends simultaneous UDP/QUIC packets from local candidates to remote candidates to open NAT mappings.

### Relay Fallback for Blocked Paths

If direct hole-punching fails—such as when both peers are behind symmetric NATs with aggressive filtering—the system falls back to the relay transport implemented in [[`iroh/src/socket/transports/relay.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/relay.rs)](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/relay.rs). This TURN-style relay server forwards packets between peers, maintaining connectivity even when direct paths are impossible. The fallback is transparent to the application layer.

## Core Implementation: The RemoteState Machine

The heart of iroh's NAT traversal logic resides in the remote-map module, which tracks the state of each remote endpoint.

### Triggering Hole-Punch Attempts

The `trigger_holepunching` method in [`remote_state.rs`](https://github.com/n0-computer/iroh/blob/main/remote_state.rs) contains the logic for deciding when to punch:

```rust
fn trigger_holepunching(&mut self) {
    // … several early‑exit checks …
    if let Some(ref last_hp) = self.state.last_holepunch {
        // Avoid hammering the remote if we already succeeded recently.
        if !self.candidates_changed && last_hp.succeeded_recently() {
            return;
        }
    }
    // Choose the most promising candidate pair and start a hole‑punch.
    self.initiate_holepunch(candidate_pair);
}

```

This method is invoked whenever new addresses are discovered or when scheduled timers fire, ensuring the system continuously attempts to find direct paths as network conditions change.

### Path State Management and Pruning

Not all candidates remain viable. The [[`iroh/src/socket/remote_map/remote_state/path_state.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map/remote_state/path_state.rs)](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map/remote_state/path_state.rs) file implements exponential back-off and pruning logic for failed attempts. When a hole-punch fails, the path state tracks the failure and prevents immediate retry attempts, avoiding network congestion and unnecessary CPU usage.

## Transport Layer Integration

The QUIC transport layer in [[`iroh/src/endpoint/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/quic.rs)](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/quic.rs) carries the actual hole-punched packets. Once `RemoteState` confirms a working path, the QUIC endpoint migrates to the new address seamlessly.

## Testing NAT Traversal Robustness

Iroh validates its NAT traversal implementation through comprehensive testing in [[`iroh/tests/patchbay/nat.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/tests/patchbay/nat.rs)](https://github.com/n0-computer/iroh/blob/main/iroh/tests/patchbay/nat.rs). This test harness simulates various NAT behaviors—including home routers and corporate symmetric NATs—to verify that hole-punching succeeds when topologically possible and falls back to relays otherwise.

## Implementation Example: Connecting Through NAT

You can leverage iroh's NAT traversal with minimal configuration. The following Rust example demonstrates creating a client that automatically handles NAT discovery and hole-punching:

```rust
use iroh::client::Client;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Create a client with the default configuration.
    // The client automatically starts the networking stack,
    // which includes NAT discovery, hole‑punching, and relay fallback.
    let client = Client::new_default().await?;

    // Connect to a remote peer (identified by its public key).
    // No extra configuration is required – the underlying
    // endpoint will perform NAT traversal as needed.
    let remote_key = "<remote‑public‑key>";
    let mut conn = client.connect(remote_key).await?;

    // Once the connection is established, we can send data.
    conn.send(b"hello from behind NAT").await?;
    Ok(())
}

```

Alternatively, using the iroh CLI:

```bash

# Start an iroh node (it automatically runs NAT discovery).

iroh --listen 0.0.0.0:0

# In another shell (perhaps on a different network),

# sync a directory with the first node – the library will

# hole‑punch or use the relay transparently.

iroh sync ./my_folder --remote <first‑node‑peer‑id>

```

Both approaches rely on the default transport and require **no manual NAT configuration**.

## Summary

- **Automatic Discovery**: Iroh gathers local, public (STUN), and relay addresses as connection candidates.
- **State Machine Driven**: The `RemoteState` actor in [`remote_state.rs`](https://github.com/n0-computer/iroh/blob/main/remote_state.rs) coordinates hole-punch attempts using `trigger_holepunching()` and tracks results via `last_holepunch`.
- **Intelligent Pruning**: Failed candidates are managed by [`path_state.rs`](https://github.com/n0-computer/iroh/blob/main/path_state.rs) with exponential back-off to prevent redundant attempts.
- **Seamless Fallback**: When direct paths fail, the relay transport in [`relay.rs`](https://github.com/n0-computer/iroh/blob/main/relay.rs) provides TURN-style packet forwarding.
- **Zero Configuration**: Applications connect via standard APIs while the library handles all NAT complexity internally.

## Frequently Asked Questions

### What is hole-punching in the context of iroh?

Hole-punching is a technique where both peers simultaneously send UDP packets to each other's public addresses to open NAT mappings. In iroh, this is coordinated by the `RemoteState` struct, which triggers attempts via `trigger_holepunching()` when new candidates are discovered or timers expire.

### When does iroh use a relay instead of a direct connection?

Iroh falls back to a relay when the `RemoteState` determines that no direct candidate pair can establish connectivity, typically when both peers are behind symmetric NATs that block incoming packets. The relay transport then forwards traffic transparently until a direct path might become available later.

### How does iroh handle symmetric NAT traversal?

Symmetric NATs assign unique external ports for each destination, making hole-punching difficult. Iroh attempts coordination through simultaneous open packets, but if this fails—as tracked in [`path_state.rs`](https://github.com/n0-computer/iroh/blob/main/path_state.rs) with exponential back-off—it immediately transitions to the relay fallback without requiring application intervention.

### Is manual configuration required for NAT traversal in iroh?

No. The library automatically performs address discovery, hole-punching, and relay selection using default configurations from [`iroh-base/src/relay_url.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-base/src/relay_url.rs). Developers only need to call `Client::new_default()` or use the CLI, and the stack handles all NAT traversal complexity internally.