How to Monitor Connection Lifecycle with iroh's Connection State Events
Use HomeRelayWatch to subscribe to a broadcast channel that emits RelayConnectionState events whenever an iroh relay connection transitions between Connecting, Connected, or Disconnected states.
iroh's networking stack uses relay servers to connect peers across the internet, with each connection managed by a background Relay Actor that tracks socket status in real time. By tapping into the connection state events published by these actors, you can monitor the full lifecycle of every relay connection—from initial handshake through to teardown. This guide shows you how to implement this monitoring using the HomeRelayWatch broadcast channel and per-connection path events as implemented in the n0-computer/iroh repository.
Understanding the Relay Connection State Architecture
The RelayActor and State Transitions
In iroh/src/socket/transports/relay/actor.rs, the RelayActor manages the lifecycle of each relay connection through a state machine represented by the RelayConnectionState enum. For each home relay it attempts to contact, the actor maintains a RelayConnectionOptions struct and drives the connection through three distinct phases:
- Connecting – When the actor initiates a TCP/TLS handshake, it calls
set_status(url, RelayConnectionState::Connecting). - Connected – Upon successful handshake completion, the state updates to
RelayConnectionState::Connected. - Disconnected – If the socket closes, the network drops, or an error occurs, the actor publishes
RelayConnectionState::Disconnected { last_error }containing the specific failure reason.
HomeRelayWatch and the Broadcast Channel
The HomeRelayWatch struct, defined in iroh/src/socket/transports/relay.rs, serves as the central hub for connection state monitoring. It stores the current state of all relays in a HashMap<RelayUrl, RelayConnectionState> and exposes a broadcast channel that callers can subscribe to. When the RelayActor updates a relay's status, the change is routed through the shared HomeRelayWatch instance, immediately notifying all subscribers.
Implementing Connection State Monitoring
To monitor connection lifecycles in your application, create a HomeRelayWatch, inject it into the node builder, and subscribe to the broadcast stream. Below is a minimal example that prints every state transition:
use iroh::node::NodeBuilder;
use iroh::socket::transports::relay::{
HomeRelayWatch, RelayConnectionState,
};
use tokio::stream::StreamExt; // for `.next()`
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// 1️⃣ Create a shared watch that will receive all relay state events.
let relay_watch = HomeRelayWatch::new();
// 2️⃣ Build and start an iroh node, handing the watch to the transport layer.
let node = NodeBuilder::default()
.relay_watch(relay_watch.clone())
.spawn()
.await?;
// 3️⃣ Subscribe to the broadcast channel.
let mut events = relay_watch.subscribe();
// 4️⃣ React to each state change.
while let Some((url, state)) = events.recv().await {
match state {
RelayConnectionState::Connecting => {
println!("🔗 Connecting to relay {}", url);
}
RelayConnectionState::Connected => {
println!("✅ Connected to relay {}", url);
}
RelayConnectionState::Disconnected { last_error } => {
println!(
"❌ Disconnected from relay {} – {:?}",
url, last_error
);
}
}
}
// Keep the node alive (or drop it when you’re done).
node.shutdown().await?;
Ok(())
}
This pattern gives you a real-time view of the transport layer lifecycle, from relay selection through handshake to eventual teardown.
Monitoring Per-Connection Path Events
For application-layer monitoring of actual data transfers, iroh provides path-level events that complement the relay state tracking. While HomeRelayWatch tracks the connection to the relay server itself, these APIs track the paths used for peer-to-peer data exchange:
conn.path_events()– Returns a'staticstream ofPathEvents that report when a new transfer path becomes active, when it closes, and provides statistics about the path.conn.paths()– Returns a snapshot of the currentPathListwith live transfer statistics.
These are implemented in iroh/src/socket/remote_map/remote_state/path_watcher.rs. You can combine the relay-state watch with per-connection path events to get a complete picture of both the transport infrastructure and the active data transfers.
Summary
RelayConnectionStateiniroh/src/socket/transports/relay/actor.rstracks three lifecycle phases:Connecting,Connected, andDisconnected.HomeRelayWatchprovides a broadcast channel viasubscribe()that emits(RelayUrl, RelayConnectionState)tuples for every state change across all relays.- Inject the watch into your node using
NodeBuilder::relay_watch()before callingspawn()to ensure the transport layer reports state changes. - For data-plane monitoring, use
conn.path_events()andconn.paths()fromiroh/src/socket/remote_map/remote_state/path_watcher.rsto track active transfer paths and statistics.
Frequently Asked Questions
What is the difference between relay connection state and path events?
Relay connection state tracks the lifecycle of the connection to the relay server itself (transport layer), managed by RelayActor and reported via HomeRelayWatch. Path events track the actual data transfer paths between peers (application layer), accessed through conn.path_events() on a per-connection basis. Use relay states to monitor infrastructure connectivity and path events to monitor active data transfers.
How do I handle connection errors when a relay disconnects?
When RelayConnectionState::Disconnected { last_error } is emitted, the optional last_error field contains the specific error that caused the disconnection. You can match on this variant to implement retry logic, exponential backoff, or failover to alternative relays. The RelayActor will automatically attempt to reconnect based on RelayConnectionOptions, but you can trigger immediate health checks using RelayActorMessage::CheckConnectionAfterNetworkChange.
Can I monitor multiple relay connections simultaneously?
Yes. The HomeRelayWatch maintains a HashMap<RelayUrl, RelayConnectionState> internally, so a single subscription receives state updates for every relay URL the node contacts. Each (url, state) tuple in the broadcast channel identifies which relay the event pertains to, allowing you to track multiple concurrent relay connections through a single stream.
Where can I find a complete working example?
The iroh repository includes iroh/examples/screening-connection.rs, which demonstrates a full implementation of hooking into connection state events. This example shows how to wire up HomeRelayWatch with a running node and process the state transitions in a real application context.
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 →