# How to Monitor Connection Lifecycle with iroh's Connection State Events

> Monitor iroh connection lifecycle easily. Subscribe to connection state events with HomeRelayWatch to track Connecting, Connected, and Disconnected states for seamless relay management.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: how-to-guide
- Published: 2026-06-18

---

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

1. **Connecting** – When the actor initiates a TCP/TLS handshake, it calls `set_status(url, RelayConnectionState::Connecting)`.
2. **Connected** – Upon successful handshake completion, the state updates to `RelayConnectionState::Connected`.
3. **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`](https://github.com/n0-computer/iroh/blob/main/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:

```rust
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 `'static` stream of `PathEvent`s 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 current `PathList` with live transfer statistics.

These are implemented in [`iroh/src/socket/remote_map/remote_state/path_watcher.rs`](https://github.com/n0-computer/iroh/blob/main/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

- **`RelayConnectionState`** in [`iroh/src/socket/transports/relay/actor.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/relay/actor.rs) tracks three lifecycle phases: `Connecting`, `Connected`, and `Disconnected`.
- **`HomeRelayWatch`** provides a broadcast channel via `subscribe()` that emits `(RelayUrl, RelayConnectionState)` tuples for every state change across all relays.
- Inject the watch into your node using `NodeBuilder::relay_watch()` before calling `spawn()` to ensure the transport layer reports state changes.
- For data-plane monitoring, use `conn.path_events()` and `conn.paths()` from [`iroh/src/socket/remote_map/remote_state/path_watcher.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map/remote_state/path_watcher.rs) to 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`](https://github.com/n0-computer/iroh/blob/main/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.