//! Implement traits from [`crate::mgr`] for the circuit types we use. use crate::build::TunnelBuilder; use crate::mgr::{self, MockablePlan}; use crate::path::OwnedPath; use crate::usage::{SupportedTunnelUsage, TargetTunnelUsage}; use crate::{DirInfo, Error, PathConfig, Result, timeouts}; use async_trait::async_trait; use educe::Educe; use futures::future::OptionFuture; use std::sync::Arc; use tor_basic_utils::skip_fmt; use tor_error::{bad_api_usage, internal}; #[cfg(feature = "vanguards")] use tor_guardmgr::vanguards::VanguardMgr; use tor_linkspec::CircTarget; use tor_proto::ClientTunnel; use tor_proto::circuit::UniqId; use tor_proto::client::circuit::{CircParameters, Path}; use tor_rtcompat::Runtime; use tracing::instrument; use web_time_compat::Instant; #[async_trait] impl mgr::AbstractTunnel for tor_proto::ClientTunnel { type Id = tor_proto::circuit::UniqId; fn id(&self) -> Self::Id { self.unique_id() } fn usable(&self) -> bool { !self.is_closing() } // TODO: replace Itertools::exactly_one() with a stdlib equivalent when there is one. // // See issue #48919 #[allow(unstable_name_collisions)] fn single_path(&self) -> tor_proto::Result> { use itertools::Itertools as _; self.all_paths().into_iter().exactly_one().map_err(|_| { bad_api_usage!("requested the single path of a multi-path tunnel?!").into() }) } fn n_hops(&self) -> tor_proto::Result { self.n_hops() } fn is_closing(&self) -> bool { self.is_closed() } fn unique_id(&self) -> UniqId { self.unique_id() } async fn extend( &self, target: &T, params: CircParameters, ) -> tor_proto::Result<()> { let circ = self.as_single_circ()?; circ.extend(target, params).await } async fn last_known_to_be_used_at(&self) -> tor_proto::Result> { self.disused_since().await } } /// The information generated by circuit planning, and used to build a /// circuit. #[derive(Educe)] #[educe(Debug)] pub(crate) struct Plan { /// The supported usage that the circuit will have when complete final_spec: SupportedTunnelUsage, /// An owned copy of the path to build. // TODO: it would be nice if this weren't owned. path: OwnedPath, /// The protocol parameters to use when constructing the circuit. params: CircParameters, /// If this path is using a guard, we'll use this object to report /// whether the circuit succeeded or failed. guard_status: Option, /// If this path is using a guard, we'll use this object to learn /// whether we're allowed to use the circuit or whether we have to /// wait a while. #[educe(Debug(method = "skip_fmt"))] guard_usable: Option, } impl MockablePlan for Plan {} #[async_trait] impl crate::mgr::AbstractTunnelBuilder for crate::build::TunnelBuilder { type Tunnel = ClientTunnel; type Plan = Plan; #[instrument(level = "trace", skip_all)] fn plan_tunnel( &self, usage: &TargetTunnelUsage, dir: DirInfo<'_>, ) -> Result<(Plan, SupportedTunnelUsage)> { let mut rng = rand::rng(); let (path, final_spec, guard_status, guard_usable) = usage.build_path( &mut rng, dir, self.guardmgr(), #[cfg(all(feature = "vanguards", feature = "hs-common"))] self.vanguardmgr(), self.path_config().as_ref(), self.runtime().wallclock(), )?; let plan = Plan { final_spec: final_spec.clone(), path: (&path).try_into()?, params: dir.circ_params(usage)?, guard_status, guard_usable, }; Ok((plan, final_spec)) } #[instrument(level = "trace", skip_all)] async fn build_tunnel(&self, plan: Plan) -> Result<(SupportedTunnelUsage, Self::Tunnel)> { use crate::build::GuardStatusHandle; use tor_guardmgr::GuardStatus; let Plan { final_spec, path, params, guard_status, guard_usable, } = plan; let guard_usable: OptionFuture<_> = guard_usable.into(); let guard_status: Arc = Arc::new(guard_status.into()); guard_status.pending(GuardStatus::AttemptAbandoned); // TODO: We may want to lower the logic for handling // guard_status and guard_usable into build.rs, so that they // can be handled correctly on user-selected paths as well. // // This will probably require a different API for circuit // construction. match self .build_owned( path, ¶ms, Arc::clone(&guard_status), final_spec.channel_usage(), ) .await { Ok(tunnel) => { // Report success to the guard manager, so it knows that // this guard is reachable. guard_status.report(GuardStatus::Success); // We have to wait for the guard manager to tell us whether // this guard is actually _usable_ or not. Possibly, // it is a speculative guard that we're only trying out // in case some preferable guard won't meet our needs. match guard_usable.await { Some(Ok(true)) | None => (), Some(Ok(false)) => return Err(Error::GuardNotUsable(tunnel.unique_id())), Some(Err(_)) => { return Err(internal!("Guard usability status cancelled").into()); } } Ok((final_spec, tunnel)) } Err(e) => { // The attempt failed; the builder should have set the // pending status on the guard to some value which will // tell the guard manager whether to blame the guard or not. guard_status.commit(); Err(e) } } } fn launch_parallelism(&self, spec: &TargetTunnelUsage) -> usize { match spec { TargetTunnelUsage::Dir => 3, _ => 1, } } fn select_parallelism(&self, spec: &TargetTunnelUsage) -> usize { self.launch_parallelism(spec) } fn learning_timeouts(&self) -> bool { TunnelBuilder::learning_timeouts(self) } fn save_state(&self) -> Result { TunnelBuilder::save_state(self) } fn path_config(&self) -> Arc { TunnelBuilder::path_config(self) } fn set_path_config(&self, new_config: PathConfig) { TunnelBuilder::set_path_config(self, new_config); } fn estimator(&self) -> &timeouts::Estimator { TunnelBuilder::estimator(self) } #[cfg(feature = "vanguards")] fn vanguardmgr(&self) -> &Arc> { TunnelBuilder::vanguardmgr(self) } #[instrument(level = "trace", skip_all)] fn upgrade_to_owned_state(&self) -> Result<()> { TunnelBuilder::upgrade_to_owned_state(self) } #[instrument(level = "trace", skip_all)] fn reload_state(&self) -> Result<()> { TunnelBuilder::reload_state(self) } fn guardmgr(&self) -> &tor_guardmgr::GuardMgr { TunnelBuilder::guardmgr(self) } fn update_network_parameters(&self, p: &tor_netdir::params::NetParameters) { TunnelBuilder::update_network_parameters(self, p); } }