1pub(crate) mod dirpath;
7pub(crate) mod exitpath;
8
9#[cfg(feature = "hs-common")]
17pub(crate) mod hspath;
18
19use std::result::Result as StdResult;
20use std::time::SystemTime;
21
22use itertools::Either;
23use rand::Rng;
24
25use tor_dircommon::fallback::FallbackDir;
26use tor_error::{Bug, bad_api_usage, internal};
27#[cfg(feature = "geoip")]
28use tor_geoip::{CountryCode, HasCountryCode};
29use tor_guardmgr::{GuardMgr, GuardMonitor, GuardUsable};
30use tor_linkspec::{HasAddrs, HasRelayIds, OwnedChanTarget, OwnedCircTarget, RelayIdSet};
31use tor_netdir::{FamilyRules, NetDir, Relay};
32use tor_relay_selection::{RelayExclusion, RelaySelectionConfig, RelaySelector, RelayUsage};
33use tor_rtcompat::Runtime;
34
35#[cfg(all(feature = "vanguards", feature = "hs-common"))]
36use tor_guardmgr::vanguards::Vanguard;
37
38use crate::usage::ExitPolicy;
39use crate::{DirInfo, Error, PathConfig, Result};
40
41pub struct TorPath<'a> {
43    inner: TorPathInner<'a>,
45}
46
47enum TorPathInner<'a> {
54    OneHop(Relay<'a>), FallbackOneHop(&'a FallbackDir),
60    OwnedOneHop(OwnedChanTarget),
62    Path(Vec<MaybeOwnedRelay<'a>>),
64}
65
66#[derive(Clone)]
72enum MaybeOwnedRelay<'a> {
73    Relay(Relay<'a>),
75    Owned(Box<OwnedCircTarget>),
83}
84
85impl<'a> MaybeOwnedRelay<'a> {
86    fn to_owned(&self) -> OwnedCircTarget {
88        match self {
89            MaybeOwnedRelay::Relay(r) => OwnedCircTarget::from_circ_target(r),
90            MaybeOwnedRelay::Owned(o) => o.as_ref().clone(),
91        }
92    }
93}
94
95impl<'a> From<OwnedCircTarget> for MaybeOwnedRelay<'a> {
96    fn from(ct: OwnedCircTarget) -> Self {
97        MaybeOwnedRelay::Owned(Box::new(ct))
98    }
99}
100impl<'a> From<Relay<'a>> for MaybeOwnedRelay<'a> {
101    fn from(r: Relay<'a>) -> Self {
102        MaybeOwnedRelay::Relay(r)
103    }
104}
105impl<'a> HasAddrs for MaybeOwnedRelay<'a> {
106    fn addrs(&self) -> impl Iterator<Item = std::net::SocketAddr> {
107        match self {
108            MaybeOwnedRelay::Relay(r) => Either::Left(r.addrs()),
109            MaybeOwnedRelay::Owned(r) => Either::Right(r.addrs()),
110        }
111    }
112}
113impl<'a> HasRelayIds for MaybeOwnedRelay<'a> {
114    fn identity(
115        &self,
116        key_type: tor_linkspec::RelayIdType,
117    ) -> Option<tor_linkspec::RelayIdRef<'_>> {
118        match self {
119            MaybeOwnedRelay::Relay(r) => r.identity(key_type),
120            MaybeOwnedRelay::Owned(r) => r.identity(key_type),
121        }
122    }
123}
124
125#[cfg(all(feature = "vanguards", feature = "hs-common"))]
126impl<'a> From<Vanguard<'a>> for MaybeOwnedRelay<'a> {
127    fn from(r: Vanguard<'a>) -> Self {
128        MaybeOwnedRelay::Relay(r.relay().clone())
129    }
130}
131
132impl<'a> TorPath<'a> {
133    pub fn new_one_hop(relay: Relay<'a>) -> Self {
136        Self {
137            inner: TorPathInner::OneHop(relay),
138        }
139    }
140
141    pub fn new_fallback_one_hop(fallback_dir: &'a FallbackDir) -> Self {
144        Self {
145            inner: TorPathInner::FallbackOneHop(fallback_dir),
146        }
147    }
148
149    pub fn new_one_hop_owned<T: tor_linkspec::ChanTarget>(target: &T) -> Self {
152        Self {
153            inner: TorPathInner::OwnedOneHop(OwnedChanTarget::from_chan_target(target)),
154        }
155    }
156
157    pub fn new_multihop(relays: impl IntoIterator<Item = Relay<'a>>) -> Self {
159        Self {
160            inner: TorPathInner::Path(relays.into_iter().map(MaybeOwnedRelay::from).collect()),
161        }
162    }
163    fn new_multihop_from_maybe_owned(relays: Vec<MaybeOwnedRelay<'a>>) -> Self {
167        Self {
168            inner: TorPathInner::Path(relays),
169        }
170    }
171
172    fn exit_relay(&self) -> Option<&MaybeOwnedRelay<'a>> {
175        match &self.inner {
176            TorPathInner::Path(relays) if !relays.is_empty() => Some(&relays[relays.len() - 1]),
177            _ => None,
178        }
179    }
180
181    pub(crate) fn exit_policy(&self) -> Option<ExitPolicy> {
185        self.exit_relay().and_then(|r| match r {
186            MaybeOwnedRelay::Relay(r) => Some(ExitPolicy::from_relay(r)),
187            MaybeOwnedRelay::Owned(_) => None,
188        })
189    }
190
191    #[cfg(feature = "geoip")]
195    pub(crate) fn country_code(&self) -> Option<CountryCode> {
196        self.exit_relay().and_then(|r| match r {
197            MaybeOwnedRelay::Relay(r) => r.country_code(),
198            MaybeOwnedRelay::Owned(_) => None,
199        })
200    }
201
202    #[allow(clippy::len_without_is_empty)]
204    pub fn len(&self) -> usize {
205        use TorPathInner::*;
206        match &self.inner {
207            OneHop(_) => 1,
208            FallbackOneHop(_) => 1,
209            OwnedOneHop(_) => 1,
210            Path(p) => p.len(),
211        }
212    }
213
214    pub(crate) fn appears_stable(&self) -> bool {
218        match &self.inner {
220            TorPathInner::OneHop(r) => r.low_level_details().is_flagged_stable(),
221            TorPathInner::FallbackOneHop(_) => true,
222            TorPathInner::OwnedOneHop(_) => true,
223            TorPathInner::Path(relays) => relays.iter().all(|maybe_owned| match maybe_owned {
224                MaybeOwnedRelay::Relay(r) => r.low_level_details().is_flagged_stable(),
225                MaybeOwnedRelay::Owned(_) => true,
226            }),
227        }
228    }
229}
230
231#[derive(Clone, Debug)]
233pub(crate) enum OwnedPath {
234    ChannelOnly(OwnedChanTarget),
236    Normal(Vec<OwnedCircTarget>),
238}
239
240impl<'a> TryFrom<&TorPath<'a>> for OwnedPath {
241    type Error = crate::Error;
242    fn try_from(p: &TorPath<'a>) -> Result<OwnedPath> {
243        use TorPathInner::*;
244
245        Ok(match &p.inner {
246            FallbackOneHop(h) => OwnedPath::ChannelOnly(OwnedChanTarget::from_chan_target(*h)),
247            OneHop(h) => OwnedPath::Normal(vec![OwnedCircTarget::from_circ_target(h)]),
248            OwnedOneHop(owned) => OwnedPath::ChannelOnly(owned.clone()),
249            Path(p) if !p.is_empty() => {
250                OwnedPath::Normal(p.iter().map(MaybeOwnedRelay::to_owned).collect())
251            }
252            Path(_) => {
253                return Err(bad_api_usage!("Path with no entries!").into());
254            }
255        })
256    }
257}
258
259impl OwnedPath {
260    #[allow(clippy::len_without_is_empty)]
262    pub(crate) fn len(&self) -> usize {
263        match self {
264            OwnedPath::ChannelOnly(_) => 1,
265            OwnedPath::Normal(p) => p.len(),
266        }
267    }
268
269    pub(crate) fn first_hop_as_chantarget(&self) -> &OwnedChanTarget {
271        match self {
272            OwnedPath::ChannelOnly(ct) => ct,
273            OwnedPath::Normal(path) => path[0].chan_target(),
275        }
276    }
277}
278
279trait AnonymousPathBuilder {
281    fn compatible_with(&self) -> Option<&OwnedChanTarget>;
283
284    fn path_kind(&self) -> &'static str;
287
288    fn pick_exit<'a, R: Rng>(
293        &self,
294        rng: &mut R,
295        netdir: &'a NetDir,
296        guard_exclusion: RelayExclusion<'a>,
297        rs_cfg: &RelaySelectionConfig<'_>,
298    ) -> Result<(Relay<'a>, RelayUsage)>;
299}
300
301fn pick_path<'a, B: AnonymousPathBuilder, R: Rng, RT: Runtime>(
304    builder: &B,
305    rng: &mut R,
306    netdir: DirInfo<'a>,
307    guards: &GuardMgr<RT>,
308    config: &PathConfig,
309    _now: SystemTime,
310) -> Result<(TorPath<'a>, GuardMonitor, GuardUsable)> {
311    let netdir = match netdir {
312        DirInfo::Directory(d) => d,
313        _ => {
314            return Err(bad_api_usage!(
315                "Tried to build a multihop path without a network directory"
316            )
317            .into());
318        }
319    };
320    let rs_cfg = config.relay_selection_config();
321    let family_rules = FamilyRules::from(netdir.params());
322
323    let target_exclusion = match builder.compatible_with() {
324        Some(ct) => {
325            let ids = RelayIdSet::from_iter(ct.identities().map(|id_ref| id_ref.to_owned()));
327            RelayExclusion::exclude_identities(ids)
333        }
334        None => RelayExclusion::no_relays_excluded(),
335    };
336
337    let (guard, mon, usable) = select_guard(netdir, guards, builder.compatible_with())?;
340
341    let guard_exclusion = match &guard {
342        MaybeOwnedRelay::Relay(r) => RelayExclusion::exclude_relays_in_same_family(
343            &config.relay_selection_config(),
344            vec![r.clone()],
345            family_rules,
346        ),
347        MaybeOwnedRelay::Owned(ct) => RelayExclusion::exclude_channel_target_family(
348            &config.relay_selection_config(),
349            ct.as_ref(),
350            netdir,
351        ),
352    };
353
354    let mut exclusion = guard_exclusion.clone();
355    exclusion.extend(&target_exclusion);
356    let (exit, middle_usage) = builder.pick_exit(rng, netdir, exclusion, &rs_cfg)?;
357
358    let mut family_exclusion =
359        RelayExclusion::exclude_relays_in_same_family(&rs_cfg, vec![exit.clone()], family_rules);
360    family_exclusion.extend(&guard_exclusion);
361    let mut exclusion = family_exclusion;
362    exclusion.extend(&target_exclusion);
363
364    let selector = RelaySelector::new(middle_usage, exclusion);
365    let (middle, info) = selector.select_relay(rng, netdir);
366    let middle = middle.ok_or_else(|| Error::NoRelay {
367        path_kind: builder.path_kind(),
368        role: "middle relay",
369        problem: info.to_string(),
370    })?;
371
372    let hops = vec![
373        guard,
374        MaybeOwnedRelay::from(middle),
375        MaybeOwnedRelay::from(exit),
376    ];
377
378    ensure_unique_hops(&hops)?;
379
380    Ok((TorPath::new_multihop_from_maybe_owned(hops), mon, usable))
381}
382
383fn ensure_unique_hops<'a>(hops: &'a [MaybeOwnedRelay<'a>]) -> StdResult<(), Bug> {
385    for (i, hop) in hops.iter().enumerate() {
386        if let Some(hop2) = hops
387            .iter()
388            .skip(i + 1)
389            .find(|hop2| hop.clone().has_any_relay_id_from(*hop2))
390        {
391            return Err(internal!(
392                "invalid path: the IDs of hops {} and {} overlap?!",
393                hop.display_relay_ids(),
394                hop2.display_relay_ids()
395            ));
396        }
397    }
398    Ok(())
399}
400
401fn select_guard<'a, RT: Runtime>(
404    netdir: &'a NetDir,
405    guardmgr: &GuardMgr<RT>,
406    compatible_with: Option<&OwnedChanTarget>,
407) -> Result<(MaybeOwnedRelay<'a>, GuardMonitor, GuardUsable)> {
408    let mut b = tor_guardmgr::GuardUsageBuilder::default();
411    b.kind(tor_guardmgr::GuardUsageKind::Data);
412    if let Some(avoid_target) = compatible_with {
413        let mut family = RelayIdSet::new();
414        family.extend(avoid_target.identities().map(|id| id.to_owned()));
415        if let Some(avoid_relay) = netdir.by_ids(avoid_target) {
416            family.extend(netdir.known_family_members(&avoid_relay).map(|r| *r.id()));
417        }
418        b.restrictions()
419            .push(tor_guardmgr::GuardRestriction::AvoidAllIds(family));
420    }
421    let guard_usage = b.build().expect("Failed while building guard usage!");
422    let (guard, mon, usable) = guardmgr.select_guard(guard_usage)?;
423    let guard = if let Some(ct) = guard.as_circ_target() {
424        MaybeOwnedRelay::from(ct.clone())
426    } else {
427        guard
429            .get_relay(netdir)
430            .ok_or_else(|| {
431                internal!(
432                    "Somehow the guardmgr gave us an unlisted guard {:?}!",
433                    guard
434                )
435            })?
436            .into()
437    };
438    Ok((guard, mon, usable))
439}
440
441#[cfg(test)]
444fn assert_same_path_when_owned(path: &TorPath<'_>) {
445    #![allow(clippy::unwrap_used)]
446    let owned: OwnedPath = path.try_into().unwrap();
447
448    match (&owned, &path.inner) {
449        (OwnedPath::ChannelOnly(c), TorPathInner::FallbackOneHop(f)) => {
450            assert!(c.same_relay_ids(*f));
451        }
452        (OwnedPath::Normal(p), TorPathInner::OneHop(h)) => {
453            assert_eq!(p.len(), 1);
454            assert!(p[0].same_relay_ids(h));
455        }
456        (OwnedPath::Normal(p1), TorPathInner::Path(p2)) => {
457            assert_eq!(p1.len(), p2.len());
458            for (n1, n2) in p1.iter().zip(p2.iter()) {
459                assert!(n1.same_relay_ids(n2));
460            }
461        }
462        (_, _) => {
463            panic!("Mismatched path types.");
464        }
465    }
466}