How iroh's DNS/Pkarr Address Lookup Service Resolves Endpoints: A Deep Dive into the Resolution Pipeline
iroh's DNS/Pkarr address lookup service resolves endpoints by constructing a DNS name from the endpoint's public key, querying TXT records containing Pkarr-signed packets, verifying cryptographic signatures, and parsing the embedded DNS data into structured connection information.
The n0-computer/iroh repository implements a decentralized endpoint resolution system that combines DNS infrastructure with Pkarr cryptographic signatures. When a client needs to connect to a remote peer, the DnsResolver constructs a specialized query, validates the cryptographic proof, and extracts connection details. This article examines the exact resolution flow implemented in iroh-dns/src/dns.rs and iroh-dns/src/pkarr.rs.
The Five-Step Resolution Process
The resolution pipeline follows a strict sequence from identifier to verified connection data. According to the iroh source code, the service completes endpoint resolution through five distinct phases:
- DNS name construction – Formatting the
_iroh.<z-base-32-encoded-public-key>.<origin>query string - TXT record lookup – Querying DNS with a 3-second timeout via the HickoryResolver backend
- Packet verification – Validating the 32-byte public key and 64-byte Ed25519 signature in the Pkarr packet
- Data extraction – Parsing the embedded DNS wire format into structured fields
- Caching and resilience – Storing results and handling network failures through staggered retries
Each phase involves specific struct methods and constant definitions found in the iroh-dns crate.
Constructing the DNS Query Name
The resolution begins in DnsResolver::lookup_endpoint_by_id, which transforms an EndpointId into a DNS-compatible hostname. The name format follows a strict pattern:
_iroh.<z-base-32-encoded-public-key>.<origin>
The <origin> suffix determines which DNS infrastructure to query. The production origin is dns.iroh.link., while testing environments use staging-dns.iroh.link.. The public key is extracted from the EndpointId and encoded using Z-base-32, a URL-safe encoding that avoids ambiguous characters.
This formatted string becomes the query target for the underlying DNS resolver.
Querying TXT Records with HickoryResolver
Once the DNS name is constructed, DnsResolver::lookup_txt initiates the network request. The implementation delegates to HickoryResolver::lookup_txt, which utilizes the hickory_resolver crate for actual DNS communication.
The lookup operates with a hardcoded 3-second timeout (DNS_TIMEOUT constant). The resolver queries for TXT records specifically, expecting a payload that contains a Pkarr-signed DNS packet. If the DNS server responds successfully, the raw TXT strings are passed to the decoding phase.
Verifying Pkarr Signed Packets
The core security mechanism resides in iroh-dns/src/pkarr.rs. The EndpointInfo::from_txt_lookup method receives the TXT record iterator and processes it through SignedPacket::from_bytes.
The binary layout of a signed packet follows this exact structure:
- 32 bytes: Public key
- 64 bytes: Ed25519 signature
- 8 bytes: Timestamp
- Variable: DNS wire-format packet
The constants HEADER_SIZE (104 bytes) and MAX_SIGNED_PACKET_SIZE define the buffer constraints. The verification process calls PublicKey::verify to validate the signature against the embedded public key and timestamp. After cryptographic verification, simple_dns::Packet::parse extracts the inner DNS packet.
Helper methods SignedPacket::txt_records and SignedPacket::all_txt_records convert the raw DNS answers into iterable strings that the endpoint parser consumes.
Extracting EndpointInfo
After successful verification, EndpointInfo::from_txt_lookup constructs the final EndpointInfo struct. This structure contains the concrete connection details extracted from the DNS TXT records, including:
relay_url: The relay server URL for NAT traversalquic_addr: QUIC protocol address and porttcp_addr: TCP protocol address and port
These fields provide the caller with multiple connection paths to the remote endpoint, supporting both direct UDP/QUIC connections and relayed TCP fallbacks.
Caching and Network Resilience
The DnsResolver implements several resilience mechanisms. The resolver caches DNS responses internally to minimize redundant network traffic. Cache invalidation occurs explicitly through DnsResolver::clear_cache, while network interface changes trigger DnsResolver::reset to rebuild the underlying resolver instance.
For unreliable networks, lookup_endpoint_by_id_staggered implements jittered retries. This method accepts a slice of millisecond delays (e.g., [200, 500]) and attempts the lookup multiple times with the specified intervals, improving success rates on flaky connections.
Code Examples
Resolve an Endpoint by EndpointId
use iroh_dns::{DnsResolver, N0_DNS_ENDPOINT_ORIGIN_PROD};
use iroh_base::EndpointId;
async fn resolve_endpoint(id: EndpointId) -> Result<(), Box<dyn std::error::Error>> {
// Create a resolver with default system DNS settings
let resolver = DnsResolver::new();
// Perform the lookup with the default 3-second timeout
let info = resolver
.lookup_endpoint_by_id(&id, N0_DNS_ENDPOINT_ORIGIN_PROD)
.await?;
println!("Resolved endpoint: {:?}", info);
Ok(())
}
Resolve by Human-Readable Domain Name
use iroh_dns::DnsResolver;
async fn resolve_by_name(name: &str) -> Result<(), Box<dyn std::error::Error>> {
let resolver = DnsResolver::default();
// Automatically adds the "_iroh." prefix if missing
let info = resolver
.lookup_endpoint_by_domain_name(name)
.await?;
println!("Endpoint info for {name}: {:?}", info);
Ok(())
}
Staggered Lookup with Retry Logic
use iroh_dns::DnsResolver;
use iroh_base::EndpointId;
async fn resolve_staggered(id: EndpointId) -> Result<(), Box<dyn std::error::Error>> {
let resolver = DnsResolver::default();
// Try up to three attempts with 0 ms, 200 ms and 500 ms delays
let delays = [200u64, 500];
let info = resolver
.lookup_endpoint_by_id_staggered(&id, iroh_dns::N0_DNS_ENDPOINT_ORIGIN_PROD, &delays)
.await?;
println!("Staggered result: {:?}", info);
Ok(())
}
Key Source Files
The resolution pipeline spans several critical files in the n0-computer/iroh repository:
iroh-dns/src/dns.rs: ImplementsDnsResolver, theResolvertrait, and high-level lookup APIs includinglookup_endpoint_by_idandlookup_endpoint_by_domain_nameiroh-dns/src/pkarr.rs: Defines theSignedPacketstruct, signature verification logic, and packet parsing constants (HEADER_SIZE,MAX_SIGNED_PACKET_SIZE)iroh-dns/src/endpoint_info.rs: Contains theEndpointInfostruct andfrom_txt_lookupmethod for extracting connection detailsiroh-base/src/endpoint_addr.rs: ImplementsEndpointIdand Z-base-32 encoding for DNS name construction
Summary
- iroh's DNS/Pkarr address lookup service combines DNS TXT records with Pkarr cryptographic signatures to distribute endpoint information securely.
- The resolution process constructs a DNS name from the Z-base-32 encoded public key, queries the
_iroh.subdomain, and expects a 3-second response timeout. - Security relies on Ed25519 signatures: The 104-byte header (32-byte pubkey + 64-byte signature + 8-byte timestamp) verifies the authenticity of the embedded DNS packet.
- Connection details are extracted into an
EndpointInfostruct containing relay URLs and direct QUIC/TCP addresses. - Resilience features include DNS caching, automatic resolver reset on network changes, and staggered lookups with configurable retry delays.
Frequently Asked Questions
What is the exact format of the DNS name used by iroh's lookup service?
The DNS name follows the pattern _iroh.<z-base-32-encoded-public-key>.<origin>. The origin is typically dns.iroh.link. for production or staging-dns.iroh.link. for testing environments. The public key is extracted from the EndpointId and encoded using Z-base-32 encoding to create a URL-safe string suitable for DNS labels.
How does iroh verify the authenticity of endpoint information?
Authenticity is ensured through Pkarr signed packets. The TXT record contains a binary payload with a 32-byte public key, 64-byte Ed25519 signature, and 8-byte timestamp. The SignedPacket::from_bytes method in iroh-dns/src/pkarr.rs verifies the signature using PublicKey::verify before parsing the embedded DNS data. This cryptographic verification prevents tampering with endpoint addresses during DNS resolution.
What is the timeout for DNS queries in iroh's resolver?
The default timeout for DNS queries is 3 seconds, defined as a constant in the DNS resolver implementation. If the query exceeds this duration, the resolution fails and returns an error. For unreliable networks, developers can use lookup_endpoint_by_id_staggered to implement retry logic with custom delays.
How does iroh handle network failures during endpoint resolution?
The resolver implements multiple resilience strategies. The DnsResolver caches successful responses and clears the cache via clear_cache() when necessary. On network interface changes, reset() rebuilds the underlying resolver. Additionally, the lookup_endpoint_by_id_staggered method accepts an array of millisecond delays (e.g., [200, 500]) to perform jittered retries, attempting the lookup multiple times before failing.
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 →