Skip to main content
Net Diagnostics lets you run network connectivity reports on your endpoints. Reports cover NAT type, UDP connectivity, relay latency, port mapping protocol availability, and direct addresses. Everything you need to debug connection issues. You can initiate reports from iroh-services, which will reach out to configured remote nodes that have authorized diagnostics, gather details about the endpoint’s connectivity context, and forward the report to your project on iroh services to assess how to help your user get the best connection they can.

1. Get your API key

Create one from your project’s Settings → API Keys tab. See API Keys for the full walkthrough. Then export it as an environment variable:
export IROH_SERVICES_API_SECRET=<your-api-key>

2. Run the diagnostics client

Clone the repository and run the net_diagnostics example:
git clone https://github.com/n0-computer/iroh-services.git
cd iroh-services
cargo run --example net_diagnostics
Leave this terminal open. The example connects to iroh services, grants the diagnostics capability to your project, and waits for incoming diagnostics requests.

3. Run a diagnostic from the dashboard

Go to your project’s Endpoints page. You should see the example client listed as an online endpoint. Click Run Diagnostics to generate a report. The report appears on the Net Diagnostics page and includes:
  • NAT Type: No NAT, Endpoint-Independent, Endpoint-Dependent, or Unknown
  • UDP Connectivity: IPv4 and IPv6 status with public addresses
  • NAT Mapping: whether mapping varies by destination (symmetric NAT detection)
  • Direct Addresses: local addresses the endpoint is listening on
  • Port Mapping: UPnP, PCP, and NAT-PMP availability
  • Relay Latencies: per-relay IPv4, IPv6, and HTTPS round-trip times
  • Captive Portal: detection of captive portal interference

Integrate Net Diagnostics into your app

The example above uses a ready-made client. To get on-demand reports from your users’ endpoints, wire the same integration into your own iroh app.

Add the dependency

cargo add iroh-services

Connect and grant the capability

Build an iroh_services::Client, grant iroh services the NetDiagnosticsCap::GetAny capability so it can request diagnostics, and run a ClientHost so it can dial back into your endpoint. The client reads the API key you exported in step 1:
use anyhow::Result;
use iroh::{Endpoint, protocol::Router};
use iroh_services::{
    ApiSecret, Client, ClientHost, CLIENT_HOST_ALPN, API_SECRET_ENV_VAR_NAME,
    caps::NetDiagnosticsCap,
};

async fn setup_net_diagnostics(endpoint: &Endpoint) -> Result<Router> {
    // Get your secret somehow, either from an environment variable or config file
    let secret = ApiSecret::from_env_var(API_SECRET_ENV_VAR_NAME)?;

    // The remote_id is the id of the endpoint we'll be sending the network
    // report to, derived from the secret's address
    let remote_id = secret.addr().id;

    // Build the client
    let client = Client::builder(endpoint)
        .api_secret(secret)?
        .build()
        .await?;

    // Grant the GetAny capability so the platform can request diagnostics
    // from this endpoint on demand
    let client2 = client.clone();
    tokio::spawn(async move {
        client2
            .grant_capability(remote_id, vec![NetDiagnosticsCap::GetAny])
            .await
            .unwrap();
    });

    // Set up a ClientHost so the platform can dial back into this endpoint
    let host = ClientHost::new(endpoint);
    let router = Router::builder(endpoint.clone())
        .accept(CLIENT_HOST_ALPN, host)
        .spawn();

    Ok(router)
}
For a complete runnable version, see the net_diagnostics example in the iroh-services repository.

Register on your existing router

The snippet above spawns a router that serves only CLIENT_HOST_ALPN. Most applications already run a Router with their own protocols. Register the handler on that same builder rather than spawning a second router:
use iroh::protocol::Router;
use iroh_services::{ClientHost, CLIENT_HOST_ALPN};

let router = Router::builder(endpoint.clone())
    .accept(MY_PROTOCOL_ALPN, my_protocol) // your application's protocols
    .accept(CLIENT_HOST_ALPN, ClientHost::new(&endpoint)) // add this line
    .spawn();
Calling .spawn() finalizes the router’s set of protocols, so the .accept(CLIENT_HOST_ALPN, ...) call has to come before it. Register every ALPN your endpoint serves on the one router you spawn for that endpoint.

Reading reports and troubleshooting

Reference for interpreting a report (NAT type, connectivity summary) and fixing common setup problems.

Next steps

Add a relay

Configure dedicated relays for your endpoints and learn why they matter for production.

See your direct data rate

Watch direct data rate and other connectivity metrics over time so you can see whether your fixes are working.

Troubleshooting

Broader debug toolkit covering logging and the iroh-doctor CLI for local diagnostics.