# How iroh Net Report Optimizes Connection Performance: Network-Aware Relay Selection

> Discover how iroh net report optimizes connection performance by probing network conditions, selecting low-latency relays, and detecting NAT mappings for faster peer-to-peer connections.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: performance
- Published: 2026-07-13

---

**The iroh net report feature continuously probes network conditions to detect UDP reachability, identify stable NAT mappings, select the lowest-latency relay, and detect captive portals, enabling the protocol stack to choose the fastest available path for peer-to-peer connections.**

The `iroh` crate in the n0-computer/iroh repository provides a sophisticated network reporting subsystem that transforms raw connectivity measurements into actionable routing decisions. By analyzing real-time data from QUIC address discovery probes and relay latency measurements, the **net report** feature eliminates guesswork from connection establishment, ensuring that the protocol always prefers the most efficient transport path available.

## Detecting UDP and QUIC Reachability

The first optimization layer determines whether direct UDP communication is possible. The net report runs QUIC address discovery (QAD) round-trips to verify IPv4 and IPv6 connectivity, storing results in `udp_v4` and `udp_v6` boolean fields.

In [`iroh/src/net_report/report.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/net_report/report.rs), the `Report::has_udp` method checks these flags:

```rust
// From iroh/src/net_report/report.rs
pub fn has_udp(&self) -> bool {
    self.udp_v4 || self.udp_v6
}

```

When `has_udp()` returns **true**, the stack knows it can establish direct QUIC connections, allowing it to skip costly fallback paths like HTTPS relays. This detection happens continuously, so the system adapts immediately when network conditions change.

## Analyzing NAT Mapping Stability

The report identifies whether the host's NAT mapping is stable across different destinations. By comparing public addresses observed when probing different relays, the system sets `mapping_varies_by_dest_ipv4` and `mapping_varies_by_dest_ipv6` flags.

The `Report::mapping_varies_by_dest` method in [`iroh/src/net_report/report.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/net_report/report.rs) merges these observations:

```rust
// Implementation merges optional booleans to determine stability
pub fn mapping_varies_by_dest(&self) -> bool {
    self.mapping_varies_by_dest_ipv4.unwrap_or(false) 
        || self.mapping_varies_by_dest_ipv6.unwrap_or(false)
}

```

When mappings are **stable**, the client can reuse discovered addresses without redundant probes, reducing connection setup latency and network overhead.

## Selecting the Fastest Relay

Net report maintains a rolling history of relay latencies using a 5-minute window. Each probe reports timing data via `RelayLatencies`, and the system aggregates this to identify the **preferred relay**—the one with the lowest recent latency.

According to the source code in [`iroh/src/net_report.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/net_report.rs), the function `add_report_history_and_set_preferred_relay` merges historic measurements and updates the preferred relay selection:

```rust
// From iroh/src/net_report.rs#L47-L84
fn add_report_history_and_set_preferred_relay(&mut self, report: &Report) {
    // Updates latency history and sets preferred_relay based on 
    // the lowest recent latency measurements
}

```

This minimizes round-trip time for subsequent data transfers by ensuring packets route through the geographically and topologically nearest healthy relay.

## Captive Portal Detection

To avoid connection stalls behind restricted networks, the net report optionally checks for captive portals on the first report. The `check_captive_portal` function in [`iroh/src/net_report/reportgen.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/net_report/reportgen.rs) fetches `/generate_204` from each relay:

```rust
// From iroh/src/net_report/reportgen.rs#L61-L66
async fn check_captive_portal(&self) -> bool {
    // Returns true if response is not 204 No Content, indicating
    // a captive portal is intercepting traffic
}

```

If the response is not **204 No Content**, the client assumes a captive portal is blocking traffic and can adapt its retry logic, delaying heavy uploads until the network clears.

## Incremental Reporting for Efficiency

