//! Experimental support for vanguards. //! //! For more information, see the [vanguards spec]. //! //! [vanguards spec]: https://spec.torproject.org/vanguards-spec/index.html. pub mod config; mod set; use std::sync::{Arc, RwLock}; use tor_config::ReconfigureError; use tor_error::{internal, ErrorKind, HasKind}; use tor_netdir::{NetDir, NetDirProvider}; use tor_persist::StateMgr; use tor_relay_selection::RelayExclusion; use tor_rtcompat::Runtime; pub use config::{VanguardConfig, VanguardConfigBuilder, VanguardParams}; pub use set::Vanguard; use set::VanguardSet; use crate::{RetireCircuits, VanguardMode}; /// The vanguard manager. #[allow(unused)] // TODO HS-VANGUARDS pub struct VanguardMgr { /// The mutable state. inner: RwLock, } /// The mutable inner state of [`VanguardMgr`]. #[allow(unused)] // TODO HS-VANGUARDS struct Inner { /// Whether to use full, lite, or no vanguards. mode: VanguardMode, /// Configuration parameters read from the consensus parameters. params: VanguardParams, /// The L2 vanguards. l2_vanguards: VanguardSet, /// The L3 vanguards. l3_vanguards: VanguardSet, } /// An error coming from the vanguards subsystem. #[derive(Clone, Debug, thiserror::Error)] #[non_exhaustive] pub enum VanguardMgrError { /// Could not find a suitable relay to use for the specifier layer. #[error("No suitable relays")] NoSuitableRelay(Layer), /// An internal error occurred. #[error("Internal error")] Bug(#[from] tor_error::Bug), } impl HasKind for VanguardMgrError { fn kind(&self) -> ErrorKind { match self { // TODO HS-VANGUARDS: this is not right VanguardMgrError::NoSuitableRelay(_) => ErrorKind::Other, VanguardMgrError::Bug(e) => e.kind(), } } } impl VanguardMgr { /// Create a new `VanguardMgr`. /// /// The `state_mgr` handle is used for persisting the "vanguards-full" guard pools to disk. #[allow(clippy::needless_pass_by_value)] // TODO HS-VANGUARDS pub fn new(config: &VanguardConfig, _state_mgr: S) -> Result where S: StateMgr + Send + Sync + 'static, { let VanguardConfig { mode } = config; let inner = Inner { mode: *mode, // TODO HS-VANGUARDS: read the params from the consensus params: Default::default(), l2_vanguards: Default::default(), l3_vanguards: Default::default(), }; // TODO HS-VANGUARDS: read the vanguards from disk if mode == VanguardsMode::Full Ok(Self { inner: RwLock::new(inner), }) } /// Launch the vanguard pool management tasks. pub fn launch_background_tasks( self: &Arc, _runtime: &R, _netdir_provider: &Arc, ) -> Result<(), VanguardMgrError> where R: Runtime, { todo!() } /// Replace the configuration in this `VanguardMgr` with the specified `config`. pub fn reconfigure(&self, config: &VanguardConfig) -> Result { let VanguardConfig { mode } = config; let mut inner = self.inner.write().expect("poisoned lock"); if *mode != inner.mode { inner.mode = *mode; return Ok(RetireCircuits::All); } Ok(RetireCircuits::None) } /// Return a [`Vanguard`] relay for use in the specified layer. /// /// The `neighbor_exclusion` must contain the relays that would neighbor this vanguard /// in the path. /// /// Specifically, it should contain /// * the last relay in the path (the one immediately preceding the vanguard): the same relay /// cannot be used in consecutive positions in the path (a relay won't let you extend the /// circuit to itself). /// * the penultimate relay of the path, if there is one: relays don't allow extending the /// circuit to their previous hop /// /// ### Example /// /// If the partially built path is of the form `G - L2` and we are selecting the L3 vanguard, /// the `RelayExclusion` should contain `G` and `L2` (to prevent building a path of the form /// `G - L2 - G`, or `G - L2 - L2`). /// /// If the path only contains the L1 guard (`G`), then the `RelayExclusion` should only /// exclude `G`. pub fn select_vanguard<'a>( &self, netdir: &'a NetDir, layer: Layer, neighbor_exclusion: &RelayExclusion<'a>, ) -> Result, VanguardMgrError> { use VanguardMode::*; let inner = self.inner.read().expect("poisoned lock"); // TODO HS-VANGUARDS: come up with something with better UX let vanguard_set = match (layer, inner.mode) { (Layer::Layer2, Full) | (Layer::Layer2, Lite) => &inner.l2_vanguards, (Layer::Layer3, Full) => &inner.l3_vanguards, // TODO HS-VANGUARDS: perhaps we need a dedicated error variant for this _ => { return Err(internal!( "vanguards for layer {layer} are supported in mode {})", inner.mode ) .into()) } }; vanguard_set .pick_relay(netdir, neighbor_exclusion) .ok_or(VanguardMgrError::NoSuitableRelay(layer)) } /// Get the current [`VanguardMode`]. pub fn mode(&self) -> VanguardMode { self.inner.read().expect("poisoned lock").mode } /// Flush the vanguard sets to storage, if the mode is "vanguards-full". #[allow(unused)] // TODO HS-VANGUARDS fn flush_to_storage(&self) -> Result<(), VanguardMgrError> { let mode = self.inner.read().expect("poisoned lock").mode; match mode { VanguardMode::Lite | VanguardMode::Disabled => Ok(()), VanguardMode::Full => todo!(), } } } /// The vanguard layer. #[allow(unused)] // TODO HS-VANGUARDS #[derive(Debug, Clone, Copy, PartialEq)] // #[derive(derive_more::Display)] // #[non_exhaustive] pub enum Layer { /// L2 vanguard. #[display(fmt = "layer 2")] Layer2, /// L3 vanguard. #[display(fmt = "layer 3")] Layer3, }