How to Bind an Iroh Endpoint to a Specific IP Address and Port

Use the bind_addr method on iroh::endpoint::Builder to supply a std::net::SocketAddr, then call bind().await to create the endpoint and open the UDP socket on your chosen interface.

The n0-computer/iroh networking library provides a flexible Endpoint abstraction for peer-to-peer connectivity. When you need to bind an Iroh endpoint to a specific IP address and port—whether for IPv4, IPv6, or dual-stack networking—you configure the underlying transport through the Builder API before the socket is created.

The Endpoint Builder Pattern

The iroh::endpoint::Builder type, defined in iroh/src/endpoint.rs, serves as the configuration interface for Endpoint creation. The builder stores transport parameters in an internal state and only applies them when you finalize construction. This deferred binding lets you specify exactly which IP address and port the underlying UDP socket should use.

Configuring the Bind Address

To bind an Iroh endpoint to a specific IP address and port, pass a SocketAddr to the builder's bind_addr method. This stores the address in the builder's transport configuration until you invoke bind().await.

Step-by-Step Binding Process

  1. Create a builder instance using Builder::new(preset) or Builder::default().
  2. Call bind_addr(socket_addr) with your target std::net::SocketAddr.
  3. Await builder.bind() to open the socket and return the configured Endpoint.

IPv4 Binding Example

The following example binds to a concrete IPv4 address and port:

use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use iroh::endpoint::{Builder, Preset};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create the builder with a preset or default configuration
    let preset = Preset::default();
    let builder = Builder::new(preset);

    // Configure the specific IPv4 address and port
    let bind_ip: IpAddr = Ipv4Addr::new(192, 168, 1, 42).into();
    let bind_port = 44323;
    let bind_addr = SocketAddr::new(bind_ip, bind_port);

    // Apply the bind address to the builder
    let builder = builder.bind_addr(bind_addr);

    // Open the socket and create the endpoint
    let endpoint = builder.bind().await?;
    
    println!("Iroh endpoint bound to {}", endpoint.local_addr());
    Ok(())
}

IPv6 Binding Example

The same API works for IPv6 by using Ipv6Addr:

use std::net::{Ipv6Addr, SocketAddr};

let bind_ip = Ipv6Addr::UNSPECIFIED; // :: 
let bind_addr = SocketAddr::new(bind_ip.into(), 8080);
let endpoint = Builder::default()
    .bind_addr(bind_addr)
    .bind()
    .await?;

Binding to Multiple Addresses

You can call bind_addr repeatedly to listen on several interfaces simultaneously. Each invocation pushes a new address onto the internal transport list. When you finally call bind(), Iroh creates a separate UDP socket for each configured address.

Transport Layer Implementation

Under the hood, your specified address flows to IpTransport::bind in iroh/src/socket/transports/ip.rs. This function invokes netwatch::UdpSocket::bind_full(addr) to perform the OS-level UDP socket binding with the exact SocketAddr you provided. If the bind succeeds, the transport is registered with the endpoint's socket coordinator in iroh/src/socket.rs.

If the bind fails—for example, because the port is already in use—the error propagates back through Builder::bind() as a Result::Err, allowing you to handle the failure gracefully.

Error Handling for Bind Failures

Since bind().await returns a Result, you must handle cases where the specific IP address and port are unavailable. Common failure modes include:

  • Address already in use: The OS reports the port is bound by another process.
  • Permission denied: Attempting to bind to a well-known port (below 1024) without sufficient privileges.
  • Address not available: The specified IP is not assigned to any local network interface.

Match on the Result to implement retry logic with ephemeral ports or exit with a descriptive error.

Summary

  • Use iroh::endpoint::Builder and its bind_addr method to specify the listening address before constructing the endpoint.
  • Supply a std::net::SocketAddr containing your target IP (IPv4 or IPv6) and port number.
  • Call bind().await to execute the socket binding via IpTransport::bind in iroh/src/socket/transports/ip.rs.
  • Support multiple bindings by calling bind_addr repeatedly before the final bind() invocation.
  • Handle binding errors, such as "address already in use," through the returned Result type.

Frequently Asked Questions

Can I bind to a specific port with IP address 0.0.0.0?

Yes. Construct a SocketAddr with Ipv4Addr::UNSPECIFIED (or Ipv6Addr::UNSPECIFIED) and your desired port. This binds the endpoint to all available network interfaces on that specific port, which is useful for servers that accept connections from any IP.

What happens if I don't call bind_addr on the builder?

The builder uses default transport behavior which may bind to ephemeral ports or unspecified addresses depending on the preset configuration. For production deployments requiring specific ports or network isolation, always explicitly set the bind address using bind_addr.

How do I handle "address already in use" errors when binding?

The bind().await method returns a Result that will be Err if the OS reports the address is unavailable. Match on this error to implement retry logic with different ports, wait for the port to be released, or exit gracefully with a user-facing message.

Can I bind to both IPv4 and IPv6 simultaneously?

Yes. Call bind_addr twice—once with an IPv4 SocketAddr and once with an IPv6 SocketAddr—before invoking bind(). According to the implementation in iroh/src/endpoint.rs, the endpoint will create separate transports for each address family, allowing dual-stack operation.

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 →