1//! This module provides the [`PathBuilder`] helper for building vanguard [`TorPath`]s.
23use std::result::Result as StdResult;
45use rand::Rng;
67use 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};
12use tor_rtcompat::Runtime;
1314use crate::path::{MaybeOwnedRelay, TorPath};
15use crate::{Error, Result};
1617/// 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.
27hops: Vec<MaybeOwnedRelay<'n>>,
28/// The network directory.
29netdir: &'n NetDir,
30/// The vanguard manager.
31vanguards: &'a VanguardMgr<RT>,
32/// An RNG for selecting vanguards and middle relays.
33rng: &'a mut R,
34/// The `HopKind` of the last hop in the path.
35last_hop_kind: HopKind,
36}
3738/// The type of a `PathBuilder` hop.
39#[derive(Copy, Clone, Debug, PartialEq, derive_more::Display)]
40enum HopKind {
41/// The L1 guard.
42Guard,
43/// A vanguard from the specified [`Layer`].
44Vanguard(Layer),
45/// A middle relay.
46Middle,
47}
4849impl<'n, 'a, RT: Runtime, R: Rng> PathBuilder<'n, 'a, RT, R> {
50/// Create a new `PathBuilder`.
51pub(super) fn new(
52 rng: &'a mut R,
53 netdir: &'n NetDir,
54 vanguards: &'a VanguardMgr<RT>,
55 l1_guard: MaybeOwnedRelay<'n>,
56 ) -> Self {
57Self {
58 hops: vec![l1_guard],
59 netdir,
60 vanguards,
61 rng,
62 last_hop_kind: HopKind::Guard,
63 }
64 }
6566/// Extend the path with a vanguard.
67pub(super) fn add_vanguard(
68mut self,
69 selector: &RelaySelector<'n>,
70 layer: Layer,
71 ) -> Result<Self> {
72let selector = selector_excluding_neighbors(selector, &self.hops);
7374let vanguard: MaybeOwnedRelay = self
75.vanguards
76 .select_vanguard(&mut self.rng, self.netdir, layer, &selector)?
77.into();
78let () = self.add_hop(vanguard, HopKind::Vanguard(layer))?;
79Ok(self)
80 }
8182/// Extend the path with a middle relay.
83pub(super) fn add_middle(mut self, selector: &RelaySelector<'n>) -> Result<Self> {
84let middle =
85 select_middle_for_vanguard_circ(&self.hops, self.netdir, selector, self.rng)?.into();
86let () = self.add_hop(middle, HopKind::Middle)?;
87Ok(self)
88 }
8990/// Return a [`TorPath`] built using the hops from this `PathBuilder`.
91pub(super) fn build(self) -> Result<TorPath<'n>> {
92use HopKind::*;
93use Layer::*;
9495match self.last_hop_kind {
96 Vanguard(Layer3) | Middle => Ok(TorPath::new_multihop_from_maybe_owned(self.hops)),
97_ => Err(internal!(
98"tried to build TorPath from incomplete PathBuilder (last_hop_kind={})",
99self.last_hop_kind
100 )
101 .into()),
102 }
103 }
104105/// Try to append `hop` to the end of the path.
106 ///
107 /// This also causes the `PathBuilder` to transition to the state represented by `hop_kind`,
108 /// if the transition is valid.
109 ///
110 /// Returns an error if the `hop_kind` is incompatible with the `HopKind` of the last hop.
111fn add_hop(&mut self, hop: MaybeOwnedRelay<'n>, hop_kind: HopKind) -> StdResult<(), Bug> {
112self.update_last_hop_kind(hop_kind)?;
113self.hops.push(hop);
114Ok(())
115 }
116117/// Transition to the state specified by `kind`.
118 ///
119 /// The state of the `PathBuilder` is represented by the [`HopKind`] of its last hop.
120 /// This function should be called whenever a new hop is added
121 /// (e.g. in [`add_hop`](PathBuilder::add_hop)), to set the current state to the
122 /// [`HopKind`] of the new hop.
123 ///
124 /// Not all transitions are valid. The permissible state transitions are:
125 /// * `G -> L2`
126 /// * `L2 -> L3`
127 /// * `L2 -> M`
128 /// * `L3 -> M`
129fn update_last_hop_kind(&mut self, kind: HopKind) -> StdResult<(), Bug> {
130use HopKind::*;
131use Layer::*;
132133match (self.last_hop_kind, kind) {
134 (Guard, Vanguard(Layer2))
135 | (Vanguard(Layer2), Vanguard(Layer3))
136 | (Vanguard(Layer2), Middle)
137 | (Vanguard(Layer3), Middle) => {
138self.last_hop_kind = kind;
139 }
140 (_, _) => {
141return Err(internal!(
142"tried to build an invalid vanguard path: cannot add a {kind} hop after {}",
143self.last_hop_kind
144 ))
145 }
146 }
147148Ok(())
149 }
150}
151152/// Build a [`RelayExclusion`] that excludes the specified relays.
153fn exclude_identities<'a, T: HasRelayIds + 'a>(exclude_ids: &[&T]) -> RelayExclusion<'a> {
154 RelayExclusion::exclude_identities(
155 exclude_ids
156 .iter()
157 .flat_map(|relay| relay.identities())
158 .map(|id| id.to_owned())
159 .collect(),
160 )
161}
162163/// Create a `RelayExclusion` suitable for selecting the next hop to add to `hops`.
164fn exclude_neighbors<'n, T: HasRelayIds + 'n>(hops: &[T]) -> RelayExclusion<'n> {
165// We must exclude the last 2 hops in the path,
166 // because a relay can't extend to itself or to its predecessor.
167let skip_n = 2;
168let neighbors = hops.iter().rev().take(skip_n).collect::<Vec<&T>>();
169 exclude_identities(&neighbors[..])
170}
171172/// Select a middle relay that can be appended to a vanguard circuit.
173///
174/// Used by [`PathBuilder`] to build [`TorPath`]s of the form
175///
176/// G - L2 - M
177/// G - L2 - L3 - M
178///
179/// If full vanguards are enabled, this is also used by [`HsCircPool`](crate::hspool::HsCircPool),
180/// for extending NAIVE circuits to become GUARDED circuits.
181pub(crate) fn select_middle_for_vanguard_circ<'n, R: Rng, T: HasRelayIds + 'n>(
182 hops: &[T],
183 netdir: &'n NetDir,
184 selector: &RelaySelector<'n>,
185 rng: &mut R,
186) -> Result<Relay<'n>> {
187let selector = selector_excluding_neighbors(selector, hops);
188let (extra_hop, info) = selector.select_relay(rng, netdir);
189 extra_hop.ok_or_else(|| Error::NoRelay {
190 path_kind: "onion-service vanguard circuit",
191 role: "extra hop",
192 problem: info.to_string(),
193 })
194}
195196/// Extend the selector T to also exclude neighbors, based on `hops`.
197fn selector_excluding_neighbors<'n, T: HasRelayIds + 'n>(
198 selector: &RelaySelector<'n>,
199 hops: &[T],
200) -> RelaySelector<'n> {
201let mut selector = selector.clone();
202let neighbor_exclusion = exclude_neighbors(hops);
203 selector.push_restriction(neighbor_exclusion.into());
204 selector
205}