How to Connect to an Iroh Endpoint Using Only Its EndpointId

You can connect to an Iroh node using just its EndpointId by enabling the N0 preset, which automatically resolves the node's network address via DNS-based discovery and establishes a QUIC connection without requiring manual IP or relay configuration.

The n0-computer/iroh crate supports address-free connections through built-in DNS resolution that maps an endpoint's public key to its network location. When you want to connect to an Iroh endpoint using only its EndpointId, the library handles all transport address discovery, hole-punching, and relay fallback internally.

What Is an EndpointId?

In Iroh, every endpoint is cryptographically identified by its EndpointId, which is the public key corresponding to the endpoint's SecretKey. This identifier is stable across network changes and serves as the sole addressing requirement when using the discovery service.

Rather than requiring you to track IP addresses or relay URLs manually, Iroh treats the EndpointId as a self-authenticating address. The library resolves this identifier to concrete network paths through a PKARR DNS backend that queries DNS records containing the endpoint's current TransportAddr list.

How DNS-Based Address Discovery Works

When you use the default N0 preset, Iroh installs a DNS resolver that transforms an EndpointId into a resolvable DNS name. The resolver encodes the ID using the BASE32_DNSSEC alphabet and constructs a query name in the format:


<base32-encoded-id>.iroh.invalid

The DNS lookup (performed via standard DNS or DoH) returns PTR and TXT records containing the remote endpoint's TransportAddrs—either direct UDP addresses or relay URLs. Simultaneously, the library encodes the EndpointId for TLS server name indication using iroh::tls::name::encode, ensuring that the TLS session ticket cache is correctly partitioned for 0-RTT support.

This resolution happens lazily during the connection attempt, allowing the endpoint to attempt direct UDP hole-punching before falling back to relay connections.

Connecting with the N0 Preset

To connect to an Iroh endpoint using only its EndpointId, construct your local endpoint with the N0 preset and call Endpoint::connect with the target identifier and your protocol's ALPN.

use iroh::{
    endpoint::{presets, Endpoint},
    EndpointId,
    Result,
};

#[tokio::main]
async fn main() -> Result<()> {
    // Build a local endpoint with the N0 preset – this enables DNS lookup.
    let endpoint = Endpoint::bind(presets::N0).await?;

    // Remote endpoint identifier obtained from the remote side.
    let remote_id: EndpointId = "7dl2ff6emqi2qol3l382krodedij45bn3nh479hqo14a32qpr8kg"
        .parse()
        .expect("valid endpoint id");

    // ALPN identifying the protocol.
    const MY_ALPN: &[u8] = b"myapp/example/0";

    // Connect – the library resolves the address from the ID internally.
    let conn = endpoint.connect(remote_id, MY_ALPN).await?;

    // Use the connection (e.g., open a bidirectional stream).
    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 N0 preset automatically adds the DNS resolver to the endpoint's address-resolution pipeline in iroh/src/endpoint.rs. When connect is invoked, the endpoint internally encodes the ID to a DNS name, queries for transport addresses, attempts direct connection, and establishes a QUIC connection authenticated by the remote's public key.

Manual Address Resolution

If you need to inspect the concrete addresses before establishing a connection, you can query the DNS resolver directly using the iroh-dns crate.

use iroh_dns::{Client, Resolver};
use iroh_base::EndpointId;

#[tokio::main]
async fn main() {
    let resolver = Resolver::default(); // uses system DNS/DoH configuration
    let remote_id = EndpointId::from_z32(
        "7dl2ff6emqi2qol3l382krodedij45bn3nh479hqo14a32qpr8kg"
    )
    .unwrap();

    // Resolve to a list of TransportAddrs (IP + relay URLs)
    let addrs = resolver.resolve(remote_id).await.unwrap();

    println!("Resolved addresses:");
    for a in addrs {
        println!("  {}", a);
    }
}

This approach is useful for debugging or when you need to implement custom connection logic while still leveraging Iroh's discovery infrastructure.

Key Implementation Details

The address discovery and connection flow is implemented across several source files:

  • iroh/src/endpoint.rs: Implements Endpoint::bind, Endpoint::connect, and the address-resolution pipeline that integrates the N0 preset's DNS resolver.
  • iroh/src/tls/name.rs: Contains the logic for encoding an EndpointId to a DNS name (<base32>.iroh.invalid) and decoding it for TLS server-name verification.
  • iroh-dns/src/endpoint_info.rs: Defines the DNS record format that carries the list of TransportAddrs for an endpoint.
  • iroh-dns/src/dns.rs: Implements the DNS and DoH resolver used by the N0 preset to perform PKARR lookups.
  • iroh/examples/connect.rs: Demonstrates low-level connection establishment when you already know direct addresses and relay URLs.
  • iroh/examples/listen.rs: Shows the listening side configuration, useful for testing connections.

Summary

  • EndpointId is the public key of an Iroh node and serves as the stable identifier for connections.
  • The N0 preset enables automatic DNS-based address discovery, eliminating the need to manage IP addresses or relay URLs manually.
  • Iroh constructs DNS names using Base32 DNSSEC encoding of the EndpointId to query for transport addresses.
  • Call Endpoint::connect with only the EndpointId and ALPN; the library handles resolution, hole-punching, and relay fallback.
  • For debugging, query the Resolver directly from iroh-dns to inspect resolved TransportAddrs before connecting.

Frequently Asked Questions

What happens if the DNS lookup fails when connecting by EndpointId?

If the DNS resolver cannot find records for the given EndpointId, the connect call will fail with a resolution error. Ensure the remote endpoint is running with the N0 preset (or equivalent DNS publishing enabled) and that your local endpoint has internet access to query the DNS infrastructure.

Do I need to configure any DNS servers manually to use the N0 preset?

No. The N0 preset automatically configures the DNS resolver to use appropriate DNS-over-HTTPS endpoints and infrastructure maintained by the Iroh network. You do not need to modify system DNS settings or manage resolver configuration manually.

Can I connect to an endpoint that is behind a NAT or firewall?

Yes. When connecting to an Iroh endpoint using only its EndpointId, the library automatically attempts UDP hole-punching using the direct addresses returned by DNS. If direct connectivity fails, it transparently falls back to using the relay URLs included in the DNS records, ensuring connectivity even when both endpoints are behind NAT.

Is the EndpointId connection method secure?

Yes. The EndpointId is the cryptographic public key of the remote endpoint. During the QUIC handshake, Iroh verifies that the remote peer possesses the corresponding private key, preventing man-in-the-middle attacks. Additionally, the TLS server name is uniquely derived from the EndpointId via iroh::tls::name::encode, ensuring certificate validation is bound to the specific endpoint identity.

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 →