1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
|
//! Code for building paths for HS circuits.
//!
//! The path builders defined here are used for creating hidden service stub circuits,
//! which are three- or four-hop circuits that have not yet been extended to a target.
//!
//! Stub circuits eventually become introduction, rendezvous, and HsDir circuits.
//! For all circuit types except client rendezvous, the stubs must first be
//! extended by an extra hop:
//!
//! ```text
//! Client hsdir: STUB+ -> HsDir
//! Client intro: STUB+ -> Ipt
//! Client rend: STUB
//! Service hsdir: STUB -> HsDir
//! Service intro: STUB -> Ipt
//! Service rend: STUB+ -> Rpt
//! ```
//!
//! If vanguards are disabled, regular stub circuits (STUB),
//! and extended stub circuits (STUB+) are the same,
//! and are built using
//! [`ExitPathBuilder`](crate::path::exitpath::ExitPathBuilder)'s
//! path selection rules.
//!
//! If vanguards are enabled, the path is built without applying family
//! or same-subnet restrictions at all, the guard is not prohibited
//! from appearing as either of the last two hops of the circuit,
//! and the two circuit stub kinds are built differently
//! depending on the type of vanguards that are in use:
//!
//! * with lite vanguards enabled:
//! ```text
//! STUB = G -> L2 -> M
//! STUB+ = G -> L2 -> M
//! ```
//!
//! * with full vanguards enabled:
//! ```text
//! STUB = G -> L2 -> L3
//! STUB+ = G -> L2 -> L3 -> M
//! ```
// TODO (#1339): we should be consistent with our terminology.
use rand::Rng;
use tor_linkspec::OwnedChanTarget;
use tor_netdir::{NetDir, Relay};
use tor_relay_selection::{RelayExclusion, RelaySelectionConfig, RelaySelector, RelayUsage};
use crate::{hspool::HsCircStubKind, Error, Result};
use super::AnonymousPathBuilder;
use {
crate::path::{pick_path, TorPath},
crate::{DirInfo, PathConfig},
std::time::SystemTime,
tor_guardmgr::{GuardMgr, GuardMonitor, GuardUsable},
tor_rtcompat::Runtime,
};
#[cfg(feature = "vanguards")]
use {
crate::path::{select_guard, MaybeOwnedRelay},
tor_error::bad_api_usage,
tor_guardmgr::vanguards::Layer,
tor_guardmgr::vanguards::VanguardMgr,
tor_guardmgr::VanguardMode,
};
/// A path builder for hidden service circuits.
///
/// See the [hspath](crate::path::hspath) docs for more details.
pub(crate) struct HsPathBuilder {
/// If present, a "target" that every chosen relay must be able to share a circuit with with.
///
/// Ignored if vanguards are in use.
compatible_with: Option<OwnedChanTarget>,
/// The type of circuit to build.
///
/// This is only used if `vanguards` are enabled.
#[cfg_attr(not(feature = "vanguards"), allow(dead_code))]
kind: HsCircStubKind,
}
impl HsPathBuilder {
/// Create a new builder that will try to build a three-hop non-exit path
/// for use with the onion services protocols
/// that is compatible with being extended to an optional given relay.
///
/// (The provided relay is _not_ included in the built path: we only ensure
/// that the path we build does not have any features that would stop us
/// extending it to that relay as a fourth hop.)
pub(crate) fn new(compatible_with: Option<OwnedChanTarget>, kind: HsCircStubKind) -> Self {
Self {
compatible_with,
kind,
}
}
/// Try to create and return a path for a hidden service circuit stub.
#[cfg_attr(feature = "vanguards", allow(unused))]
pub(crate) fn pick_path<'a, R: Rng, RT: Runtime>(
&self,
rng: &mut R,
netdir: DirInfo<'a>,
guards: Option<&GuardMgr<RT>>,
config: &PathConfig,
now: SystemTime,
) -> Result<(TorPath<'a>, Option<GuardMonitor>, Option<GuardUsable>)> {
pick_path(self, rng, netdir, guards, config, now)
}
/// Try to create and return a path for a hidden service circuit stub.
///
/// If vanguards are disabled, this has the same behavior as
/// [pick_path](HsPathBuilder::pick_path).
#[cfg(feature = "vanguards")]
#[cfg_attr(not(feature = "vanguards"), allow(unused))]
pub(crate) fn pick_path_with_vanguards<'a, R: Rng, RT: Runtime>(
&self,
rng: &mut R,
netdir: DirInfo<'a>,
guards: Option<&GuardMgr<RT>>,
vanguards: &VanguardMgr,
config: &PathConfig,
now: SystemTime,
) -> Result<(TorPath<'a>, Option<GuardMonitor>, Option<GuardUsable>)> {
let mode = vanguards.mode();
if mode == VanguardMode::Disabled {
return pick_path(self, rng, netdir, guards, config, now);
}
VanguardHsPathBuilder(self.kind).pick_path(rng, netdir, guards, vanguards, config)
}
}
impl<'a> AnonymousPathBuilder<'a> for HsPathBuilder {
fn chosen_exit(&self) -> Option<&Relay<'_>> {
None
}
fn compatible_with(&self) -> Option<&OwnedChanTarget> {
self.compatible_with.as_ref()
}
fn path_kind(&self) -> &'static str {
"onion-service circuit"
}
fn pick_exit<'s, R: Rng>(
&'s self,
rng: &mut R,
netdir: &'a NetDir,
guard_exclusion: RelayExclusion<'a>,
_rs_cfg: &RelaySelectionConfig<'_>,
) -> Result<(Relay<'a>, RelayUsage)> {
// TODO: This usage is a bit convoluted, and some onion-service-
// related circuits don't need this much stability.
let usage = RelayUsage::middle_relay(Some(&RelayUsage::new_intro_point()));
let selector = RelaySelector::new(usage, guard_exclusion);
let (relay, info) = selector.select_relay(rng, netdir);
let relay = relay.ok_or_else(|| Error::NoRelay {
path_kind: self.path_kind(),
role: "final hop",
problem: info.to_string(),
})?;
Ok((relay, RelayUsage::middle_relay(Some(selector.usage()))))
}
}
/// A path builder for hidden service circuits that use vanguards.
///
/// Used by [`HsPathBuilder`] when vanguards are enabled.
///
/// See the [`HsPathBuilder`] documentation for more details.
#[cfg(feature = "vanguards")]
struct VanguardHsPathBuilder(HsCircStubKind);
#[cfg(feature = "vanguards")]
impl VanguardHsPathBuilder {
/// Try to create and return a path for a hidden service circuit stub.
fn pick_path<'a, R: Rng, RT: Runtime>(
&self,
rng: &mut R,
netdir: DirInfo<'a>,
guards: Option<&GuardMgr<RT>>,
vanguards: &VanguardMgr,
config: &PathConfig,
) -> Result<(TorPath<'a>, Option<GuardMonitor>, Option<GuardUsable>)> {
// TODO: this is copied from pick_path
let netdir = match netdir {
DirInfo::Directory(d) => d,
_ => {
return Err(bad_api_usage!(
"Tried to build a multihop path without a network directory"
)
.into())
}
};
// TODO HS-VANGUARDS: this is probably all wrong!
// Select the guard, allowing it to appear as
// either of the last two hops of the circuit.
let (l1_guard, mon, usable) =
select_guard(rng, netdir, guards, config, None, None, self.path_kind())?;
// Select the vanguards
// We must exclude the guard, because it cannot be selected again as an L2 vanguard
// (a relay won't let you extend the circuit to itself).
//
// TODO #504: Unaccompanied RelayExclusions
let exclude_guard = exclude_identities(&[&l1_guard]);
let l2_guard: MaybeOwnedRelay = vanguards
.select_vanguard(netdir, Layer::Layer2, &exclude_guard)?
.into();
// We exclude
// * the L2 vanguard, because it cannot be selected again as an L3 vanguard
// (a relay won't let you extend the circuit to itself).
// * the guard, because relays won't let you extend the circuit to their previous hop
let neighbor_exclusion = exclude_identities(&[&l2_guard, &l1_guard]);
let mut hops = vec![l1_guard, l2_guard.clone()];
// If needed, select an L3 vanguard too
if vanguards.mode() == VanguardMode::Full {
let l3_guard: MaybeOwnedRelay = vanguards
.select_vanguard(netdir, Layer::Layer3, &neighbor_exclusion)?
.into();
hops.push(l3_guard.clone());
// If full vanguards are enabled, we need an extra hop for STUB+:
// STUB = G -> L2 -> L3
// STUB+ = G -> L2 -> L3 -> M
if self.0 == HsCircStubKind::Extended {
// TODO: this usage has need_stable = true, but we probably
// don't necessarily need a stable relay here.
let usage = RelayUsage::middle_relay(None);
let neighbor_exclusion = exclude_identities(&[&l2_guard, &l3_guard]);
// We exclude
// * the L3 vanguard, because it cannot be selected again as the following
// extra hop (a relay won't let you extend the circuit to itself).
// * the L2 vanguard, because relays won't let you extend the circuit to their previous hop
let selector = RelaySelector::new(usage, neighbor_exclusion);
let (extra_hop, info) = selector.select_relay(rng, netdir);
let extra_hop = extra_hop.ok_or_else(|| Error::NoRelay {
path_kind: self.path_kind(),
role: "extra hop",
problem: info.to_string(),
})?;
hops.push(MaybeOwnedRelay::from(extra_hop));
}
}
Ok((TorPath::new_multihop_from_maybe_owned(hops), mon, usable))
}
/// Return a short description of the path we're trying to build,
/// for error reporting purposes.
fn path_kind(&self) -> &'static str {
"onion-service vanguard circuit"
}
}
/// Build a [`RelayExclusion`] that excludes the specified relays.
#[cfg(feature = "vanguards")]
fn exclude_identities<'a>(exclude_ids: &[&MaybeOwnedRelay<'a>]) -> RelayExclusion<'a> {
use tor_linkspec::HasRelayIds;
RelayExclusion::exclude_identities(
exclude_ids
.iter()
.flat_map(|relay| relay.identities())
.map(|id| id.to_owned())
.collect(),
)
}
|