checkpoint: integrate authenticated transports
This commit is contained in:
1 parent
313ab00765
commit
586ff8f223
42 files changed
+6277
-771
No files matched your search
+141
@@ -0,0 +1,141 @@
|
||||
use std::{net::SocketAddr, sync::Arc, thread, time::Duration};
|
||||
|
||||
use wgcloak::{
|
||||
config::{self, LimitsConfig},
|
||||
transport::{AdmissionController, AdmissionError},
|
||||
};
|
||||
|
||||
fn limits(
|
||||
max_sessions: usize,
|
||||
max_sessions_per_ip: usize,
|
||||
max_pending_handshakes: usize,
|
||||
max_auth_attempts_per_ip: usize,
|
||||
auth_attempt_window_secs: u64,
|
||||
) -> LimitsConfig {
|
||||
LimitsConfig {
|
||||
max_sessions,
|
||||
max_sessions_per_ip,
|
||||
max_pending_handshakes,
|
||||
max_auth_attempts_per_ip,
|
||||
auth_attempt_window_secs,
|
||||
handshake_timeout_ms: 1_000,
|
||||
}
|
||||
}
|
||||
|
||||
fn address(ip: &str) -> SocketAddr {
|
||||
format!("{ip}:40000").parse().unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pending_limit_and_failed_handshake_release_reservation() {
|
||||
let controller = AdmissionController::new(&limits(8, 4, 1, 100, 60));
|
||||
let first = controller.begin(address("192.0.2.1")).unwrap();
|
||||
assert_eq!(controller.pending_handshakes(), 1);
|
||||
assert!(matches!(
|
||||
controller.begin(address("192.0.2.2")),
|
||||
Err(AdmissionError::Busy)
|
||||
));
|
||||
|
||||
// Dropping the pending guard models any failed or timed-out handshake.
|
||||
drop(first);
|
||||
assert_eq!(controller.pending_handshakes(), 0);
|
||||
let second = controller.begin(address("192.0.2.2")).unwrap();
|
||||
drop(second);
|
||||
assert_eq!(controller.pending_handshakes(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn active_and_per_ip_limits_release_on_close_or_drop() {
|
||||
let controller = AdmissionController::new(&limits(2, 1, 2, 100, 60));
|
||||
let first = controller.begin(address("192.0.2.1")).unwrap().promote();
|
||||
assert_eq!(controller.active_sessions(), 1);
|
||||
assert!(matches!(
|
||||
controller.begin(address("192.0.2.1")),
|
||||
Err(AdmissionError::Busy)
|
||||
));
|
||||
|
||||
let second = controller.begin(address("192.0.2.2")).unwrap().promote();
|
||||
assert_eq!(controller.active_sessions(), 2);
|
||||
assert!(matches!(
|
||||
controller.begin(address("192.0.2.3")),
|
||||
Err(AdmissionError::Busy)
|
||||
));
|
||||
|
||||
drop(first);
|
||||
assert_eq!(controller.active_sessions(), 1);
|
||||
drop(second);
|
||||
assert_eq!(controller.active_sessions(), 0);
|
||||
assert!(controller.begin(address("192.0.2.3")).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concurrent_admissions_never_exceed_global_or_per_ip_capacity() {
|
||||
let controller = AdmissionController::new(&limits(3, 3, 32, 100, 60));
|
||||
let barrier = Arc::new(std::sync::Barrier::new(33));
|
||||
let (sender, receiver) = std::sync::mpsc::channel();
|
||||
let mut workers = Vec::new();
|
||||
for _ in 0..32 {
|
||||
let controller = controller.clone();
|
||||
let barrier = barrier.clone();
|
||||
let sender = sender.clone();
|
||||
workers.push(thread::spawn(move || {
|
||||
barrier.wait();
|
||||
let permit = controller
|
||||
.begin(address("192.0.2.10"))
|
||||
.ok()
|
||||
.map(|pending| pending.promote());
|
||||
barrier.wait();
|
||||
sender.send(permit).unwrap();
|
||||
}));
|
||||
}
|
||||
barrier.wait();
|
||||
barrier.wait();
|
||||
drop(sender);
|
||||
let permits: Vec<_> = receiver.into_iter().flatten().collect();
|
||||
for worker in workers {
|
||||
worker.join().unwrap();
|
||||
}
|
||||
assert_eq!(permits.len(), 3);
|
||||
assert_eq!(controller.active_sessions(), 3);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn authentication_attempt_limit_is_per_ip_and_expires() {
|
||||
let controller = AdmissionController::new(&limits(8, 4, 8, 2, 1));
|
||||
for _ in 0..2 {
|
||||
drop(controller.begin(address("192.0.2.1")).unwrap());
|
||||
}
|
||||
assert!(matches!(
|
||||
controller.begin(address("192.0.2.1")),
|
||||
Err(AdmissionError::RateLimited)
|
||||
));
|
||||
assert!(controller.begin(address("192.0.2.2")).is_ok());
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(1_050)).await;
|
||||
assert!(controller.begin(address("192.0.2.1")).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_limits_have_safe_defaults_and_reject_out_of_bounds_values() {
|
||||
let defaults = LimitsConfig::default();
|
||||
assert_eq!(defaults.max_sessions, 1024);
|
||||
assert_eq!(defaults.max_sessions_per_ip, 32);
|
||||
assert_eq!(defaults.max_pending_handshakes, 256);
|
||||
assert_eq!(defaults.max_auth_attempts_per_ip, 20);
|
||||
assert_eq!(defaults.auth_attempt_window_secs, 60);
|
||||
assert_eq!(defaults.handshake_timeout_ms, 3_000);
|
||||
|
||||
let valid = "[server]\nlisten='127.0.0.1'\n[backend]\ntarget='127.0.0.1:51820'\n[[auth.clients]]\nid='one'\nsecret='AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'\n";
|
||||
assert!(config::Config::from_str(valid).unwrap().validate().is_ok());
|
||||
|
||||
let invalid = format!("{valid}\n[limits]\nmax_auth_attempts_per_ip=0\n");
|
||||
assert!(config::Config::from_str(&invalid)
|
||||
.unwrap()
|
||||
.validate()
|
||||
.is_err());
|
||||
let too_long = format!("{valid}\n[limits]\nhandshake_timeout_ms=300001\n");
|
||||
assert!(config::Config::from_str(&too_long)
|
||||
.unwrap()
|
||||
.validate()
|
||||
.is_err());
|
||||
}
|
||||
Reference in new issue
Block a user