---
title: Traverse NAT
description: Configure relay and AutoNAT, establish the first usable path, and observe direct upgrades or relay fallback.
---

This guide starts where direct [listen and dial](/rust/listen-and-dial) leaves off. It configures relays and AutoNAT, races direct and relayed paths, and tracks later upgrades or fallback. Relayed paths require a reachable Circuit Relay v2 server, which minip2p can host.

<Badge>pre-1.0</Badge>

The `nat` feature adds an orchestrator that can race a direct QUIC or TCP dial against a Circuit Relay v2 path. DCUtR hole punching still needs QUIC.

## Host the relay

The independent std-only `relay-server` feature hosts Circuit Relay v2 over QUIC, TCP, or both. The bundled operator example uses both:

```bash
cargo run -p minip2p-relay-server-example -- \
  --key var/relay.ed25519 \
  --announce /dns4/relay.example.com/udp/19876/quic-v1 \
  --announce /dns4/relay.example.com/tcp/19876
```

Its default is the small `.relay_server().bind_*()` application path. Optional flags expose frozen resource/rate/control limits, and stdin `pause`/`resume` changes only new admissions. Explicit addresses are trusted operator input; otherwise selection prefers AutoNAT-confirmed direct addresses and then concrete listeners. Address changes affect future Identify responses only. There is no automatic relay discovery: distribute the printed peer address to clients explicitly. See the [hosting guide](https://github.com/deepso7/minip2p/tree/main/examples/relay-server).

## Enable NAT traversal

```bash
cargo add minip2p-rs --features nat
```

Builder configuration determines the available behavior:

| Builder configuration | What it enables |
| --- | --- |
| `.relay(relay)` | Relay connects, reservations, and relay-assisted DCUtR |
| `.autonat_server(server)` | Reachability probes only, unless a relay is also configured |
| `.nat_config(config)` | Explicit timeouts, retries, relays, probes, and reservation policy |
| `.discovery()` | NAT coordination as part of discovery, with whatever relay/probe infrastructure is also configured |

```rust
use minip2p::{Endpoint, PeerAddr};

let relay: PeerAddr = std::env::var("MINIP2P_RELAY")?.parse()?;
let mut node = Endpoint::builder()
    .relay(relay)
    .bind_quic_dual_stack()?;

node.listen_all()?;
```

Calling `.autonat_server(...)` alone enables reachability probes, but it does not create a relay leg. Configure a relay for relayed connections, inbound reservations, and relay-assisted hole punching.

## Choose a connect method

`Endpoint::connect` is the one entry. Pass a peer ID, one `PeerAddr`, or a set of addresses that all name the same peer:

| Known information                                  | Call                  |
| -------------------------------------------------- | --------------------- |
| Peer ID; known addresses and/or a configured relay | `connect(&peer_id)`   |
| One complete `PeerAddr`                            | `connect(&peer_addr)` |
| Several complete `PeerAddr`s for the same peer     | `connect(addrs)?`     |

A Peer-ID target with neither known addresses nor a relay settles `ConnectSettled { Failed(NoUsableRoute) }` through one terminal event.

```rust
use std::time::Duration;

use minip2p::{EndpointEvent, NatEvent};

let connect_id = node.connect(&target)?;
match node.nat_wait_path(connect_id, Duration::from_secs(60))? {
    Some(path) => println!("first usable path: {path:?}"),
    None => {
        let failed = node.take_nat_events().into_iter().find(|event| {
            matches!(
                event,
                NatEvent::ConnectFailed { connect_id: id, .. } if *id == connect_id
            )
        });
        match failed {
            Some(NatEvent::ConnectFailed { error, .. }) => {
                eprintln!("connect failed: {error:?}");
            }
            _ => eprintln!("no usable path before the deadline"),
        }
    }
}
```

`nat_wait_path` is a migration helper until #181. It returns `Ok(Some(path))` when it consumes the matching `NatEvent::PathEstablished` — the first usable path, which may be a provisional Relayed circuit. The attempt's terminal is `EndpointEvent::ConnectSettled` (connected after DCUtR settles, or immediately under `force_relay`). It returns `Ok(None)` in two cases:

- the attempt failed; `NatEvent::ConnectFailed` stays queued for `take_nat_events` when the NAT leg failed (a Peer target with no route may settle only as `ConnectSettled`);
- the deadline expired before either outcome; no `ConnectFailed` is synthesized.

## Path selection and upgrade

```mermaid
flowchart TB
  A["connect"] --> B["Direct candidates"]
  A --> C["Configured relay leg"]
  B -->|"wins"| D["DirectDialed"]
  C -->|"first usable path"| E["Relayed (provisional)"]
  E --> F["DCUtR coordination"]
  F -->|"punch succeeds"| G["DirectPunched"]
  F -->|"retries exhausted"| H["ConnectSettled on Relayed"]
```

The first usable path is explicit:

- `Path::DirectDialed`: a direct candidate connected;
- `Path::DirectPunched`: a DCUtR hole punch connected;
- `Path::Relayed { relay }`: the protected relay circuit is usable.

That Relayed path is provisional until `ConnectSettled`. Applications that need the circuit immediately (echo, ping) can use it as soon as `path(peer)` is `Relayed`; the attempt is not finished until DCUtR settles or `force_relay` skips punching. When a relayed path upgrades later, the application receives `NatEvent::PathUpgraded`. If punch windows fail, it receives `HolePunchFailed` events and eventually `FellBackToRelay`; the relayed connection stays usable.

## Reservations and reachability

A private listener needs a reservation before another peer can reach it through the relay. Watch for:

```rust
for event in node.take_nat_events() {
    match event {
        minip2p::NatEvent::RelayReserved { relay, .. } => {
            println!("relay reservation ready: {relay}");
        }
        minip2p::NatEvent::ReachabilityChanged { new, .. } => {
            println!("reachability={new:?}");
        }
        _ => {}
    }
}
```

`node.reachability()` reports the current AutoNAT verdict. `node.active_reservation()` returns the currently held reservation, if any. AutoNAT servers are caller-supplied; minip2p does not discover them automatically.

## Keep driving after the first path

`nat_wait_path` returns when traffic can flow. Keep calling focused waits, `next_wake`, `next_event`, or `poll` afterward so DCUtR, reservation renewal, and connection events continue to progress. Each call drives only its own endpoint, so blocking on one endpoint can delay others sharing the same thread.

## Custom entropy without `std`

Most applications do not configure circuit entropy: `Endpoint` uses the operating system's random source by default. When embedding `minip2p-circuit` with default features disabled, provide an `EntropySource` that never substitutes predictable bytes on failure. See the [`minip2p-circuit` README](https://github.com/deepso7/minip2p/blob/main/crates/circuit/README.md) for the trait contract and a custom-source example.

For a complete live demonstration, use the existing [`minip2p-peer` example](https://github.com/deepso7/minip2p/tree/main/examples/peer). It shows loopback, relay reservations, relay fallback, and RTT changes after a direct upgrade.
