# How Iroh Handles NAT Traversal for Peer-to-Peer Connections

> Discover how Iroh tackles NAT traversal for p2p connections. Learn about address discovery, UDP hole-punching, and relay fallbacks in this detailed guide.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: how-to-guide
- Published: 2026-06-25

---

**Iroh establishes peer-to-peer connections through NATs using a three-stage process: address discovery, UDP hole-punching orchestrated by the `RemoteState` actor, and a relay fallback when direct paths fail.**

Iroh is an open-source peer-to-peer networking stack written in Rust that eliminates the complexity of NAT traversal for application developers. The library automatically discovers public addresses, attempts simultaneous hole-punching, and transparently falls back to relay servers when needed, all implemented in the `iroh/src/socket/remote_map/` module.

## The Three-Stage NAT Traversal Process

Iroh's approach to NAT traversal follows a deterministic progression from direct connection to relayed fallback:

1. **Address Discovery** – Each endpoint gathers local and public (STUN-discovered) addresses through the QUIC transport layer.
2. **Direct Path Attempt** – The `RemoteState` actor triggers hole-punching attempts between candidate address pairs.
3. **Relay Fallback** – When hole-punching fails, traffic routes through the iroh-relay server using TURN-style forwarding.

## Managing Candidate Addresses with RemoteState

The core of Iroh's NAT traversal logic resides 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). This file implements the `RemoteState` struct, which maintains a collection of candidate addresses for each remote peer and tracks hole-punching outcomes.

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

```

When the endpoint learns new addresses—whether local interface updates or STUN-discovered public addresses—the state machine evaluates whether to initiate a new hole-punch attempt.

## Triggering Hole-Punching Attempts

The `trigger_holepunching()` method in [`remote_state.rs`](https://github.com/n0-computer/iroh/blob/main/remote_state.rs) implements the decision logic for starting hole-punch attempts:

```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 function triggers in three scenarios:
- Local addresses update (new network interfaces)
- Remote addresses update (new candidates discovered via STUN or relay)
- Scheduled timer fires for retry attempts

## Path State Management and Pruning

Not all candidates succeed. Iroh tracks per-path success rates in [[`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). This module implements exponential back-off logic to prevent repeatedly attempting dead paths while ensuring responsive retry intervals for promising candidates.

When a hole-punch attempt fails, the `PathState` marks the candidate as failed and schedules it for pruning if subsequent attempts also fail. Successful attempts mark the path as valid, allowing immediate use for QUIC connections.

## Relay Transport Fallback

When symmetric NATs block all direct candidates, Iroh 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 server forwards packets between peers.

The relay URL configuration resides 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 `RemoteState` automatically injects the relay address into the candidate list, ensuring seamless fallback without application intervention.

## Testing NAT Traversal Across Network Types

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 Router and Corporate NAT configurations—to verify that hole-punching succeeds when topologically possible and that relay fallback activates reliably when direct paths are impossible.

## Practical Implementation

Using Iroh's NAT traversal requires no manual configuration. The following Rust example demonstrates creating a client that automatically handles NAT traversal:

```rust
use iroh::client::Client;
use iroh::endpoint::{self, Endpoint};

#[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(())
}

```

The same automatic NAT traversal works via the 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 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) to carry hole-punched packets.

## Summary

- **Address Discovery**: Iroh automatically gathers local and STUN-discovered public addresses for each endpoint.
- **Hole-Punching Orchestration**: The `RemoteState` actor in [`remote_state.rs`](https://github.com/n0-computer/iroh/blob/main/remote_state.rs) manages candidate pairs and triggers simultaneous UDP hole-punch attempts.
- **Intelligent Pruning**: `PathState` implements exponential back-off to avoid retrying failed paths indefinitely while prioritizing successful candidates.
- **Seamless Fallback**: When direct paths fail, the relay transport automatically forwards traffic without application-level changes.
- **Zero Configuration**: Applications use standard connection APIs while Iroh handles all NAT complexity internally.

## Frequently Asked Questions

### How does Iroh decide when to attempt hole-punching?

Iroh triggers hole-punching via the `trigger_holepunching()` method whenever new local or remote addresses appear, or when a scheduled retry timer fires. The method checks whether recent attempts succeeded and whether candidates have changed before initiating a new hole-punch, preventing unnecessary network traffic.

### What happens if both peers are behind symmetric NATs?

When symmetric NATs prevent direct UDP connectivity, Iroh falls back to the relay transport. The relay server—configured via [`iroh-base/src/relay_url.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-base/src/relay_url.rs)—forwards packets between peers using a TURN-style protocol, maintaining connectivity despite restrictive firewall rules.

### Does Iroh require manual STUN server configuration?

No, Iroh includes default STUN and relay configuration. The `Client::new_default()` constructor automatically initializes the networking stack with discovery services, requiring no manual NAT configuration from developers.

### How does Iroh prevent repeatedly attempting dead paths?

The `PathState` module in [`remote_state/path_state.rs`](https://github.com/n0-computer/iroh/blob/main/remote_state/path_state.rs) tracks per-candidate success and failure rates. Failed candidates are subject to exponential back-off and eventual pruning, while the `RemoteState` prioritizes recently successful paths for subsequent connection attempts.