Traverse NAT
Configure relay and AutoNAT, establish the first usable path, and observe direct upgrades or relay fallback.
This guide starts where direct 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.
pre-1.0The 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:
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.
Enable NAT traversal
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 |
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
| Known information | Method |
|---|---|
| Peer ID only; rely on configured relay | connect(&peer_id) |
One direct PeerAddr, optionally raced against relays |
connect_addr(&peer_addr) |
Peer ID and several transport Multiaddr candidates |
connect_with_addrs(peer_id, addrs) |
use std::time::Duration;
use minip2p::NatEvent;
let connect_id = node.connect_addr(&target)?;
match node.wait_path(connect_id, Duration::from_secs(60))? {
Some(path) => println!("connected via {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"),
}
}
}
wait_path returns Ok(Some(path)) when it consumes the matching
NatEvent::PathEstablished. It returns Ok(None) in two cases:
- the attempt failed;
NatEvent::ConnectFailedstays queued fortake_nat_events; - the deadline expired before either outcome; no
ConnectFailedis synthesized.
Path selection and upgrade
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.
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:
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
wait_path returns when traffic can flow. Keep calling next_event, poll,
or feature-focused waits afterward so DCUtR, reservation renewal, and
connection events continue to progress.
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
for the trait contract and a custom-source example.
For a complete live demonstration, use the existing
minip2p-peer example.
It shows loopback, relay reservations, relay fallback, and RTT changes after a
direct upgrade.