# webtor-rs Relay Selection: How the Tor Circuit Builder Chooses Relays

> Discover how webtor-rs selects Tor relays. Learn about its filtering sorting and circuit building process for efficient anonymous browsing.

- Repository: [igor53627/webtor-rs](https://github.com/igor53627/webtor-rs)
- Tags: internals
- Published: 2026-03-04

---

**webtor-rs isolates relay selection logic in [`webtor/src/relay.rs`](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/relay.rs), using a `RelayManager` that filters candidates by flags and bandwidth, sorts by consensus weight, and returns the top matches for circuit construction.**

webtor-rs is a Rust implementation of a Tor client that requires sophisticated relay selection to build secure circuits. The **webtor-rs relay selection** system is implemented in the [`relay.rs`](https://github.com/igor53627/webtor-rs/blob/main/relay.rs) module, which provides the `RelayManager` struct and associated criteria for filtering and ranking Tor relays based on consensus data.

## Core Components of the Relay Selection System

The relay selection architecture centers on three main abstractions defined in [`webtor/src/relay.rs`](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/relay.rs).

### The Relay Struct

The **`Relay`** struct holds all data parsed from the Tor consensus for a single relay. It stores the fingerprint, cryptographic keys (RSA and Ed25519), bandwidth, flags, and the ntor onion key required for circuit handshakes.

Source: [`Relay` struct definition](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/relay.rs#L14-L33).

### RelayCriteria for Filtering

The **`RelayCriteria`** struct describes the constraints a caller wants when picking relays. It supports builder-style methods to specify required or excluded flags (such as `FAST`, `STABLE`, `EXIT`), a fingerprint blacklist, minimum bandwidth thresholds, and the maximum number of results to return.

Source: [`RelayCriteria` definition](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/relay.rs#L37-L46).

### RelayManager for Selection Logic

The **`RelayManager`** holds the full relay list and implements the selection algorithms. It maintains the consensus state and provides the primary entry points for filtering and ranking relays.

Source: [`RelayManager` definition](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/relay.rs#L98-L101).

## How webtor-rs Filters and Ranks Relays

The `RelayManager::select_relays` method implements a multi-stage pipeline to choose the best relays for a circuit.

### Step 1: Filtering by Criteria

The algorithm first iterates over the stored relays and discards any that violate the provided `RelayCriteria`:

- Matches an excluded fingerprint in the blacklist.
- Missing a required flag (e.g., `FAST` or `STABLE`).
- Contains an excluded flag (e.g., `BAD_EXIT`).
- Has bandwidth below the `min_bandwidth` threshold.

Source: [Filtering logic](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/relay.rs#L102-L124).

### Step 2: Sorting by Consensus Weight

If no relays remain after filtering, the method returns a `TorError::relay_selection` error. Otherwise, the remaining candidates are sorted by **consensus weight** in descending order. Higher weight indicates a more reliable and performant relay according to the Tor network consensus.

Source: [Sorting logic](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/relay.rs#L143-L145).

### Step 3: Truncation and Random Selection

The sorted list is truncated to the `max_selection` limit specified in the criteria. For cases requiring a single relay, the `select_relay` convenience wrapper picks a **random** entry from the already-filtered top set using `rand::seq::SliceRandom`. This randomization prevents deterministic paths and achieves load balancing across suitable relays.

Source: [Truncation](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/relay.rs#L147-L152) and [`select_relay`](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/relay.rs#L164-L180).

## Building Circuit Targets from Selected Relays

Once a `Relay` is selected, it must be converted into a format usable by the Arti Tor stack. The `Relay::as_circ_target` method performs this transformation.

It validates the relay's address, RSA identity, Ed25519 identity, and ntor onion key, then constructs an **`OwnedCircTarget`** that the circuit builder can use to open connections. This method ensures that only properly validated relays with complete cryptographic material are used for circuit construction.

Source: [`as_circ_target`](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/relay.rs#L61-L135).

## Pre-Configured Selection Criteria

The `selection` sub-module in [`webtor/src/relay.rs`](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/relay.rs) provides ready-made `RelayCriteria` for common Tor circuit roles. These helpers encapsulate the standard flag requirements used by the Tor protocol:

- **`middle_relays()`** – Requires `FAST`, `STABLE`, and `V2Dir` flags for middle hops.
- **`exit_relays()`** – Requires `FAST`, `STABLE`, and `EXIT` flags while excluding `BAD_EXIT` for the final hop.
- **`guard_relays()`** – Requires `FAST`, `STABLE`, and `GUARD` flags for entry positions.

Source: [`selection` module](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/relay.rs#L155-L172).

## Updating the Relay List from Consensus

Tor networks periodically publish new consensus documents containing updated relay information. The `RelayManager::update_relays` method accepts a new `Vec<Relay>` parsed from a fresh consensus (typically produced by `webtor::directory::ConsensusParser`) and swaps the internal relay list atomically.

This ensures that the selection algorithms always operate on current network state without requiring reconstruction of the `RelayManager` instance.

Source: [`update_relays`](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/relay.rs#L184-L190).

## Practical Code Examples

### Selecting Middle Relays for a Circuit

```rust
use webtor::relay::{RelayManager, selection};

// Assume manager is initialized with consensus data
let criteria = selection::middle_relays()
    .with_min_bandwidth(5_000_000)  // 5 MiB/s minimum
    .with_max_selection(3);         // Need three middle hops

let middle_relays = manager.select_relays(&criteria)
    .expect("No suitable middle relays found");

```

### Picking a Single Guard Relay Randomly

```rust
use webtor::relay::selection;

// select_relay randomly chooses from the top candidates
let guard = manager.select_relay(&selection::guard_relays())
    .expect("Failed to select a guard relay");

println!("Selected guard: {}", guard.fingerprint);

```

### Converting a Relay to a Circuit Target

```rust
use webtor::relay::Relay;

// After selecting a relay, convert it for the Arti stack
let circ_target = relay.as_circ_target()
    .expect("Relay missing required cryptographic keys");

// circ_target can now be used to open connections via the circuit builder

```

### Updating Relays After Consensus Refresh

```rust
use webtor::relay::RelayManager;
use webtor::directory::ConsensusParser;

fn refresh_relays(manager: &mut RelayManager, consensus_bytes: &[u8]) {
    let parser = ConsensusParser::new();
    let new_relays = parser.parse(consensus_bytes)
        .expect("Failed to parse consensus");
    
    // Atomically update the relay list
    manager.update_relays(new_relays);
}

```

## Summary

- **webtor-rs relay selection** is implemented in [`webtor/src/relay.rs`](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/relay.rs) through the `RelayManager` struct.
- The system uses **`RelayCriteria`** to filter relays by flags (Fast, Stable, Exit, etc.), bandwidth, and fingerprints.
- Selection follows a four-stage pipeline: **filtering** by criteria, **sorting** by consensus weight, **truncating** to the requested limit, and **randomizing** the final pick.
- The **`as_circ_target`** method converts selected relays into `OwnedCircTarget` objects compatible with the Arti Tor stack.
- Pre-configured helpers in the `selection` module provide standard criteria for **guard**, **middle**, and **exit** relays.
- The relay list is updated atomically via `update_relays` when new consensus documents are fetched.

## Frequently Asked Questions

### How does webtor-rs ensure selected relays are trustworthy?

webtor-rs relies on the Tor consensus document to establish trust. The `Relay` struct validates cryptographic material including RSA identity keys, Ed25519 identities, and ntor onion keys through the `as_circ_target` method. Additionally, the `RelayCriteria` system allows exclusion of relays with the `BAD_EXIT` flag or specific fingerprints, ensuring only properly flagged and validated relays are selected for circuit construction.

### What happens if no relays match the selection criteria?

If the filtering stage eliminates all available relays, the `RelayManager::select_relays` method returns a `TorError::relay_selection` error. This typically occurs when criteria are too restrictive—for example, requiring a minimum bandwidth higher than any available relay, or excluding all relays with specific flags. The error signals that the caller must relax constraints or wait for updated consensus data.

### Can I customize the relay selection beyond the pre-canned criteria?

Yes, the `RelayCriteria` struct provides a builder pattern for complete customization. You can chain methods like `with_flag()`, `without_flag()`, `with_min_bandwidth()`, `with_max_selection()`, and `with_excluded_fingerprint()` to create specific constraints. This allows implementation of custom path selection strategies, such as avoiding specific countries' relays or requiring experimental flags not covered by the standard `guard_relays()`, `middle_relays()`, or `exit_relays()` helpers.

### How often should the relay list be updated?

The relay list should be updated whenever a new Tor consensus document is published, which typically occurs every hour in the live Tor network. The `RelayManager::update_relays` method accepts a new `Vec<Relay>` parsed from fresh consensus data and atomically replaces the internal relay list. Frequent updates ensure that the selection algorithms operate on current relay statuses, bandwidth measurements, and flags, preventing the use of relays that have gone offline or changed their exit policies.