How to Configure Multiple Address Lookup Services in iroh

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.

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) 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)
  • DnsAddressLookup – Resolves peers via standard DNS using the iroh-dns crate (located in iroh-dns/src/dns.rs)
  • PkarrPublisher – Publishes to and resolves from pkarr relays via HTTP (located in 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:

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:

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:

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:

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, 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) 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 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, while the pkarr publisher and resolver are in 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.

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 →