# Setting up Pkarr and DNS Address Lookup in Iroh: A Complete Guide

> Learn to set up Pkarr and DNS address lookup in Iroh using the AddressLookup trait. Publish transport addresses and resolve peers via DNS TXT records and Pkarr relays.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: how-to-guide
- Published: 2026-07-08

---

**You configure Pkarr and DNS address lookup in Iroh by registering implementations of the `AddressLookup` trait with the `Endpoint` builder, allowing the endpoint to publish its transport addresses to both DNS TXT records and Pkarr relays while resolving remote peers through merged streams from both services.**

Setting up Pkarr and DNS address lookup in Iroh enables automatic peer discovery by translating `EndpointId` values into reachable network addresses. The `iroh` crate provides built-in services that implement the `AddressLookup` trait, letting you publish endpoint data to DNS zones and Pkarr relays while resolving remote peers through parallel redundant lookups. This architecture ensures that peers can discover each other even if one lookup mechanism fails.

## How Address Lookup Works in Iroh

Iroh discovers peers by translating an `EndpointId` into an `EndpointAddr` through pluggable **address-lookup services**. According to the iroh source code, these services integrate into an `Endpoint` via the builder API found in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs).

The architecture follows four distinct phases:

1. **Endpoint Builder** – `Endpoint::builder(presets::Minimal)` creates a mutable `Builder` instance.
2. **Service Registration** – `Builder::address_lookup` accepts any type implementing `AddressLookupBuilder`. Types that already implement `AddressLookup` receive a blanket implementation automatically (see lines 91-98 of [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs)).
3. **Publishing** – When local transport addresses change, the `Endpoint` calls `AddressLookupServices::publish`, distributing `EndpointData` to each configured service.
4. **Resolving** – Remote resolution calls `AddressLookupServices::resolve(endpoint_id)`, returning a `BoxStream` of `Item` structs containing `EndpointInfo` and provenance strings. The `AddressLookupStream` merges streams from all services, yielding results as they arrive (lines 540-566).

The `AddressLookupServices` registry lives in [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs) and uses `Arc<RwLock>` for thread-safe access, supporting runtime modification via `add`, `clear`, and `set_addr_filter` methods.

## Configuring DNS Address Lookup

The DNS address lookup service resolves `_iroh.<z-base-32-pubkey>.<origin>` TXT records through the `DnsResolver` type defined in [`iroh-dns/src/dns.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/dns.rs).

### Default Resolver Behavior

By default, `DnsResolver` reads the system's [`/etc/resolv.conf`](https://github.com/n0-computer/iroh/blob/main//etc/resolv.conf) (or Android JNI bridge) and falls back to Google DNS if that fails (see `HickoryResolver::build_resolver` lines 30-40).

### Custom Nameserver Configuration

You can force a specific nameserver using `DnsResolver::with_nameserver(addr)`, which creates a resolver that talks exclusively to the supplied UDP server (lines 50-55):

```rust
use iroh_dns::DnsResolver;
use std::net::SocketAddr;

let resolver = DnsResolver::with_nameserver("10.0.0.53:53".parse::<SocketAddr>()?);

```

### Transport Protocol Selection

The `DnsProtocol` enum selects between UDP, TCP, TLS, or HTTPS (lines 38-65). For TLS and HTTPS connections, you can provide custom certificate verification via `Builder::tls_client_config` (lines 14-18).

The high-level wrapper `address_lookup::DnsAddressLookup` internally holds a `DnsResolver` and implements `AddressLookup`, exposing the helper `n0_dns()` which configures the resolver to query the public DNS zone `dns.iroh.link.` (constants `N0_DNS_ENDPOINT_ORIGIN_PROD` in [`iroh-dns/src/dns.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/dns.rs) lines 44-48).

## Publishing Endpoint Data with Pkarr

