mirror of
https://github.com/telemt/telemt.git
synced 2026-04-18 19:14:09 +03:00
Implement aggressive shape hardening mode and related tests
This commit is contained in:
114
src/tests/ip_tracker_encapsulation_adversarial_tests.rs
Normal file
114
src/tests/ip_tracker_encapsulation_adversarial_tests.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::ip_tracker::UserIpTracker;
|
||||
|
||||
fn ip_from_idx(idx: u32) -> IpAddr {
|
||||
IpAddr::V4(Ipv4Addr::new(
|
||||
172,
|
||||
((idx >> 16) & 0xff) as u8,
|
||||
((idx >> 8) & 0xff) as u8,
|
||||
(idx & 0xff) as u8,
|
||||
))
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn encapsulation_queue_len_helper_matches_enqueue_and_drain_lifecycle() {
|
||||
let tracker = UserIpTracker::new();
|
||||
let user = "encap-len-user";
|
||||
|
||||
for idx in 0..32 {
|
||||
tracker.enqueue_cleanup(user.to_string(), ip_from_idx(idx));
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
tracker.cleanup_queue_len_for_tests(),
|
||||
32,
|
||||
"test helper must reflect queued cleanup entries before drain"
|
||||
);
|
||||
|
||||
tracker.drain_cleanup_queue().await;
|
||||
|
||||
assert_eq!(
|
||||
tracker.cleanup_queue_len_for_tests(),
|
||||
0,
|
||||
"cleanup queue must be empty after drain"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn encapsulation_repeated_queue_poison_recovery_preserves_forward_progress() {
|
||||
let tracker = UserIpTracker::new();
|
||||
tracker.set_user_limit("encap-poison", 1).await;
|
||||
|
||||
let ip_primary = ip_from_idx(10_001);
|
||||
let ip_alt = ip_from_idx(10_002);
|
||||
|
||||
tracker.check_and_add("encap-poison", ip_primary).await.unwrap();
|
||||
|
||||
for _ in 0..128 {
|
||||
let queue = tracker.cleanup_queue_mutex_for_tests();
|
||||
let _ = std::panic::catch_unwind(move || {
|
||||
let _guard = queue.lock().unwrap();
|
||||
panic!("intentional cleanup queue poison in encapsulation regression test");
|
||||
});
|
||||
|
||||
tracker.enqueue_cleanup("encap-poison".to_string(), ip_primary);
|
||||
|
||||
assert!(
|
||||
tracker.check_and_add("encap-poison", ip_alt).await.is_ok(),
|
||||
"poison recovery must not block admission progress"
|
||||
);
|
||||
|
||||
tracker.remove_ip("encap-poison", ip_alt).await;
|
||||
tracker
|
||||
.check_and_add("encap-poison", ip_primary)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||
async fn encapsulation_parallel_poison_and_churn_maintains_queue_and_limit_invariants() {
|
||||
let tracker = Arc::new(UserIpTracker::new());
|
||||
tracker.set_user_limit("encap-stress", 4).await;
|
||||
|
||||
let mut tasks = Vec::new();
|
||||
for worker in 0..32u32 {
|
||||
let t = tracker.clone();
|
||||
tasks.push(tokio::spawn(async move {
|
||||
let user = "encap-stress";
|
||||
let ip = ip_from_idx(20_000 + worker);
|
||||
|
||||
for iter in 0..64u32 {
|
||||
let _ = t.check_and_add(user, ip).await;
|
||||
t.enqueue_cleanup(user.to_string(), ip);
|
||||
|
||||
if iter % 3 == 0 {
|
||||
let queue = t.cleanup_queue_mutex_for_tests();
|
||||
let _ = std::panic::catch_unwind(move || {
|
||||
let _guard = queue.lock().unwrap();
|
||||
panic!("intentional lock poison during parallel stress");
|
||||
});
|
||||
}
|
||||
|
||||
t.drain_cleanup_queue().await;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
for task in tasks {
|
||||
task.await.expect("stress worker must not panic");
|
||||
}
|
||||
|
||||
tracker.drain_cleanup_queue().await;
|
||||
assert_eq!(
|
||||
tracker.cleanup_queue_len_for_tests(),
|
||||
0,
|
||||
"queue must converge to empty after stress drain"
|
||||
);
|
||||
assert!(
|
||||
tracker.get_active_ip_count("encap-stress").await <= 4,
|
||||
"active unique IP count must remain bounded by configured limit"
|
||||
);
|
||||
}
|
||||
@@ -509,8 +509,9 @@ async fn enqueue_cleanup_recovers_from_poisoned_mutex() {
|
||||
let ip = ip_from_idx(99);
|
||||
|
||||
// Poison the lock by panicking while holding it
|
||||
let result = std::panic::catch_unwind(|| {
|
||||
let _guard = tracker.cleanup_queue.lock().unwrap();
|
||||
let cleanup_queue = tracker.cleanup_queue_mutex_for_tests();
|
||||
let result = std::panic::catch_unwind(move || {
|
||||
let _guard = cleanup_queue.lock().unwrap();
|
||||
panic!("Intentional poison panic");
|
||||
});
|
||||
assert!(result.is_err(), "Expected panic to poison mutex");
|
||||
@@ -612,8 +613,9 @@ async fn poisoned_cleanup_queue_still_releases_slot_for_next_ip() {
|
||||
tracker.check_and_add("poison-slot", ip1).await.unwrap();
|
||||
|
||||
// Poison the queue lock as an adversarial condition.
|
||||
let _ = std::panic::catch_unwind(|| {
|
||||
let _guard = tracker.cleanup_queue.lock().unwrap();
|
||||
let cleanup_queue = tracker.cleanup_queue_mutex_for_tests();
|
||||
let _ = std::panic::catch_unwind(move || {
|
||||
let _guard = cleanup_queue.lock().unwrap();
|
||||
panic!("intentional queue poison");
|
||||
});
|
||||
|
||||
@@ -660,8 +662,9 @@ async fn stress_repeated_queue_poison_recovery_preserves_admission_progress() {
|
||||
.unwrap();
|
||||
|
||||
for _ in 0..64 {
|
||||
let _ = std::panic::catch_unwind(|| {
|
||||
let _guard = tracker.cleanup_queue.lock().unwrap();
|
||||
let cleanup_queue = tracker.cleanup_queue_mutex_for_tests();
|
||||
let _ = std::panic::catch_unwind(move || {
|
||||
let _guard = cleanup_queue.lock().unwrap();
|
||||
panic!("intentional queue poison in stress loop");
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user