diff --git a/crates/tor-circmgr/src/err.rs b/crates/tor-circmgr/src/err.rs index a69bd9ac2..2a7def323 100644 --- a/crates/tor-circmgr/src/err.rs +++ b/crates/tor-circmgr/src/err.rs @@ -24,6 +24,11 @@ pub enum Error { #[error("Circuit took too long to build")] CircTimeout, + /// We started building a circuit on a guard, but later decided not + /// to use that guard. + #[error("Discarded circuit because of speculative guard selection")] + GuardNotUsable, + /// Tried to take a circuit for a purpose it doesn't support. #[error("Circuit usage not supported: {0}")] UsageNotSupported(String), diff --git a/crates/tor-circmgr/src/impls.rs b/crates/tor-circmgr/src/impls.rs index 23a5f77ad..d6aeae483 100644 --- a/crates/tor-circmgr/src/impls.rs +++ b/crates/tor-circmgr/src/impls.rs @@ -3,8 +3,9 @@ use crate::mgr::{self}; use crate::path::OwnedPath; use crate::usage::{SupportedCircUsage, TargetCircUsage}; -use crate::{DirInfo, Result}; +use crate::{DirInfo, Error, Result}; use async_trait::async_trait; +use futures::future::OptionFuture; use rand::{rngs::StdRng, SeedableRng}; use std::convert::TryInto; use std::sync::Arc; @@ -31,6 +32,13 @@ pub(crate) struct Plan { 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 ot use the circuit or whether we have to + /// wait a while. + guard_usable: Option, } #[async_trait] @@ -45,12 +53,15 @@ impl crate::mgr::AbstractCircBuilder for crate::build::CircuitBuilde dir: DirInfo<'_>, ) -> Result<(Plan, SupportedCircUsage)> { let mut rng = rand::thread_rng(); - let (path, final_spec) = usage.build_path(&mut rng, dir, self.path_config())?; + let (path, final_spec, guard_status, guard_usable) = + usage.build_path(&mut rng, dir, self.path_config())?; let plan = Plan { final_spec: final_spec.clone(), path: (&path).try_into()?, params: dir.circ_params(), + guard_status, + guard_usable, }; Ok((plan, final_spec)) @@ -61,11 +72,50 @@ impl crate::mgr::AbstractCircBuilder for crate::build::CircuitBuilde final_spec, path, params, + guard_status, + guard_usable, } = plan; let rng = StdRng::from_rng(rand::thread_rng()).expect("couldn't construct temporary rng"); - let circuit = self.build_owned(path, ¶ms, rng).await?; - Ok((final_spec, circuit)) + let guard_usable: OptionFuture<_> = guard_usable.into(); + + // 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, rng).await { + Ok(circuit) => { + if let Some(mon) = guard_status { + // Report success to the guard manager, so it knows that + // this guard is reachable. + // TODO: We may someday want to report two-hop circuits + // as successful. But not today. + mon.succeeded(); + } + // 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), + Some(Err(_)) => { + return Err(Error::Internal("Guard usability status cancelled".into())) + } + } + Ok((final_spec, circuit)) + } + Err(e) => { + if let Some(mon) = guard_status { + // Report failure to the guard manager, so it knows + // not to use this guard in the future. + mon.failed(); + } + Err(e) + } + } } fn launch_parallelism(&self, spec: &TargetCircUsage) -> usize { diff --git a/crates/tor-circmgr/src/usage.rs b/crates/tor-circmgr/src/usage.rs index 911d3a1a9..366a3120d 100644 --- a/crates/tor-circmgr/src/usage.rs +++ b/crates/tor-circmgr/src/usage.rs @@ -4,11 +4,11 @@ use rand::Rng; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; +use crate::path::{dirpath::DirPathBuilder, exitpath::ExitPathBuilder, TorPath}; +use tor_guardmgr::{GuardMonitor, GuardUsable}; use tor_netdir::Relay; use tor_netdoc::types::policy::PortPolicy; -use crate::path::{dirpath::DirPathBuilder, exitpath::ExitPathBuilder, TorPath}; - use crate::{Error, Result}; /// An exit policy, as supported by the last hop of a circuit. @@ -173,11 +173,16 @@ impl TargetCircUsage { rng: &mut R, netdir: crate::DirInfo<'a>, config: &crate::PathConfig, - ) -> Result<(TorPath<'a>, SupportedCircUsage)> { + ) -> Result<( + TorPath<'a>, + SupportedCircUsage, + Option, + Option, + )> { match self { TargetCircUsage::Dir => { let path = DirPathBuilder::new().pick_path(rng, netdir)?; - Ok((path, SupportedCircUsage::Dir)) + Ok((path, SupportedCircUsage::Dir, None, None)) } TargetCircUsage::Exit { ports: p, @@ -194,6 +199,8 @@ impl TargetCircUsage { policy, isolation_group: Some(*isolation_group), }, + None, + None, )) } TargetCircUsage::TimeoutTesting => { @@ -207,7 +214,7 @@ impl TargetCircUsage { _ => SupportedCircUsage::NoUsage, }; - Ok((path, usage)) + Ok((path, usage, None, None)) } } } @@ -468,7 +475,7 @@ mod test { // Only doing basic tests for now. We'll test the path // building code a lot more closely in the tests for TorPath // and friends. - let (p_dir, u_dir) = TargetCircUsage::Dir + let (p_dir, u_dir, _, _) = TargetCircUsage::Dir .build_path(&mut rng, di, &config) .unwrap(); assert!(matches!(u_dir, SupportedCircUsage::Dir)); @@ -479,7 +486,7 @@ mod test { ports: vec![TargetPort::ipv4(995)], isolation_group, }; - let (p_exit, u_exit) = exit_usage.build_path(&mut rng, di, &config).unwrap(); + let (p_exit, u_exit, _, _) = exit_usage.build_path(&mut rng, di, &config).unwrap(); assert!(matches!( u_exit, SupportedCircUsage::Exit {