Integrate GuardUsability and GuardMonitor into CircuitBuilder.

(When we're building a path with a guard, we need to tell the guard
manager whether the path succeeded, and we need to wait to hear
whether the guard is usable.)
This commit is contained in:
Nick Mathewson 2021-10-10 13:34:38 -04:00
parent 33ba697b5d
commit 34b576a815
3 changed files with 73 additions and 11 deletions

View File

@ -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),

View File

@ -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<tor_guardmgr::GuardMonitor>,
/// 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<tor_guardmgr::GuardUsable>,
}
#[async_trait]
@ -45,12 +53,15 @@ impl<R: Runtime> 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<R: Runtime> 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, &params, 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, &params, 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 {

View File

@ -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<GuardMonitor>,
Option<GuardUsable>,
)> {
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 {