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
- Create a builder instance using
Builder::new(preset)orBuilder::default(). - Call
bind_addr(socket_addr)with your targetstd::net::SocketAddr. - Await
builder.bind()to open the socket and return the configuredEndpoint.
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::Builderand itsbind_addrmethod to specify the listening address before constructing the endpoint. - Supply a
std::net::SocketAddrcontaining your target IP (IPv4 or IPv6) and port number. - Call
bind().awaitto execute the socket binding viaIpTransport::bindiniroh/src/socket/transports/ip.rs. - Support multiple bindings by calling
bind_addrrepeatedly before the finalbind()invocation. - Handle binding errors, such as "address already in use," through the returned
Resulttype.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →