Iroh Socket Module Usage: Examples for Multipath Networking in Rust
The Iroh socket module provides a multiplexed, transport-agnostic communication layer that supports UDP/IP, QUIC-based custom transports, and relay fallback with automatic path selection.
The socket module in the n0-computer/iroh repository implements the core networking primitive that underpins every Iroh endpoint. Understanding Iroh socket module usage enables developers to configure custom transports, monitor network path changes, and transmit datagrams across multiple simultaneous connections. This guide extracts practical patterns directly from the source code in iroh/src/socket.rs to demonstrate initialization, inspection, and data handling.
Creating a Socket with Custom Transports
In iroh/src/socket.rs, the Socket struct is initialized through the Options builder pattern and the listen method. You configure available transports by constructing TransportConfig values that determine whether the socket uses direct UDP/IP, relay servers, or custom implementations.
The following example demonstrates creating a socket with an IP transport:
use iroh::{
socket::{self, Options},
transports::TransportConfig,
endpoint::Endpoint,
iroh_base::SecretKey,
};
use std::net::SocketAddr;
use std::collections::BTreeSet;
// 1. Create a secret key for the endpoint.
let secret_key = SecretKey::generate();
// 2. Choose the transports – here we enable plain UDP/IP.
let transports = vec![TransportConfig::Ip(Default::default())];
// 3. Assemble the socket options.
let opts = Options {
transports,
secret_key,
address_lookup_user_data: None,
#[cfg(not(wasm_browser))]
dns_resolver: iroh::dns::DnsResolver::new(),
proxy_url: None,
tls_config: rustls::ClientConfig::default(),
server_config: noq_proto::ServerConfig::default(),
metrics: iroh::metrics::EndpointMetrics::default(),
hooks: iroh::endpoint::hooks::EndpointHooksList::default(),
path_selector: socket::biased_rtt_path_selector::default_selector(),
portmapper_config: iroh::portmapper::PortmapperConfig::default(),
net_report_config: iroh::net_report::NetReportConfig::default(),
static_config: socket::StaticConfig::default(),
configured_addrs: BTreeSet::new(),
};
// 4. Start the socket (requires an async runtime like Tokio).
let (socket, socket_handle) = socket::Socket::listen(opts).await?;
Key concept: The socket is completely transport-agnostic. You specify which transports to enable through the transports field in Options, and the socket handles multiplexing between them automatically.
Monitoring Network Paths and Relays
Once initialized, the socket exposes watcher methods that react to network topology changes. In iroh/src/socket.rs, the my_relay method returns the current relay URL, while ip_addrs and home_relay return Watcher objects that yield updates whenever local addresses or relay assignments change.
// Assume `socket` is the Socket instance from initialization.
// Check which relay (if any) the socket currently uses.
if let Some(relay_url) = socket.my_relay() {
println!("Using relay {}", relay_url);
}
// Watch for local address changes.
let mut addr_watcher = socket.ip_addrs();
while let Some(addrs) = addr_watcher.next().await {
println!("Local addresses updated: {:#?}", addrs);
}
// Watch for home-relay updates.
let mut relay_watcher = socket.home_relay();
while let Some(relays) = relay_watcher.next().await {
println!("Home relays: {:?}", relays);
}
These watchers monitor the underlying LocalAddrsWatch and home-relay state, updating automatically when the OS reports interface changes or when the relay connection migrates.
Sending and Receiving Datagrams
The socket exposes a datagram interface via send_to and recv_from, abstracting the underlying transport selection. When you call send_to, the socket consults its path selector (implemented in iroh/src/socket/remote_map.rs) to choose the optimal transport—direct IP, relay, or custom—and forwards the payload accordingly.
use iroh::socket::TransportAddr;
use std::net::SocketAddr;
let payload = b"hello iroh";
// Destination can be direct IP or relay-based.
let destination = TransportAddr::Ip(SocketAddr::new(
"203.0.113.42".parse().unwrap(),
4000,
));
// Send the packet (internally selects best path).
socket.send_to(payload, destination).await?;
// Receive a packet (buffer size typically 1500 bytes for MTU).
let mut buf = [0u8; 1500];
let (len, src) = socket.recv_from(&mut buf).await?;
println!("Got {} bytes from {}", len, src);
The implementation in iroh/src/socket/transports/ip.rs handles UDP socket binding and packet polling, while iroh/src/socket/transports/relay.rs manages WebSocket-based relay fallback. The Socket::handle_actor_message function (lines 1128-1155 in iroh/src/socket.rs) processes NetworkChangeHint events to update paths dynamically.
Testing Utilities for Socket Development
The test suite in iroh/src/test_utils.rs demonstrates how to create deterministic socket environments using TestDnsServer. This utility wraps a standard UdpSocket with a custom DNS resolver for isolated unit tests.
use iroh::test_utils::TestDnsServer;
use tokio::net::UdpSocket;
// Bind a UDP socket for the test DNS server.
let bind_addr = "127.0.0.1:0".parse().unwrap();
let socket = UdpSocket::bind(bind_addr).await?;
let resolver = iroh::dns::DnsResolver::new();
let mut test_dns = TestDnsServer { socket, resolver };
// Drive the DNS server manually in tests.
let mut buf = [0u8; 512];
let (len, src) = test_dns.socket.recv_from(&mut buf).await?;
test_dns.handle_query(&buf[..len], src).await?;
This pattern validates address discovery and NAT-traversal logic without requiring network access.
Summary
- Transport configuration happens through
OptionsandTransportConfigbefore callingSocket::listeniniroh/src/socket.rs. - Path monitoring uses
my_relay(),ip_addrs(), andhome_relay()watchers to react to network changes. - Datagram transmission relies on
send_toandrecv_from, which internally select between direct IP (iroh/src/socket/transports/ip.rs) and relay (iroh/src/socket/transports/relay.rs) transports. - Network adaptation occurs via
NetworkChangeHinthandling inSocket::handle_actor_message, updating theLocalAddrsWatchand transport states. - Testing support includes
TestDnsServeriniroh/src/test_utils.rsfor mocking DNS resolution in unit tests.
Frequently Asked Questions
What transports does the Iroh socket module support?
The socket supports UDP/IP via iroh/src/socket/transports/ip.rs, WebSocket-based relay transport via iroh/src/socket/transports/relay.rs, and custom user-provided transports through iroh/src/socket/transports/custom.rs. You configure which transports to enable by passing TransportConfig variants to Options when constructing the socket.
How does the socket handle network interface changes?
The socket registers a netwatch::netmon listener that triggers NetworkChangeHint messages. The Socket::handle_actor_message method (lines 1128-1155 in iroh/src/socket.rs) processes these hints to update the ShutdownState, refresh the LocalAddrsWatch, and restart affected transports automatically.
Can I use the socket without the high-level Endpoint API?
Yes. While most applications use the Endpoint API, you can instantiate Socket directly using Socket::listen with custom Options. This approach gives you direct control over transport selection, path metrics, and the TransportAddr routing logic defined in iroh/src/socket/mapped_addrs.rs.
How do I determine if the socket is using a relay?
Call socket.my_relay() to retrieve an Option<Url> indicating the current relay, if any. For continuous monitoring, use socket.home_relay() to obtain a Watcher that yields updates whenever the relay assignment changes due to network conditions or path selection algorithms.
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 →