# Iroh Socket Module Usage: Examples for Multipath Networking in Rust

> Explore Iroh socket module usage examples for Rust multipath networking. Discover how to leverage UDP IP QUIC and relay fallback for robust connectivity.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: examples
- Published: 2026-07-06

---

**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`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs) to demonstrate initialization, inspection, and data handling.

## Creating a Socket with Custom Transports

In [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/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:

```rust
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`](https://github.com/n0-computer/iroh/blob/main/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.

```rust
// 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`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map.rs)) to choose the optimal transport—direct IP, relay, or custom—and forwards the payload accordingly.

```rust
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`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/ip.rs) handles UDP socket binding and packet polling, while [`iroh/src/socket/transports/relay.rs`](https://github.com/n0-computer/iroh/blob/main/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`](https://github.com/n0-computer/iroh/blob/main/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`](https://github.com/n0-computer/iroh/blob/main/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.

```rust
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 `Options` and `TransportConfig` before calling `Socket::listen` in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs).
- **Path monitoring** uses `my_relay()`, `ip_addrs()`, and `home_relay()` watchers to react to network changes.
- **Datagram transmission** relies on `send_to` and `recv_from`, which internally select between direct IP ([`iroh/src/socket/transports/ip.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/ip.rs)) and relay ([`iroh/src/socket/transports/relay.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/relay.rs)) transports.
- **Network adaptation** occurs via `NetworkChangeHint` handling in `Socket::handle_actor_message`, updating the `LocalAddrsWatch` and transport states.
- **Testing support** includes `TestDnsServer` in [`iroh/src/test_utils.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/test_utils.rs) for 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`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/ip.rs), WebSocket-based relay transport via [`iroh/src/socket/transports/relay.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/relay.rs), and custom user-provided transports through [`iroh/src/socket/transports/custom.rs`](https://github.com/n0-computer/iroh/blob/main/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`](https://github.com/n0-computer/iroh/blob/main/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`](https://github.com/n0-computer/iroh/blob/main/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.