1
//! Code to construct paths through the Tor network
2
//!
3
//! TODO: I'm not sure this belongs in circmgr, but this is the best place
4
//! I can think of for now.  I'm also not sure this should be public.
5

            
6
pub(crate) mod dirpath;
7
pub(crate) mod exitpath;
8

            
9
// Care must be taken if/when we decide to make this pub.
10
//
11
// The `HsPathBuilder` exposes two path building functions,
12
// one that uses vanguards, and one that doesn't.
13
// We want to strongly encourage the use of the vanguards-aware
14
// version of the function whenever the `vanguards` feature is enabled,
15
// without breaking any of its existing non-vanguard uses.
16
#[cfg(feature = "hs-common")]
17
pub(crate) mod hspath;
18

            
19
use std::result::Result as StdResult;
20
use std::time::SystemTime;
21

            
22
use rand::Rng;
23

            
24
use tor_error::{bad_api_usage, internal, Bug};
25
#[cfg(feature = "geoip")]
26
use tor_geoip::{CountryCode, HasCountryCode};
27
use tor_guardmgr::fallback::FallbackDir;
28
use tor_guardmgr::{GuardMgr, GuardMonitor, GuardUsable};
29
use tor_linkspec::{HasAddrs, HasRelayIds, OwnedChanTarget, OwnedCircTarget, RelayIdSet};
30
use tor_netdir::{FamilyRules, NetDir, Relay};
31
use tor_relay_selection::{RelayExclusion, RelaySelectionConfig, RelaySelector, RelayUsage};
32
use tor_rtcompat::Runtime;
33

            
34
#[cfg(all(feature = "vanguards", feature = "hs-common"))]
35
use tor_guardmgr::vanguards::Vanguard;
36

            
37
use crate::usage::ExitPolicy;
38
use crate::{DirInfo, Error, PathConfig, Result};
39

            
40
/// A list of Tor relays through the network.
41
pub struct TorPath<'a> {
42
    /// The inner TorPath state.
43
    inner: TorPathInner<'a>,
44
}
45

            
46
/// Non-public helper type to represent the different kinds of Tor path.
47
///
48
/// (This is a separate type to avoid exposing its details to the user.)
49
///
50
/// NOTE: This type should NEVER be visible outside of path.rs and its
51
/// sub-modules.
52
enum TorPathInner<'a> {
53
    /// A single-hop path for use with a directory cache, when a relay is
54
    /// known.
55
    OneHop(Relay<'a>), // This could just be a routerstatus.
56
    /// A single-hop path for use with a directory cache, when we don't have
57
    /// a consensus.
58
    FallbackOneHop(&'a FallbackDir),
59
    /// A single-hop path taken from an OwnedChanTarget.
60
    OwnedOneHop(OwnedChanTarget),
61
    /// A multi-hop path, containing one or more relays.
62
    Path(Vec<MaybeOwnedRelay<'a>>),
63
}
64

            
65
/// Identifier for a relay that could be either known from a NetDir, or
66
/// specified as an OwnedCircTarget.
67
///
68
/// NOTE: This type should NEVER be visible outside of path.rs and its
69
/// sub-modules.
70
#[derive(Clone)]
71
enum MaybeOwnedRelay<'a> {
72
    /// A relay from the netdir.
73
    Relay(Relay<'a>),
74
    /// An owned description of a relay.
75
    //
76
    // TODO: I don't love boxing this, but it fixes a warning about
77
    // variant sizes and is probably not the worst thing we could do.  OTOH, we
78
    // could probably afford to use an Arc here and in guardmgr? -nickm
79
    //
80
    // TODO: Try using an Arc. -nickm
81
    Owned(Box<OwnedCircTarget>),
82
}
83

            
84
impl<'a> MaybeOwnedRelay<'a> {
85
    /// Extract an OwnedCircTarget from this relay.
86
50464
    fn to_owned(&self) -> OwnedCircTarget {
87
50464
        match self {
88
50464
            MaybeOwnedRelay::Relay(r) => OwnedCircTarget::from_circ_target(r),
89
            MaybeOwnedRelay::Owned(o) => o.as_ref().clone(),
90
        }
91
50464
    }
92
}
93

            
94
impl<'a> From<OwnedCircTarget> for MaybeOwnedRelay<'a> {
95
    fn from(ct: OwnedCircTarget) -> Self {
96
        MaybeOwnedRelay::Owned(Box::new(ct))
97
    }
98
}
99
impl<'a> From<Relay<'a>> for MaybeOwnedRelay<'a> {
100
49880
    fn from(r: Relay<'a>) -> Self {
101
49880
        MaybeOwnedRelay::Relay(r)
102
49880
    }
103
}
104
impl<'a> HasAddrs for MaybeOwnedRelay<'a> {
105
    fn addrs(&self) -> &[std::net::SocketAddr] {
106
        match self {
107
            MaybeOwnedRelay::Relay(r) => r.addrs(),
108
            MaybeOwnedRelay::Owned(r) => r.addrs(),
109
        }
110
    }
111
}
112
impl<'a> HasRelayIds for MaybeOwnedRelay<'a> {
113
394368
    fn identity(
114
394368
        &self,
115
394368
        key_type: tor_linkspec::RelayIdType,
116
394368
    ) -> Option<tor_linkspec::RelayIdRef<'_>> {
117
394368
        match self {
118
394368
            MaybeOwnedRelay::Relay(r) => r.identity(key_type),
119
            MaybeOwnedRelay::Owned(r) => r.identity(key_type),
120
        }
121
394368
    }
122
}
123

            
124
#[cfg(all(feature = "vanguards", feature = "hs-common"))]
125
impl<'a> From<Vanguard<'a>> for MaybeOwnedRelay<'a> {
126
80
    fn from(r: Vanguard<'a>) -> Self {
127
80
        MaybeOwnedRelay::Relay(r.relay().clone())
128
80
    }
