arti/crates/tor-circmgr/src/path.rs

154 lines
4.8 KiB
Rust

//! Code to construct paths through the Tor network
//!
//! TODO: I'm not sure this belongs in circmgr, but this is the best place
//! I can think of for now. I'm also not sure this should be public.
pub mod dirpath;
pub mod exitpath;
use tor_linkspec::{OwnedChanTarget, OwnedCircTarget};
use tor_netdir::{fallback::FallbackDir, Relay};
use std::convert::TryFrom;
use crate::usage::ExitPolicy;
use crate::{Error, Result};
/// A list of Tor relays through the network.
pub struct TorPath<'a> {
/// The inner TorPath state.
inner: TorPathInner<'a>,
}
/// Non-public helper type to represent the different kinds of Tor path.
///
/// (This is a separate type to avoid exposing its details to the user.)
enum TorPathInner<'a> {
/// A single-hop path for use with a directory cache, when a relay is
/// known.
OneHop(Relay<'a>), // This could just be a routerstatus.
/// A single-hop path for use with a directory cache, when we don't have
/// a consensus.
FallbackOneHop(&'a FallbackDir),
/// A multi-hop path, containing one or more relays.
Path(Vec<Relay<'a>>),
}
impl<'a> TorPath<'a> {
/// Create a new one-hop path for use with a directory cache with a known
/// relay.
pub fn new_one_hop(relay: Relay<'a>) -> Self {
Self {
inner: TorPathInner::OneHop(relay),
}
}
/// Create a new one-hop path for use with a directory cache when we don't
/// have a consensus.
pub fn new_fallback_one_hop(fallback_dir: &'a FallbackDir) -> Self {
Self {
inner: TorPathInner::FallbackOneHop(fallback_dir),
}
}
/// Create a new multi-hop path with a given number of ordered relays.
pub fn new_multihop(relays: impl IntoIterator<Item = Relay<'a>>) -> Self {
Self {
inner: TorPathInner::Path(relays.into_iter().collect()),
}
}
/// Return the final relay in this path, if this is a path for use
/// with exit circuits.
fn exit_relay(&self) -> Option<&Relay<'a>> {
match &self.inner {
TorPathInner::Path(relays) if !relays.is_empty() => Some(&relays[relays.len() - 1]),
_ => None,
}
}
/// Return the exit policy of the final relay in this path, if this
/// is a path for use with exit circuits.
pub(crate) fn exit_policy(&self) -> Option<ExitPolicy> {
self.exit_relay().map(ExitPolicy::from_relay)
}
/// Return the number of relays in this path.
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
use TorPathInner::*;
match &self.inner {
OneHop(_) => 1,
FallbackOneHop(_) => 1,
Path(p) => p.len(),
}
}
}
/// A path composed entirely of owned components.
#[derive(Clone)]
pub(crate) enum OwnedPath {
/// A path where we only know how to make circuits via CREATE_FAST.
ChannelOnly(OwnedChanTarget),
/// A path of one or more hops created via normal Tor handshakes.
Normal(Vec<OwnedCircTarget>),
}
impl<'a> TryFrom<&TorPath<'a>> for OwnedPath {
type Error = crate::Error;
fn try_from(p: &TorPath<'a>) -> Result<OwnedPath> {
use TorPathInner::*;
Ok(match &p.inner {
FallbackOneHop(h) => OwnedPath::ChannelOnly(OwnedChanTarget::from_chan_target(*h)),
OneHop(h) => OwnedPath::Normal(vec![OwnedCircTarget::from_circ_target(h)]),
Path(p) if !p.is_empty() => {
OwnedPath::Normal(p.iter().map(OwnedCircTarget::from_circ_target).collect())
}
Path(_) => {
return Err(Error::NoRelays("Path with no entries!".into()));
}
})
}
}
impl OwnedPath {
/// Return the number of hops in this path.
#[allow(clippy::len_without_is_empty)]
pub(crate) fn len(&self) -> usize {
match self {
OwnedPath::ChannelOnly(_) => 1,
OwnedPath::Normal(p) => p.len(),
}
}
}
/// For testing: make sure that `path` is the same when it is an owned
/// path.
#[cfg(test)]
fn assert_same_path_when_owned(path: &TorPath<'_>) {
#![allow(clippy::unwrap_used)]
use std::convert::TryInto;
use tor_linkspec::ChanTarget;
let owned: OwnedPath = path.try_into().unwrap();
match (&owned, &path.inner) {
(OwnedPath::ChannelOnly(c), TorPathInner::FallbackOneHop(f)) => {
assert_eq!(c.ed_identity(), f.ed_identity());
}
(OwnedPath::Normal(p), TorPathInner::OneHop(h)) => {
assert_eq!(p.len(), 1);
assert_eq!(p[0].ed_identity(), h.ed_identity());
}
(OwnedPath::Normal(p1), TorPathInner::Path(p2)) => {
assert_eq!(p1.len(), p2.len());
for (n1, n2) in p1.iter().zip(p2.iter()) {
assert_eq!(n1.ed_identity(), n2.ed_identity());
}
}
(_, _) => {
panic!("Mismatched path types.")
}
}
}