How to Use DnsAddressLookup with Iroh: A Complete Guide to Distributed Naming
Iroh's DnsResolver provides asynchronous DNS resolution for IPv4, IPv6, TXT records, and specialized Iroh endpoint lookups, featuring configurable timeouts, staggered retries, and runtime-swappable resolver implementations.
Iroh (n0-computer/iroh) is a Rust library for building distributed systems that relies on robust peer discovery mechanisms. The DnsAddressLookup functionality enables your application to resolve hostnames and cryptographic node identities via DNS, including custom _iroh. endpoint records that bridge public keys to network addresses. This guide covers the implementation details found in iroh-dns/src/dns.rs and demonstrates practical usage patterns.
Understanding the DnsResolver Architecture
At the core of Iroh's DNS functionality is a flexible resolver system designed for production distributed workloads.
The Resolver Trait
The Resolver trait defines the contract for all DNS operations in Iroh. Located at [lines 52-64 in iroh-dns/src/dns.rs](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/dns.rs#L52-L64), this trait specifies async methods for resolving IPv4 addresses, IPv6 addresses, and TXT records. By programming against this trait rather than concrete implementations, you can swap resolver backends at runtime for testing or specialized network environments.
The DnsResolver Struct
The DnsResolver struct serves as the primary entry point for DNS operations. As implemented at lines 40-55, it maintains an Arc<Inner> containing an ArcSwap<Box<dyn Resolver>>. This architecture allows Iroh to atomically swap resolver implementations without interrupting in-flight queries—critical for handling network configuration changes in long-running applications.
All lookup operations flow through Inner::op, which handles timeout enforcement, error translation, and cache coordination.
Creating a DNS Resolver
Using the Builder API
For applications requiring custom nameservers or DNS-over-TLS/HTTPS, Iroh exposes a builder pattern at lines 29-61. The builder supports system default resolution, custom upstream servers, or fallback to public resolvers like Google DNS.
use iroh_dns::dns::{DnsResolver, N0_DNS_ENDPOINT_ORIGIN_PROD};
use std::time::Duration;
// Create resolver with system defaults
let resolver = DnsResolver::new();
// The resolver is now ready for async DNS operations
Performing DNS Address Lookups
Standard IP Resolution
Resolve standard hostnames to IPv4 or IPv6 addresses using lookup_ipv4 and lookup_ipv6. Both methods accept a hostname string and a Duration timeout, returning async iterators over IpAddr values.
// IPv4 lookup with 3-second timeout
let ipv4_iter = resolver
.lookup_ipv4("example.com", Duration::from_secs(3))
.await?;
for ip in ipv4_iter {
println!("Resolved IPv4: {}", ip);
}
Iroh Endpoint Resolution
Iroh extends standard DNS with endpoint records that map EndpointId values (Z-base-32 encoded public keys) to connection information. The lookup_endpoint_by_id method, found at lines 49-57, constructs the proper DNS name format _iroh.<z32encoded-pubkey>.<origin> and queries the TXT record for EndpointInfo.
use iroh_base::EndpointId;
// EndpointId from a Z-base-32 encoded public key
let endpoint_id = EndpointId::from_z32("z32encodedpubkey...")?;
// Lookup against the production Iroh DNS origin
let info = resolver
.lookup_endpoint_by_id(&endpoint_id, N0_DNS_ENDPOINT_ORIGIN_PROD)
.await?;
println!("Relay URL: {:?}", info.relay_url);
Resilient Lookup Patterns
Staggered Lookups for Network Resilience
When operating over flaky networks, the stagger_call implementation at lines 333-369 provides retry logic with configurable delays. Staggered methods like lookup_ipv6_staggered and lookup_endpoint_by_domain_name_staggered issue multiple requests with specified millisecond intervals, returning the first successful result.
// IPv6 lookup with staggered retries at 200ms and 500ms
let ipv6_iter = resolver
.lookup_ipv6_staggered("example.com", Duration::from_secs(3), &[200, 500])
.await?;
// Staggered endpoint lookup by domain name
let endpoint_info = resolver
.lookup_endpoint_by_domain_name_staggered("_iroh.myexample.com", &[100, 300])
.await?;
Managing Resolver State
Cache Handling and Reset
Long-running applications must handle network changes such as VPN connections or Wi-Fi roaming. The DnsResolver provides two state management methods:
clear_cache()(lines 73-77): Empties the internal DNS cache while preserving the current resolver configuration.reset()(lines 78-85): Rebuilds the underlying Hickory DNS client with fresh system configuration, useful after network interface changes.
// Clear cached entries without rebuilding the client
resolver.clear_cache();
// Rebuild resolver with updated system DNS settings
resolver.reset();
Complete Working Example
use iroh_dns::dns::{DnsResolver, DnsError, LookupError, N0_DNS_ENDPOINT_ORIGIN_PROD};
use iroh_base::EndpointId;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Build a resolver with the system defaults
let resolver = DnsResolver::new();
// Simple IPv4 lookup
let host = "example.com";
let ipv4_iter = resolver
.lookup_ipv4(host, Duration::from_secs(3))
.await?;
for ip in ipv4_iter {
println!("IPv4 for {}: {}", host, ip);
}
// IPv6 lookup with staggered retry delays
let ipv6_iter = resolver
.lookup_ipv6_staggered(host, Duration::from_secs(3), &[200, 500])
.await?;
for ip in ipv6_iter {
println!("IPv6 for {}: {}", host, ip);
}
// Resolve a TXT record for generic service discovery
let txt_iter = resolver
.lookup_txt("_myservice.example", Duration::from_secs(3))
.await?;
for txt in txt_iter {
println!("TXT: {}", txt);
}
// Iroh endpoint lookup by EndpointId
let endpoint_id = EndpointId::from_z32("z32encodedpubkey...")?;
let info = resolver
.lookup_endpoint_by_id(&endpoint_id, N0_DNS_ENDPOINT_ORIGIN_PROD)
.await?;
println!("Endpoint info: {:#?}", info);
// Staggered endpoint lookup by domain name for extra resilience
let endpoint_info = resolver
.lookup_endpoint_by_domain_name_staggered("_iroh.myexample.com", &[100, 300])
.await?;
println!("Staggered endpoint info: {:#?}", endpoint_info);
// Reset the resolver after a network change
resolver.reset();
Ok(())
}
Summary
DnsResolveriniroh-dns/src/dns.rsserves as the primary interface for all DNS operations in Iroh, wrapping a Hickory DNS client with超时 and caching logic.- The
Resolvertrait (lines 52-64) abstracts lookup operations, enabling runtime swapping of resolver implementations viaArcSwap. - Endpoint lookups translate
EndpointIdvalues into DNS queries against_iroh.<z32id>.<origin>TXT records, bridging cryptographic identities with network addresses. - Staggered lookups (lines 333-369) improve reliability on unstable networks by retrying with configurable delays until the first success.
- Use
reset()(lines 78-85) to rebuild the resolver after network configuration changes, andclear_cache()(lines 73-77) to invalidate stale DNS entries.
Frequently Asked Questions
How do I configure custom DNS servers instead of system defaults?
Use the Builder API in iroh-dns/src/dns.rs (lines 29-61) to specify custom nameservers, DNS-over-TLS, or DNS-over-HTTPS endpoints before constructing the DnsResolver. If no custom configuration is provided, DnsResolver::new() automatically falls back to system settings with Google DNS as a backup.
What is the format of an Iroh DNS endpoint record?
Iroh endpoint records follow the pattern _iroh.<z32encoded-pubkey>.<origin>, where the public key is encoded using Z-base-32. The lookup_endpoint_by_id method automatically constructs this name from an EndpointId and queries the corresponding TXT record, parsing the result into an EndpointInfo struct containing relay URLs and direct connection addresses.
How do staggered lookups improve DNS reliability?
Staggered lookups implement a race-with-retry pattern defined at lines 333-369. Instead of failing immediately on timeout, the resolver issues multiple queries with specified millisecond delays (e.g., [200, 500]), returning the first successful response. This pattern is particularly effective for IPv6 resolution or endpoint discovery in mobile environments with intermittent connectivity.
When should I call reset() versus clear_cache() on the resolver?
Call clear_cache() when you suspect cached DNS entries are stale but the underlying network configuration remains valid. Call reset() (lines 78-85) after system network changes—such as switching from Wi-Fi to cellular or connecting to a VPN—to rebuild the Hickory DNS client with updated system nameserver settings.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →