mirror of
https://github.com/telemt/telemt.git
synced 2026-04-16 18:14:10 +03:00
- 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.
188 lines
5.4 KiB
Rust
188 lines
5.4 KiB
Rust
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));
|
|
}
|