Pkarr lookup uses HTTP/TLS/DoH queries to a Pkarr relay that stores signed packets containing endpoint information. The implementation resides in [`iroh-dns/src/pkarr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/pkarr.rs).

### The PkarrPublisher

`PkarrPublisher` signs endpoint data with the node's secret key and stores the result in a Pkarr relay. The published packet format follows:

```

<32-byte pubkey><64-byte signature><8-byte timestamp><DNS packet>

```

**Signing Process** – `SignedPacket::from_bytes` verifies the signature, parses the DNS packet, and returns a `SignedPacket` (lines 90-114). **Timestamp Monotonicity** – `Timestamp::now` guarantees strictly increasing values even when the system clock jumps backward (lines 25-62).

Construct a publisher using the builder pattern:

```rust
use iroh::address_lookup::PkarrPublisher;

let publisher = PkarrPublisher::builder(pkarr_url)
    .build(secret_key, tls_config);

```

The helper `PkarrPublisher::n0_dns()` creates a pre-configured publisher targeting the public relay at `https://pkarr.n0.com/`.

## Combining DNS and Pkarr for Redundancy

A production configuration typically enables **both** services so peers remain discoverable even if one lookup mechanism fails. This pattern appears in [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs) lines 71-86:

```rust
use iroh::{
    Endpoint, SecretKey,
    address_lookup::{self, AddrFilter, PkarrPublisher},
    endpoint::presets,
};

let secret = SecretKey::generate();

let ep = Endpoint::builder(presets::Minimal)
    .addr_filter(AddrFilter::relay_only())
    .address_lookup(PkarrPublisher::n0_dns())
    .address_lookup(address_lookup::DnsAddressLookup::n0_dns())
    .bind()
    .await?;

```

**Key configuration details:**

- **`AddrFilter::relay_only()`** – Removes direct IP addresses before publishing, leaving only the relay URL. This prevents leaking direct addresses while still allowing relay-based connectivity.
- **`PkarrPublisher::n0_dns()`** – Targets the public Pkarr relay.
- **`DnsAddressLookup::n0_dns()`** – Queries the public iroh DNS zone.

When resolving a remote `EndpointId`, the `AddressLookupStream` merges results from both services. The first usable address wins while the slower service continues in the background, ensuring minimal connection latency.

## Customizing Lookups and Resolvers

You can replace the default resolver with your own implementation of the `Resolver` trait (see the `custom_resolver` test in [`iroh-dns/src/dns.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/dns.rs) lines 735-784). Similarly, implement `AddressLookup` and optionally `AddressLookupBuilder` to create entirely custom discovery mechanisms.

**Publishing only via Pkarr (no DNS):**

```rust
let endpoint = Endpoint::builder(presets::Minimal)
    .address_lookup(PkarrPublisher::n0_dns())
    .bind()
    .await?;

```

**Custom DNS resolver with specific nameserver:**

```rust
use iroh::address_lookup::DnsAddressLookup;
use iroh_dns::DnsResolver;

let resolver = DnsResolver::with_nameserver("10.0.0.53:53".parse::<SocketAddr>()?);
let dns_lookup = DnsAddressLookup::new(resolver);

let endpoint = Endpoint::builder(presets::Minimal)
    .address_lookup(dns_lookup)
    .bind()
    .await?;

```

## Runtime Behavior and Error Handling

Understanding how these services behave at runtime helps diagnose connectivity issues.

**Publishing** – Operations are fire-and-forget. If the underlying service requires async work, it spawns its own task (see `AddressLookup::publish` default implementation lines 33-41).

**Resolving** – Each service returns a `BoxStream`. The `AddressLookupStream` merges them, buffers per-service errors, and only fails after all services have errored with `AddressLookupFailed::NoResults`. If no service is configured, the error `NoServiceConfigured` is emitted (lines 548-555).

This design guarantees that a fast-failing resolver (e.g., temporary network error) does not suppress later results from a slower but working resolver. A regression test covering this scenario exists in [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs) lines 913-951.

**Resolving a remote peer manually:**

```rust
let remote_id = /* known EndpointId */;
let endpoint_addr = endpoint
    .address_lookup()
    .expect("lookup configured")
    .resolve(remote_id)
    .await?
    .next()
    .await
    .ok_or("no address found")??
    .to_endpoint_addr();

```

## Summary

- **Address lookup in Iroh** is implemented through the `AddressLookup` trait, registered via `Endpoint::builder().address_lookup()`.
- **DNS resolution** uses `DnsResolver` in [`iroh-dns/src/dns.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/dns.rs), supporting custom nameservers, transport protocols (UDP/TCP/TLS/HTTPS), and the public `dns.iroh.link.` zone.
- **Pkarr publishing** uses `PkarrPublisher` in [`iroh-dns/src/pkarr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/pkarr.rs), signing packets with your secret key and storing them at relays like `https://pkarr.n0.com/`.
- **Redundant lookups** are achieved by registering multiple services; `AddressLookupStream` merges results and uses the first available.
- **Filtering** via `AddrFilter::relay_only()` prevents direct IP leakage while maintaining relay connectivity.
- **Custom implementations** are supported through the `Resolver` and `AddressLookup` traits for private DNS zones or alternative discovery protocols.

## Frequently Asked Questions

### What is the difference between DNS and Pkarr lookup in Iroh?

DNS lookup resolves `_iroh.<z-base-32-pubkey>.<origin>` TXT records from traditional DNS servers, while Pkarr lookup queries HTTP-based relays that store cryptographically signed packets containing the same endpoint data. DNS relies on the global DNS infrastructure and caching, whereas Pkarr provides a decentralized alternative where users publish signed records to specific relays. Iroh can use both simultaneously for redundancy.

### How do I configure a custom DNS resolver for Iroh?

Use `DnsResolver::with_nameserver(addr)` to create a resolver targeting a specific UDP server, then wrap it in `DnsAddressLookup::new(resolver)`. Pass this to the endpoint builder via `.address_lookup()`. You can also configure TLS/HTTPS DNS by providing a custom `rustls::ClientConfig` through the resolver builder.

### Can I use Pkarr without DNS in Iroh?

Yes. Simply register only the `PkarrPublisher` with your endpoint builder using `.address_lookup(PkarrPublisher::n0_dns())`. The endpoint will publish its addresses to the configured Pkarr relay and resolve peers exclusively through Pkarr queries without performing any DNS lookups.

### How does Iroh handle failures when multiple lookup services are configured?

The `AddressLookupStream` merges results from all configured services and returns the first successful result immediately. If one service fails quickly (e.g., network timeout), it does not block results from slower but working services. The operation only fails with `AddressLookupFailed::NoResults` after all services have returned errors, ensuring maximum connectivity across unreliable networks.