mirror of
https://github.com/telemt/telemt.git
synced 2026-04-22 13:04:10 +03:00
Add comprehensive security tests for quota management and relay functionality
- Introduced `relay_dual_lock_race_harness_security_tests.rs` to validate user liveness during lock hold and release cycles. - Added `relay_quota_extended_attack_surface_security_tests.rs` to cover various quota scenarios including positive, negative, edge cases, and adversarial conditions. - Implemented `relay_quota_lock_eviction_lifecycle_tdd_tests.rs` to ensure proper eviction of stale entries and lifecycle management of quota locks. - Created `relay_quota_lock_eviction_stress_security_tests.rs` to stress test the eviction mechanism under high churn conditions. - Enhanced `relay_quota_lock_pressure_adversarial_tests.rs` to verify reclaiming of unreferenced entries after explicit eviction. - Developed `relay_quota_retry_allocation_latency_security_tests.rs` to benchmark and validate latency and allocation behavior under contention.
This commit is contained in:
@@ -62,6 +62,7 @@ use std::sync::{Arc, Mutex, OnceLock};
|
||||
use std::task::{Context, Poll};
|
||||
use std::time::Duration;
|
||||
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt, ReadBuf, copy_bidirectional_with_sizes};
|
||||
use tokio::sync::Mutex as AsyncMutex;
|
||||
use tokio::time::{Instant, Sleep};
|
||||
use tracing::{debug, trace, warn};
|
||||
|
||||
@@ -210,7 +211,7 @@ struct StatsIo<S> {
|
||||
stats: Arc<Stats>,
|
||||
user: String,
|
||||
quota_lock: Option<Arc<Mutex<()>>>,
|
||||
cross_mode_quota_lock: Option<Arc<Mutex<()>>>,
|
||||
cross_mode_quota_lock: Option<Arc<AsyncMutex<()>>>,
|
||||
quota_limit: Option<u64>,
|
||||
quota_exceeded: Arc<AtomicBool>,
|
||||
quota_read_wake_scheduled: bool,
|
||||
@@ -289,6 +290,21 @@ const QUOTA_CONTENTION_RETRY_MAX_INTERVAL: Duration = Duration::from_millis(16);
|
||||
#[cfg(not(test))]
|
||||
const QUOTA_CONTENTION_RETRY_MAX_INTERVAL: Duration = Duration::from_millis(64);
|
||||
|
||||
#[cfg(test)]
|
||||
static QUOTA_RETRY_SLEEP_ALLOCS: AtomicU64 = AtomicU64::new(0);
|
||||
#[cfg(test)]
|
||||
static QUOTA_USER_LOCK_EVICTOR_SPAWN_COUNT: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn reset_quota_retry_sleep_allocs_for_tests() {
|
||||
QUOTA_RETRY_SLEEP_ALLOCS.store(0, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn quota_retry_sleep_allocs_for_tests() -> u64 {
|
||||
QUOTA_RETRY_SLEEP_ALLOCS.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn quota_contention_retry_delay(retry_attempt: u8) -> Duration {
|
||||
let shift = u32::from(retry_attempt.min(5));
|
||||
@@ -317,6 +333,8 @@ fn poll_quota_retry_sleep(
|
||||
) {
|
||||
if !*wake_scheduled {
|
||||
*wake_scheduled = true;
|
||||
#[cfg(test)]
|
||||
QUOTA_RETRY_SLEEP_ALLOCS.fetch_add(1, Ordering::Relaxed);
|
||||
*sleep_slot = Some(Box::pin(tokio::time::sleep(quota_contention_retry_delay(
|
||||
*retry_attempt,
|
||||
))));
|
||||
@@ -368,16 +386,47 @@ fn quota_overflow_user_lock(user: &str) -> Arc<Mutex<()>> {
|
||||
Arc::clone(&stripes[hash % stripes.len()])
|
||||
}
|
||||
|
||||
pub(crate) fn quota_user_lock_evict() {
|
||||
if let Some(locks) = QUOTA_USER_LOCKS.get() {
|
||||
locks.retain(|_, value| Arc::strong_count(value) > 1);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn spawn_quota_user_lock_evictor(interval: Duration) -> tokio::task::JoinHandle<()> {
|
||||
let interval = interval.max(Duration::from_millis(1));
|
||||
#[cfg(test)]
|
||||
QUOTA_USER_LOCK_EVICTOR_SPAWN_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::time::sleep(interval).await;
|
||||
quota_user_lock_evict();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn spawn_quota_user_lock_evictor_for_tests(
|
||||
interval: Duration,
|
||||
) -> tokio::task::JoinHandle<()> {
|
||||
spawn_quota_user_lock_evictor(interval)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn reset_quota_user_lock_evictor_spawn_count_for_tests() {
|
||||
QUOTA_USER_LOCK_EVICTOR_SPAWN_COUNT.store(0, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn quota_user_lock_evictor_spawn_count_for_tests() -> u64 {
|
||||
QUOTA_USER_LOCK_EVICTOR_SPAWN_COUNT.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn quota_user_lock(user: &str) -> Arc<Mutex<()>> {
|
||||
let locks = QUOTA_USER_LOCKS.get_or_init(DashMap::new);
|
||||
if let Some(existing) = locks.get(user) {
|
||||
return Arc::clone(existing.value());
|
||||
}
|
||||
|
||||
if locks.len() >= QUOTA_USER_LOCKS_MAX {
|
||||
locks.retain(|_, value| Arc::strong_count(value) > 1);
|
||||
}
|
||||
|
||||
if locks.len() >= QUOTA_USER_LOCKS_MAX {
|
||||
return quota_overflow_user_lock(user);
|
||||
}
|
||||
@@ -393,7 +442,7 @@ fn quota_user_lock(user: &str) -> Arc<Mutex<()>> {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn cross_mode_quota_user_lock_for_tests(user: &str) -> Arc<Mutex<()>> {
|
||||
pub(crate) fn cross_mode_quota_user_lock_for_tests(user: &str) -> Arc<AsyncMutex<()>> {
|
||||
crate::proxy::quota_lock_registry::cross_mode_quota_user_lock(user)
|
||||
}
|
||||
|
||||
@@ -410,14 +459,7 @@ impl<S: AsyncRead + Unpin> AsyncRead for StatsIo<S> {
|
||||
|
||||
let _quota_guard = if let Some(lock) = this.quota_lock.as_ref() {
|
||||
match lock.try_lock() {
|
||||
Ok(guard) => {
|
||||
reset_quota_retry_scheduler(
|
||||
&mut this.quota_read_retry_sleep,
|
||||
&mut this.quota_read_wake_scheduled,
|
||||
&mut this.quota_read_retry_attempt,
|
||||
);
|
||||
Some(guard)
|
||||
}
|
||||
Ok(guard) => Some(guard),
|
||||
Err(_) => {
|
||||
poll_quota_retry_sleep(
|
||||
&mut this.quota_read_retry_sleep,
|
||||
@@ -434,14 +476,7 @@ impl<S: AsyncRead + Unpin> AsyncRead for StatsIo<S> {
|
||||
|
||||
let _cross_mode_quota_guard = if let Some(lock) = this.cross_mode_quota_lock.as_ref() {
|
||||
match lock.try_lock() {
|
||||
Ok(guard) => {
|
||||
reset_quota_retry_scheduler(
|
||||
&mut this.quota_read_retry_sleep,
|
||||
&mut this.quota_read_wake_scheduled,
|
||||
&mut this.quota_read_retry_attempt,
|
||||
);
|
||||
Some(guard)
|
||||
}
|
||||
Ok(guard) => Some(guard),
|
||||
Err(_) => {
|
||||
poll_quota_retry_sleep(
|
||||
&mut this.quota_read_retry_sleep,
|
||||
@@ -456,6 +491,12 @@ impl<S: AsyncRead + Unpin> AsyncRead for StatsIo<S> {
|
||||
None
|
||||
};
|
||||
|
||||
reset_quota_retry_scheduler(
|
||||
&mut this.quota_read_retry_sleep,
|
||||
&mut this.quota_read_wake_scheduled,
|
||||
&mut this.quota_read_retry_attempt,
|
||||
);
|
||||
|
||||
if let Some(limit) = this.quota_limit
|
||||
&& this.stats.get_user_total_octets(&this.user) >= limit
|
||||
{
|
||||
@@ -523,14 +564,7 @@ impl<S: AsyncWrite + Unpin> AsyncWrite for StatsIo<S> {
|
||||
|
||||
let _quota_guard = if let Some(lock) = this.quota_lock.as_ref() {
|
||||
match lock.try_lock() {
|
||||
Ok(guard) => {
|
||||
reset_quota_retry_scheduler(
|
||||
&mut this.quota_write_retry_sleep,
|
||||
&mut this.quota_write_wake_scheduled,
|
||||
&mut this.quota_write_retry_attempt,
|
||||
);
|
||||
Some(guard)
|
||||
}
|
||||
Ok(guard) => Some(guard),
|
||||
Err(_) => {
|
||||
poll_quota_retry_sleep(
|
||||
&mut this.quota_write_retry_sleep,
|
||||
@@ -547,14 +581,7 @@ impl<S: AsyncWrite + Unpin> AsyncWrite for StatsIo<S> {
|
||||
|
||||
let _cross_mode_quota_guard = if let Some(lock) = this.cross_mode_quota_lock.as_ref() {
|
||||
match lock.try_lock() {
|
||||
Ok(guard) => {
|
||||
reset_quota_retry_scheduler(
|
||||
&mut this.quota_write_retry_sleep,
|
||||
&mut this.quota_write_wake_scheduled,
|
||||
&mut this.quota_write_retry_attempt,
|
||||
);
|
||||
Some(guard)
|
||||
}
|
||||
Ok(guard) => Some(guard),
|
||||
Err(_) => {
|
||||
poll_quota_retry_sleep(
|
||||
&mut this.quota_write_retry_sleep,
|
||||
@@ -569,6 +596,12 @@ impl<S: AsyncWrite + Unpin> AsyncWrite for StatsIo<S> {
|
||||
None
|
||||
};
|
||||
|
||||
reset_quota_retry_scheduler(
|
||||
&mut this.quota_write_retry_sleep,
|
||||
&mut this.quota_write_wake_scheduled,
|
||||
&mut this.quota_write_retry_attempt,
|
||||
);
|
||||
|
||||
let write_buf = if let Some(limit) = this.quota_limit {
|
||||
let used = this.stats.get_user_total_octets(&this.user);
|
||||
if used >= limit {
|
||||
@@ -861,6 +894,10 @@ mod relay_quota_model_adversarial_tests;
|
||||
#[path = "tests/relay_quota_overflow_regression_tests.rs"]
|
||||
mod relay_quota_overflow_regression_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/relay_quota_extended_attack_surface_security_tests.rs"]
|
||||
mod relay_quota_extended_attack_surface_security_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/relay_watchdog_delta_security_tests.rs"]
|
||||
mod relay_watchdog_delta_security_tests;
|
||||
@@ -889,6 +926,14 @@ mod relay_quota_retry_scheduler_tdd_tests;
|
||||
#[path = "tests/relay_cross_mode_quota_fairness_tdd_tests.rs"]
|
||||
mod relay_cross_mode_quota_fairness_tdd_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/relay_cross_mode_pipeline_hol_integration_security_tests.rs"]
|
||||
mod relay_cross_mode_pipeline_hol_integration_security_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/relay_cross_mode_pipeline_latency_benchmark_security_tests.rs"]
|
||||
mod relay_cross_mode_pipeline_latency_benchmark_security_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/relay_quota_retry_backoff_security_tests.rs"]
|
||||
mod relay_quota_retry_backoff_security_tests;
|
||||
@@ -896,3 +941,31 @@ mod relay_quota_retry_backoff_security_tests;
|
||||
#[cfg(test)]
|
||||
#[path = "tests/relay_quota_retry_backoff_benchmark_security_tests.rs"]
|
||||
mod relay_quota_retry_backoff_benchmark_security_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/relay_dual_lock_backoff_regression_security_tests.rs"]
|
||||
mod relay_dual_lock_backoff_regression_security_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/relay_dual_lock_contention_matrix_security_tests.rs"]
|
||||
mod relay_dual_lock_contention_matrix_security_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/relay_dual_lock_race_harness_security_tests.rs"]
|
||||
mod relay_dual_lock_race_harness_security_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/relay_dual_lock_alternating_contention_security_tests.rs"]
|
||||
mod relay_dual_lock_alternating_contention_security_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/relay_quota_retry_allocation_latency_security_tests.rs"]
|
||||
mod relay_quota_retry_allocation_latency_security_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/relay_quota_lock_eviction_lifecycle_tdd_tests.rs"]
|
||||
mod relay_quota_lock_eviction_lifecycle_tdd_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/relay_quota_lock_eviction_stress_security_tests.rs"]
|
||||
mod relay_quota_lock_eviction_stress_security_tests;
|
||||
|
||||
Reference in New Issue
Block a user