129
}
130

            
131
impl<'a> TorPath<'a> {
132
    /// Create a new one-hop path for use with a directory cache with a known
133
    /// relay.
134
    pub fn new_one_hop(relay: Relay<'a>) -> Self {
135
        Self {
136
            inner: TorPathInner::OneHop(relay),
137
        }
138
    }
139

            
140
    /// Create a new one-hop path for use with a directory cache when we don't
141
    /// have a consensus.
142
    pub fn new_fallback_one_hop(fallback_dir: &'a FallbackDir) -> Self {
143
        Self {
144
            inner: TorPathInner::FallbackOneHop(fallback_dir),
145
        }
146
    }
147

            
148
    /// Construct a new one-hop path for directory use from an arbitrarily
149
    /// chosen channel target.
150
328
    pub fn new_one_hop_owned<T: tor_linkspec::ChanTarget>(target: &T) -> Self {
151
328
        Self {
152
328
            inner: TorPathInner::OwnedOneHop(OwnedChanTarget::from_chan_target(target)),
153
328
        }
154
328
    }
155

            
156
    /// Create a new multi-hop path with a given number of ordered relays.
157
    pub fn new_multihop(relays: impl IntoIterator<Item = Relay<'a>>) -> Self {
158
        Self {
159
            inner: TorPathInner::Path(relays.into_iter().map(MaybeOwnedRelay::from).collect()),
160
        }
161
    }
162
    /// Construct a new multi-hop path from a vector of `MaybeOwned`.
163
    ///
164
    /// Internal only; do not expose without fixing up this API a bit.
165
16632
    fn new_multihop_from_maybe_owned(relays: Vec<MaybeOwnedRelay<'a>>) -> Self {
166
16632
        Self {
167
16632
            inner: TorPathInner::Path(relays),
168
16632
        }
169
16632
    }
170

            
171
    /// Return the final relay in this path, if this is a path for use
172
    /// with exit circuits.
173
52
    fn exit_relay(&self) -> Option<&MaybeOwnedRelay<'a>> {
174
52
        match &self.inner {
175
52
            TorPathInner::Path(relays) if !relays.is_empty() => Some(&relays[relays.len() - 1]),
176
4
            _ => None,
177
        }
178
52
    }
179

            
180
    /// Return the exit policy of the final relay in this path, if this is a
181
    /// path for use with exit circuits with an exit taken from the network
182
    /// directory.
183
26
    pub(crate) fn exit_policy(&self) -> Option<ExitPolicy> {
184
38
        self.exit_relay().and_then(|r| match r {
185
24
            MaybeOwnedRelay::Relay(r) => Some(ExitPolicy::from_relay(r)),
186
            MaybeOwnedRelay::Owned(_) => None,
187
38
        })
188
26
    }
