diff options
Diffstat (limited to 'crates/tor-circmgr')
| -rw-r--r-- | crates/tor-circmgr/src/hspool.rs | 49 | ||||
| -rw-r--r-- | crates/tor-circmgr/src/hspool/pool.rs | 164 |
2 files changed, 166 insertions, 47 deletions
diff --git a/crates/tor-circmgr/src/hspool.rs b/crates/tor-circmgr/src/hspool.rs index d94841f4d..da69805c9 100644 --- a/crates/tor-circmgr/src/hspool.rs +++ b/crates/tor-circmgr/src/hspool.rs @@ -23,7 +23,7 @@ use tor_rtcompat::{ scheduler::{TaskHandle, TaskSchedule}, Runtime, SleepProviderExt, }; -use tracing::warn; +use tracing::{debug, trace, warn}; use std::result::Result as StdResult; @@ -77,7 +77,6 @@ pub(crate) struct HsCircStub { /// The circuit. pub(crate) circ: Arc<ClientCirc>, /// Whether the circuit is STUB or STUB+. - #[allow(dead_code)] // TODO HS-VANGUARDS pub(crate) kind: HsCircStubKind, } @@ -129,12 +128,13 @@ impl HsCircStub { /// STUB = G -> L2 -> L3 /// STUB+ = G -> L2 -> L3 -> M /// ``` -#[allow(dead_code)] // TODO HS-VANGUARDS -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq, derive_more::Display)] pub(crate) enum HsCircStubKind { /// A stub circuit (STUB). + #[display(fmt = "STUB")] Stub, /// An extended stub circuit (STUB+). + #[display(fmt = "STUB+")] Extended, } @@ -573,13 +573,22 @@ async fn launch_hs_circuits_as_needed<R: Runtime>( }; let now = pool.circmgr.mgr.peek_runtime().now(); pool.remove_closed(); - let mut n_to_launch = { + let mut circs_to_launch = { let mut inner = pool.inner.lock().expect("poisioned_lock"); inner.pool.update_target_size(now); - inner.pool.n_to_launch() + inner.pool.circs_to_launch() }; + let n_to_launch = circs_to_launch.n_to_launch(); let mut max_attempts = n_to_launch * 2; - 'inner: while n_to_launch > 0 { + + debug!( + "launching {} STUB and {} STUB+ circuits", + circs_to_launch.stub(), + circs_to_launch.ext_stub() + ); + + // TODO: refactor this to launch the circuits in parallel + 'inner: while circs_to_launch.n_to_launch() > 0 { max_attempts -= 1; if max_attempts == 0 { // We want to avoid retrying over and over in a tight loop if all our attempts @@ -595,34 +604,20 @@ async fn launch_hs_circuits_as_needed<R: Runtime>( // launching several of these in parallel. If we do, we should think about // whether taking the fastest will expose us to any attacks. let no_target: Option<&OwnedCircTarget> = None; - - // TODO HS-VANGUARDS: we will need to launch N STUB circuits and M STUB+ - // circuits, for some N, M. - // - // We will need Pool to have two different targets, one for STUB circuits and - // another for STUB+. Otherwise, if we only know the overall circuit target, each - // time the pool is low on circuits we'll have no choice but to spawn both kinds of - // stub circuits (in a ratio of N/M), even if we don't necessarily need the deficit - // to be replenished in the N/M ratio. IOW, if the pool's overall target number of - // circuits is T = N + M, and the actual number of circuits in the pool is - // L = T - D, we'll need to spawn D circuits that consist of X STUBs and Y - // STUB+s, where X/Y is not necessarily N/M (but the overall STUB/STUB+ ratio - // *is* N/M). - let stub_kind = HsCircStubKind::Stub; + let for_launch = circs_to_launch.for_launch(); // TODO HS: We should catch panics, here or in launch_hs_unmanaged. match pool .circmgr - .launch_hs_unmanaged(no_target, &netdir, stub_kind) + .launch_hs_unmanaged(no_target, &netdir, for_launch.kind()) .await { Ok(circ) => { - let circ = HsCircStub { - circ, - kind: stub_kind, - }; + let kind = for_launch.kind(); + let circ = HsCircStub { circ, kind }; pool.inner.lock().expect("poisoned lock").pool.insert(circ); - n_to_launch -= 1; + trace!("successfully launched {kind} circuit"); + for_launch.note_circ_launched(); } Err(err) => { debug_report!(err, "Unable to build preemptive circuit for onion services"); diff --git a/crates/tor-circmgr/src/hspool/pool.rs b/crates/tor-circmgr/src/hspool/pool.rs index 67f1b8264..5209ce9fb 100644 --- a/crates/tor-circmgr/src/hspool/pool.rs +++ b/crates/tor-circmgr/src/hspool/pool.rs @@ -2,7 +2,7 @@ use std::time::{Duration, Instant}; -use crate::hspool::HsCircStub; +use crate::hspool::{HsCircStub, HsCircStubKind}; use rand::Rng; use tor_basic_utils::RngExt as _; @@ -16,11 +16,11 @@ pub(super) struct Pool { /// The collection of circuits themselves, in no particular order. circuits: Vec<HsCircStub>, - /// The number of elements that we would like to have in our pool. - /// - /// We do not discard when we are _above_ this threshold, but we do - /// try to build when we are low. - target: usize, + /// The number of STUB elements that we would like to have in our pool. + stub_target: usize, + + /// The number of STUB+ elements that we would like to have in our pool. + ext_stub_target: usize, /// True if we have exhausted our pool since the last time we decided /// whether to change our target level. @@ -39,18 +39,94 @@ pub(super) struct Pool { mode: VanguardMode, } -/// Our default (and minimum) target pool size. -const DEFAULT_TARGET: usize = 4; +/// Our default (and minimum) target STUB pool size. +const DEFAULT_STUB_TARGET: usize = 3; + +/// Our default (and minimum) target STUB+ pool size. +const DEFAULT_EXT_STUB_TARGET: usize = 1; + +/// Our maximum target STUB pool size. We will never let our STUB target grow above this +/// value. +const MAX_STUB_TARGET: usize = 384; -/// Our maximum target pool size. We will never let our target grow above this +/// Our maximum target STUB+ pool size. We will never let our STUB+ target grow above this /// value. -const MAX_TARGET: usize = 512; +const MAX_EXT_STUB_TARGET: usize = 128; + +/// A type of circuit we would like to launch. +/// +/// [`ForLaunch::note_circ_launched`] should be called whenever a circuit +/// of this [`HsCircStubKind`] is launched, to decrement the internal target `count`. +pub(super) struct ForLaunch<'a> { + /// The kind of circuit we want to launch. + kind: HsCircStubKind, + /// How many circuits of this kind do we need? + /// + /// This is a mutable reference to one of the target values from [`CircsToLaunch`]; + /// we decrement it when we have launched a circuit of this type. + count: &'a mut usize, +} + +impl<'a> ForLaunch<'a> { + /// A circuit was launched, decrement the current target for its kind. + pub(super) fn note_circ_launched(self) { + *self.count -= 1; + } + + /// The kind of circuit we want to launch. + pub(super) fn kind(&self) -> HsCircStubKind { + self.kind + } +} + +/// The circuits we need to launch. +pub(super) struct CircsToLaunch { + /// The number of STUB circuits we want to launch. + stub_target: usize, + /// The number of STUB+ circuits we want to launch. + ext_stub_target: usize, +} + +impl CircsToLaunch { + /// Return a [`ForLaunch`] representing a circuit we would like to launch. + pub(super) fn for_launch(&mut self) -> ForLaunch { + // We start by launching STUB circuits. + if self.stub_target > 0 { + ForLaunch { + kind: HsCircStubKind::Stub, + count: &mut self.stub_target, + } + } else { + // If we have enough STUB circuits, we can start launching STUB+ ones too. + ForLaunch { + kind: HsCircStubKind::Extended, + count: &mut self.ext_stub_target, + } + } + } + + /// Return the number of STUB circuits we would like to launch. + pub(super) fn stub(&self) -> usize { + self.stub_target + } + + /// Return the number of STUB+ circuits we would like to launch. + pub(super) fn ext_stub(&self) -> usize { + self.ext_stub_target + } + + /// Return the total number of circuits we would currently like to launch. + pub(super) fn n_to_launch(&self) -> usize { + self.stub_target + self.ext_stub_target + } +} impl Default for Pool { fn default() -> Self { Self { circuits: Vec::new(), - target: DEFAULT_TARGET, + stub_target: DEFAULT_STUB_TARGET, + ext_stub_target: DEFAULT_EXT_STUB_TARGET, have_been_exhausted: false, have_been_under_highwater: false, last_changed_target: None, @@ -75,12 +151,45 @@ impl Pool { /// Return true if we are very low on circuits and should build more immediately. pub(super) fn very_low(&self) -> bool { - self.circuits.len() <= self.target / 3 + self.circuits.len() <= self.target() / 3 } - /// Return the number of circuits we would currently like to launch. - pub(super) fn n_to_launch(&self) -> usize { - self.target.saturating_sub(self.circuits.len()) + /// Return a [`CircsToLaunch`] describing the circuits we would currently like to launch. + pub(super) fn circs_to_launch(&self) -> CircsToLaunch { + CircsToLaunch { + stub_target: self.stubs_to_launch(), + ext_stub_target: self.ext_stubs_to_launch(), + } + } + + /// Return the number of STUB circuits we would currently like to launch. + fn stubs_to_launch(&self) -> usize { + let circ_count = self + .circuits + .iter() + .filter(|c| c.kind == HsCircStubKind::Stub) + .count(); + + self.stub_target.saturating_sub(circ_count) + } + + /// Return the number of STUB+ circuits we would currently like to launch. + fn ext_stubs_to_launch(&self) -> usize { + let circ_count = self + .circuits + .iter() + .filter(|c| c.kind == HsCircStubKind::Extended) + .count(); + + self.ext_stub_target.saturating_sub(circ_count) + } + + /// Return the total number of circuits we would like to launch. + /// + /// We do not discard when we are _above_ this threshold, but we do + /// try to build when we are low. + fn target(&self) -> usize { + self.stub_target + self.ext_stub_target } /// If there is any circuit in this pool for which `f` returns true, return one such circuit at random, and remove it from the pool. @@ -98,14 +207,16 @@ impl Pool { if self.circuits.is_empty() { self.have_been_exhausted = true; self.have_been_under_highwater = true; - } else if self.circuits.len() < self.target * 4 / 5 { + } else if self.circuits.len() < self.target() * 4 / 5 { self.have_been_under_highwater = true; } rv } - /// Update the target size for our pool. + /// Update the target sizes for our pool. + /// + /// This updates our target numbers of STUB and STUB+ circuits. pub(super) fn update_target_size(&mut self, now: Instant) { /// Minimum amount of time that must elapse between a change and a /// decision to grow our pool. We use this to control the rate of @@ -121,20 +232,33 @@ impl Pool { let last_changed = self.last_changed_target.get_or_insert(now); let time_since_last_change = now.saturating_duration_since(*last_changed); + // TODO: we may want to have separate have_been_exhausted/have_been_under_highwater + // flags for STUB and STUB+ circuits. + // + // TODO: stub_target and ext_stub_target currently grow/shrink at the same rate, + // which is not ideal. + // + // Instead, we should switch to an adaptive strategy, where the two targets are updated + // based on how many STUB/STUB+ circuit requests we got. if self.have_been_exhausted { if time_since_last_change < MIN_TIME_TO_GROW { return; } - self.target *= 2; + self.stub_target *= 2; + self.ext_stub_target *= 2; } else if !self.have_been_under_highwater { if time_since_last_change < MIN_TIME_TO_SHRINK { return; } - self.target /= 2; + self.stub_target /= 2; + self.ext_stub_target /= 2; } self.last_changed_target = Some(now); - self.target = self.target.clamp(DEFAULT_TARGET, MAX_TARGET); + self.stub_target = self.stub_target.clamp(DEFAULT_STUB_TARGET, MAX_STUB_TARGET); + self.ext_stub_target = self + .ext_stub_target + .clamp(DEFAULT_EXT_STUB_TARGET, MAX_EXT_STUB_TARGET); self.have_been_exhausted = false; self.have_been_under_highwater = false; } |
