tor_circmgr/path/hspath/
vanguards.rs

1//! This module provides the [`PathBuilder`] helper for building vanguard [`TorPath`]s.
2
3use std::result::Result as StdResult;
4
5use rand::Rng;
6
7use tor_error::{internal, Bug};
8use tor_guardmgr::vanguards::{Layer, VanguardMgr};
9use tor_linkspec::HasRelayIds;
10use tor_netdir::{NetDir, Relay};
11use tor_relay_selection::{RelayExclusion, RelaySelector, RelayUsage};
12use tor_rtcompat::Runtime;
13
14use crate::path::{MaybeOwnedRelay, TorPath};
15use crate::{Error, Result};
16
17/// A vanguard path builder.
18///
19/// A `PathBuilder` is a state machine whose current state is the [`HopKind`] of its last hop.
20/// Not all state transitions are valid. For the permissible state transitions, see
21/// [update_last_hop_kind](PathBuilder::update_last_hop_kind).
22///
23/// This type is an implementation detail that should remain private.
24/// Used by [`VanguardHsPathBuilder`](super::VanguardHsPathBuilder).
25pub(super) struct PathBuilder<'n, 'a, RT: Runtime, R: Rng> {
26    /// The relays in the path.
27    hops: Vec<MaybeOwnedRelay<'n>>,
28    /// The network directory.
29    netdir: &'n NetDir,
30    /// The vanguard manager.
31    vanguards: &'a VanguardMgr<RT>,
32    /// An RNG for selecting vanguards and middle relays.
33    rng: &'a mut R,
34    /// The `HopKind` of the last hop in the path.
35    last_hop_kind: HopKind,
36}
37
38/// The type of a `PathBuilder` hop.
39#[derive(Copy, Clone, Debug, PartialEq, derive_more::Display)]
40enum HopKind {
41    /// The L1 guard.
42    Guard,
43    /// A vanguard from the specified [`Layer`].
44    Vanguard(Layer),
45    /// A middle relay.
46    Middle,
47}
48
49impl<'n, 'a, RT: Runtime, R: Rng> PathBuilder<'n, 'a, RT, R> {
50    /// Create a new `PathBuilder`.
51    pub(super) fn new(
52        rng: &'a mut R,
53        netdir: &'n NetDir,
54        vanguards: &'a VanguardMgr<RT>,
55        l1_guard: MaybeOwnedRelay<'n>,
56    ) -> Self {
57        Self {
58            hops: vec![l1_guard],
59            netdir,
60            vanguards,
61            rng,
62            last_hop_kind: HopKind::Guard,
63        }
64    }
65
66    /// Extend the path with a vanguard.
67    pub(super) fn add_vanguard(
68        mut self,
69        target_exclusion: &RelayExclusion<'n>,
70        layer: Layer,
71    ) -> Result<Self> {
72        let mut neighbor_exclusion = exclude_neighbors(&self.hops);
73        neighbor_exclusion.extend(target_exclusion);
74        let vanguard: MaybeOwnedRelay = self
75            .vanguards
76            .select_vanguard(&mut self.rng, self.netdir, layer, &neighbor_exclusion)?
77            .into();
78        let () = self.add_hop(vanguard, HopKind::Vanguard(layer))?;
79        Ok(self)
80    }
81
82    /// Extend the path with a middle relay.
83    pub(super) fn add_middle(mut self, target_exclusion: &RelayExclusion<'n>) -> Result<Self> {
84        let middle =
85            select_middle_for_vanguard_circ(&self.hops, self.netdir, target_exclusion, self.rng)?
86                .into();
87        let () = self.add_hop(middle, HopKind::Middle)?;
88        Ok(self)
89    }
90
91    /// Return a [`TorPath`] built using the hops from this `PathBuilder`.
92    pub(super) fn build(self) -> Result<TorPath<'n>> {
93        use HopKind::*;
94        use Layer::*;
95
96        match self.last_hop_kind {
97            Vanguard(Layer3) | Middle => Ok(TorPath::new_multihop_from_maybe_owned(self.hops)),
98            _ => Err(internal!(
99                "tried to build TorPath from incomplete PathBuilder (last_hop_kind={})",
100                self.last_hop_kind
101            )
102            .into()),
103        }
104    }
105
106    /// Try to append `hop` to the end of the path.
107    ///
108    /// This also causes the `PathBuilder` to transition to the state represented by `hop_kind`,
109    /// if the transition is valid.
110    ///
111    /// Returns an error if the `hop_kind` is incompatible with the `HopKind` of the last hop.
112    fn add_hop(&mut self, hop: MaybeOwnedRelay<'n>, hop_kind: HopKind) -> StdResult<(), Bug> {
113        self.update_last_hop_kind(hop_kind)?;
114        self.hops.push(hop);
115        Ok(())
116    }
117
118    /// Transition to the state specified by `kind`.
119    ///
120    /// The state of the `PathBuilder` is represented by the [`HopKind`] of its last hop.
121    /// This function should be called whenever a new hop is added
122    /// (e.g. in [`add_hop`](PathBuilder::add_hop)), to set the current state to the
123    /// [`HopKind`] of the new hop.
124    ///
125    /// Not all transitions are valid. The permissible state transitions are:
126    ///   * `G  -> L2`
127    ///   * `L2 -> L3`
128    ///   * `L2 -> M`
129    ///   * `L3 -> M`
130    fn update_last_hop_kind(&mut self, kind: HopKind) -> StdResult<(), Bug> {
131        use HopKind::*;
132        use Layer::*;
133
134        match (self.last_hop_kind, kind) {
135            (Guard, Vanguard(Layer2))
136            | (Vanguard(Layer2), Vanguard(Layer3))
137            | (Vanguard(Layer2), Middle)
138            | (Vanguard(Layer3), Middle) => {
139                self.last_hop_kind = kind;
140            }
141            (_, _) => {
142                return Err(internal!(
143                    "tried to build an invalid vanguard path: cannot add a {kind} hop after {}",
144                    self.last_hop_kind
145                ))
146            }
147        }
148
149        Ok(())
150    }
151}
152
153/// Build a [`RelayExclusion`] that excludes the specified relays.
154fn exclude_identities<'a, T: HasRelayIds + 'a>(exclude_ids: &[&T]) -> RelayExclusion<'a> {
155    RelayExclusion::exclude_identities(
156        exclude_ids
157            .iter()
158            .flat_map(|relay| relay.identities())
159            .map(|id| id.to_owned())
160            .collect(),
161    )
162}
163
164/// Create a `RelayExclusion` suitable for selecting the next hop to add to `hops`.
165fn exclude_neighbors<'n, T: HasRelayIds + 'n>(hops: &[T]) -> RelayExclusion<'n> {
166    // We must exclude the last 2 hops in the path,
167    // because a relay can't extend to itself or to its predecessor.
168    let skip_n = 2;
169    let neighbors = hops.iter().rev().take(skip_n).collect::<Vec<&T>>();
170    exclude_identities(&neighbors[..])
171}
172
173/// Select a middle relay that can be appended to a vanguard circuit.
174///
175/// Used by [`PathBuilder`] to build [`TorPath`]s of the form
176///
177///   G - L2 - M
178///   G - L2 - L3 - M
179///
180/// If full vanguards are enabled, this is also used by [`HsCircPool`](crate::hspool::HsCircPool),
181/// for extending NAIVE circuits to become GUARDED circuits.
182pub(crate) fn select_middle_for_vanguard_circ<'n, R: Rng, T: HasRelayIds + 'n>(
183    hops: &[T],
184    netdir: &'n NetDir,
185    target_exclusion: &RelayExclusion<'n>,
186    rng: &mut R,
187) -> Result<Relay<'n>> {
188    let mut neighbor_exclusion = exclude_neighbors(hops);
189    neighbor_exclusion.extend(target_exclusion);
190
191    // TODO: this usage has need_stable = true, but we probably
192    // don't necessarily need a stable relay here.
193    let usage = RelayUsage::middle_relay(None);
194    let selector = RelaySelector::new(usage, neighbor_exclusion);
195
196    let (extra_hop, info) = selector.select_relay(rng, netdir);
197    extra_hop.ok_or_else(|| Error::NoRelay {
198        path_kind: "onion-service vanguard circuit",
199        role: "extra hop",
200        problem: info.to_string(),
201    })
202}