# What Happens If Hole-Punching Fails in Iroh: Path State Management and Relay Fallback

> Discover what happens when hole-punching fails in Iroh. Learn about path state management, relay fallback, and exponential back-off for robust connection handling in the n0-computer/iroh repository.

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

---

**When hole-punching fails in Iroh, the affected path is marked as `Unusable` in the remote path state, pruned after exceeding configurable limits, and the connection gracefully falls back to relay transport while the scheduler applies exponential back-off to prevent wasteful retries.**

Iroh's networking layer treats hole-punching as an attempted path-establishment mechanism between peers behind NATs. When direct NAT traversal fails, the system records the failure state 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) and automatically degrades to reliable relay transport to maintain connectivity.

## Path Status Transition to Unusable

When a hole-punch attempt does not succeed, the path’s `PathStatus` is set to `Unusable` according to the source code in [`path_state.rs`](https://github.com/n0-computer/iroh/blob/main/path_state.rs) (lines 49-53). This status marks the address as “never usable” and prevents future allocation attempts to this specific path candidate.

The `PathStatus` enum supports multiple states including `Open`, `Inactive`, `Unusable`, and `Unknown`. The transition to `Unusable` is terminal for that specific path, signaling to the connection manager that direct NAT traversal is impossible for this address pairing.

## Pruning of Failed Paths

The `RemotePathState` struct periodically runs `prune_paths()` to remove hopeless candidates. Paths that have repeatedly failed hole-punching—that is, those marked as `Unusable`—are removed from the candidate set once the number of such paths exceeds system limits.

Two specific constants govern this behavior:

- **`MAX_NON_RELAY_PATHS`**: Limits the total number of direct paths tracked per remote
- **`MAX_INACTIVE_NON_RELAY_PATHS`**: Limits inactive direct paths before pruning occurs

This prevents the system from repeatedly retrying candidates that reside behind permanently non-punchable NAT configurations.

## Exponential Back-off and Retry Logic

The `Remote` actor maintains a record of the last hole-punch attempt in the `last_holepunch` field. If a path is marked `Unusable`, the scheduler does not immediately retry; instead, it schedules the next attempt based on exponential back-off logic implemented in `Remote::trigger_holepunching`.

```rust
// Inside `remote_state.rs` – triggering a hole‑punch.
fn trigger_holepunching(&mut self) {
    // … various checks …
    trace!("not holepunching: no client connection");
    // The function returns early; no new attempt is made until conditions improve.
}

```

This back-off mechanism avoids generating wasteful traffic on networks where NAT traversal is systematically blocked.

## Automatic Fallback to Relay Transport

When no direct (hole-punched) paths remain available, Iroh falls back to its relay transport. The relay path is always treated as usable, ensuring that communication continues even when all NAT traversal attempts fail.

This fallback is transparent to the application layer. The connection remains active via the relay path while the system continues to monitor for new direct path opportunities.

## Observability and Metrics

Iroh emits metrics for each path state transition. These metrics are accessible via the built-in `SocketMetrics` interface:

- **`transport_ip_paths_removed`**: Incremented when a direct IP path is abandoned
- **`transport_relay_paths_removed`**: Incremented when relay paths are pruned

```rust
// Example from the test suite – a hole‑punch that is expected to succeed.
let result = conn.wait_ip(timeout).await.context("holepunch to direct");

// If the attempt fails, the connection’s internal state will set the path to Unusable.
if let Err(e) = result {
    // The error propagates up; the remote path state marks the address as unusable.
    tracing::error!("hole‑punch failed: {}", e);
}

```

Integration tests in [`iroh/tests/patchbay/nat.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/tests/patchbay/nat.rs) exercise these failure paths across different NAT configurations, validating the state’s transition to `Unusable` and subsequent pruning behavior.

## Summary

- **Failed hole-punching** results in the path being marked as `Unusable` in [`path_state.rs`](https://github.com/n0-computer/iroh/blob/main/path_state.rs)
- **Pruning logic** removes unusable paths when limits (`MAX_NON_RELAY_PATHS` and `MAX_INACTIVE_NON_RELAY_PATHS`) are exceeded
- **Exponential back-off** in `trigger_holepunching` prevents immediate retry of failed paths
- **Relay fallback** ensures connectivity persists when direct paths are unavailable
- **Metrics** via `SocketMetrics` track path removals and state transitions for observability

## Frequently Asked Questions

### What does Iroh do when hole-punching fails?

When hole-punching fails, Iroh sets the path’s `PathStatus` to `Unusable` in [`path_state.rs`](https://github.com/n0-computer/iroh/blob/main/path_state.rs), schedules the next attempt using exponential back-off, and removes the path from consideration if limits are exceeded. The connection continues via relay transport.

### How does Iroh prevent infinite retry loops for failed hole-punches?

The `Remote` actor tracks the `last_holepunch` attempt and implements exponential back-off in `trigger_holepunching`. Additionally, `prune_paths()` removes `Unusable` paths once they exceed `MAX_NON_RELAY_PATHS` or `MAX_INACTIVE_NON_RELAY_PATHS`, eliminating hopeless candidates from the pool.

### What metrics does Iroh expose for failed hole-punch attempts?

Iroh exposes `transport_ip_paths_removed` and `transport_relay_paths_removed` metrics through `SocketMetrics`. These counters increment when paths are abandoned or pruned, providing visibility into connection health and NAT traversal success rates.

### Does Iroh support automatic fallback when direct connections fail?

Yes. When all direct paths are marked `Unusable` and pruned, Iroh automatically falls back to relay transport. The relay path remains always usable, ensuring uninterrupted communication even when hole-punching is impossible.