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

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 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. 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, 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, 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.

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.
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:

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 (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, 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.

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 →