mirror of
https://github.com/telemt/telemt.git
synced 2026-04-19 03:24:10 +03:00
Add regression and security tests for relay quota and TLS stream handling
- Introduced regression tests for relay quota wake liveness to ensure proper handling of contention and wake events. - Added adversarial tests to validate the behavior of the quota system under stress and contention scenarios. - Implemented security tests for the TLS stream to verify the preservation of pending plaintext during state transitions. - Enhanced the pool writer tests to ensure proper quarantine behavior and validate the removal of writers from the registry. - Included fuzz testing to assess the robustness of the quota and TLS handling mechanisms against unexpected inputs and states.
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
use super::*;
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
fn auth_probe_test_guard() -> std::sync::MutexGuard<'static, ()> {
|
||||
auth_probe_test_lock()
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn positive_preauth_throttle_activates_after_failure_threshold() {
|
||||
let _guard = auth_probe_test_guard();
|
||||
clear_auth_probe_state_for_testing();
|
||||
|
||||
let ip = IpAddr::V4(Ipv4Addr::new(198, 51, 100, 20));
|
||||
let now = Instant::now();
|
||||
|
||||
for _ in 0..AUTH_PROBE_BACKOFF_START_FAILS {
|
||||
auth_probe_record_failure(ip, now);
|
||||
}
|
||||
|
||||
assert!(
|
||||
auth_probe_is_throttled(ip, now),
|
||||
"peer must be throttled once fail streak reaches threshold"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn negative_unrelated_peer_remains_unthrottled() {
|
||||
let _guard = auth_probe_test_guard();
|
||||
clear_auth_probe_state_for_testing();
|
||||
|
||||
let attacker = IpAddr::V4(Ipv4Addr::new(203, 0, 113, 12));
|
||||
let benign = IpAddr::V4(Ipv4Addr::new(203, 0, 113, 13));
|
||||
let now = Instant::now();
|
||||
|
||||
for _ in 0..AUTH_PROBE_BACKOFF_START_FAILS {
|
||||
auth_probe_record_failure(attacker, now);
|
||||
}
|
||||
|
||||
assert!(auth_probe_is_throttled(attacker, now));
|
||||
assert!(
|
||||
!auth_probe_is_throttled(benign, now),
|
||||
"throttle state must stay scoped to normalized peer key"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edge_expired_entry_is_pruned_and_no_longer_throttled() {
|
||||
let _guard = auth_probe_test_guard();
|
||||
clear_auth_probe_state_for_testing();
|
||||
|
||||
let ip = IpAddr::V4(Ipv4Addr::new(192, 0, 2, 41));
|
||||
let base = Instant::now();
|
||||
for _ in 0..AUTH_PROBE_BACKOFF_START_FAILS {
|
||||
auth_probe_record_failure(ip, base);
|
||||
}
|
||||
|
||||
let expired_at = base + Duration::from_secs(AUTH_PROBE_TRACK_RETENTION_SECS + 1);
|
||||
assert!(
|
||||
!auth_probe_is_throttled(ip, expired_at),
|
||||
"expired entries must not keep throttling peers"
|
||||
);
|
||||
|
||||
let state = auth_probe_state_map();
|
||||
assert!(
|
||||
state.get(&normalize_auth_probe_ip(ip)).is_none(),
|
||||
"expired lookup should prune stale state"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adversarial_saturation_grace_requires_extra_failures_before_preauth_throttle() {
|
||||
let _guard = auth_probe_test_guard();
|
||||
clear_auth_probe_state_for_testing();
|
||||
|
||||
let ip = IpAddr::V4(Ipv4Addr::new(198, 18, 0, 7));
|
||||
let now = Instant::now();
|
||||
|
||||
for _ in 0..AUTH_PROBE_BACKOFF_START_FAILS {
|
||||
auth_probe_record_failure(ip, now);
|
||||
}
|
||||
auth_probe_note_saturation(now);
|
||||
|
||||
assert!(
|
||||
!auth_probe_should_apply_preauth_throttle(ip, now),
|
||||
"during global saturation, peer must receive configured grace window"
|
||||
);
|
||||
|
||||
for _ in 0..AUTH_PROBE_SATURATION_GRACE_FAILS {
|
||||
auth_probe_record_failure(ip, now + Duration::from_millis(1));
|
||||
}
|
||||
|
||||
assert!(
|
||||
auth_probe_should_apply_preauth_throttle(ip, now + Duration::from_millis(1)),
|
||||
"after grace failures are exhausted, preauth throttle must activate"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn integration_over_cap_insertion_keeps_probe_map_bounded() {
|
||||
let _guard = auth_probe_test_guard();
|
||||
clear_auth_probe_state_for_testing();
|
||||
|
||||
let now = Instant::now();
|
||||
for idx in 0..(AUTH_PROBE_TRACK_MAX_ENTRIES + 1024) {
|
||||
let ip = IpAddr::V4(Ipv4Addr::new(
|
||||
10,
|
||||
((idx / 65_536) % 256) as u8,
|
||||
((idx / 256) % 256) as u8,
|
||||
(idx % 256) as u8,
|
||||
));
|
||||
auth_probe_record_failure(ip, now);
|
||||
}
|
||||
|
||||
let tracked = auth_probe_state_map().len();
|
||||
assert!(
|
||||
tracked <= AUTH_PROBE_TRACK_MAX_ENTRIES,
|
||||
"probe map must remain hard bounded under insertion storm"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn light_fuzz_randomized_failures_preserve_cap_and_nonzero_streaks() {
|
||||
let _guard = auth_probe_test_guard();
|
||||
clear_auth_probe_state_for_testing();
|
||||
|
||||
let mut seed = 0x4D53_5854_6F66_6175u64;
|
||||
let now = Instant::now();
|
||||
|
||||
for _ in 0..8192 {
|
||||
seed ^= seed << 7;
|
||||
seed ^= seed >> 9;
|
||||
seed ^= seed << 8;
|
||||
|
||||
let ip = IpAddr::V4(Ipv4Addr::new(
|
||||
(seed >> 24) as u8,
|
||||
(seed >> 16) as u8,
|
||||
(seed >> 8) as u8,
|
||||
seed as u8,
|
||||
));
|
||||
auth_probe_record_failure(ip, now + Duration::from_millis((seed & 0x3f) as u64));
|
||||
}
|
||||
|
||||
let state = auth_probe_state_map();
|
||||
assert!(state.len() <= AUTH_PROBE_TRACK_MAX_ENTRIES);
|
||||
for entry in state.iter() {
|
||||
assert!(entry.value().fail_streak > 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||
async fn stress_parallel_failure_flood_keeps_state_hard_capped() {
|
||||
let _guard = auth_probe_test_guard();
|
||||
clear_auth_probe_state_for_testing();
|
||||
|
||||
let start = Instant::now();
|
||||
let mut tasks = Vec::new();
|
||||
|
||||
for worker in 0..8u8 {
|
||||
tasks.push(tokio::spawn(async move {
|
||||
for i in 0..4096u32 {
|
||||
let ip = IpAddr::V4(Ipv4Addr::new(
|
||||
172,
|
||||
worker,
|
||||
((i >> 8) & 0xff) as u8,
|
||||
(i & 0xff) as u8,
|
||||
));
|
||||
auth_probe_record_failure(ip, start + Duration::from_millis((i % 4) as u64));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
for task in tasks {
|
||||
task.await.expect("stress worker must not panic");
|
||||
}
|
||||
|
||||
let tracked = auth_probe_state_map().len();
|
||||
assert!(
|
||||
tracked <= AUTH_PROBE_TRACK_MAX_ENTRIES,
|
||||
"parallel failure flood must not exceed cap"
|
||||
);
|
||||
|
||||
let probe = IpAddr::V4(Ipv4Addr::new(172, 3, 4, 5));
|
||||
let _ = auth_probe_is_throttled(probe, start + Duration::from_millis(2));
|
||||
}
|
||||
Reference in New Issue
Block a user