# Configuring DNS and Pkarr Address Lookup Services in iroh

> Learn to configure iroh's DNS and Pkarr address lookup services using the Endpoint builder API for peer discovery. Translate EndpointId to EndpointAddr efficiently.

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

---

**Iroh discovers peers by translating `EndpointId` into `EndpointAddr` through pluggable address lookup services that support both DNS TXT records and Pkarr signed packets, configured via the `Endpoint` builder API.**

The `iroh` networking library enables decentralized peer discovery by publishing endpoint data to multiple resolution mechanisms simultaneously. By configuring both traditional DNS resolution and modern Pkarr (Public Key Addressable Resource Records) relays, applications can ensure connectivity across diverse network topologies. This guide examines the builder patterns, trait implementations, and configuration options found in the iroh source code for setting up robust address lookup services.

## How Address Lookup Services Work

Iroh abstracts peer discovery through the `AddressLookup` trait, which bridges endpoint identifiers with actual network addresses. The system separates **publishing** (advertising your endpoint's addresses) from **resolving** (discovering a remote peer's addresses), allowing each to scale independently.

### The Endpoint Builder Pattern

Configuration begins with `Endpoint::builder(presets::Minimal)`, which returns a mutable `Builder` struct. The `Builder::address_lookup` method accepts any type implementing `AddressLookupBuilder`, including types that already implement `AddressLookup` through a blanket implementation (lines 91-98 of [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs)).

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

let secret = SecretKey::generate();

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

```

### Publishing and Resolving Endpoints

When local transport addresses change, the `Endpoint` invokes `AddressLookupServices::publish`, distributing `EndpointData` to all configured services. For resolution, calling `AddressLookupServices::resolve(endpoint_id)` returns a `BoxStream` of `Item` structs containing `EndpointInfo` and provenance metadata. The `AddressLookupStream` (lines 540-566 of [`address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/address_lookup.rs)) merges results from all services, yielding the first available address while slower services continue in the background.

## Configuring DNS Resolution

The DNS implementation resides in [`iroh-dns/src/dns.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/dns.rs) and provides the `DnsResolver` type for querying `_iroh.<z-base-32-pubkey>.<origin>` TXT records. By default, the resolver reads system configuration from [`/etc/resolv.conf`](https://github.com/n0-computer/iroh/blob/main//etc/resolv.conf) (or Android JNI bridges) and falls back to Google DNS if initialization fails (lines 30-40 of [`dns.rs`](https://github.com/n0-computer/iroh/blob/main/dns.rs)).

### Custom Nameservers and Transport Protocols

The `DnsResolver::builder` API supports explicit nameserver configuration via `DnsResolver::with_nameserver(addr)`, which bypasses system settings and targets a specific UDP server (lines 50-55). Transport security is controlled through the `DnsProtocol` enum, supporting UDP, TCP, TLS, and HTTPS variants (lines 38-65).

```rust
use iroh::address_lookup::DnsAddressLookup;
use iroh_dns::DnsResolver;
use std::net::SocketAddr;

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

```

### TLS Verification

For TLS or HTTPS transports, customize the TLS verification via `Builder::tls_client_config`, accepting a `rustls::ClientConfig` (lines 14-18 of [`dns.rs`](https://github.com/n0-computer/iroh/blob/main/dns.rs)). This enables enterprise deployments with private certificate authorities or mutual TLS authentication.

## Implementing Pkarr Publishing

Pkarr provides censorship-resistant address publishing through cryptographically signed DNS packets stored on public relays. The implementation in [`iroh-dns/src/pkarr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/pkarr.rs) defines `PkarrPublisher`, which signs endpoint data using the node's secret key and uploads it to a relay via HTTP/TLS/DoH.

### Signed Packets and Relay Configuration

Each published packet contains a 32-byte public key, 64-byte signature, 8-byte timestamp, and the serialized DNS packet (see `SignedPacket::from_txt_strings`, lines 41-89). The `Timestamp::now` implementation guarantees strictly monotonic values even during system clock adjustments (lines 25-62).

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

let publisher = PkarrPublisher::n0_dns();