189

            
190
    /// Return the country code of the final relay in this path, if this is a
191
    /// path for use with exit circuits with an exit taken from the network
192
    /// directory.
193
    #[cfg(feature = "geoip")]
194
24
    pub(crate) fn country_code(&self) -> Option<CountryCode> {
195
36
        self.exit_relay().and_then(|r| match r {
196
24
            MaybeOwnedRelay::Relay(r) => r.country_code(),
197
            MaybeOwnedRelay::Owned(_) => None,
198
36
        })
199
24
    }
200

            
201
    /// Return the number of relays in this path.
202
    #[allow(clippy::len_without_is_empty)]
203
666
    pub fn len(&self) -> usize {
204
        use TorPathInner::*;
205
666
        match &self.inner {
206
            OneHop(_) => 1,
207
            FallbackOneHop(_) => 1,
208
8
            OwnedOneHop(_) => 1,
209
658
            Path(p) => p.len(),
210
        }
211
666
    }
212

            
213
    /// Return true if every `Relay` in this path has the stable flag.
214
    ///
215
    /// Assumes that Owned elements of this path are stable.
216
16
    pub(crate) fn appears_stable(&self) -> bool {
217
16
        // TODO #504: this looks at low_level_details() in questionable way.
218
16
        match &self.inner {
219
            TorPathInner::OneHop(r) => r.low_level_details().is_flagged_stable(),
220
            TorPathInner::FallbackOneHop(_) => true,
221
            TorPathInner::OwnedOneHop(_) => true,
222
56
            TorPathInner::Path(relays) => relays.iter().all(|maybe_owned| match maybe_owned {
223
48
                MaybeOwnedRelay::Relay(r) => r.low_level_details().is_flagged_stable(),
224
                MaybeOwnedRelay::Owned(_) => true,
225
56
            }),
226
        }
227
16
    }
228
}
229

            
230
/// A path composed entirely of owned components.
231
#[derive(Clone, Debug)]
232
pub(crate) enum OwnedPath {
233
    /// A path where we only know how to make circuits via CREATE_FAST.
234
    ChannelOnly(OwnedChanTarget),
235
    /// A path of one or more hops created via normal Tor handshakes.
236
    Normal(Vec<OwnedCircTarget>),
237
}
238

            
239
impl<'a> TryFrom<&TorPath<'a>> for OwnedPath {
240
    type Error = crate::Error;
241
16818
    fn try_from(p: &TorPath<'a>) -> Result<OwnedPath> {
242
        use TorPathInner::*;
243

            
244
16818
        Ok(match &p.inner {
245
            FallbackOneHop(h) => OwnedPath::ChannelOnly(OwnedChanTarget::from_chan_target(*h)),
246
            OneHop(h) => OwnedPath::Normal(vec![OwnedCircTarget::from_circ_target(h)]),
247
            OwnedOneHop(owned) => OwnedPath::ChannelOnly(owned.clone()),
248
16818
            Path(p) if !p.is_empty() => {
249
16816
                OwnedPath::Normal(p.iter().map(MaybeOwnedRelay::to_owned).collect())
250
            }
251
            Path(_) => {
252
2
                return Err(bad_api_usage!("Path with no entries!").into());
253
            }
254
        })
255
16818
    }
256
}
257

            
258
impl OwnedPath {
259
    /// Return the number of hops in this path.
260
    #[allow(clippy::len_without_is_empty)]
261
16
    pub(crate) fn len(&self) -> usize {
262
16
        match self {
263
4
            OwnedPath::ChannelOnly(_) => 1,
264
12
            OwnedPath::Normal(p) => p.len(),
265
        }
266
16
    }
267
}
268

            
269
/// A path builder that builds multi-hop, anonymous paths.
270
trait AnonymousPathBuilder {
271
    /// Return the "target" that every chosen relay must be able to share a circuit with with.
272
    fn compatible_with(&self) -> Option<&OwnedChanTarget>;
273

            
274
    /// Return a short description of the path we're trying to build,
275
    /// for error reporting purposes.
276
    fn path_kind(&self) -> &'static str;
277

            
278
    /// Find a suitable exit node from either the chosen exit or from the network directory.
279
    ///
280
    /// Return the exit, along with the usage for a middle node corresponding
281
    /// to this exit.
282
    fn pick_exit<'a, R: Rng>(
283
        &self,
284
        rng: &mut R,
285
        netdir: &'a NetDir,
286
        guard_exclusion: RelayExclusion<'a>,
287
        rs_cfg: &RelaySelectionConfig<'_>,
288
    ) -> Result<(Relay<'a>, RelayUsage)>;
289
}
290

            
291
/// Try to create and return a path corresponding to the requirements of
292
/// this builder.
293
16612
fn pick_path<'a, B: AnonymousPathBuilder, R: Rng, RT: Runtime>(
294
16612
    builder: &B,
