# Understanding the iroh Node Address Lookup Module: A Deep Dive into Endpoint Discovery

> Discover how the iroh node address lookup module uses EndpointId to find peer transport addresses. Explore its pluggable, asynchronous system for publishing and streaming resolution.

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

---

**The iroh node address lookup module provides a pluggable, asynchronous system that enables endpoints to discover peer transport addresses using only an `EndpointId`, combining fire-and-forget publishing with streaming resolution through the `AddressLookup` trait.**

The iroh node address lookup module sits at the heart of peer discovery in the [n0-computer/iroh](https://github.com/n0-computer/iroh) stack. It bridges the gap between static endpoint identifiers and dynamic network conditions, allowing nodes to find each other across NATs, changing IP addresses, and intermittent connectivity without hardcoded addresses.

## Core Architecture of the iroh Node Address Lookup Module

### The AddressLookup Trait

At the center of the module lies the `AddressLookup` trait, defined in [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs). This trait establishes the contract for all address resolution services within the ecosystem.

The trait requires two fundamental operations:

- **`publish(&self, data: &EndpointData)`** – A fire-and-forget method that announces the local endpoint's addressing information, including relay URLs and direct transport addresses.
- **`resolve(&self, endpoint_id: EndpointId)`** – Returns an optional `BoxStream` that yields `Item` records or errors for the requested peer.

Implementations are free to execute these operations synchronously or spawn background tasks. The trait bounds require `Send + Sync + 'static`, ensuring thread-safe usage across the async runtime.

### Builder Integration with AddressLookupBuilder

The module integrates seamlessly with endpoint construction through the `AddressLookupBuilder` trait. The `Builder::address_lookup` method accepts any type implementing this builder pattern.

For simple services, a blanket implementation allows direct registration: `impl<T> AddressLookupBuilder for T where T: AddressLookup`. More complex builders that require access to the finished endpoint—such as those needing the secret key—implement `into_address_lookup` to receive the completed endpoint reference before producing the lookup service.

## Service Registry and Concurrent Resolution

The `AddressLookupServices` struct, located in [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs), functions as the central registry for lookup providers. This orchestration layer maintains a vector of boxed `AddressLookup` objects and implements several critical behaviors:

- **Broadcast publishing:** Incoming `EndpointData` propagates to all registered services simultaneously, with optional filtering via a global `AddrFilter`.
- **Parallel resolution:** The `AddressLookupStream` merges per-service resolve streams, yielding results as soon as any service produces them while buffering individual service errors internally.
- **Fault tolerance:** If one lookup service fails or times out, the registry continues querying remaining services, ensuring robustness against partial network failures.

## Built-in Address Lookup Implementations

The module ships with several production-ready implementations, each targeting different discovery scenarios.

### MemoryLookup for Testing and Custom Injection

`MemoryLookup`, defined in [`iroh/src/memory.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/memory.rs), provides an in-process, out-of-band store ideal for unit tests and controlled deployments. It maintains a local hashmap of endpoint IDs to addressing data without network overhead.

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

async fn endpoint_with_memory() -> n0_error::Result<Endpoint> {
    let memory = MemoryLookup::new();
    let ep = Endpoint::builder(presets::Minimal)
        .address_lookup(memory)
        .bind()
        .await?;
    Ok(ep)
}

```

### DNS and PKARR Services

For public internet discovery, the module offers DNS-based resolution coupled with PKARR (Public Key Address Resolution Resource) publishing:

- **`PkarrPublisher`** – Publishes endpoint addressing information to PKARR relay servers over HTTP.
- **`DnsAddressLookup`** – Resolves peers through standard DNS queries, typically paired with a `PkarrPublisher` for complete bidirectional functionality.

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

async fn make_endpoint() -> n0_error::Result<Endpoint> {
    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?;
    Ok(ep)
}

```

### Extended Protocols

Beyond the core crate, the ecosystem supports additional discovery mechanisms through separate packages:

- **`iroh-mdns-address-lookup`** – Enables local network discovery via multicast DNS.
- **`iroh-mainline-address-lookup`** – Utilizes the BitTorrent Mainline DHT for decentralized address storage.

## Resolving Peer Addresses in Practice

When an application needs to connect to a remote peer, it interacts with the lookup system through the endpoint's address lookup interface. The following pattern demonstrates consuming the resolution stream:

```rust
let remote_id = SecretKey::from_bytes(&rng.random()).public();
let resolver = ep.address_lookup().expect("open").clone();
let mut stream = resolver.resolve(remote_id);

while let Some(item) = stream.next().await {
    match item {
        Ok(Ok(resolved)) => {
            println!("found address: {}", resolved.to_endpoint_addr());
            break;
        }
        Ok(Err(err)) => eprintln!("lookup error: {err:#}"),
        Err(no_res) => eprintln!("lookup failed: {no_res:#}"),
    }
}

```

This example leverages the stream-merging logic found in [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs) (lines 331-367), which handles the complexity of multiplexing multiple lookup sources while surfacing the first successful result.

## Summary

- The **iroh node address lookup module** provides the `AddressLookup` trait in [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs), defining standardized `publish` and `resolve` operations for endpoint discovery.
- **Concurrent resolution** happens through `AddressLookupServices`, which queries all registered lookup providers simultaneously and returns the first usable address.
- **Multiple implementations** support diverse deployment scenarios, from in-process `MemoryLookup` for testing to DNS/PKARR for public internet discovery and mDNS for local networks.
- **Builder integration** allows flexible composition of lookup services through `AddressLookupBuilder`, supporting both simple direct registration and complex endpoint-dependent initialization.

## Frequently Asked Questions

### What is the primary purpose of the iroh node address lookup module?

The module solves the bootstrap problem in peer-to-peer networking by allowing iroh endpoints to discover how to reach other nodes using only a cryptographic `EndpointId`. It abstracts over various discovery mechanisms—DNS, DHT, mDNS, or custom protocols—providing a unified interface for publishing local addressing information and resolving remote addresses.

### How does the AddressLookup trait handle concurrent lookups?

The trait itself defines the interface, but the `AddressLookupServices` registry manages concurrency. When `resolve` is called, the registry spawns all registered lookup services simultaneously and merges their output streams. Results yield immediately upon arrival from any provider, while errors buffer internally unless all services fail.

### What is the difference between PkarrPublisher and DnsAddressLookup?

`PkarrPublisher` handles the outbound direction, publishing your endpoint's relay URLs and addresses to PKARR relay servers. `DnsAddressLookup` handles the inbound direction, querying DNS to resolve other peers' addresses. Typically, you configure both on an endpoint to enable bidirectional discovery through the public DNS infrastructure.

### Can I implement custom address lookup services for iroh?

Yes. Any type implementing the `AddressLookup` trait can integrate with the endpoint builder. For simple cases, implement the trait directly and pass the instance to `Builder::address_lookup`. For services requiring the endpoint's secret key or other runtime data, implement `AddressLookupBuilder` to receive the finished endpoint during construction.