# How to Configure Multiple Address Lookup Services in iroh

> Configure multiple address lookup services in iroh by registering AddressLookup trait implementations. Iroh automatically merges resolution streams for efficient remote EndpointId resolution.

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

---

**You configure multiple address lookup services in iroh by registering implementations of the `AddressLookup` trait or `AddressLookupBuilder` via `Endpoint::builder().address_lookup()`, which stores them in an `AddressLookupServices` registry that automatically merges resolution streams from all services when resolving remote EndpointIds.**

When building peer-to-peer applications with the n0-computer/iroh framework, endpoints need a mechanism to discover how to reach remote peers knowing only their cryptographic **EndpointId**. The address lookup subsystem solves this by allowing you to publish local addressing information (relay URLs, direct sockets) to external discovery services and resolve others' addresses through multiple parallel channels.

## Understanding the Core Architecture

The address lookup system in iroh is built around three primary components defined in [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs).

### The AddressLookup Trait

At the heart of the system is the **`AddressLookup`** trait (lines 33-50), which defines the minimal interface for any discovery service. Implementations must provide two capabilities: publishing local endpoint data via `publish()` and resolving remote endpoints via `resolve()`. The trait returns a boxed stream of results, allowing for asynchronous resolution.

### AddressLookupServices Registry

Each `Endpoint` maintains an **`AddressLookupServices`** registry (lines 60-89 in [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs)) that stores a list of boxed `AddressLookup` instances. This registry handles the lifecycle of address publication and resolution. When you call `Builder::address_lookup()`, you are adding a service to this registry. The registry stores the last published `EndpointData` and forwards it to all registered services whenever addresses change.

### AddrFilter for Address Control

Before data reaches any service, you can apply an **`AddrFilter`** to prune or reorder addresses. This user-supplied function filters the `EndpointData` before publication, useful for scenarios like "relay-only" mode where you want to hide direct IP addresses from external services.

## Built-in Service Types

Iroh provides several production-ready and testing implementations:

- **`MemoryLookup`** – In-process registry ideal for unit tests (located in [`iroh/src/memory.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/memory.rs))
- **`DnsAddressLookup`** – Resolves peers via standard DNS using the `iroh-dns` crate (located in [`iroh-dns/src/dns.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/dns.rs))
- **`PkarrPublisher`** – Publishes to and resolves from pkarr relays via HTTP (located in [`iroh-dns/src/pkarr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/pkarr.rs))

## Configuring Multiple Lookup Services

To configure multiple address lookup services in iroh, chain multiple `.address_lookup()` calls on the `Endpoint` builder. The system automatically merges results from all registered services using an `AddressLookupStream`.

### Combining DNS and Pkarr Services

The most common production setup combines DNS-based resolution with pkarr publishing:

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

#[tokio::main]
async fn main() -> Result<(), iroh::Error> {
    let ep = Endpoint::builder(presets::Minimal)
        .addr_filter(AddrFilter::relay_only())
        .address_lookup(address_lookup::PkarrPublisher::n0_dns())
        .address_lookup(address_lookup::DnsAddressLookup::n0_dns())
        .bind()
        .await?;
    
    Ok(())
}

```

This configuration publishes the local endpoint's relay URL to the public **n0.computer** pkarr service while simultaneously enabling DNS resolution for discovering other peers. Both services receive the same filtered address data.

### Adding In-Memory Lookup for Testing

For integration tests or embedded scenarios, register a `MemoryLookup` alongside production services:

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

let memory = MemoryLookup::new();
let ep = Endpoint::builder(presets::Minimal)
    .address_lookup(memory)
    .bind()
    .await?;

```

### Implementing Custom Services

You can implement the `AddressLookup` trait for custom discovery mechanisms:

```rust
struct MyResolver;

impl iroh::address_lookup::AddressLookup for MyResolver {
    fn resolve(&self, endpoint_id: iroh_base::EndpointId)
        -> Option<BoxStream<Result<iroh::address_lookup::Item, iroh::address_lookup::Error>>> {
        // Custom resolution logic returning a stream of results
        None
    }
}

let ep = Endpoint::builder(presets::Minimal).bind().await?;
ep.address_lookup().unwrap().add(MyResolver);

```

## Configuring Address Filters

Apply filters to control what information gets published to all services. You can set this during building or dynamically after the endpoint starts:

```rust
let ep = Endpoint::builder(presets::Minimal).bind().await?;
let lookup = ep.address_lookup().expect("endpoint still open");

lookup.set_addr_filter(address_lookup::AddrFilter::relay_only());

```

This ensures only relay URLs are published, keeping direct socket addresses private from all lookup services.

## How Merged Resolution Works

When `Endpoint::connect` needs to resolve a remote peer, `AddressLookupServices::resolve` creates an **`AddressLookupStream`** that merges streams from all registered services. According to the implementation in [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs), the merge logic buffers errors and yields results as soon as any service returns them. A fast-failing service does not prevent the system from using results from slower services, as demonstrated in the test `address_lookup_succeeds_after_other_resolver_errors`. The stream only ends with `AddressLookupFailed::NoResults` if **all** services return empty results.

## Summary

- Configure multiple services by chaining `.address_lookup()` calls on `Endpoint::builder()`
- The `AddressLookup` trait (in [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs)) defines the interface for custom discovery services, while `AddressLookupBuilder` allows services to access endpoint configuration during construction
- `AddressLookupServices` automatically merges resolution streams from all registered services using `AddressLookupStream`
- Use `AddrFilter` to control what endpoint data gets published to external services
- Common combinations include `PkarrPublisher` with `DnsAddressLookup` for production deployments
- The merge algorithm ensures reliability by not letting a single failing service block successful resolutions from others

## Frequently Asked Questions

### How do I add a custom address lookup service to an iroh endpoint?

Implement the `AddressLookup` trait and register it using `Endpoint::builder().address_lookup()` before calling `bind()`. The trait requires implementing `publish()` for advertising your local endpoint and `resolve()` for querying remote endpoints. Alternatively, implement `AddressLookupBuilder` (lines 41-50) if your service needs to read the endpoint's configuration before becoming active. You can also add services after the endpoint is built by calling `ep.address_lookup().unwrap().add(service)`.

### What happens if one address lookup service fails but another succeeds?

The `AddressLookupServices` registry in [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs) merges streams from all services and buffers errors separately. Results are yielded as soon as any service returns them, so a fast failure does not hide later successes from slower services. Only if **all** services return no results does the resolution fail with `AddressLookupFailed::NoResults`.

### Can I filter which addresses get published to lookup services?

Yes. Set an `AddrFilter` using `Builder::addr_filter()` or `lookup.set_addr_filter()` after the endpoint is built. This function filters the `EndpointData` before it reaches any registered service, allowing you to restrict published information to relay URLs only, direct addresses only, or apply custom logic.

### Where are the built-in DNS and pkarr services implemented?

The DNS address lookup implementation resides in [`iroh-dns/src/dns.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/dns.rs), while the pkarr publisher and resolver are in [`iroh-dns/src/pkarr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/pkarr.rs). These are re-exported through the main `iroh` crate's address lookup module. The memory lookup implementation for testing is in [`iroh/src/memory.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/memory.rs).