Rather than running full network scans continuously, the net report uses an incremental architecture. The `Client::get_report` method in [`iroh/src/net_report.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/net_report.rs) decides between full and incremental scans based on elapsed time, previous results, and current UDP status:

```rust
// From iroh/src/net_report.rs#L99-L108
pub async fn get_report(&mut self) -> Result<Report> {
    // Logic to determine if full scan needed or if we can use
    // incremental updates based on staleness
}

```

This approach reduces bandwidth and CPU usage while keeping latency data fresh, as subsequent reports only probe relays that have changed or when network state appears stale.

## Accessing Net Report Data via the API

Applications can query the current report through the public API exposed in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs). The `Endpoint::net_report()` method returns a watcher for the latest `Report`, allowing programmatic access to performance metrics.

Here is a practical example for optimizing connection logic:

```rust
use iroh::endpoint::Endpoint;
use std::time::Duration;

// Create an endpoint (using default config here)
let ep = Endpoint::new().await?;

// Request a fresh network report – this runs the probes in the background
let report = ep.net_report().initialized().await;

// Use the preferred relay for a new upload
if let Some(relay) = report.preferred_relay {
    println!("Best relay is {}", relay);
    // e.g. pass the relay URL to a transfer builder
    // let tx = ep.send_file(...).relay(relay);
}

// Check whether UDP is usable (QUIC) – if false, fall back to HTTPS only
if report.has_udp() {
    println!("UDP reachable – we can use QUIC");
} else {
    println!("UDP not reachable – will use HTTPS latency probes only");
}

// Detect captive‑portal status
if let Some(true) = report.captive_portal {
    println!("A captive portal is blocking traffic – delay heavy uploads");
}

```

This pattern allows applications to **prefill connection attempts** with the best-known relay address, **toggle transport choices** between QUIC and HTTPS based on UDP reachability, and **react to captive portal detection** before attempting heavy transfers.

## Summary

The iroh net report subsystem provides continuous network intelligence that drives connection optimization:

- **UDP Detection**: `Report::has_udp` in [`iroh/src/net_report/report.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/net_report/report.rs) verifies direct QUIC capability, enabling the stack to bypass costly fallbacks.
- **NAT Stability Analysis**: `mapping_varies_by_dest` fields identify stable mappings for address reuse, reducing probe overhead.
- **Intelligent Relay Selection**: A 5-minute latency history maintained via `add_report_history_and_set_preferred_relay` in [`iroh/src/net_report.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/net_report.rs) ensures traffic routes through the fastest available relay.
- **Captive Portal Awareness**: `check_captive_portal` in [`iroh/src/net_report/reportgen.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/net_report/reportgen.rs) prevents stalled connections behind restricted networks.
- **Incremental Efficiency**: `Client::get_report` optimizes scan frequency to minimize resource usage while maintaining accurate metrics.

## Frequently Asked Questions

### What is iroh net report?

The iroh net report is a continuous network measurement subsystem within the n0-computer/iroh repository that probes UDP reachability, relay latencies, and NAT mappings to provide real-time connection optimization data. It exposes this information through the `Endpoint::net_report()` API, allowing applications to make informed routing decisions based on current network conditions.

### How does iroh detect captive portals?

Iroh detects captive portals by requesting `/generate_204` from each relay during the initial report generation, as implemented in [`iroh/src/net_report/reportgen.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/net_report/reportgen.rs). If the response code is not 204 No Content, the system sets the `captive_portal` flag in the `Report` struct, signaling that the network may be intercepting traffic and requiring alternative connection strategies.

### What is the preferred relay in iroh?

The **preferred relay** is the relay server with the lowest recent latency as determined by a rolling 5-minute measurement window. The `add_report_history_and_set_preferred_relay` function in [`iroh/src/net_report.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/net_report.rs) updates this selection after each report, ensuring that subsequent connections use the currently fastest available relay path.

### How does iroh handle NAT mapping variations?

Iroh tracks NAT mapping stability by comparing public addresses observed when probing different relays, storing results in `mapping_varies_by_dest_ipv4` and `mapping_varies_by_dest_ipv6` fields. The `Report::mapping_varies_by_dest` method in [`iroh/src/net_report/report.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/net_report/report.rs) exposes this data, allowing the stack to determine whether it can reuse discovered addresses or must perform additional discovery probes for each new destination.