Refactor and enhance tests for proxy and relay functionality

- Renamed test functions in `client_tls_clienthello_truncation_adversarial_tests.rs` to remove "but_leaks" suffix for clarity.
- Added new tests in `direct_relay_business_logic_tests.rs` to validate business logic for data center resolution and scope hints.
- Introduced tests in `direct_relay_common_mistakes_tests.rs` to cover common mistakes in direct relay configurations.
- Added security tests in `direct_relay_security_tests.rs` to ensure proper handling of symlink and parent swap scenarios.
- Created `direct_relay_subtle_adversarial_tests.rs` to stress test concurrent logging and validate scope hint behavior.
- Implemented `relay_quota_lock_pressure_adversarial_tests.rs` to test quota lock behavior under high contention and stress.
- Updated `relay_security_tests.rs` to include quota lock contention tests ensuring proper behavior under concurrent access.
- Introduced `ip_tracker_hotpath_adversarial_tests.rs` to validate the performance and correctness of the IP tracking logic under various scenarios.
This commit is contained in:
David Osipov
2026-03-21 13:38:17 +04:00
parent 8188fedf6a
commit 5933b5e821
13 changed files with 1138 additions and 21 deletions

View File

@@ -0,0 +1,168 @@
use std::net::{IpAddr, Ipv4Addr};
use std::sync::Arc;
use std::time::Duration;
use crate::config::UserMaxUniqueIpsMode;
use crate::ip_tracker::UserIpTracker;
fn ip_from_idx(idx: u32) -> IpAddr {
IpAddr::V4(Ipv4Addr::new(10, ((idx >> 16) & 0xff) as u8, ((idx >> 8) & 0xff) as u8, (idx & 0xff) as u8))
}
#[tokio::test]
async fn hotpath_empty_drain_is_idempotent() {
let tracker = UserIpTracker::new();
for _ in 0..128 {
tracker.drain_cleanup_queue().await;
}
assert_eq!(tracker.get_active_ip_count("none").await, 0);
}
#[tokio::test]
async fn hotpath_batch_cleanup_drain_clears_all_active_entries() {
let tracker = UserIpTracker::new();
tracker.set_user_limit("u", 100).await;
for idx in 0..32 {
let ip = ip_from_idx(idx);
tracker.check_and_add("u", ip).await.unwrap();
tracker.enqueue_cleanup("u".to_string(), ip);
}
tracker.drain_cleanup_queue().await;
assert_eq!(tracker.get_active_ip_count("u").await, 0);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn hotpath_parallel_enqueue_and_drain_does_not_deadlock() {
let tracker = Arc::new(UserIpTracker::new());
tracker.set_user_limit("p", 64).await;
let mut tasks = Vec::new();
for worker in 0..32u32 {
let t = tracker.clone();
tasks.push(tokio::spawn(async move {
let ip = ip_from_idx(1_000 + worker);
for _ in 0..64 {
let _ = t.check_and_add("p", ip).await;
t.enqueue_cleanup("p".to_string(), ip);
t.drain_cleanup_queue().await;
}
}));
}
for task in tasks {
tokio::time::timeout(Duration::from_secs(3), task)
.await
.expect("worker must not deadlock")
.expect("worker task must not panic");
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn hotpath_parallel_unique_ip_limit_never_exceeds_cap() {
let tracker = Arc::new(UserIpTracker::new());
tracker.set_user_limit("limit", 5).await;
let mut tasks = Vec::new();
for idx in 0..64u32 {
let t = tracker.clone();
tasks.push(tokio::spawn(async move { t.check_and_add("limit", ip_from_idx(idx)).await.is_ok() }));
}
let mut admitted = 0usize;
for task in tasks {
if task.await.expect("task must not panic") {
admitted += 1;
}
}
assert!(admitted <= 5, "admitted unique IPs must not exceed configured cap");
assert!(tracker.get_active_ip_count("limit").await <= 5);
}
#[tokio::test]
async fn hotpath_repeated_same_ip_counter_balances_to_zero() {
let tracker = UserIpTracker::new();
tracker.set_user_limit("same", 1).await;
let ip = ip_from_idx(77);
for _ in 0..512 {
tracker.check_and_add("same", ip).await.unwrap();
}
for _ in 0..512 {
tracker.remove_ip("same", ip).await;
}
assert_eq!(tracker.get_active_ip_count("same").await, 0);
}
#[tokio::test]
async fn hotpath_light_fuzz_mixed_operations_preserve_limit_invariants() {
let tracker = UserIpTracker::new();
tracker.set_user_limit("fuzz", 4).await;
let mut state: u64 = 0xA55A_5AA5_D15C_B00B;
for _ in 0..4_000 {
state ^= state << 7;
state ^= state >> 9;
state ^= state << 8;
let ip = ip_from_idx((state as u32) % 8);
match state & 0x3 {
0 | 1 => {
let _ = tracker.check_and_add("fuzz", ip).await;
}
_ => {
tracker.remove_ip("fuzz", ip).await;
}
}
assert!(
tracker.get_active_ip_count("fuzz").await <= 4,
"active count must stay within configured cap"
);
}
}
#[tokio::test]
async fn hotpath_multi_user_churn_keeps_isolation() {
let tracker = UserIpTracker::new();
tracker.set_user_limit("u1", 2).await;
tracker.set_user_limit("u2", 3).await;
for idx in 0..200u32 {
let ip1 = ip_from_idx(idx % 5);
let ip2 = ip_from_idx(100 + (idx % 7));
let _ = tracker.check_and_add("u1", ip1).await;
let _ = tracker.check_and_add("u2", ip2).await;
if idx % 2 == 0 {
tracker.remove_ip("u1", ip1).await;
}
if idx % 3 == 0 {
tracker.remove_ip("u2", ip2).await;
}
}
assert!(tracker.get_active_ip_count("u1").await <= 2);
assert!(tracker.get_active_ip_count("u2").await <= 3);
}
#[tokio::test]
async fn hotpath_time_window_expiry_allows_new_ip_after_window() {
let tracker = UserIpTracker::new();
tracker.set_user_limit("tw", 1).await;
tracker
.set_limit_policy(UserMaxUniqueIpsMode::TimeWindow, 1)
.await;
let ip1 = ip_from_idx(901);
let ip2 = ip_from_idx(902);
tracker.check_and_add("tw", ip1).await.unwrap();
tracker.remove_ip("tw", ip1).await;
assert!(tracker.check_and_add("tw", ip2).await.is_err());
tokio::time::sleep(Duration::from_millis(1_100)).await;
assert!(tracker.check_and_add("tw", ip2).await.is_ok());
}