How to Establish Direct Peer-to-Peer Connections Without Relay Servers in Iroh
To establish direct peer-to-peer connections without relay servers in iroh, configure the Endpoint with RelayMode::Disabled to force pure UDP hole-punching and bypass the relay fallback layer.
The iroh networking stack provides a robust QUIC-based peer-to-peer communication layer that defaults to relay-assisted NAT traversal. For scenarios requiring guaranteed direct connections—whether for privacy, latency, or network isolation—you can disable relay servers entirely and rely solely on UDP hole-punching between peers.
Architecture Overview
Iroh's networking layer is built on QUIC (via the noq library) with a custom relay protocol for fallback. By default, an endpoint attempts to hole-punch a direct UDP path and only falls back to public relays when that path fails (e.g., due to symmetric NATs or strict firewalls).
Key components in iroh/src/endpoint.rs govern this behavior:
Endpoint– The main API providingbuilder,bind,connect, andacceptmethods for connection managementRelayMode– Configuration enum at line 1912 that controls relay usage, including theDisabledvariant- PathSelector – Internal logic that prefers direct IP paths over relay paths when both are available
When RelayMode::Disabled is set, the endpoint does not contact relay servers for home-relay lookups, use HTTPS-based relay protocols, or advertise relay URLs in its EndpointAddr.
How Direct Hole-Punching Works
Direct connections rely on the underlying QUIC implementation (noq) sending packets over UDP sockets. The process follows these steps:
- The endpoint binds local UDP sockets via
Endpoint::bind(creating a random socket by default) - Peers exchange their public UDP addresses through out-of-band signaling (e.g., manual configuration or application-specific discovery)
- The
noqlibrary performs STUN-style NAT traversal probing to establish a direct path - The
PathSelectorchooses the direct IP route since no relay candidates exist when relays are disabled
If the remote addresses are reachable across the network (both peers share the same NAT or have public IPs), the connection succeeds. Otherwise, connect returns a ConnectError::Noq error.
Implementation Guide
To force pure direct connections, you must disable relays during endpoint construction on both sides of the connection.
Listener Configuration
Create a listener that accepts direct connections without relay advertisement:
use iroh::{Endpoint, RelayMode, endpoint::presets, TransportAddr};
use tracing::info;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Build an endpoint that will *not* use any relay servers.
let endpoint = Endpoint::builder(presets::N0)
.relay_mode(RelayMode::Disabled) // <‑‑ crucial line
.bind()
.await?;
// Wait until the endpoint is online (i.e. sockets are bound).
endpoint.online().await;
// Print our public key – peers need this to address us.
println!("My endpoint id: {}", endpoint.id());
// Accept a single incoming connection on a custom ALPN.
let alpn = b"my-app/0";
let (conn, _) = endpoint.accept(alpn).await?;
info!("direct connection accepted");
// Echo whatever the peer sends.
let (mut send, mut recv) = conn.open_bi().await?;
tokio::io::copy(&mut recv, &mut send).await?;
Ok(())
}
Source: iroh/examples/listen.rs
Connector Configuration
Connect to a peer using only their direct UDP addresses:
use std::{net::SocketAddr, sync::Arc};
use clap::Parser;
use iroh::{
Endpoint, EndpointAddr, RelayMode, SecretKey, TransportAddr, endpoint::presets,
};
use tracing::info;
#[derive(Debug, Parser)]
struct Cli {
/// Remote endpoint public key
#[clap(long)]
endpoint_id: iroh::EndpointId,
/// Remote UDP addresses (space‑separated)
#[clap(long, value_delimiter = ' ', num_args = 1..)]
addrs: Vec<SocketAddr>,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let args = Cli::parse();
// Create a local endpoint; again we disable relays.
let endpoint = Endpoint::builder(presets::N0)
.relay_mode(RelayMode::Disabled) // <‑‑ crucial line
.bind()
.await?;
// Build the address that the remote advertised (only UDP transports).
let transports = args
.addrs
.into_iter()
.map(TransportAddr::Ip)
.collect::<Vec<_>>();
let remote = EndpointAddr::from_parts(args.endpoint_id, transports);
// Try to open a direct QUIC connection.
let alpn = b"my-app/0";
let conn = endpoint.connect(remote, alpn).await?;
info!("direct connection established");
// Send a greeting.
let (mut send, mut recv) = conn.open_bi().await?;
send.write_all(b"hello from client").await?;
send.finish().await?;
// Receive the echo.
let mut buf = Vec::new();
recv.read_to_end(&mut buf).await?;
println!("peer replied: {}", String::from_utf8_lossy(&buf));
endpoint.close().await;
Ok(())
}
Source: iroh/examples/connect.rs
Verification Steps
To verify a pure-direct connection:
- Run the listener and note the printed endpoint ID and bound UDP port
- Execute the connector with the listener's endpoint ID and UDP address
- Confirm successful
connectcompletion without relay timeout delays
The connection will succeed only if the UDP ports are mutually reachable; otherwise, it fails immediately with ConnectError::Noq rather than falling back to a relay.
Testing and Validation
The iroh repository includes comprehensive NAT testing in iroh/tests/patchbay/nat.rs. The nat_none_x_none test case specifically validates direct peer-to-peer connections without relay involvement, simulating environments where both endpoints have public IP addresses or exist on the same local network.
For manual verification, ensure your firewall rules allow inbound UDP on the bound ports. The add_external_addr method at line 939 in iroh/src/endpoint.rs handles the advertisement of external UDP sockets that enable direct traversal.
When to Use Direct-Only Mode
Appropriate scenarios for RelayMode::Disabled include:
- Corporate LANs or VPNs where you own the network infrastructure and can guarantee UDP reachability
- Privacy-sensitive environments where relay traffic is undesirable
- Low-latency requirements where relay hop introduces unacceptable delay
- Testing and debugging where you must guarantee traffic never leaves the local network
Critical caveats:
- If any NAT or firewall blocks inbound UDP on the advertised ports, the connection fails outright with no fallback
- Symmetric NAT configurations cannot be traversed without relay assistance; these scenarios require
RelayMode::Defaultor a custom relay map - Both peers must use
RelayMode::Disabledto ensure neither attempts relay discovery
Summary
- Disable relays by setting
.relay_mode(RelayMode::Disabled)on theEndpoint::builderto force direct connections - Direct connections rely on UDP hole-punching via the
noqQUIC implementation without third-party relay assistance - Failure mode changes from relay fallback to immediate
ConnectError::Noqwhen the direct path is unreachable - Configuration requires both peers to disable relays and exchange
EndpointAddrinformation containing direct UDP transport addresses - Validation is available through the
patchbaytest suite, specifically thenat_none_x_nonescenario
Frequently Asked Questions
What happens if a direct connection fails when RelayMode::Disabled is set?
The connect call returns a ConnectError::Noq error immediately. Unlike the default configuration, which falls back to a relay server after attempting hole-punching, disabled relay mode provides no fallback mechanism. You must handle this error by either retrying with different addresses or reconfiguring the endpoint with relay support enabled.
Does disabling relays affect the security of the connection?
No. The security model remains identical because iroh uses QUIC's built-in TLS 1.3 encryption for all connections regardless of transport path. Disabling relays only removes the relay server from the network path; the cryptographic handshake and certificate validation between peers function identically whether traversing directly or through a relay.
Can I use direct connections across the public internet?
Yes, but only if both peers have public IP addresses or can traverse their respective NATs via hole-punching. If either peer sits behind a symmetric NAT or strict firewall that blocks unsolicited UDP packets, the direct connection will fail. For reliable public internet connectivity, retain the default relay configuration as a fallback.
How do I verify that no relay is being used for an active connection?
Monitor the connection's ConnectionInfo metadata, which reports the selected network path. When operating in direct mode, the path will show a direct IP route rather than a relay URL. Additionally, network traffic analysis will show UDP packets flowing directly between peer endpoints on their bound ports, with no HTTPS traffic to relay servers.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →