# How iroh's Address Lookup Service Works with DNS and Pkarr

> Learn how iroh's address lookup service uses DNS and Pkarr to resolve endpoint addresses by verifying signed DNS TXT records and extracting connection data.

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

---

**TLDR:** iroh resolves endpoint addresses by querying DNS TXT records that contain cryptographically signed Pkarr packets, verifying the signatures against the public key, and extracting connection endpoints from the embedded DNS data.

The iroh address lookup service in the `n0-computer/iroh` repository provides a decentralized mechanism for discovering peer connection information using DNS and Pkarr (Public Key Addressable Resource Records). This system allows clients to retrieve endpoint addresses, relay URLs, and ports by querying human-readable domain names that encode Ed25519 public keys, eliminating the need for centralized directory services.

## Constructing the DNS Query Name

The resolution process begins with constructing a specialized DNS name that encodes the target's public key. The format follows `_iroh.<z-base-32-encoded-public-key>.<origin>`, where:

- The `<origin>` is either `dns.iroh.link.` for production environments or `staging-dns.iroh.link.` for staging.
- The public key is extracted from the `EndpointId` that the caller already possesses and encoded using Z-base-32.

This naming logic is implemented in `DnsResolver::lookup_endpoint_by_id` within [`iroh-dns/src/dns.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/dns.rs). The method formats the query string before delegating to the underlying resolver.

## Retrieving Signed Packets via DNS TXT Lookup

Once the DNS name is constructed, the system performs a TXT record lookup using the `DnsResolver` struct, which is backed by `HickoryResolver`. The lookup executes with a default **3-second timeout** defined as `DNS_TIMEOUT` in the resolver configuration.

The query flow proceeds through `DnsResolver::lookup_txt` → `HickoryResolver::lookup_txt` → the `hickory_resolver` crate. The returned TXT payload contains a **Pkarr signed packet**—a self-authenticating data structure defined in [`iroh-dns/src/pkarr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/pkarr.rs) that encapsulates the endpoint's connection details.

## Cryptographic Verification and Packet Parsing

After retrieving the TXT records, the raw data undergoes strict verification and parsing before being trusted. The `EndpointInfo::from_txt_lookup` method concatenates the TXT strings and constructs a `SignedPacket` via `SignedPacket::from_bytes`.

