# How to Implement Custom Path Selection in iroh: A Complete Guide

> Learn how to implement custom path selection in iroh. This guide explains creating a `PathSelector` trait, registering it with `EndpointBuilder`, and enhancing your iroh node.

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

---

**To implement custom path selection in iroh, create a type that implements the `PathSelector` trait and register it with `EndpointBuilder::path_selector` using an `Arc<dyn PathSelector>`.**

The iroh networking library uses a pluggable **PathSelector** trait to determine which network path (IPv4, IPv6, relay, or custom transport) to use for QUIC connections. While the default **BiasedRttPathSelector** ranks paths by latency and transport tier, you can implement your own strategy to prioritize specific networks, enforce application-level constraints, or integrate custom metrics.

## Understanding the PathSelector Trait

The `PathSelector` trait defines the contract for path selection logic in iroh. Located 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) at line 1419, this trait requires implementing a single method that evaluates available paths and returns your selection.

### Trait Definition and Required Method

The trait definition requires:

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

```

This method receives a **PathSelectionContext** containing the currently selected path and all candidate paths, and must return a **PathSelection** indicating which path to use.

### PathSelectionContext and PathSelectionData

The context provides two essential methods:

- `ctx.current()` – Returns the currently active path
- `ctx.paths()` – Returns an iterator over **PathSelectionData** for all candidate paths

Each `PathSelectionData` exposes:

- `network_path().remote()` – The underlying **FourTuple** (remote address)
- Optional **PathStats** including RTT, loss rates, and other metrics

## Implementing a Custom PathSelector

To create a custom selector, define a struct and implement the `PathSelector` trait. The implementation should iterate through available paths using `ctx.paths()`, evaluate each path based on your criteria, and return a `PathSelection` containing your chosen path.

### Core Implementation Pattern

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

struct MyCustomSelector;

impl PathSelector for MyCustomSelector {
    fn select(&self, ctx: &PathSelectionContext<'_>) -> PathSelection {
        let mut sel = PathSelection::none();
        
        // Your selection logic here
        for psd in ctx.paths() {
            let remote = psd.network_path().remote();
            // Evaluate path...
            if should_select_this_path(remote) {
                sel.set(&psd);
                return sel;
            }
        }
        
        sel // Return empty to keep current path
    }
}

```

## Registering Your Selector with the Endpoint

After implementing the trait, register your selector using the endpoint builder API in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) (lines 818-840). The `EndpointBuilder::path_selector` method accepts an `Arc<dyn PathSelector>`:

```rust
let endpoint = iroh::Endpoint::builder()
    .path_selector(Arc::new(MyCustomSelector))
    .build()?;

```

The `Arc` allows the selector to be shared safely across threads, and the endpoint will use your logic for all subsequent path selections.

## Example: Preferring Custom Transports

Here is a complete implementation that prioritizes a custom transport named `TestTransport`, falling back to the default **BiasedRttPathSelector** when unavailable:

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

struct PreferTestTransport;

impl PathSelector for PreferTestTransport {
    fn select(&self, ctx: &PathSelectionContext<'_>) -> PathSelection {
        // First pass: look for custom transport paths
        // Addr::Other indicates user-defined transports
        for psd in ctx.paths() {
            let remote = psd.network_path().remote();
            if let Addr::Other(_) = remote {
                let mut sel = PathSelection::none();
                sel.set(&psd);
                return sel;
            }
        }

        // Fallback to default biased-RTT selection
        let default = iroh::socket::biased_rtt_path_selector::BiasedRttPathSelector::default();
        default.select(ctx)
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let endpoint = iroh::Endpoint::builder()
        .path_selector(Arc::new(PreferTestTransport))
        .build()?;
    
    // Use endpoint for connections...
    Ok(())
}

```

This example checks if `Addr::Other(_)` is present (indicating a user-defined transport in [`iroh/src/socket/transports.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports.rs)) and delegates to the built-in selector otherwise.

## Key Source Files and Implementation Details

Understanding these specific locations in the iroh codebase helps when implementing custom path selection:

- **[`iroh/src/socket/remote_map/remote_state.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map/remote_state.rs)** (line 1419) – Contains the `PathSelector` trait definition
- **[`iroh/src/socket/biased_rtt_path_selector.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/biased_rtt_path_selector.rs)** – Default implementation ranking paths by transport tier and biased RTT
- **[`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs)** (lines 818-840) – Provides `EndpointBuilder::path_selector` for registration
- **[`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs)** (lines 2141, 2182, 2598) – Shows where the default selector is installed in the socket implementation
- **[`iroh/examples/custom-transport.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs)** – Working example of a custom transport with path selection

## Summary

- Implement the `PathSelector` trait from [`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 define custom path selection logic
- Use `PathSelectionContext` to access the current path and iterate over candidates via `ctx.paths()`
- Return `PathSelection::none()` to retain the current path, or call `set()` on a candidate to switch
- Register your selector with `EndpointBuilder::path_selector` using an `Arc<dyn PathSelector>`
- Delegate to `BiasedRttPathSelector::default()` for fallback behavior when custom criteria are not met

## Frequently Asked Questions

### What is the default path selection algorithm in iroh?

The default algorithm is **BiasedRttPathSelector**, implemented 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). It ranks paths first by transport tier (primary versus backup) and then by a biased RTT calculation that gives preference to lower-latency connections.

### How do I access path statistics in my custom selector?

Each `PathSelectionData` in the iterator returned by `ctx.paths()` contains optional **PathStats** accessible through the path's metadata. These statistics include RTT measurements and packet loss rates, allowing you to implement latency-aware or reliability-based selection criteria.

### Can I combine multiple selection criteria?

Yes. You can implement multi-stage selection by evaluating paths in order of priority. For example, first check for custom transports, then filter by maximum RTT thresholds, and finally fall back to the default **BiasedRttPathSelector** if no paths meet your specific criteria.

### What happens if my selector returns an empty PathSelection?

Returning `PathSelection::none()` (the default empty selection) signals the endpoint to keep the currently active path. This is useful when no better alternative exists or when you want to maintain stability rather than switching paths frequently.