tor_circmgr/path/hspath/
vanguards.rs1use std::result::Result as StdResult;
4
5use rand::Rng;
6
7use tor_error::{Bug, internal};
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;
13
14use crate::path::{MaybeOwnedRelay, TorPath};
15use crate::{Error, Result};
16
17pub(super) struct PathBuilder<'n, 'a, RT: Runtime, R: Rng> {
26 hops: Vec<MaybeOwnedRelay<'n>>,
28 netdir: &'n NetDir,
30 vanguards: &'a VanguardMgr<RT>,
32 rng: &'a mut R,
34 last_hop_kind: HopKind,
36}
37
38#[derive(Copy, Clone, Debug, PartialEq, derive_more::Display)]
40enum HopKind {
41 Guard,
43 Vanguard(Layer),
45 Middle,
47}
48
49impl<'n, 'a, RT: Runtime, R: Rng> PathBuilder<'n, 'a, RT, R> {
50 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 pub(super) fn add_vanguard(
68 mut self,
69 selector: &RelaySelector<'n>,
70 layer: Layer,
71 ) -> Result<Self> {
72 let selector = selector_excluding_neighbors(selector, &self.hops);
73
74 let vanguard: MaybeOwnedRelay = self
75 .vanguards
76 .select_vanguard(&mut self.rng, self.netdir, layer, &selector)?
77 .into();
78 let () = self.add_hop(vanguard, HopKind::Vanguard(layer))?;
79 Ok(self)
80 }
81
82 pub(super) fn add_middle(mut self, selector: &RelaySelector<'n>) -> Result<Self> {
84 let middle =
85 select_middle_for_vanguard_circ(&self.hops, self.netdir, selector, self.rng)?.into();
86 let () = self.add_hop(middle, HopKind::Middle)?;
87 Ok(self)
88 }
89
90 pub(super) fn build(self) -> Result<TorPath<'n>> {
92 use HopKind::*;
93 use Layer::*;
94
95 match 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={})",
99 self.last_hop_kind
100 )
101 .into()),
102 }
103 }
104
105 fn add_hop(&mut self, hop: MaybeOwnedRelay<'n>, hop_kind: HopKind) -> StdResult<(), Bug> {
112 self.update_last_hop_kind(hop_kind)?;
113 self.hops.push(hop);
114 Ok(())
115 }
116
117 fn update_last_hop_kind(&mut self, kind: HopKind) -> StdResult<(), Bug> {
130 use HopKind::*;
131 use Layer::*;
132
133 match (self.last_hop_kind, kind) {
134 (Guard, Vanguard(Layer2))
135 | (Vanguard(Layer2), Vanguard(Layer3))
136 | (Vanguard(Layer2), Middle)
137 | (Vanguard(Layer3), Middle) => {
138 self.last_hop_kind = kind;
139 }
140 (_, _) => {
141 return Err(internal!(
142 "tried to build an invalid vanguard path: cannot add a {kind} hop after {}",
143 self.last_hop_kind
144 ));
145 }
146 }
147
148 Ok(())
149 }
150}
151
152fn 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}
162
163fn exclude_neighbors<'n, T: HasRelayIds + 'n>(hops: &[T]) -> RelayExclusion<'n> {
165 let skip_n = 2;
168 let neighbors = hops.iter().rev().take(skip_n).collect::<Vec<&T>>();
169 exclude_identities(&neighbors[..])
170}
171
172pub(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>> {
187 let selector = selector_excluding_neighbors(selector, hops);
188 let (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}
195
196fn selector_excluding_neighbors<'n, T: HasRelayIds + 'n>(
198 selector: &RelaySelector<'n>,
199 hops: &[T],
200) -> RelaySelector<'n> {
201 let mut selector = selector.clone();
202 let neighbor_exclusion = exclude_neighbors(hops);
203 selector.push_restriction(neighbor_exclusion.into());
204 selector
205}