The packet layout follows a precise binary format defined in [`iroh-dns/src/pkarr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/pkarr.rs):

- **32 bytes**: Ed25519 public key
- **64 bytes**: Signature
- **8 bytes**: Timestamp
- **Variable**: DNS wire-format packet

The total size is bounded by `MAX_SIGNED_PACKET_SIZE`, with `HEADER_SIZE` accounting for the non-DNS prefix.

The verification process:

1. **Signature validation** using `PublicKey::verify` to ensure the packet was signed by the private key corresponding to the embedded public key.
2. **DNS parsing** of the inner payload using `simple_dns::Packet::parse` to extract the actual resource records.
3. **Record extraction** via helper methods `SignedPacket::txt_records` and `SignedPacket::all_txt_records`, which convert raw DNS answers into structured strings that `EndpointInfo` interprets.

## Extracting Endpoint Information

Upon successful verification, the system instantiates an `EndpointInfo` struct defined in [`iroh-dns/src/endpoint_info.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/endpoint_info.rs). This structure extracts critical connection fields including `relay_url`, `quic_addr`, and `tcp_addr` from the validated DNS records. The `EndpointId` used in the initial query (handled in [`iroh-base/src/endpoint_addr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-base/src/endpoint_addr.rs)) ensures that the resolved information cryptographically matches the expected peer identity.

## Caching and Network Resilience

The resolver implements several strategies to ensure reliable operation across flaky networks:

- **DNS caching**: Responses are cached to reduce redundant queries and improve resolution latency.
- **Cache management**: The `clear_cache` method on `DnsResolver` allows explicit cache invalidation, while `reset` automatically rebuilds the resolver when network changes are detected.
- **Staggered lookups**: The `lookup_endpoint_by_id_staggered` method implements jittered retry logic with configurable delays (e.g., 0ms, 200ms, 500ms) to improve success rates on unstable connections.

## Implementation Examples

### Resolving by Endpoint ID

To resolve an endpoint using its public key identifier and the production DNS infrastructure:

```rust
use iroh_dns::{DnsResolver, N0_DNS_ENDPOINT_ORIGIN_PROD};
use iroh_base::EndpointId;

async fn resolve_endpoint(id: EndpointId) -> Result<(), Box<dyn std::error::Error>> {
    // Create a resolver with default system DNS settings
    let resolver = DnsResolver::new();

    // Perform the lookup (3 s timeout is the default DNS_TIMEOUT)
    let info = resolver
        .lookup_endpoint_by_id(&id, N0_DNS_ENDPOINT_ORIGIN_PROD)
        .await?;

    println!("Resolved endpoint: {:?}", info);
    Ok(())
}

```

### Resolving by Human-Readable Domain

For lookups using traditional domain names, the resolver automatically handles the `_iroh.` prefix:

```rust
use iroh_dns::DnsResolver;

async fn resolve_by_name(name: &str) -> Result<(), Box<dyn std::error::Error>> {
    let resolver = DnsResolver::default();          // same as DnsResolver::new()
    let info = resolver
        .lookup_endpoint_by_domain_name(name)        // adds the "_iroh." prefix if missing
        .await?;
    println!("Endpoint info for {name}: {:?}", info);
    Ok(())
}

```

### Staggered Resolution with Retries

For improved reliability in adverse network conditions, use staggered lookups with custom delay patterns:

```rust
use iroh_dns::DnsResolver;
use iroh_base::EndpointId;

async fn resolve_staggered(id: EndpointId) -> Result<(), Box<dyn std::error::Error>> {
    let resolver = DnsResolver::default();
    // Try up to three attempts with 0 ms, 200 ms and 500 ms delays
    let delays = [200u64, 500];
    let info = resolver
        .lookup_endpoint_by_id_staggered(&id, iroh_dns::N0_DNS_ENDPOINT_ORIGIN_PROD, &delays)
        .await?;
    println!("Staggered result: {:?}", info);
    Ok(())
}

```

## Key Source Files

The iroh address lookup service spans several critical files in the repository:

- **[`iroh-dns/src/pkarr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/pkarr.rs)**: Defines the Pkarr signed-packet format, parsing logic, signature verification using `PublicKey::verify`, and helper methods for TXT record extraction.
- **[`iroh-dns/src/dns.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/dns.rs)**: Implements the high-level `DnsResolver`, the `Resolver` trait, and public APIs including `lookup_endpoint_by_id`, `lookup_endpoint_by_domain_name`, and `lookup_endpoint_by_id_staggered`.
- **[`iroh-dns/src/endpoint_info.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/endpoint_info.rs)**: Parses validated TXT data into the `EndpointInfo` struct, mapping DNS records to typed fields like relay URLs and transport addresses.
- **[`iroh-base/src/endpoint_addr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-base/src/endpoint_addr.rs)**: Contains the `EndpointId` type and Z-base-32 encoding logic used in DNS name construction.
- **[`iroh-base/src/relay_url.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-base/src/relay_url.rs)**: Represents relay URLs stored within endpoint TXT records.

## Summary

- iroh stores endpoint connection data in **Pkarr-signed DNS packets** published as TXT records.
- DNS names follow the format `_iroh.<z-base-32-pubkey>.<origin>`, encoding the target's public key in the query itself.
- The `DnsResolver` queries these records with a 3-second timeout, using `HickoryResolver` for underlying DNS operations.
- Retrieved packets undergo cryptographic verification via `PublicKey::verify` before parsing with `simple_dns::Packet::parse`.
- Validated data populates an `EndpointInfo` struct containing `relay_url`, `quic_addr`, and `tcp_addr`.
- The system provides **caching**, **cache clearing**, and **staggered retry mechanisms** for robust operation across network conditions.

## Frequently Asked Questions

### What is Pkarr and why does iroh use it for address lookup?

Pkarr (Public Key Addressable Resource Records) is a system for publishing signed DNS records that are self-authenticating using Ed25519 public keys. iroh uses Pkarr to enable decentralized endpoint discovery where the DNS name itself encodes the public key, allowing clients to verify that retrieved addresses were published by the legitimate key owner without trusting the DNS server.

### How does the DNS name format encode the public key?

The DNS name uses the format `_iroh.<z-base-32-encoded-public-key>.<origin>`, where the public key extracted from the `EndpointId` is encoded using Z-base-32 (a variant of base32 that avoids visually similar characters). This encoding appears between the `_iroh.` prefix and the origin domain (e.g., `dns.iroh.link.`), making the public key both human-readable and DNS-compatible.

### What happens if the DNS lookup fails or the signature is invalid?

If the DNS query returns no TXT records or the network times out (default 3 seconds), the resolver returns an error indicating the endpoint could not be found. If TXT records exist but `SignedPacket::from_bytes` fails to verify the signature via `PublicKey::verify`, or if the packet structure violates `MAX_SIGNED_PACKET_SIZE` constraints, the resolution fails with a verification error, preventing connection to potentially malicious or corrupted endpoints.

### How does iroh handle network changes during resolution?

The `DnsResolver` provides a `reset` method that rebuilds the underlying resolver when network changes are detected, ensuring the system uses current DNS servers and network routes. Additionally, applications can call `clear_cache` to invalidate stale DNS responses, and use `lookup_endpoint_by_id_staggered` to implement jittered retries that accommodate transient network failures during the lookup process.