```

The `n0_dns()` helper configures the publisher to use the public relay at `https://pkarr.n0.com/` with appropriate TLS settings. For verification, `SignedPacket::from_bytes` validates signatures and parses DNS packets (lines 90-114).

## Combining DNS and Pkarr for Robust Discovery

Production deployments typically enable both services to maximize discoverability. The `AddressLookupStream` merges results from both mechanisms, ensuring that a fast DNS response immediately establishes connectivity while the Pkarr lookup continues asynchronously.

```rust
use iroh::address_lookup::{AddrFilter, DnsAddressLookup};

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

```

The `AddrFilter::relay_only()` filter (defined in [`address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/address_lookup.rs)) strips direct IP addresses before publishing, exposing only the relay URL for enhanced privacy. `DnsAddressLookup::n0_dns()` targets the public zone `dns.iroh.link.` (constants defined in [`dns.rs`](https://github.com/n0-computer/iroh/blob/main/dns.rs) lines 44-48).

## Creating Custom Lookup Services

You can extend iroh's discovery by implementing the `AddressLookup` and `AddressLookupBuilder` traits. The `AddressLookupServices` registry maintains service state in an `Arc<RwLock>`, supporting runtime modification via `add`, `clear`, and `set_addr_filter` methods.

The `Resolver` trait (used by `DnsResolver`) can also be implemented for custom resolution logic, as demonstrated in 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). This pattern enables integration with private DNS infrastructure or alternative discovery protocols.

## Runtime Behavior and Error Handling

**Publishing** operates fire-and-forget; services spawn internal tasks for async network operations (default implementation in `AddressLookup::publish`, lines 33-41 of [`address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/address_lookup.rs)).

**Resolving** aggregates streams from all services. The `AddressLookupStream` buffers per-service errors and only returns `AddressLookupFailed::NoResults` after all services have failed (lines 548-555). If no services are configured, it immediately returns `NoServiceConfigured`. This design prevents temporary network failures from blocking successful resolution via alternative methods, as verified by regression tests in the source (lines 913-951).

### Resolving Remote Peers

To resolve a specific `EndpointId`:

```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 services** translate `EndpointId` to `EndpointAddr` through pluggable traits defined in [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs).
- **DNS resolution** uses `DnsResolver` with configurable nameservers, transport protocols, and TLS settings located in [`iroh-dns/src/dns.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/dns.rs).
- **Pkarr publishing** signs endpoint data and publishes to relays via `PkarrPublisher` in [`iroh-dns/src/pkarr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/pkarr.rs), using monotonic timestamps for replay protection.
- **Combined configuration** uses the `Endpoint` builder to register multiple services, with `AddressLookupStream` merging results for optimal connectivity.
- **Custom implementations** can replace default resolvers by implementing the `Resolver` or `AddressLookup` traits, with thread-safe runtime registration.

## Frequently Asked Questions

### How do I configure a private DNS resolver instead of using public DNS?

Create a `DnsResolver` using `DnsResolver::with_nameserver(socket_addr)` to specify your internal DNS server IP and port, then wrap it in `DnsAddressLookup::new(resolver)`. This bypasses the default system resolver configuration and directs all queries to your specified endpoint.

### What is the difference between `PkarrPublisher` and `PkarrResolver`?

`PkarrPublisher` signs and uploads your local endpoint's addresses to a Pkarr relay using your secret key, while `PkarrResolver` queries relays to retrieve signed packets for remote peers. The publisher handles the "advertising" side, whereas the resolver handles the "discovery" side of the peer connection flow.

### Why would I use `AddrFilter::relay_only()` when configuring my endpoint?

This filter removes direct IP addresses from the published endpoint data, exposing only the relay URL. According to the implementation in [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs), this prevents direct connections to your node, forcing all traffic through configured relays for enhanced privacy and NAT traversal reliability.

### Can I add or remove address lookup services after the endpoint has started?

Yes. The `AddressLookupServices` registry uses an `Arc<RwLock>` internally, providing methods like `add`, `clear`, and `set_addr_filter` that modify the active service set at runtime. However, these changes only affect future publish and resolve operations; existing connections remain unaffected.