# How iroh's Hole-Punching Mechanism Works: NAT Traversal in n0-computer/iroh

> Explore how iroh's hole-punching mechanism achieves direct P2P UDP connections for NAT traversal. Learn about simultaneous probes, holepunch IDs, and fallback relays.

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

---

**iroh establishes direct peer-to-peer UDP connections between NAT-ed devices by coordinating simultaneous probe packets through a remote state machine, validating attempts via unique holepunch IDs, and promoting successful paths while falling back to relays for symmetric NATs.**

iroh is an open-source distributed systems toolkit that implements robust NAT traversal to establish low-latency direct connections between peers. At the heart of this capability lies a sophisticated hole-punching mechanism orchestrated by the remote map state machine, which automatically negotiates direct UDP paths between nodes even when both reside behind restrictive network address translators.

## The Remote State Machine: Coordinating NAT Traversal

The hole-punching process is driven by the *remote map* state machine located 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). This component manages the lifecycle of connections to remote peers, deciding when to attempt direct traversal and tracking the status of each candidate path.

### Triggering Hole-Punch Attempts

The system initiates hole-punching whenever a remote peer's address list changes—such as when a new public endpoint is discovered via STUN—or when a scheduled maintenance interval expires. The `trigger_holepunching()` function in [`remote_state.rs`](https://github.com/n0-computer/iroh/blob/main/remote_state.rs) (lines 289-314) schedules an immediate attempt, but only if no hole-punching operation is currently in progress to prevent redundant traffic and socket congestion.

### Selecting Optimal Candidates

Before punching, iroh evaluates potential connection paths using the **biased-RTT path selector** 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). The remote state maintains a collection of `NatCandidate` addresses, each annotated with a NAT type classification indicating the likelihood of successful traversal:

- **Easiest**: Destination-independent NATs with high success probability
- **Easy**: Moderate difficulty, likely to succeed with few retries
- **Hard**: Restrictive NAT types requiring aggressive parallel attempts

The selector orders these candidates by estimated round-trip time and predicted success rate, prioritizing the most promising pairs first to minimize connection latency.

## Executing the Hole-Punch

Once candidates are selected, iroh constructs a `Holepunch` struct to manage the attempt, recording metadata in a `HolepunchAttempt` structure. The actual coordination follows a synchronized dual-probe pattern:

```rust
// Conceptual structure based on iroh/src/socket/remote_map/remote_state.rs
pub enum HolepunchResult {
    Success { path_id: PathId, addr: EndpointAddr },
    Failed { attempt: HolepunchAttempt },
}

pub struct HolepunchAttempt {
    pub holepunch_id: u64, // Short-lived session identifier
    pub local_candidate: SocketAddr,
    pub remote_candidate: SocketAddr,
}

```

1. **Local Probe Transmission:** The local node sends a hole-punch probe packet from its UDP socket to the remote candidate address via the `transports` module.
2. **Remote Coordination:** Simultaneously, iroh instructs the remote peer via the control channel to send its own probe to the local candidate address.
3. **Socket Demultiplexing:** Both peers listen for inbound probes on their existing UDP sockets using [`iroh/src/socket/transports.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports.rs) to demultiplex the traffic.

Each probe carries a short-lived `holepunch_id` that both sides verify to ensure they are participating in the same punching session, preventing cross-traffic interference and spoofing attempts.

## Detecting Success and Promoting Paths

When a peer receives a probe with a matching `holepunch_id`, it returns `HolepunchResult::Success` to the remote state machine. Upon success:

- The corresponding `PathId` is marked as **hole-punched** and promoted to the usable path set.
- The successful address pair is cached in the local `EndpointAddr` storage for rapid reconnection on subsequent sessions.

If probes remain unacknowledged beyond the timeout threshold, the attempt is marked as failed. The pruning logic in [`path_state.rs`](https://github.com/n0-computer/iroh/blob/main/path_state.rs) (lines 49-55 and 243-255) then either schedules a retry with exponential backoff or removes the candidate from the active list if it repeatedly fails validation.

## Handling Asymmetric and Symmetric NATs

iroh distinguishes NAT behaviors based on address origin (relay-provided versus STUN-derived). For **destination-independent NATs** (Easiest/Easy classifications), the system aggressively attempts multiple candidate pairs in parallel to maximize success probability.

However, when detecting **symmetric NATs**—where the NAT mapping depends on the destination address—the remote state aborts hole-punching early. Since symmetric NATs prevent the predictable port reuse required for punching, iroh immediately falls back to relayed connections rather than wasting time on impossible direct paths.

## Maintaining Connection Resilience

The hole-punching system does not stop after initial success. The remote state machine continuously monitors path health through the [`path_state.rs`](https://github.com/n0-computer/iroh/blob/main/path_state.rs) tracker. If a previously punched path later reports failures—triggered by network migration, NAT mapping timeout, or interface changes—the state machine automatically schedules a new `trigger_holepunching()` cycle. This ensures that transient direct connections can be re-established without manual intervention, maintaining optimal peer-to-peer performance.

## Summary

- **Automatic Triggering:** Hole-punch attempts launch via `trigger_holepunching()` in [`remote_state.rs`](https://github.com/n0-computer/iroh/blob/main/remote_state.rs) when addresses update or timers expire, ensuring only one attempt runs at a time per peer.
- **Smart Selection:** The biased-RTT path selector in [`biased_rtt_path_selector.rs`](https://github.com/n0-computer/iroh/blob/main/biased_rtt_path_selector.rs) ranks `NatCandidate` addresses by latency and NAT difficulty (Easiest, Easy, Hard).
- **Coordinated Probing:** Simultaneous bidirectional probes with verified `holepunch_id` values punch holes through destination-independent NATs, coordinated via the `transports` module.
- **Robust Fallback:** Symmetric NAT detection triggers immediate relay fallback, while successful punches are cached in `EndpointAddr` and monitored via [`path_state.rs`](https://github.com/n0-computer/iroh/blob/main/path_state.rs) for automatic healing.

## Frequently Asked Questions

### What triggers a hole-punch attempt in iroh?

A hole-punch attempt triggers when the remote state machine detects changes to a peer's candidate addresses—such as new relay or STUN-discovered endpoints—or when internal timers request periodic health checks. The `trigger_holepunching()` function 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) queues these attempts, ensuring only one punch operation runs at a time per remote peer to prevent network flooding.

### How does iroh select which addresses to use for hole-punching?

iroh uses the biased-RTT path selector 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) to evaluate `NatCandidate` structures. Each candidate carries a NAT type classification (Easiest, Easy, Hard) derived from its discovery method. The selector prioritizes candidates with lower estimated latency and higher success probability, attempting the most promising address pairs first.

### What happens if hole-punching fails against a symmetric NAT?

When iroh identifies a symmetric NAT—where port mappings change per destination—it immediately aborts the hole-punch attempt. Since symmetric NATs prevent the consistent endpoint mapping required for successful punching, the system skips further probing and falls back to relayed communication, preserving connection reliability without wasting resources on impossible direct paths.

### How does iroh verify that a hole-punch probe is legitimate?

Each hole-punch probe contains a cryptographically random `holepunch_id` generated for that specific session. When the remote peer receives a probe, it validates this ID against the expected value stored in its `HolepunchAttempt` record. Only probes with matching IDs are accepted as valid, preventing malicious or stale packets from interfering with the NAT traversal state machine.