Performance Tradeoffs Between Relay and Direct Connections in iroh

Relay connections guarantee connectivity across NATs and firewalls but introduce additional latency and bandwidth overhead, whereas direct connections offer lower latency and higher throughput but require both endpoints to be directly reachable.

When building peer-to-peer applications with the iroh networking library, understanding the performance tradeoffs between relay and direct connections is crucial for optimizing your application's behavior. The iroh codebase implements two distinct transport paths—relay-mediated and direct UDP/QUIC—each with specific characteristics regarding latency, resource usage, and NAT traversal capabilities. This analysis examines the implementation details found in the n0-computer/iroh repository to help you choose the right connection strategy for your use case.

Connection Architecture: Relay vs Direct Paths

Relay-Based Connection Flow

In the relay-based path, traffic routes through a relay server that both peers know about. According to iroh/docs/relays.md, the relay acts as a middle-man for hole-punching and packet forwarding, providing a reachable address when direct connectivity isn't possible. The relay logic lives primarily in the iroh-relay crate, with the RelayUrl type defined in iroh-base/src/relay_url.rs handling URL parsing and validation.

Direct UDP/QUIC Connections

Direct connections bypass intermediaries entirely. As implemented in iroh/src/socket/transports/ip.rs, peers send packets directly to each other over UDP using QUIC. The DirectAddr structures in iroh/src/protocol.rs (lines 176-178) represent UDP socket addresses that endpoints use for direct communication. This path consumes bandwidth only on the two endpoints, eliminating the relay bottleneck.

Performance Tradeoffs Analysis

Latency Implications

Relay connections add an extra network hop, increasing round-trip time (RTT) latency. The iroh codebase mitigates this by measuring latency to each known relay at startup and selecting the "home" relay with the lowest latency. However, direct connections still typically offer lower latency because packets travel the shortest possible route without relay-induced delays.

Bandwidth and Throughput

Every byte in a relay connection traverses the relay server, consuming its bandwidth and CPU resources. This makes the relay a potential bottleneck when many high-throughput streams share the same server. Direct connections avoid this limitation entirely, with bandwidth consumed only on the endpoints, resulting in higher overall throughput.

NAT Traversal and Reachability

Relay connections are essential when either endpoint sits behind a symmetric NAT or firewall that blocks inbound UDP. The relay provides a reachable address for hole-punching, guaranteeing connectivity. Direct connections only work when both endpoints are directly reachable without symmetric NAT or inbound firewall blocks. If NAT traversal fails, iroh automatically falls back to a relay connection.

Reliability and Resource Usage

While relay connections depend on the relay server's uptime, they remain robust as long as the selected relay stays online. Direct connections are vulnerable to NAT time-outs, packet loss, or network paths that silently drop UDP, potentially requiring periodic keep-alives. From a resource perspective, relay mode requires running the iroh-relay binary (implemented in iroh-relay/src/main.rs) with TLS certificates and load balancing, whereas direct connections require no extra server resources.

Security Considerations

Both connection types use end-to-end encryption, but relay connections expose packet sizes and timing to the relay server, which can also enforce policies like rate-limiting. Direct connections offer pure end-to-end encryption with metadata limited to source and destination IPs.

Implementation in the iroh Codebase

The transport selection logic is split across specific modules. The relay client implementation resides in iroh/src/socket/transports/relay.rs, handling the connection lifecycle and packet forwarding through the relay. For direct connections, iroh/src/socket/transports/ip.rs manages UDP socket handling and address discovery.

When an endpoint starts, it performs latency tests against configured relays to select the optimal home relay. If both peers report reachable UDP addresses, iroh prefers the direct path to avoid relay overhead. Otherwise, it falls back to the relay path, ensuring connectivity across restrictive network environments.

Configuring Connection Modes

You can control the connection behavior through the endpoint configuration. Here are examples showing how to force direct mode, specify a relay, or allow automatic selection:

use iroh::net::{Endpoint, RelayUrl};
use iroh::transport::TransportOpts;

// Force direct connections only (no relay)
let mut opts = TransportOpts::default();
opts.relay = None;  // Disable relay usage
let ep = Endpoint::builder()
    .transport(opts)
    .bind()
    .await?;  // Endpoint will only attempt direct UDP

// Connect via a specific relay
let relay = RelayUrl::try_from("https://relay-0.example.com")?;
let mut opts = TransportOpts::default();
opts.relay = Some(relay);  // Override auto-selection
let ep = Endpoint::builder()
    .transport(opts)
    .bind()
    .await?;  // Uses specified relay for hole-punching

// Automatic relay selection (recommended)
let ep = iroh::Endpoint::builder()
    .bind()
    .await?;  // iroh tests latency and selects the fastest home relay

These configurations compile against iroh crate version 0.13 and above. The TransportOpts::relay field controls whether the endpoint uses the relay transport (src/socket/transports/relay.rs) or the direct UDP transport (src/socket/transports/ip.rs).

Summary

  • Relay connections provide guaranteed connectivity across NATs and firewalls at the cost of additional latency and bandwidth overhead.
  • Direct connections deliver optimal performance with lower latency and higher throughput but require both endpoints to be directly reachable.
  • The iroh implementation automatically selects the best path, falling back to relays when direct connectivity fails.
  • Key files include iroh-base/src/relay_url.rs for relay configuration, iroh/src/socket/transports/ip.rs for direct UDP handling, and iroh/src/socket/transports/relay.rs for relay client logic.
  • Configure connection behavior via TransportOpts when building your endpoint.

Frequently Asked Questions

When should I use a relay instead of a direct connection?

Use a relay when either endpoint is behind a symmetric NAT or restrictive firewall that blocks inbound UDP packets. The relay acts as a trusted intermediary for hole-punching, ensuring connectivity even when direct paths are blocked by network infrastructure.

How does iroh select which relay to use?

iroh measures latency to each configured relay at startup and selects the "home" relay with the lowest RTT. This process is documented in iroh/docs/relays.md and implemented in the relay client code to minimize the performance penalty of relayed traffic.

Can I force my application to only use direct connections?

Yes, by setting TransportOpts::relay to None when constructing your endpoint. However, this will prevent connections from succeeding if either peer is behind a restrictive NAT or firewall, so use this option only when you control the network environment of both endpoints.

What are the security implications of using relay servers?

While traffic remains encrypted end-to-end, the relay server can observe packet sizes and timing metadata. The relay may also enforce rate-limiting policies. For applications requiring minimal metadata exposure, direct connections are preferable as they eliminate the relay as a potential observer.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →