How to Use Address Lookup Services (DNS and Pkarr) to Discover Peer Addresses in iroh

iroh resolves peer EndpointId public keys into routable EndpointAddr transport addresses by implementing the AddressLookup trait, which supports both DNS TXT record queries and PKARR HTTP relay requests for decentralized peer discovery.

iroh is a peer-to-peer networking library that eliminates the need for manual address exchange by integrating address lookup services directly into the connection flow. By leveraging DNS-based resolution and the PKARR (Public Key Address Resource Record) protocol, applications can publish their network endpoints to distributed or centralized registries and resolve peer addresses dynamically. This guide explores how to configure and use these services according to the n0-computer/iroh source code.

Understanding the Address Lookup Architecture

At the core of iroh’s discovery mechanism is the AddressLookup trait defined in iroh/src/address_lookup.rs. This trait abstracts the conversion of an EndpointId (a peer’s public key) into a full EndpointAddr containing transport addresses, relay URLs, and metadata.

The library provides two primary implementations:

  • DnsAddressLookup – Resolves signed PKARR packets stored in DNS zones via TXT record queries.
  • PkarrResolver – Communicates directly with PKARR HTTP relays (such as pkarr.org) to fetch mutable address records.

Both services operate concurrently. When an endpoint attempts to connect to a peer, iroh calls resolve on all registered lookup services simultaneously, merging the resulting streams so the first successful response is used immediately while slower services continue in the background (see AddressLookupServices::resolve in iroh/src/address_lookup.rs, lines 534–566).

Configuring DNS and Pkarr Resolution

Configure an endpoint to publish its own address and resolve others via both DNS and PKARR using the Builder pattern. The PkarrPublisher publishes your endpoint’s information, while DnsAddressLookup enables resolving peers via DNS.

use iroh::{
    address_lookup::{self, AddrFilter, PkarrPublisher},
    endpoint::{Builder, presets},
};

async fn configure_endpoint() -> iroh::Result<()> {
    let ep = Builder::new(presets::Minimal)
        .addr_filter(AddrFilter::relay_only())          // Optional: restrict published addresses
        .address_lookup(PkarrPublisher::n0_dns())       // Publish to pkarr.org relay
        .address_lookup(address_lookup::DnsAddressLookup::n0_dns()) // Resolve via DNS
        .bind()
        .await?;
    
    println!("Endpoint ID: {}", ep.id().fmt_short());
    Ok(())
}

In this configuration, PkarrPublisher::n0_dns() (defined in iroh-dns/src/pkarr.rs, line 104) creates a publisher targeting the default pkarr.org relay, while DnsAddressLookup::n0_dns() (defined in iroh/src/address_lookup.rs, line 94) configures resolution via standard DNS infrastructure.

The Address Resolution Workflow

The discovery process follows a publish-store-resolve pipeline involving signed cryptographic packets:

  1. Publish: When the endpoint’s addressing information changes (e.g., new relay URLs), the PkarrPublisher creates a signed PKARR packet containing the EndpointInfo (relay URLs, direct IPs, and user data). This packet is signed with the endpoint’s secret key (see iroh-dns/src/pkarr.rs, lines 37–84).

  2. Store: The signed packet is distributed via either:

    • PKARR HTTP Relay: Posted to an HTTP endpoint where it is stored in a mutable DHT.
    • DNS TXT Record: Base64-encoded and placed under a DNS name derived from the public key (using SignedPacket::from_txt_strings in iroh-dns/src/pkarr.rs, lines 41–70).
  3. Resolve: Peers locate the endpoint using:

    • DNS Resolution: DnsAddressLookup queries TXT records for <public-key>.example.com, parses the signed packet using SignedPacket::from_bytes, verifies the signature, and extracts the EndpointInfo (handled by DnsResolver::lookup_endpoint_by_id in iroh-dns/src/dns.rs, lines 112–124).
    • PKARR Resolution: PkarrResolver fetches the mutable item from the HTTP relay and performs the same signature verification.

Both resolvers return a BoxStream<Result<Item, Error>> where Item contains the discovered EndpointInfo and provenance metadata ("dns" or "pkarr").

Filtering Published Addresses

Restrict which transport addresses get published by supplying an AddrFilter to the PkarrPublisher. The filter receives the complete set of candidate addresses and returns an ordered Vec<TransportAddr> that the service will actually publish.

use iroh::{
    address_lookup::AddrFilter,
    dns::PkarrPublisher,
    base::TransportAddr,
};
use std::sync::Arc;

fn create_filtered_publisher(secret_key: iroh_base::SecretKey) -> PkarrPublisher {
    let filter = AddrFilter::custom(|addrs| {
        addrs
            .into_iter()
            .filter(|addr| matches!(addr, TransportAddr::Ip(_)))
            .collect()
    });

    PkarrPublisher::builder("https://pkarr.org".parse().unwrap())
        .addr_filter(filter)
        .build(secret_key, Default::default())
}

The filter is applied once before data is handed to each service (see AddressLookupServices::publish in iroh/src/address_lookup.rs, lines 516–525).

Manual Resolution and Custom Relays

For scenarios requiring explicit control, resolve peer addresses manually or integrate custom PKARR relays at runtime.

Resolve via DNS using DnsResolver:

use iroh::{
    dns::DnsResolver,
    base::EndpointId,
};

async fn resolve_via_dns(peer_id: EndpointId) -> iroh::Result<iroh::base::EndpointInfo> {
    let resolver = DnsResolver::with_nameserver("127.0.0.1:53".parse()?);
    let info = resolver.lookup_endpoint_by_id(&peer_id, "example.org").await?;
    Ok(info)
}

Add a custom PKARR resolver to an existing endpoint:

use iroh::{address_lookup::PkarrResolver, endpoint::Endpoint};

fn add_custom_relay(ep: &Endpoint) -> iroh::Result<()> {
    let resolver = PkarrResolver::new("https://my-relay.example".parse().unwrap());
    ep.address_lookup()
        .expect("endpoint is still open")
        .add(resolver);
    Ok(())
}

The PkarrResolver implementation resides in iroh/src/address_lookup.rs (module pkarr, lines 118–135), while signature verification occurs in iroh-dns/src/pkarr.rs (lines 90–115).

Summary

  • Address lookup services bridge the gap between cryptographic identities (EndpointId) and network addresses (EndpointAddr) in iroh.
  • The AddressLookup trait in iroh/src/address_lookup.rs enables pluggable resolution via DNS or PKARR relays.
  • PkarrPublisher signs and publishes endpoint data, while DnsAddressLookup and PkarrResolver query these records.
  • AddrFilter controls which addresses are published, allowing fine-grained control over network exposure.
  • Resolution calls are concurrent and merged, ensuring fast discovery without waiting for slow services.

Frequently Asked Questions

What is the difference between DNS and PKARR address lookup in iroh?

DNS lookup queries traditional DNS infrastructure for TXT records containing base64-encoded PKARR packets, making it compatible with existing DNS servers and domain-based discovery. PKARR lookup communicates directly with specialized HTTP relays (like pkarr.org) that store mutable items in a DHT-like structure. Both methods verify the same signed packet format, but DNS offers broader infrastructure compatibility while PKARR provides direct DHT semantics.

How do I publish my endpoint information to a custom PKARR relay?

Use PkarrPublisher::builder() with a custom URL instead of the n0_dns() convenience method. Provide your secret key and optional TLS configuration, then register it via the endpoint builder’s .address_lookup() method. The publisher will automatically push updates whenever your endpoint’s addressing information changes.

Can I use multiple address lookup services simultaneously?

Yes. The Builder accepts multiple .address_lookup() calls, and the Endpoint maintains a registry of all services. When resolving a peer, iroh queries all registered services concurrently and merges the results, using the first successful response while allowing slower services to complete in the background. This is handled by AddressLookupServices::resolve in iroh/src/address_lookup.rs.

How does iroh verify the integrity of address lookup results?

All address records are transmitted as signed PKARR packets. Upon resolution, iroh reconstructs the packet from DNS TXT strings or HTTP response bytes and verifies the cryptographic signature against the EndpointId (public key) using SignedPacket::from_bytes (see iroh-dns/src/pkarr.rs, lines 90–115). This ensures that only the holder of the corresponding private key could have published the address information, preventing man-in-the-middle attacks during discovery.

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 →