# How to Implement Path Selection Strategies in Iroh: A Complete Guide to Custom Network Path Selection

> Learn to implement custom path selection strategies in Iroh using the PathSelector trait. Guide your network traffic for optimal performance with this comprehensive tutorial.

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

---

**Iroh enables custom path selection strategies through the `PathSelector` trait, which allows you to implement network path selection logic by processing candidate paths via `PathSelectionContext` and returning a `PathSelection` to determine which QUIC path carries application traffic.**

The n0-computer/iroh library provides a pluggable architecture for network path selection that determines which QUIC path carries your application traffic. By implementing path selection strategies in iroh, you can customize whether connections prioritize low latency, specific transport types, or custom policies. The selector integrates directly into the endpoint lifecycle, evaluating candidates on every path change.

## The Path Selection Architecture

### The PathSelector Trait Contract

The foundation of path selection in iroh rests on the `PathSelector` trait defined in [`iroh/src/socket/remote_map/remote_state.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map/remote_state.rs). This trait defines a single required method:

```rust
fn select(&self, ctx: &PathSelectionContext<'_>) -> PathSelection;

```

Implementers receive a **selection context** containing the current active path and an iterator over candidate paths. The method must return a `PathSelection` indicating which path to use. Create a new selection with `PathSelection::none()`, then call `selection.set(&candidate)` to activate a specific path.

### Selection Context and Candidate Data

The `PathSelectionContext` struct bundles the currently selected transport as an `Option<&FourTuple>` together with live connection state. Calling `ctx.paths()` yields `PathSelectionData` instances for every open QUIC path.

Each `PathSelectionData` provides access to the network path and statistics via the `stats()` method, which returns `PathStats` containing live RTT measurements, loss rates, and other transport metrics. Check for `None` if the path closed concurrently.

## The Default Implementation: Biased RTT Selection

Iroh ships with `BiasedRttPathSelector` in [`iroh/src/socket/biased_rtt_path_selector.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/biased_rtt_path_selector.rs), which implements a sophisticated default strategy combining transport preferences with latency measurements.

### Transport Type Bias

The selector classifies each `AddrKind` (IPv4, IPv6, Relay, or custom) as either **Primary** or **Backup**. Primary transports are preferred over backups. By default, only the Relay transport serves as backup, while IPv4 and IPv6 are primary.

### RTT Bias and Switching Logic

The implementation adds a configurable nanosecond bias (`rtt_bias`) to measured RTT values. IPv6 receives a small default advantage of approximately 3 milliseconds (`IPV6_RTT_ADVANTAGE`).

The selection algorithm sorts paths by `(transport_type, biased_rtt)`. When the best candidate belongs to a different transport tier, the selector switches immediately. Within the same tier, switching occurs only if the candidate's biased RTT exceeds `RTT_SWITCHING_MIN` (approximately 5 milliseconds) better than the current path, preventing flapping under network jitter.

## Implementing a Custom Path Selector

To create a custom path selection strategy in iroh, implement the `PathSelector` trait and wire it into your endpoint.

### Example: Lowest-RTT Selector

This implementation ignores transport tiers and always selects the path with the smallest raw RTT:

```rust
use iroh::socket::remote_map::{
    PathSelection, PathSelectionContext, PathSelectionData, PathSelector,
};
use std::sync::Arc;

/// A selector that always picks the path with the smallest raw RTT,
/// regardless of whether it is primary or backup.
#[derive(Debug, Default)]
struct LowestRttSelector;

impl PathSelector for LowestRttSelector {
    fn select(&self, ctx: &PathSelectionContext<'_>) -> PathSelection {
        let mut best: Option<(&PathSelectionData<'_>, i128)> = None;

        for psd in ctx.paths() {
            // If stats are unavailable (e.g. the path closed concurrently) skip it.
            let Some(stats) = psd.stats() else { continue };
            let rtt = stats.rtt.as_nanos() as i128;

            if best.as_ref().map_or(true, |(_, cur_rtt)| rtt < *cur_rtt) {
                best = Some((psd, rtt));
            }
        }

        let mut selection = PathSelection::none();
        if let Some((best_psd, _)) = best {
            selection.set(best_psd);
        }
        selection
    }
}

```

### Example: Custom Transport Bias

When using the `unstable-custom-transports` feature, you can assign custom biases to new transport types:

```rust
use iroh::socket::{
    biased_rtt_path_selector::{BiasedRttPathSelector, TransportBias},
    transports::AddrKind,
};
use std::time::Duration;
use std::sync::Arc;

// Give the new transport a 10 ms RTT advantage.
let selector = BiasedRttPathSelector::default()
    .with_bias(AddrKind::Custom, TransportBias::primary().with_rtt_advantage(Duration::from_millis(10)));

let endpoint = iroh::Endpoint::builder()
    .path_selector(Arc::new(selector))
    .bind()
    .await?;

```

## Wiring Your Selector to the Endpoint

The selector lives as an `Arc<dyn PathSelector>` inside the `Socket` struct ([`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs)). By default, the endpoint builder creates `Arc::new(BiasedRttPathSelector::default())` as seen in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs).

To use a custom selector, call the `path_selector` method on the endpoint builder before binding:

```rust
let endpoint = iroh::Endpoint::builder()
    .path_selector(Arc::new(LowestRttSelector::default()))
    .bind()
    .await?;

```

## Summary

- **Path selection in iroh** is governed by the `PathSelector` trait defined in [`iroh/src/socket/remote_map/remote_state.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map/remote_state.rs).
- Implement the `select` method to receive `PathSelectionContext` and return `PathSelection` to control which QUIC path carries traffic.
- **Default behavior** uses `BiasedRttPathSelector` in [`iroh/src/socket/biased_rtt_path_selector.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/biased_rtt_path_selector.rs), which prioritizes primary transports and uses RTT bias with a 5ms switching threshold.
- Access candidate statistics through `PathSelectionData.stats()`, returning `PathStats` with live RTT and loss data.
- **Plug your implementation** into the endpoint via `Endpoint::builder().path_selector(Arc::new(your_selector))` before calling `bind()`.

## Frequently Asked Questions

### What is the PathSelector trait in iroh?

The `PathSelector` trait defines the contract for path selection strategies in iroh. It requires a single `select` method that receives a `PathSelectionContext` reference and returns a `PathSelection` indicating which network path should carry application traffic. The trait is defined in [`iroh/src/socket/remote_map/remote_state.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map/remote_state.rs) and enables pluggable path selection logic.

### How does the default BiasedRttPathSelector work?

`BiasedRttPathSelector` implements a two-tier strategy that classifies transports as Primary or Backup, preferring primary paths. It adds configurable RTT biases (giving IPv6 a ~3ms advantage by default) and sorts paths by transport tier then biased latency. It switches immediately between tiers, but requires a ~5ms improvement (`RTT_SWITCHING_MIN`) within the same tier to prevent flapping.

### Can I implement custom transport biases?

Yes, when using the `unstable-custom-transports` feature, you can configure `BiasedRttPathSelector` with custom biases for new `AddrKind` variants using `with_bias()`. Alternatively, implement a completely custom `PathSelector` that applies your own policy logic to the `PathSelectionData` and `PathStats` available in the selection context.

### Where is path selection evaluated in the iroh codebase?

Path selection is evaluated in the socket layer, specifically within [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs) where the selector is stored as `Arc<dyn PathSelector>`. The selection logic triggers when path conditions change, using the `PathSelectionContext` constructed in [`iroh/src/socket/remote_map/remote_state.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map/remote_state.rs) to provide candidates and current state to your selector's `select` method.