295
16612
    rng: &mut R,
296
16612
    netdir: DirInfo<'a>,
297
16612
    guards: &GuardMgr<RT>,
298
16612
    config: &PathConfig,
299
16612
    _now: SystemTime,
300
16612
) -> Result<(TorPath<'a>, GuardMonitor, GuardUsable)> {
301
16612
    let netdir = match netdir {
302
16612
        DirInfo::Directory(d) => d,
303
        _ => {
304
            return Err(bad_api_usage!(
305
                "Tried to build a multihop path without a network directory"
306
            )
307
            .into())
308
        }
309
    };
310
16612
    let rs_cfg = config.relay_selection_config();
311
16612
    let family_rules = FamilyRules::from(netdir.params());
312

            
313
16612
    let target_exclusion = match builder.compatible_with() {
314
202
        Some(ct) => {
315
202
            // Exclude the target from appearing in other positions in the path.
316
404
            let ids = RelayIdSet::from_iter(ct.identities().map(|id_ref| id_ref.to_owned()));
317
202
            // TODO torspec#265: we do not apply same-family restrictions
318
202
            // (a relay in the same family as the target can occur in the path).
319
202
            //
320
202
            // We need to decide if this is the correct behavior,
321
202
            // and if so, document it in torspec.
322
202
            RelayExclusion::exclude_identities(ids)
323
        }
324
16410
        None => RelayExclusion::no_relays_excluded(),
325
    };
326

            
327
    // TODO-SPEC: Because of limitations in guard selection, we have to
328
    // pick the guard before the exit, which is not what our spec says.
329
16612
    let (guard, mon, usable) = select_guard(netdir, guards, builder.compatible_with())?;
330

            
331
16612
    let guard_exclusion = match &guard {
332
16612
        MaybeOwnedRelay::Relay(r) => RelayExclusion::exclude_relays_in_same_family(
333
16612
            &config.relay_selection_config(),
334
16612
            vec![r.clone()],
335
16612
            family_rules,
336
16612
        ),
337
        MaybeOwnedRelay::Owned(ct) => RelayExclusion::exclude_channel_target_family(
338
            &config.relay_selection_config(),
339
            ct.as_ref(),
340
            netdir,
341
        ),
342
    };
343

            
344
16612
    let mut exclusion = guard_exclusion.clone();
345
16612
    exclusion.extend(&target_exclusion);
346
16612
    let (exit, middle_usage) = builder.pick_exit(rng, netdir, exclusion, &rs_cfg)?;
347

            
348
16594
    let mut family_exclusion =
349
16594
        RelayExclusion::exclude_relays_in_same_family(&rs_cfg, vec![exit.clone()], family_rules);
350
16594
    family_exclusion.extend(&guard_exclusion);
351
16594
    let mut exclusion = family_exclusion;
352
16594
    exclusion.extend(&target_exclusion);
353
16594

            
354
16594
    let selector = RelaySelector::new(middle_usage, exclusion);
355
16594
    let (middle, info) = selector.select_relay(rng, netdir);
356
16594
    let middle = middle.ok_or_else(|| Error::NoRelay {
357
2
        path_kind: builder.path_kind(),
358
2
        role: "middle relay",
359
2
        problem: info.to_string(),
360
16594
    })?;
361

            
362
16592
    let hops = vec![
363
16592
        guard,
364
16592
        MaybeOwnedRelay::from(middle),
365
16592
        MaybeOwnedRelay::from(exit),
366
16592
    ];
367
16592

            
368
16592
    ensure_unique_hops(&hops)?;
369

            
370
16592
    Ok((TorPath::new_multihop_from_maybe_owned(hops), mon, usable))
371
16612
}
372

            
373
/// Returns an error if the specified hop list contains duplicates.
374
16592
fn ensure_unique_hops<'a>(hops: &'a [MaybeOwnedRelay<'a>]) -> StdResult<(), Bug> {
375
49776
    for (i, hop) in hops.iter().enumerate() {
376
49776
        if let Some(hop2) = hops
377
49776
            .iter()
378
49776
            .skip(i + 1)
379
74664
            .find(|hop2| hop.clone().has_any_relay_id_from(*hop2))
380
        {
381
            return Err(internal!(
382
                "invalid path: the IDs of hops {} and {} overlap?!",
383
                hop.display_relay_ids(),
384
                hop2.display_relay_ids()
385
            ));
386
49776
        }
387
    }
388
16592
    Ok(())
389
16592
}
390

            
391
/// Try to select a guard corresponding to the requirements of
392
/// this builder.
393
16668
fn select_guard<'a, RT: Runtime>(
394
16668
    netdir: &'a NetDir,
