How to Set Up DNS/Pkarr for iroh Endpoint Connections Using Only an EndpointId

You can connect to an iroh peer using only its EndpointId by configuring a PkarrResolver that queries the public DNS/PKARR relay at dns.iroh.link to resolve the peer's network addresses automatically.

The iroh networking library (n0-computer/iroh) enables peer-to-peer connections without manual IP configuration by leveraging cryptographic peer identities. When you need to set up DNS/Pkarr for iroh endpoint connection using only an EndpointId, the library provides built-in resolvers that query public relays to translate public keys into routable addresses. This approach eliminates the need for static IP addresses or custom DNS infrastructure.

Understanding iroh's DNS and PKARR Resolution Architecture

iroh resolves peer addresses through two complementary HTTP-based services that map an EndpointId (the peer's public key) to IP/port pairs:

  • DNS-over-HTTPS: The dns.iroh.link server publishes TXT records containing address lists. The client implementation lives in iroh/src/util.rs as the DnsResolver type.
  • PKARR Relay: Stores signed packets with address information at https://dns.iroh.link/pkarr, accessible without traditional DNS. Implemented in iroh/src/address_lookup/pkarr.rs as the PkarrResolver type.

The PKARR relay also functions as a DNS resolver, so clients only need a single HTTPS-based resolver to retrieve full address lists. Both production and staging environments are available, with the production URL defined as N0_DNS_PKARR_RELAY_PROD in the source code.

Step-by-Step Implementation

1. Create the Local Endpoint

Initialize a QUIC endpoint that will handle outgoing connections. According to iroh/src/endpoint.rs, the Endpoint::bind() method creates the local socket:

let endpoint = Endpoint::bind().await?;

2. Prepare TLS Configuration

The HTTPS resolver requires a rustls::ClientConfig to establish secure connections to the relay. Build a configuration with standard root certificates:

let mut roots = RootCertStore::empty();
roots.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| {
    rustls::OwnedTrustAnchor::from_subject_spki_name_constraints(
        ta.subject,
        ta.spki,
        ta.name_constraints,
    )
}));
let tls_config = ClientConfig::builder()
    .with_safe_defaults()
    .with_root_certificates(roots)
    .with_no_client_auth();

3. Build the PKARR Resolver

Use the PkarrResolver::n0_dns() constructor to create a resolver pre-configured with the production relay URL. As implemented in iroh/src/address_lookup/pkarr.rs at lines 127-132, this method references the N0_DNS_PKARR_RELAY_PROD constant:

let resolver = PkarrResolver::n0_dns().build(tls_config)?;

4. Attach the Resolver to the Endpoint

The Endpoint struct stores the resolver in its dns_resolver field (defined in iroh/src/socket.rs at line 174). Replace the default resolver with your PKARR instance:

endpoint.set_address_lookup(resolver);

5. Connect Using Only the EndpointId

With the resolver attached, initiate connections using only the peer's public key. The resolver automatically fetches and verifies the signed PKARR packet, extracts the address list, and attempts QUIC connections:

let conn = endpoint
    .connect(endpoint_id, ALPN)   // endpoint_id is the peer's EndpointId
    .await?;

Complete Integration Example

This minimal example demonstrates the full flow from binding an endpoint to establishing a connection using only an EndpointId:

use iroh::endpoint::{Endpoint, EndpointId};
use iroh::address_lookup::pkarr::PkarrResolver;
use rustls::{ClientConfig, RootCertStore};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // 1. Create a local endpoint
    let endpoint = Endpoint::bind().await?;

    // 2. Prepare TLS config for HTTPS resolver
    let mut roots = RootCertStore::empty();
    roots.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| {
        rustls::OwnedTrustAnchor::from_subject_spki_name_constraints(
            ta.subject,
            ta.spki,
            ta.name_constraints,
        )
    }));
    let tls_config = ClientConfig::builder()
        .with_safe_defaults()
        .with_root_certificates(roots)
        .with_no_client_auth();

    // 3. Build the PKARR resolver targeting the public relay
    let pkarr_resolver = PkarrResolver::n0_dns().build(tls_config)?;

    // 4. Attach resolver to endpoint
    endpoint.set_address_lookup(pkarr_resolver);

    // 5. Remote peer's EndpointId (obtained out-of-band)
    let remote_id: EndpointId = "...".parse()?; // Replace with actual ID

    // 6. Connect - resolver automatically looks up addresses
    const ALPN: &[u8] = b"iroh-example/echo/0";
    let conn = endpoint.connect(remote_id, ALPN).await?;

    // 7. Use the connection
    let (mut send, mut recv) = conn.open_bi().await?;
    send.write_all(b"hello from PKARR!").await?;
    send.finish()?;
    let reply = recv.read_to_end(1024).await?;
    println!("peer replied: {}", String::from_utf8_lossy(&reply));

    Ok(())
}

Key Source Files and Implementation Details

File Purpose Key Components
iroh/src/address_lookup/pkarr.rs PKARR resolution logic PkarrResolver, N0_DNS_PKARR_RELAY_PROD constant (lines 127-132), packet verification (lines 115-126)
iroh/src/util.rs DNS resolver types DnsResolver type definition
iroh/src/socket.rs Endpoint state management dns_resolver field storage (line 174)
iroh/src/endpoint.rs Core endpoint API Endpoint::bind() implementation (line 62)
iroh/tests/integration.rs Real-world usage examples PkarrResolver::n0_dns() usage (line 95)

Summary

  • EndpointId represents identity: The peer's public key serves as the sole identifier needed for connection.
  • PKARR eliminates manual addressing: The PkarrResolver automatically resolves EndpointIds to IP/port pairs via the n0-dns relay.
  • Single resolver required: The PKARR relay doubles as a DNS resolver, requiring only one HTTPS client configuration.
  • Built-in verification: Signed packets are verified automatically before addresses are returned to the endpoint.
  • No infrastructure needed: The public dns.iroh.link service handles publication and resolution without requiring custom DNS servers.

Frequently Asked Questions

What is an EndpointId in iroh?

An EndpointId is the 32-byte public key derived from the peer's secret key, serving as the permanent cryptographic identity of an iroh endpoint. According to the n0-computer/iroh source code, this identifier remains stable even as the peer moves between networks or changes IP addresses, making it the only piece of information required to initiate a connection when using DNS/PKARR resolution.

Do I need to run my own DNS server to use PKARR resolution?

No. The PkarrResolver::n0_dns() constructor configures your endpoint to use the public n0-dns relay at https://dns.iroh.link/pkarr, maintained by number-0. This relay stores signed PKARR packets and serves DNS TXT records, allowing you to resolve peer addresses without deploying any custom DNS infrastructure or maintaining static IP mappings.

How does the PkarrResolver verify the signed packets?

The resolver verifies cryptographic signatures on PKARR packets before extracting addresses, as implemented in iroh/src/address_lookup/pkarr.rs between lines 115-126. Each packet contains a signed statement binding the EndpointId to specific network addresses, ensuring that only the peer holding the corresponding private key can publish or update their address records, preventing man-in-the-middle attacks during address resolution.

Can I use a custom PKARR relay instead of the public n0-dns service?

Yes. While PkarrResolver::n0_dns() provides a convenient pre-configured resolver using the production relay, you can instantiate PkarrResolver with custom URLs pointing to your own PKARR relay infrastructure. This allows private networks or specialized deployments to maintain isolated address resolution systems while using the same underlying EndpointId-based connection logic.

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 →