How to Connect to an Iroh Endpoint by EndpointId: Complete Guide
You can connect to an Iroh endpoint using only its EndpointId by leveraging the library's built-in DNS resolution service, which automatically resolves the cryptographic identifier to network addresses without requiring manual IP or relay configuration.
The n0-computer/iroh crate enables peer-to-peer networking using cryptographic identifiers rather than traditional IP addresses. When you need to connect to an iroh endpoint by EndpointId, the library internally handles all address discovery through its PKARR DNS backend, allowing you to initiate connections with nothing more than the remote endpoint's public key identifier.
How EndpointId-Based Connection Works
Understanding the EndpointId
In Iroh, every endpoint is identified by an EndpointId — the public key of its SecretKey. This 32-byte identifier serves as the permanent, cryptographically-verifiable address for the node, regardless of its current network location or IP address.
The DNS Resolution Pipeline
Iroh achieves address-free connections through its address-lookup service, which uses a PKARR DNS backend. The resolution process works as follows:
-
DNS Name Construction: The library encodes the raw EndpointId using the DNS-SEC-compatible Base-32 alphabet (
BASE32_DNSSEC) and constructs a DNS name in the format<base32-encoded-id>.iroh.invalid. -
TLS Server Name Generation: The resolver generates the TLS server name by encoding the same ID via
iroh::tls::name::encode, ensuring the TLS session-ticket cache is correctly partitioned for 0-RTT support. -
Address Retrieval: The resolver queries DNS (or DoH) for
PTR/TXTrecords that return a list ofTransportAddrvalues, containing either direct UDP addresses or relay URLs. -
Connection Establishment: The endpoint attempts direct UDP hole-punching; if that fails, it falls back to the relay URL(s) returned by the lookup, then establishes a QUIC connection authenticated by the remote's public key.
Setting Up the N0 Preset for Automatic Resolution
To enable address discovery by EndpointId, you must configure your local endpoint with the N0 preset. This preset automatically installs the DNS resolver into the endpoint's address-resolution pipeline.
use iroh::endpoint::{presets, Endpoint};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create endpoint with N0 preset to enable DNS lookup
let endpoint = Endpoint::bind(presets::N0).await?;
println!("Local endpoint ready: {}", endpoint.id());
Ok(())
}
As implemented in iroh/src/endpoint.rs, the N0 preset configures the endpoint to resolve EndpointId values to EndpointAddr objects automatically, making the Endpoint::connect method capable of working with raw identifiers.
Connecting to a Remote Endpoint by EndpointId
Once your endpoint is configured with the N0 preset, you can initiate connections using only the remote EndpointId and an ALPN (Application-Layer Protocol Negotiation) identifier.
use iroh::{
endpoint::{presets, Endpoint},
EndpointId,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Build a local endpoint with the N0 preset
let endpoint = Endpoint::bind(presets::N0).await?;
// 2. Parse the remote EndpointId (obtain from remote side, QR code, etc.)
let remote_id: EndpointId = "7dl2ff6emqi2qol3l382krodedij45bn3nh479hqo14a32qpr8kg"
.parse()
.expect("valid endpoint id");
// 3. Define your protocol's ALPN identifier
const MY_ALPN: &[u8] = b"myapp/example/0";
// 4. Connect - the library resolves addresses automatically
let conn = endpoint.connect(remote_id, MY_ALPN).await?;
// 5. Open a bidirectional stream and communicate
let (mut send, mut recv) = conn.open_bi().await?;
send.write_all(b"Hello from the client!").await?;
send.finish()?;
let reply = recv.read_to_end(1024).await?;
println!("Remote replied: {}", String::from_utf8_lossy(&reply));
// Clean shutdown
endpoint.close().await;
Ok(())
}
The Endpoint::connect method in iroh/src/endpoint.rs handles the entire resolution workflow internally, including encoding the ID to the DNS name format, querying for transport addresses, and managing the QUIC handshake.
Manual Address Resolution (Optional)
If you need to inspect the concrete network addresses before establishing a connection, you can query the DNS resolver directly using the lower-level API defined in iroh-dns.
use iroh_dns::Resolver;
use iroh_base::EndpointId;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a resolver using system DNS/DoH configuration
let resolver = Resolver::default();
// Parse the EndpointId from its z32 encoding
let remote_id = EndpointId::from_z32(
"7dl2ff6emqi2qol3l382krodedij45bn3nh479hqo14a32qpr8kg"
)?;
// Resolve to a list of TransportAddrs (IPs and relay URLs)
let addrs = resolver.resolve(remote_id).await?;
println!("Resolved addresses:");
for addr in addrs {
println!(" {}", addr);
}
Ok(())
}
According to the source code in iroh-dns/src/dns.rs, the resolver returns TransportAddr values that the endpoint uses internally for hole-punching attempts and relay fallback.
Key Implementation Files
The following source files in the n0-computer/iroh repository contain the core implementation details:
iroh/src/endpoint.rs: ImplementsEndpoint::bind,Endpoint::connect, and the address-resolution pipeline that integrates theN0preset.iroh/src/tls/name.rs: Contains the logic for encoding anEndpointIdto a DNS name (<base32>.iroh.invalid) for TLS server-name indication.iroh-dns/src/endpoint_info.rs: Defines the DNS record format that carries the list ofTransportAddrvalues for an endpoint.iroh-dns/src/dns.rs: Implements the DNS (and DoH) resolver used by theN0preset.
Summary
- EndpointId as Address: Iroh uses the endpoint's public key (
EndpointId) as its permanent identifier, eliminating the need to track changing IP addresses. - Automatic Resolution: The
N0preset enables a DNS resolver that mapsEndpointIdtoEndpointAddrusing the PKARR backend with Base-32 encoded DNS names. - Simple API: Call
Endpoint::connectwith just the remoteEndpointIdand ALPN; the library handles address discovery, hole-punching, and relay fallback automatically. - TLS Integration: The resolution system encodes the
EndpointIdinto the TLS server name usingiroh::tls::name::encode, ensuring proper certificate validation and 0-RTT support.
Frequently Asked Questions
What DNS encoding does Iroh use for EndpointId resolution?
Iroh encodes the EndpointId using the BASE32_DNSSEC alphabet (z-base-32 variant) and appends .iroh.invalid to form the DNS query name. This encoding is implemented in iroh/src/tls/name.rs and ensures compatibility with DNS-SEC while producing case-insensitive, pronounceable identifiers.
Does the N0 preset use standard DNS or DNS over HTTPS?
The N0 preset configures the resolver to use both standard DNS and DNS over HTTPS (DoH) depending on system configuration. According to iroh-dns/src/dns.rs, the resolver defaults to system DNS settings but can be configured to use specific DoH endpoints for enhanced privacy and security.
Can I connect to an Iroh endpoint without using the N0 preset?
Yes, but you must provide the full EndpointAddr (including direct UDP addresses and relay URLs) manually. Without the N0 preset, the endpoint lacks the DNS resolver component, so Endpoint::connect cannot resolve raw EndpointId values. You would need to obtain addresses through an out-of-band mechanism and pass them to the connection method.
How does Iroh handle NAT traversal when connecting by EndpointId?
The address resolution process returns both direct UDP addresses and relay URLs. Iroh automatically attempts direct UDP hole-punching using the returned addresses; if direct connectivity fails, it falls back to connecting via the relay URL. This process is transparent to the caller and happens automatically during the connect call.
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 →