395
16668
    guardmgr: &GuardMgr<RT>,
396
16668
    compatible_with: Option<&OwnedChanTarget>,
397
16668
) -> Result<(MaybeOwnedRelay<'a>, GuardMonitor, GuardUsable)> {
398
16668
    // TODO: Extract this section into its own function, and see
399
16668
    // what it can share with tor_relay_selection.
400
16668
    let mut b = tor_guardmgr::GuardUsageBuilder::default();
401
16668
    b.kind(tor_guardmgr::GuardUsageKind::Data);
402
16668
    if let Some(avoid_target) = compatible_with {
403
202
        let mut family = RelayIdSet::new();
404
404
        family.extend(avoid_target.identities().map(|id| id.to_owned()));
405
202
        if let Some(avoid_relay) = netdir.by_ids(avoid_target) {
406
            family.extend(netdir.known_family_members(&avoid_relay).map(|r| *r.id()));
407
202
        }
408
202
        b.restrictions()
409
202
            .push(tor_guardmgr::GuardRestriction::AvoidAllIds(family));
410
16466
    }
411
16668
    let guard_usage = b.build().expect("Failed while building guard usage!");
412
16668
    let (guard, mon, usable) = guardmgr.select_guard(guard_usage)?;
413
16668
    let guard = if let Some(ct) = guard.as_circ_target() {
414
        // This is a bridge; we will not look for it in the network directory.
415
        MaybeOwnedRelay::from(ct.clone())
416
    } else {
417
        // Look this up in the network directory: we expect to find a relay.
418
16668
        guard
419
16668
            .get_relay(netdir)
420
16668
            .ok_or_else(|| {
421
                internal!(
422
                    "Somehow the guardmgr gave us an unlisted guard {:?}!",
423
                    guard
424
                )
425
16668
            })?
426
16668
            .into()
427
    };
428
16668
    Ok((guard, mon, usable))
429
16668
}
430

            
431
/// For testing: make sure that `path` is the same when it is an owned
432
/// path.
433
#[cfg(test)]
434
16160
fn assert_same_path_when_owned(path: &TorPath<'_>) {
435
16160
    #![allow(clippy::unwrap_used)]
436
16160
    let owned: OwnedPath = path.try_into().unwrap();
437
16160

            
438
16160
    match (&owned, &path.inner) {
439
        (OwnedPath::ChannelOnly(c), TorPathInner::FallbackOneHop(f)) => {
440
            assert!(c.same_relay_ids(*f));
441
        }
442
        (OwnedPath::Normal(p), TorPathInner::OneHop(h)) => {
443
            assert_eq!(p.len(), 1);
444
            assert!(p[0].same_relay_ids(h));
445
        }
446
16160
        (OwnedPath::Normal(p1), TorPathInner::Path(p2)) => {
447
16160
            assert_eq!(p1.len(), p2.len());
448
48480
            for (n1, n2) in p1.iter().zip(p2.iter()) {
449
48480
                assert!(n1.same_relay_ids(n2));
450
            }
451
        }
452
        (_, _) => {
453
            panic!("Mismatched path types.");
454
        }
455
    }
456
16160
}