Compare commits

..

No commits in common. "5f5582865e19069bd6cd61061c53ac7ca4af59eb" and "486e439ae6139c0411b415cf5355cd215a063bc2" have entirely different histories.

35 changed files with 229 additions and 877 deletions

View File

@ -100,7 +100,7 @@ pub(crate) fn default_fake_cert_len() -> usize {
} }
pub(crate) fn default_tls_front_dir() -> String { pub(crate) fn default_tls_front_dir() -> String {
"/etc/telemt/tlsfront".to_string() "tlsfront".to_string()
} }
pub(crate) fn default_replay_check_len() -> usize { pub(crate) fn default_replay_check_len() -> usize {
@ -302,7 +302,7 @@ pub(crate) fn default_me2dc_fallback() -> bool {
} }
pub(crate) fn default_me2dc_fast() -> bool { pub(crate) fn default_me2dc_fast() -> bool {
true false
} }
pub(crate) fn default_keepalive_interval() -> u64 { pub(crate) fn default_keepalive_interval() -> u64 {
@ -558,7 +558,7 @@ pub(crate) fn default_beobachten_flush_secs() -> u64 {
} }
pub(crate) fn default_beobachten_file() -> String { pub(crate) fn default_beobachten_file() -> String {
"/etc/telemt/beobachten.txt".to_string() "cache/beobachten.txt".to_string()
} }
pub(crate) fn default_tls_new_session_tickets() -> u8 { pub(crate) fn default_tls_new_session_tickets() -> u8 {

View File

@ -947,11 +947,7 @@ impl ProxyConfig {
} }
if matches!(config.server.conntrack_control.mode, ConntrackMode::Hybrid) if matches!(config.server.conntrack_control.mode, ConntrackMode::Hybrid)
&& config && config.server.conntrack_control.hybrid_listener_ips.is_empty()
.server
.conntrack_control
.hybrid_listener_ips
.is_empty()
{ {
return Err(ProxyError::Config( return Err(ProxyError::Config(
"server.conntrack_control.hybrid_listener_ips must be non-empty in mode=hybrid" "server.conntrack_control.hybrid_listener_ips must be non-empty in mode=hybrid"
@ -2507,9 +2503,9 @@ mod tests {
let path = dir.join("telemt_conntrack_high_watermark_invalid_test.toml"); let path = dir.join("telemt_conntrack_high_watermark_invalid_test.toml");
std::fs::write(&path, toml).unwrap(); std::fs::write(&path, toml).unwrap();
let err = ProxyConfig::load(&path).unwrap_err().to_string(); let err = ProxyConfig::load(&path).unwrap_err().to_string();
assert!(err.contains( assert!(
"server.conntrack_control.pressure_high_watermark_pct must be within [1, 100]" err.contains("server.conntrack_control.pressure_high_watermark_pct must be within [1, 100]")
)); );
let _ = std::fs::remove_file(path); let _ = std::fs::remove_file(path);
} }
@ -2574,9 +2570,9 @@ mod tests {
let path = dir.join("telemt_conntrack_hybrid_requires_ips_test.toml"); let path = dir.join("telemt_conntrack_hybrid_requires_ips_test.toml");
std::fs::write(&path, toml).unwrap(); std::fs::write(&path, toml).unwrap();
let err = ProxyConfig::load(&path).unwrap_err().to_string(); let err = ProxyConfig::load(&path).unwrap_err().to_string();
assert!(err.contains( assert!(
"server.conntrack_control.hybrid_listener_ips must be non-empty in mode=hybrid" err.contains("server.conntrack_control.hybrid_listener_ips must be non-empty in mode=hybrid")
)); );
let _ = std::fs::remove_file(path); let _ = std::fs::remove_file(path);
} }

View File

@ -56,11 +56,7 @@ pub(crate) fn spawn_conntrack_controller(
shared: Arc<ProxySharedState>, shared: Arc<ProxySharedState>,
) { ) {
if !cfg!(target_os = "linux") { if !cfg!(target_os = "linux") {
let enabled = config_rx let enabled = config_rx.borrow().server.conntrack_control.inline_conntrack_control;
.borrow()
.server
.conntrack_control
.inline_conntrack_control;
stats.set_conntrack_control_enabled(enabled); stats.set_conntrack_control_enabled(enabled);
stats.set_conntrack_control_available(false); stats.set_conntrack_control_available(false);
stats.set_conntrack_pressure_active(false); stats.set_conntrack_pressure_active(false);
@ -69,9 +65,7 @@ pub(crate) fn spawn_conntrack_controller(
shared.disable_conntrack_close_sender(); shared.disable_conntrack_close_sender();
shared.set_conntrack_pressure_active(false); shared.set_conntrack_pressure_active(false);
if enabled { if enabled {
warn!( warn!("conntrack control is configured but unsupported on this OS; disabling runtime worker");
"conntrack control is configured but unsupported on this OS; disabling runtime worker"
);
} }
return; return;
} }
@ -94,13 +88,7 @@ async fn run_conntrack_controller(
let mut delete_budget_tokens = cfg.server.conntrack_control.delete_budget_per_sec; let mut delete_budget_tokens = cfg.server.conntrack_control.delete_budget_per_sec;
let mut backend = pick_backend(cfg.server.conntrack_control.backend); let mut backend = pick_backend(cfg.server.conntrack_control.backend);
apply_runtime_state( apply_runtime_state(stats.as_ref(), shared.as_ref(), &cfg, backend.is_some(), false);
stats.as_ref(),
shared.as_ref(),
&cfg,
backend.is_some(),
false,
);
reconcile_rules(&cfg, backend, stats.as_ref()).await; reconcile_rules(&cfg, backend, stats.as_ref()).await;
loop { loop {
@ -327,9 +315,7 @@ fn pick_backend(configured: ConntrackBackend) -> Option<NetfilterBackend> {
} }
} }
ConntrackBackend::Nftables => command_exists("nft").then_some(NetfilterBackend::Nftables), ConntrackBackend::Nftables => command_exists("nft").then_some(NetfilterBackend::Nftables),
ConntrackBackend::Iptables => { ConntrackBackend::Iptables => command_exists("iptables").then_some(NetfilterBackend::Iptables),
command_exists("iptables").then_some(NetfilterBackend::Iptables)
}
} }
} }
@ -410,12 +396,7 @@ fn notrack_targets(cfg: &ProxyConfig) -> (Vec<Option<IpAddr>>, Vec<Option<IpAddr
} }
async fn apply_nft_rules(cfg: &ProxyConfig) -> Result<(), String> { async fn apply_nft_rules(cfg: &ProxyConfig) -> Result<(), String> {
let _ = run_command( let _ = run_command("nft", &["delete", "table", "inet", "telemt_conntrack"], None).await;
"nft",
&["delete", "table", "inet", "telemt_conntrack"],
None,
)
.await;
if matches!(cfg.server.conntrack_control.mode, ConntrackMode::Tracked) { if matches!(cfg.server.conntrack_control.mode, ConntrackMode::Tracked) {
return Ok(()); return Ok(());
} }
@ -465,12 +446,7 @@ async fn apply_iptables_rules_for_binary(
return Ok(()); return Ok(());
} }
let chain = "TELEMT_NOTRACK"; let chain = "TELEMT_NOTRACK";
let _ = run_command( let _ = run_command(binary, &["-t", "raw", "-D", "PREROUTING", "-j", chain], None).await;
binary,
&["-t", "raw", "-D", "PREROUTING", "-j", chain],
None,
)
.await;
let _ = run_command(binary, &["-t", "raw", "-F", chain], None).await; let _ = run_command(binary, &["-t", "raw", "-F", chain], None).await;
let _ = run_command(binary, &["-t", "raw", "-X", chain], None).await; let _ = run_command(binary, &["-t", "raw", "-X", chain], None).await;
@ -480,20 +456,8 @@ async fn apply_iptables_rules_for_binary(
run_command(binary, &["-t", "raw", "-N", chain], None).await?; run_command(binary, &["-t", "raw", "-N", chain], None).await?;
run_command(binary, &["-t", "raw", "-F", chain], None).await?; run_command(binary, &["-t", "raw", "-F", chain], None).await?;
if run_command( if run_command(binary, &["-t", "raw", "-C", "PREROUTING", "-j", chain], None).await.is_err() {
binary, run_command(binary, &["-t", "raw", "-I", "PREROUTING", "1", "-j", chain], None).await?;
&["-t", "raw", "-C", "PREROUTING", "-j", chain],
None,
)
.await
.is_err()
{
run_command(
binary,
&["-t", "raw", "-I", "PREROUTING", "1", "-j", chain],
None,
)
.await?;
} }
let (v4_targets, v6_targets) = notrack_targets(cfg); let (v4_targets, v6_targets) = notrack_targets(cfg);
@ -523,26 +487,11 @@ async fn apply_iptables_rules_for_binary(
} }
async fn clear_notrack_rules_all_backends() { async fn clear_notrack_rules_all_backends() {
let _ = run_command( let _ = run_command("nft", &["delete", "table", "inet", "telemt_conntrack"], None).await;
"nft", let _ = run_command("iptables", &["-t", "raw", "-D", "PREROUTING", "-j", "TELEMT_NOTRACK"], None).await;
&["delete", "table", "inet", "telemt_conntrack"],
None,
)
.await;
let _ = run_command(
"iptables",
&["-t", "raw", "-D", "PREROUTING", "-j", "TELEMT_NOTRACK"],
None,
)
.await;
let _ = run_command("iptables", &["-t", "raw", "-F", "TELEMT_NOTRACK"], None).await; let _ = run_command("iptables", &["-t", "raw", "-F", "TELEMT_NOTRACK"], None).await;
let _ = run_command("iptables", &["-t", "raw", "-X", "TELEMT_NOTRACK"], None).await; let _ = run_command("iptables", &["-t", "raw", "-X", "TELEMT_NOTRACK"], None).await;
let _ = run_command( let _ = run_command("ip6tables", &["-t", "raw", "-D", "PREROUTING", "-j", "TELEMT_NOTRACK"], None).await;
"ip6tables",
&["-t", "raw", "-D", "PREROUTING", "-j", "TELEMT_NOTRACK"],
None,
)
.await;
let _ = run_command("ip6tables", &["-t", "raw", "-F", "TELEMT_NOTRACK"], None).await; let _ = run_command("ip6tables", &["-t", "raw", "-F", "TELEMT_NOTRACK"], None).await;
let _ = run_command("ip6tables", &["-t", "raw", "-X", "TELEMT_NOTRACK"], None).await; let _ = run_command("ip6tables", &["-t", "raw", "-X", "TELEMT_NOTRACK"], None).await;
} }

View File

@ -88,10 +88,8 @@ pub fn init_logging(
// Use a custom fmt layer that writes to syslog // Use a custom fmt layer that writes to syslog
let fmt_layer = fmt::Layer::default() let fmt_layer = fmt::Layer::default()
.with_ansi(false) .with_ansi(false)
.with_target(false) .with_target(true)
.with_level(false) .with_writer(SyslogWriter::new);
.without_time()
.with_writer(SyslogMakeWriter::new());
tracing_subscriber::registry() tracing_subscriber::registry()
.with(filter_layer) .with(filter_layer)
@ -139,17 +137,12 @@ pub fn init_logging(
/// Syslog writer for tracing. /// Syslog writer for tracing.
#[cfg(unix)] #[cfg(unix)]
#[derive(Clone, Copy)]
struct SyslogMakeWriter;
#[cfg(unix)]
#[derive(Clone, Copy)]
struct SyslogWriter { struct SyslogWriter {
priority: libc::c_int, _private: (),
} }
#[cfg(unix)] #[cfg(unix)]
impl SyslogMakeWriter { impl SyslogWriter {
fn new() -> Self { fn new() -> Self {
// Open syslog connection on first use // Open syslog connection on first use
static INIT: std::sync::Once = std::sync::Once::new(); static INIT: std::sync::Once = std::sync::Once::new();
@ -160,18 +153,7 @@ impl SyslogMakeWriter {
libc::openlog(ident, libc::LOG_PID | libc::LOG_NDELAY, libc::LOG_DAEMON); libc::openlog(ident, libc::LOG_PID | libc::LOG_NDELAY, libc::LOG_DAEMON);
} }
}); });
Self Self { _private: () }
}
}
#[cfg(unix)]
fn syslog_priority_for_level(level: &tracing::Level) -> libc::c_int {
match *level {
tracing::Level::ERROR => libc::LOG_ERR,
tracing::Level::WARN => libc::LOG_WARNING,
tracing::Level::INFO => libc::LOG_INFO,
tracing::Level::DEBUG => libc::LOG_DEBUG,
tracing::Level::TRACE => libc::LOG_DEBUG,
} }
} }
@ -186,13 +168,26 @@ impl std::io::Write for SyslogWriter {
return Ok(buf.len()); return Ok(buf.len());
} }
// Determine priority based on log level in the message
let priority = if msg.contains(" ERROR ") || msg.contains(" error ") {
libc::LOG_ERR
} else if msg.contains(" WARN ") || msg.contains(" warn ") {
libc::LOG_WARNING
} else if msg.contains(" INFO ") || msg.contains(" info ") {
libc::LOG_INFO
} else if msg.contains(" DEBUG ") || msg.contains(" debug ") {
libc::LOG_DEBUG
} else {
libc::LOG_INFO
};
// Write to syslog // Write to syslog
let c_msg = std::ffi::CString::new(msg.as_bytes()) let c_msg = std::ffi::CString::new(msg.as_bytes())
.unwrap_or_else(|_| std::ffi::CString::new("(invalid utf8)").unwrap()); .unwrap_or_else(|_| std::ffi::CString::new("(invalid utf8)").unwrap());
unsafe { unsafe {
libc::syslog( libc::syslog(
self.priority, priority,
b"%s\0".as_ptr() as *const libc::c_char, b"%s\0".as_ptr() as *const libc::c_char,
c_msg.as_ptr(), c_msg.as_ptr(),
); );
@ -207,19 +202,11 @@ impl std::io::Write for SyslogWriter {
} }
#[cfg(unix)] #[cfg(unix)]
impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for SyslogMakeWriter { impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for SyslogWriter {
type Writer = SyslogWriter; type Writer = SyslogWriter;
fn make_writer(&'a self) -> Self::Writer { fn make_writer(&'a self) -> Self::Writer {
SyslogWriter { SyslogWriter::new()
priority: libc::LOG_INFO,
}
}
fn make_writer_for(&'a self, meta: &tracing::Metadata<'_>) -> Self::Writer {
SyslogWriter {
priority: syslog_priority_for_level(meta.level()),
}
} }
} }
@ -315,29 +302,4 @@ mod tests {
LogDestination::Syslog LogDestination::Syslog
)); ));
} }
#[cfg(unix)]
#[test]
fn test_syslog_priority_for_level_mapping() {
assert_eq!(
syslog_priority_for_level(&tracing::Level::ERROR),
libc::LOG_ERR
);
assert_eq!(
syslog_priority_for_level(&tracing::Level::WARN),
libc::LOG_WARNING
);
assert_eq!(
syslog_priority_for_level(&tracing::Level::INFO),
libc::LOG_INFO
);
assert_eq!(
syslog_priority_for_level(&tracing::Level::DEBUG),
libc::LOG_DEBUG
);
assert_eq!(
syslog_priority_for_level(&tracing::Level::TRACE),
libc::LOG_DEBUG
);
}
} }

View File

@ -18,38 +18,19 @@ use crate::transport::middle_proxy::{
pub(crate) fn resolve_runtime_config_path( pub(crate) fn resolve_runtime_config_path(
config_path_cli: &str, config_path_cli: &str,
startup_cwd: &std::path::Path, startup_cwd: &std::path::Path,
config_path_explicit: bool,
) -> PathBuf { ) -> PathBuf {
if config_path_explicit {
let raw = PathBuf::from(config_path_cli); let raw = PathBuf::from(config_path_cli);
let absolute = if raw.is_absolute() { let absolute = if raw.is_absolute() {
raw raw
} else { } else {
startup_cwd.join(raw) startup_cwd.join(raw)
}; };
return absolute.canonicalize().unwrap_or(absolute); absolute.canonicalize().unwrap_or(absolute)
}
let etc_telemt = std::path::Path::new("/etc/telemt");
let candidates = [
startup_cwd.join("config.toml"),
startup_cwd.join("telemt.toml"),
etc_telemt.join("telemt.toml"),
etc_telemt.join("config.toml"),
];
for candidate in candidates {
if candidate.is_file() {
return candidate.canonicalize().unwrap_or(candidate);
}
}
startup_cwd.join("config.toml")
} }
/// Parsed CLI arguments. /// Parsed CLI arguments.
pub(crate) struct CliArgs { pub(crate) struct CliArgs {
pub config_path: String, pub config_path: String,
pub config_path_explicit: bool,
pub data_path: Option<PathBuf>, pub data_path: Option<PathBuf>,
pub silent: bool, pub silent: bool,
pub log_level: Option<String>, pub log_level: Option<String>,
@ -58,7 +39,6 @@ pub(crate) struct CliArgs {
pub(crate) fn parse_cli() -> CliArgs { pub(crate) fn parse_cli() -> CliArgs {
let mut config_path = "config.toml".to_string(); let mut config_path = "config.toml".to_string();
let mut config_path_explicit = false;
let mut data_path: Option<PathBuf> = None; let mut data_path: Option<PathBuf> = None;
let mut silent = false; let mut silent = false;
let mut log_level: Option<String> = None; let mut log_level: Option<String> = None;
@ -94,20 +74,6 @@ pub(crate) fn parse_cli() -> CliArgs {
s.trim_start_matches("--data-path=").to_string(), s.trim_start_matches("--data-path=").to_string(),
)); ));
} }
"--working-dir" => {
i += 1;
if i < args.len() {
data_path = Some(PathBuf::from(args[i].clone()));
} else {
eprintln!("Missing value for --working-dir");
std::process::exit(0);
}
}
s if s.starts_with("--working-dir=") => {
data_path = Some(PathBuf::from(
s.trim_start_matches("--working-dir=").to_string(),
));
}
"--silent" | "-s" => { "--silent" | "-s" => {
silent = true; silent = true;
} }
@ -145,12 +111,14 @@ pub(crate) fn parse_cli() -> CliArgs {
i += 1; i += 1;
} }
} }
s if !s.starts_with('-') => { s if s.starts_with("--working-dir") => {
if !matches!(s, "run" | "start" | "stop" | "reload" | "status") { if !s.contains('=') {
config_path = s.to_string(); i += 1;
config_path_explicit = true;
} }
} }
s if !s.starts_with('-') => {
config_path = s.to_string();
}
other => { other => {
eprintln!("Unknown option: {}", other); eprintln!("Unknown option: {}", other);
} }
@ -160,7 +128,6 @@ pub(crate) fn parse_cli() -> CliArgs {
CliArgs { CliArgs {
config_path, config_path,
config_path_explicit,
data_path, data_path,
silent, silent,
log_level, log_level,
@ -185,7 +152,6 @@ fn print_help() {
eprintln!( eprintln!(
" --data-path <DIR> Set data directory (absolute path; overrides config value)" " --data-path <DIR> Set data directory (absolute path; overrides config value)"
); );
eprintln!(" --working-dir <DIR> Alias for --data-path");
eprintln!(" --silent, -s Suppress info logs"); eprintln!(" --silent, -s Suppress info logs");
eprintln!(" --log-level <LEVEL> debug|verbose|normal|silent"); eprintln!(" --log-level <LEVEL> debug|verbose|normal|silent");
eprintln!(" --help, -h Show this help"); eprintln!(" --help, -h Show this help");
@ -244,7 +210,7 @@ mod tests {
let target = startup_cwd.join("config.toml"); let target = startup_cwd.join("config.toml");
std::fs::write(&target, " ").unwrap(); std::fs::write(&target, " ").unwrap();
let resolved = resolve_runtime_config_path("config.toml", &startup_cwd, true); let resolved = resolve_runtime_config_path("config.toml", &startup_cwd);
assert_eq!(resolved, target.canonicalize().unwrap()); assert_eq!(resolved, target.canonicalize().unwrap());
let _ = std::fs::remove_file(&target); let _ = std::fs::remove_file(&target);
@ -260,45 +226,11 @@ mod tests {
let startup_cwd = std::env::temp_dir().join(format!("telemt_cfg_path_missing_{nonce}")); let startup_cwd = std::env::temp_dir().join(format!("telemt_cfg_path_missing_{nonce}"));
std::fs::create_dir_all(&startup_cwd).unwrap(); std::fs::create_dir_all(&startup_cwd).unwrap();
let resolved = resolve_runtime_config_path("missing.toml", &startup_cwd, true); let resolved = resolve_runtime_config_path("missing.toml", &startup_cwd);
assert_eq!(resolved, startup_cwd.join("missing.toml")); assert_eq!(resolved, startup_cwd.join("missing.toml"));
let _ = std::fs::remove_dir(&startup_cwd); let _ = std::fs::remove_dir(&startup_cwd);
} }
#[test]
fn resolve_runtime_config_path_uses_startup_candidates_when_not_explicit() {
let nonce = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
let startup_cwd =
std::env::temp_dir().join(format!("telemt_cfg_startup_candidates_{nonce}"));
std::fs::create_dir_all(&startup_cwd).unwrap();
let telemt = startup_cwd.join("telemt.toml");
std::fs::write(&telemt, " ").unwrap();
let resolved = resolve_runtime_config_path("config.toml", &startup_cwd, false);
assert_eq!(resolved, telemt.canonicalize().unwrap());
let _ = std::fs::remove_file(&telemt);
let _ = std::fs::remove_dir(&startup_cwd);
}
#[test]
fn resolve_runtime_config_path_defaults_to_startup_config_when_none_found() {
let nonce = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
let startup_cwd = std::env::temp_dir().join(format!("telemt_cfg_startup_default_{nonce}"));
std::fs::create_dir_all(&startup_cwd).unwrap();
let resolved = resolve_runtime_config_path("config.toml", &startup_cwd, false);
assert_eq!(resolved, startup_cwd.join("config.toml"));
let _ = std::fs::remove_dir(&startup_cwd);
}
} }
pub(crate) fn print_proxy_links(host: &str, port: u16, config: &ProxyConfig) { pub(crate) fn print_proxy_links(host: &str, port: u16, config: &ProxyConfig) {

View File

@ -28,8 +28,8 @@ use tracing::{error, info, warn};
use tracing_subscriber::{EnvFilter, fmt, prelude::*, reload}; use tracing_subscriber::{EnvFilter, fmt, prelude::*, reload};
use crate::api; use crate::api;
use crate::config::{LogLevel, ProxyConfig};
use crate::conntrack_control; use crate::conntrack_control;
use crate::config::{LogLevel, ProxyConfig};
use crate::crypto::SecureRandom; use crate::crypto::SecureRandom;
use crate::ip_tracker::UserIpTracker; use crate::ip_tracker::UserIpTracker;
use crate::network::probe::{decide_network_capabilities, log_probe_result, run_probe}; use crate::network::probe::{decide_network_capabilities, log_probe_result, run_probe};
@ -112,7 +112,6 @@ async fn run_inner(
.await; .await;
let cli_args = parse_cli(); let cli_args = parse_cli();
let config_path_cli = cli_args.config_path; let config_path_cli = cli_args.config_path;
let config_path_explicit = cli_args.config_path_explicit;
let data_path = cli_args.data_path; let data_path = cli_args.data_path;
let cli_silent = cli_args.silent; let cli_silent = cli_args.silent;
let cli_log_level = cli_args.log_level; let cli_log_level = cli_args.log_level;
@ -124,8 +123,7 @@ async fn run_inner(
std::process::exit(1); std::process::exit(1);
} }
}; };
let mut config_path = let config_path = resolve_runtime_config_path(&config_path_cli, &startup_cwd);
resolve_runtime_config_path(&config_path_cli, &startup_cwd, config_path_explicit);
let mut config = match ProxyConfig::load(&config_path) { let mut config = match ProxyConfig::load(&config_path) {
Ok(c) => c, Ok(c) => c,
@ -135,99 +133,11 @@ async fn run_inner(
std::process::exit(1); std::process::exit(1);
} else { } else {
let default = ProxyConfig::default(); let default = ProxyConfig::default();
std::fs::write(&config_path, toml::to_string_pretty(&default).unwrap()).unwrap();
let serialized =
match toml::to_string_pretty(&default).or_else(|_| toml::to_string(&default)) {
Ok(value) => Some(value),
Err(serialize_error) => {
eprintln!(
"[telemt] Warning: failed to serialize default config: {}",
serialize_error
);
None
}
};
if config_path_explicit {
if let Some(serialized) = serialized.as_ref() {
if let Err(write_error) = std::fs::write(&config_path, serialized) {
eprintln!(
"[telemt] Error: failed to create explicit config at {}: {}",
config_path.display(),
write_error
);
std::process::exit(1);
}
eprintln!( eprintln!(
"[telemt] Created default config at {}", "[telemt] Created default config at {}",
config_path.display() config_path.display()
); );
} else {
eprintln!(
"[telemt] Warning: running with in-memory default config without writing to disk"
);
}
} else {
let system_dir = std::path::Path::new("/etc/telemt");
let system_config_path = system_dir.join("telemt.toml");
let startup_config_path = startup_cwd.join("config.toml");
let mut persisted = false;
if let Some(serialized) = serialized.as_ref() {
match std::fs::create_dir_all(system_dir) {
Ok(()) => match std::fs::write(&system_config_path, serialized) {
Ok(()) => {
config_path = system_config_path;
eprintln!(
"[telemt] Created default config at {}",
config_path.display()
);
persisted = true;
}
Err(write_error) => {
eprintln!(
"[telemt] Warning: failed to write default config at {}: {}",
system_config_path.display(),
write_error
);
}
},
Err(create_error) => {
eprintln!(
"[telemt] Warning: failed to create {}: {}",
system_dir.display(),
create_error
);
}
}
if !persisted {
match std::fs::write(&startup_config_path, serialized) {
Ok(()) => {
config_path = startup_config_path;
eprintln!(
"[telemt] Created default config at {}",
config_path.display()
);
persisted = true;
}
Err(write_error) => {
eprintln!(
"[telemt] Warning: failed to write default config at {}: {}",
startup_config_path.display(),
write_error
);
}
}
}
}
if !persisted {
eprintln!(
"[telemt] Warning: running with in-memory default config without writing to disk"
);
}
}
default default
} }
} }

View File

@ -2,8 +2,8 @@
mod api; mod api;
mod cli; mod cli;
mod config;
mod conntrack_control; mod conntrack_control;
mod config;
mod crypto; mod crypto;
#[cfg(unix)] #[cfg(unix)]
mod daemon; mod daemon;

View File

@ -246,9 +246,7 @@ pub fn seed_tier_for_user(user: &str) -> AdaptiveTier {
if now.saturating_duration_since(value.seen_at) <= PROFILE_TTL { if now.saturating_duration_since(value.seen_at) <= PROFILE_TTL {
return value.tier; return value.tier;
} }
profiles().remove_if(user, |_, v| { profiles().remove_if(user, |_, v| now.saturating_duration_since(v.seen_at) > PROFILE_TTL);
now.saturating_duration_since(v.seen_at) > PROFILE_TTL
});
} }
AdaptiveTier::Base AdaptiveTier::Base
} }

View File

@ -17,13 +17,13 @@ use crate::crypto::SecureRandom;
use crate::error::{ProxyError, Result}; use crate::error::{ProxyError, Result};
use crate::protocol::constants::*; use crate::protocol::constants::*;
use crate::proxy::handshake::{HandshakeSuccess, encrypt_tg_nonce_with_ciphers, generate_tg_nonce}; use crate::proxy::handshake::{HandshakeSuccess, encrypt_tg_nonce_with_ciphers, generate_tg_nonce};
use crate::proxy::shared_state::{
ConntrackCloseEvent, ConntrackClosePublishResult, ConntrackCloseReason, ProxySharedState,
};
use crate::proxy::route_mode::{ use crate::proxy::route_mode::{
ROUTE_SWITCH_ERROR_MSG, RelayRouteMode, RouteCutoverState, affected_cutover_state, ROUTE_SWITCH_ERROR_MSG, RelayRouteMode, RouteCutoverState, affected_cutover_state,
cutover_stagger_delay, cutover_stagger_delay,
}; };
use crate::proxy::shared_state::{
ConntrackCloseEvent, ConntrackClosePublishResult, ConntrackCloseReason, ProxySharedState,
};
use crate::stats::Stats; use crate::stats::Stats;
use crate::stream::{BufferPool, CryptoReader, CryptoWriter}; use crate::stream::{BufferPool, CryptoReader, CryptoWriter};
use crate::transport::UpstreamManager; use crate::transport::UpstreamManager;

View File

@ -118,11 +118,7 @@ fn auth_probe_state_expired(state: &AuthProbeState, now: Instant) -> bool {
now.duration_since(state.last_seen) > retention now.duration_since(state.last_seen) > retention
} }
fn auth_probe_eviction_offset_in( fn auth_probe_eviction_offset_in(shared: &ProxySharedState, peer_ip: IpAddr, now: Instant) -> usize {
shared: &ProxySharedState,
peer_ip: IpAddr,
now: Instant,
) -> usize {
let hasher_state = &shared.handshake.auth_probe_eviction_hasher; let hasher_state = &shared.handshake.auth_probe_eviction_hasher;
let mut hasher = hasher_state.build_hasher(); let mut hasher = hasher_state.build_hasher();
peer_ip.hash(&mut hasher); peer_ip.hash(&mut hasher);

View File

@ -255,11 +255,7 @@ async fn wait_mask_connect_budget(started: Instant) {
// sigma is chosen so ~99% of raw samples land inside [floor, ceiling] before clamp. // sigma is chosen so ~99% of raw samples land inside [floor, ceiling] before clamp.
// When floor > ceiling (misconfiguration), returns ceiling (the smaller value). // When floor > ceiling (misconfiguration), returns ceiling (the smaller value).
// When floor == ceiling, returns that value. When both are 0, returns 0. // When floor == ceiling, returns that value. When both are 0, returns 0.
pub(crate) fn sample_lognormal_percentile_bounded( pub(crate) fn sample_lognormal_percentile_bounded(floor: u64, ceiling: u64, rng: &mut impl Rng) -> u64 {
floor: u64,
ceiling: u64,
rng: &mut impl Rng,
) -> u64 {
if ceiling == 0 && floor == 0 { if ceiling == 0 && floor == 0 {
return 0; return 0;
} }
@ -300,9 +296,7 @@ fn mask_outcome_target_budget(config: &ProxyConfig) -> Duration {
} }
if ceiling > floor { if ceiling > floor {
let mut rng = rand::rng(); let mut rng = rand::rng();
return Duration::from_millis(sample_lognormal_percentile_bounded( return Duration::from_millis(sample_lognormal_percentile_bounded(floor, ceiling, &mut rng));
floor, ceiling, &mut rng,
));
} }
// ceiling <= floor: use the larger value (fail-closed: preserve longer delay) // ceiling <= floor: use the larger value (fail-closed: preserve longer delay)
return Duration::from_millis(floor.max(ceiling)); return Duration::from_millis(floor.max(ceiling));

View File

@ -3,12 +3,12 @@ use std::collections::hash_map::DefaultHasher;
use std::collections::{BTreeSet, HashMap}; use std::collections::{BTreeSet, HashMap};
#[cfg(test)] #[cfg(test)]
use std::future::Future; use std::future::Future;
use std::hash::{BuildHasher, Hash};
#[cfg(test)] #[cfg(test)]
use std::hash::Hasher; use std::hash::Hasher;
use std::hash::{BuildHasher, Hash};
use std::net::{IpAddr, SocketAddr}; use std::net::{IpAddr, SocketAddr};
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
@ -21,13 +21,13 @@ use crate::crypto::SecureRandom;
use crate::error::{ProxyError, Result}; use crate::error::{ProxyError, Result};
use crate::protocol::constants::{secure_padding_len, *}; use crate::protocol::constants::{secure_padding_len, *};
use crate::proxy::handshake::HandshakeSuccess; use crate::proxy::handshake::HandshakeSuccess;
use crate::proxy::shared_state::{
ConntrackCloseEvent, ConntrackClosePublishResult, ConntrackCloseReason, ProxySharedState,
};
use crate::proxy::route_mode::{ use crate::proxy::route_mode::{
ROUTE_SWITCH_ERROR_MSG, RelayRouteMode, RouteCutoverState, affected_cutover_state, ROUTE_SWITCH_ERROR_MSG, RelayRouteMode, RouteCutoverState, affected_cutover_state,
cutover_stagger_delay, cutover_stagger_delay,
}; };
use crate::proxy::shared_state::{
ConntrackCloseEvent, ConntrackClosePublishResult, ConntrackCloseReason, ProxySharedState,
};
use crate::stats::{ use crate::stats::{
MeD2cFlushReason, MeD2cQuotaRejectStage, MeD2cWriteMode, QuotaReserveError, Stats, UserStats, MeD2cFlushReason, MeD2cQuotaRejectStage, MeD2cWriteMode, QuotaReserveError, Stats, UserStats,
}; };
@ -257,7 +257,9 @@ impl RelayClientIdlePolicy {
if self.soft_idle > self.hard_idle { if self.soft_idle > self.hard_idle {
self.soft_idle = self.hard_idle; self.soft_idle = self.hard_idle;
} }
self.legacy_frame_read_timeout = self.legacy_frame_read_timeout.min(pressure_hard_idle_cap); self.legacy_frame_read_timeout = self
.legacy_frame_read_timeout
.min(pressure_hard_idle_cap);
if self.grace_after_downstream_activity > self.hard_idle { if self.grace_after_downstream_activity > self.hard_idle {
self.grace_after_downstream_activity = self.hard_idle; self.grace_after_downstream_activity = self.hard_idle;
} }
@ -459,15 +461,12 @@ fn report_desync_frame_too_large_in(
.map(|b| matches!(b[0], b'G' | b'P' | b'H' | b'C' | b'D')) .map(|b| matches!(b[0], b'G' | b'P' | b'H' | b'C' | b'D'))
.unwrap_or(false); .unwrap_or(false);
let now = Instant::now(); let now = Instant::now();
let dedup_key = hash_value_in( let dedup_key = hash_value_in(shared, &(
shared,
&(
state.user.as_str(), state.user.as_str(),
state.peer_hash, state.peer_hash,
proto_tag, proto_tag,
DESYNC_ERROR_CLASS, DESYNC_ERROR_CLASS,
), ));
);
let emit_full = should_emit_full_desync_in(shared, dedup_key, state.desync_all_full, now); let emit_full = should_emit_full_desync_in(shared, dedup_key, state.desync_all_full, now);
let duration_ms = state.started_at.elapsed().as_millis() as u64; let duration_ms = state.started_at.elapsed().as_millis() as u64;
let bytes_me2c = state.bytes_me2c.load(Ordering::Relaxed); let bytes_me2c = state.bytes_me2c.load(Ordering::Relaxed);
@ -632,10 +631,7 @@ fn observe_me_d2c_flush_event(
} }
#[cfg(test)] #[cfg(test)]
pub(crate) fn mark_relay_idle_candidate_for_testing( pub(crate) fn mark_relay_idle_candidate_for_testing(shared: &ProxySharedState, conn_id: u64) -> bool {
shared: &ProxySharedState,
conn_id: u64,
) -> bool {
let registry = &shared.middle_relay.relay_idle_registry; let registry = &shared.middle_relay.relay_idle_registry;
let mut guard = match registry.lock() { let mut guard = match registry.lock() {
Ok(guard) => guard, Ok(guard) => guard,
@ -720,10 +716,7 @@ pub(crate) fn relay_pressure_event_seq_for_testing(shared: &ProxySharedState) ->
#[cfg(test)] #[cfg(test)]
pub(crate) fn relay_idle_mark_seq_for_testing(shared: &ProxySharedState) -> u64 { pub(crate) fn relay_idle_mark_seq_for_testing(shared: &ProxySharedState) -> u64 {
shared shared.middle_relay.relay_idle_mark_seq.load(Ordering::Relaxed)
.middle_relay
.relay_idle_mark_seq
.load(Ordering::Relaxed)
} }
#[cfg(test)] #[cfg(test)]
@ -872,7 +865,10 @@ pub(crate) fn desync_dedup_insert_for_testing(shared: &ProxySharedState, key: u6
} }
#[cfg(test)] #[cfg(test)]
pub(crate) fn desync_dedup_get_for_testing(shared: &ProxySharedState, key: u64) -> Option<Instant> { pub(crate) fn desync_dedup_get_for_testing(
shared: &ProxySharedState,
key: u64,
) -> Option<Instant> {
shared shared
.middle_relay .middle_relay
.desync_dedup .desync_dedup
@ -881,9 +877,7 @@ pub(crate) fn desync_dedup_get_for_testing(shared: &ProxySharedState, key: u64)
} }
#[cfg(test)] #[cfg(test)]
pub(crate) fn desync_dedup_keys_for_testing( pub(crate) fn desync_dedup_keys_for_testing(shared: &ProxySharedState) -> std::collections::HashSet<u64> {
shared: &ProxySharedState,
) -> std::collections::HashSet<u64> {
shared shared
.middle_relay .middle_relay
.desync_dedup .desync_dedup

View File

@ -8,7 +8,7 @@ use std::time::Instant;
use dashmap::DashMap; use dashmap::DashMap;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use crate::proxy::handshake::{AuthProbeSaturationState, AuthProbeState}; use crate::proxy::handshake::{AuthProbeState, AuthProbeSaturationState};
use crate::proxy::middle_relay::{DesyncDedupRotationState, RelayIdleCandidateRegistry}; use crate::proxy::middle_relay::{DesyncDedupRotationState, RelayIdleCandidateRegistry};
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -136,8 +136,7 @@ impl ProxySharedState {
} }
pub(crate) fn set_conntrack_pressure_active(&self, active: bool) { pub(crate) fn set_conntrack_pressure_active(&self, active: bool) {
self.conntrack_pressure_active self.conntrack_pressure_active.store(active, Ordering::Relaxed);
.store(active, Ordering::Relaxed);
} }
pub(crate) fn conntrack_pressure_active(&self) -> bool { pub(crate) fn conntrack_pressure_active(&self) -> bool {

View File

@ -1,6 +1,6 @@
use super::*; use super::*;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
static RACE_TEST_KEY_COUNTER: AtomicUsize = AtomicUsize::new(1_000_000); static RACE_TEST_KEY_COUNTER: AtomicUsize = AtomicUsize::new(1_000_000);

View File

@ -65,15 +65,9 @@ fn adaptive_base_tier_buffers_unchanged() {
fn adaptive_tier1_buffers_within_caps() { fn adaptive_tier1_buffers_within_caps() {
let (c2s, s2c) = direct_copy_buffers_for_tier(AdaptiveTier::Tier1, 65536, 262144); let (c2s, s2c) = direct_copy_buffers_for_tier(AdaptiveTier::Tier1, 65536, 262144);
assert!(c2s > 65536, "Tier1 c2s should exceed Base"); assert!(c2s > 65536, "Tier1 c2s should exceed Base");
assert!( assert!(c2s <= 128 * 1024, "Tier1 c2s should not exceed DIRECT_C2S_CAP_BYTES");
c2s <= 128 * 1024,
"Tier1 c2s should not exceed DIRECT_C2S_CAP_BYTES"
);
assert!(s2c > 262144, "Tier1 s2c should exceed Base"); assert!(s2c > 262144, "Tier1 s2c should exceed Base");
assert!( assert!(s2c <= 512 * 1024, "Tier1 s2c should not exceed DIRECT_S2C_CAP_BYTES");
s2c <= 512 * 1024,
"Tier1 s2c should not exceed DIRECT_S2C_CAP_BYTES"
);
} }
#[test] #[test]

View File

@ -19,8 +19,7 @@ fn adversarial_large_state_offsets_escape_first_scan_window() {
((i.wrapping_mul(131)) & 0xff) as u8, ((i.wrapping_mul(131)) & 0xff) as u8,
)); ));
let now = base + Duration::from_nanos(i); let now = base + Duration::from_nanos(i);
let start = let start = auth_probe_scan_start_offset_in(shared.as_ref(), ip, now, state_len, scan_limit);
auth_probe_scan_start_offset_in(shared.as_ref(), ip, now, state_len, scan_limit);
if start >= scan_limit { if start >= scan_limit {
saw_offset_outside_first_window = true; saw_offset_outside_first_window = true;
break; break;
@ -49,8 +48,7 @@ fn stress_large_state_offsets_cover_many_scan_windows() {
((i.wrapping_mul(17)) & 0xff) as u8, ((i.wrapping_mul(17)) & 0xff) as u8,
)); ));
let now = base + Duration::from_micros(i); let now = base + Duration::from_micros(i);
let start = let start = auth_probe_scan_start_offset_in(shared.as_ref(), ip, now, state_len, scan_limit);
auth_probe_scan_start_offset_in(shared.as_ref(), ip, now, state_len, scan_limit);
covered_windows.insert(start / scan_limit); covered_windows.insert(start / scan_limit);
} }
@ -82,8 +80,7 @@ fn light_fuzz_offset_always_stays_inside_state_len() {
let state_len = ((seed >> 16) as usize % 200_000).saturating_add(1); let state_len = ((seed >> 16) as usize % 200_000).saturating_add(1);
let scan_limit = ((seed >> 40) as usize % 2_048).saturating_add(1); let scan_limit = ((seed >> 40) as usize % 2_048).saturating_add(1);
let now = base + Duration::from_nanos(seed & 0x0fff); let now = base + Duration::from_nanos(seed & 0x0fff);
let start = let start = auth_probe_scan_start_offset_in(shared.as_ref(), ip, now, state_len, scan_limit);
auth_probe_scan_start_offset_in(shared.as_ref(), ip, now, state_len, scan_limit);
assert!( assert!(
start < state_len, start < state_len,

View File

@ -87,11 +87,7 @@ fn adversarial_saturation_grace_requires_extra_failures_before_preauth_throttle(
} }
assert!( assert!(
auth_probe_should_apply_preauth_throttle_in( auth_probe_should_apply_preauth_throttle_in(shared.as_ref(), ip, now + Duration::from_millis(1)),
shared.as_ref(),
ip,
now + Duration::from_millis(1)
),
"after grace failures are exhausted, preauth throttle must activate" "after grace failures are exhausted, preauth throttle must activate"
); );
} }
@ -138,11 +134,7 @@ fn light_fuzz_randomized_failures_preserve_cap_and_nonzero_streaks() {
(seed >> 8) as u8, (seed >> 8) as u8,
seed as u8, seed as u8,
)); ));
auth_probe_record_failure_in( auth_probe_record_failure_in(shared.as_ref(), ip, now + Duration::from_millis((seed & 0x3f) as u64));
shared.as_ref(),
ip,
now + Duration::from_millis((seed & 0x3f) as u64),
);
} }
let state = auth_probe_state_for_testing_in_shared(shared.as_ref()); let state = auth_probe_state_for_testing_in_shared(shared.as_ref());
@ -170,11 +162,7 @@ async fn stress_parallel_failure_flood_keeps_state_hard_capped() {
((i >> 8) & 0xff) as u8, ((i >> 8) & 0xff) as u8,
(i & 0xff) as u8, (i & 0xff) as u8,
)); ));
auth_probe_record_failure_in( auth_probe_record_failure_in(shared.as_ref(), ip, start + Duration::from_millis((i % 4) as u64));
shared.as_ref(),
ip,
start + Duration::from_millis((i % 4) as u64),
);
} }
})); }));
} }

View File

@ -31,8 +31,7 @@ fn adversarial_large_state_must_allow_start_offset_outside_scan_budget_window()
(i & 0xff) as u8, (i & 0xff) as u8,
)); ));
let now = base + Duration::from_micros(i as u64); let now = base + Duration::from_micros(i as u64);
let start = let start = auth_probe_scan_start_offset_in(shared.as_ref(), ip, now, state_len, scan_limit);
auth_probe_scan_start_offset_in(shared.as_ref(), ip, now, state_len, scan_limit);
assert!( assert!(
start < state_len, start < state_len,
"start offset must stay within state length; start={start}, len={state_len}" "start offset must stay within state length; start={start}, len={state_len}"
@ -84,8 +83,7 @@ fn light_fuzz_scan_offset_budget_never_exceeds_effective_window() {
let state_len = ((seed >> 8) as usize % 131_072).saturating_add(1); let state_len = ((seed >> 8) as usize % 131_072).saturating_add(1);
let scan_limit = ((seed >> 32) as usize % 512).saturating_add(1); let scan_limit = ((seed >> 32) as usize % 512).saturating_add(1);
let now = base + Duration::from_nanos(seed & 0xffff); let now = base + Duration::from_nanos(seed & 0xffff);
let start = let start = auth_probe_scan_start_offset_in(shared.as_ref(), ip, now, state_len, scan_limit);
auth_probe_scan_start_offset_in(shared.as_ref(), ip, now, state_len, scan_limit);
assert!( assert!(
start < state_len, start < state_len,

View File

@ -36,13 +36,7 @@ fn adversarial_many_ips_same_time_spreads_offsets_without_bias_collapse() {
i as u8, i as u8,
(255 - (i as u8)), (255 - (i as u8)),
)); ));
uniq.insert(auth_probe_scan_start_offset_in( uniq.insert(auth_probe_scan_start_offset_in(shared.as_ref(), ip, now, 65_536, 16));
shared.as_ref(),
ip,
now,
65_536,
16,
));
} }
assert!( assert!(
@ -69,11 +63,7 @@ async fn stress_parallel_failure_churn_under_saturation_remains_capped_and_live(
((i >> 8) & 0xff) as u8, ((i >> 8) & 0xff) as u8,
(i & 0xff) as u8, (i & 0xff) as u8,
)); ));
auth_probe_record_failure_in( auth_probe_record_failure_in(shared.as_ref(), ip, start + Duration::from_micros((i % 128) as u64));
shared.as_ref(),
ip,
start + Duration::from_micros((i % 128) as u64),
);
} }
})); }));
} }
@ -83,17 +73,12 @@ async fn stress_parallel_failure_churn_under_saturation_remains_capped_and_live(
} }
assert!( assert!(
auth_probe_state_for_testing_in_shared(shared.as_ref()).len() auth_probe_state_for_testing_in_shared(shared.as_ref()).len() <= AUTH_PROBE_TRACK_MAX_ENTRIES,
<= AUTH_PROBE_TRACK_MAX_ENTRIES,
"state must remain hard-capped under parallel saturation churn" "state must remain hard-capped under parallel saturation churn"
); );
let probe = IpAddr::V4(Ipv4Addr::new(10, 4, 1, 1)); let probe = IpAddr::V4(Ipv4Addr::new(10, 4, 1, 1));
let _ = auth_probe_should_apply_preauth_throttle_in( let _ = auth_probe_should_apply_preauth_throttle_in(shared.as_ref(), probe, start + Duration::from_millis(1));
shared.as_ref(),
probe,
start + Duration::from_millis(1),
);
} }
#[test] #[test]
@ -117,8 +102,7 @@ fn light_fuzz_scan_offset_stays_within_window_for_randomized_inputs() {
let scan_limit = ((seed >> 40) as usize % 1024).saturating_add(1); let scan_limit = ((seed >> 40) as usize % 1024).saturating_add(1);
let now = base + Duration::from_nanos(seed & 0x1fff); let now = base + Duration::from_nanos(seed & 0x1fff);
let offset = let offset = auth_probe_scan_start_offset_in(shared.as_ref(), ip, now, state_len, scan_limit);
auth_probe_scan_start_offset_in(shared.as_ref(), ip, now, state_len, scan_limit);
assert!( assert!(
offset < state_len, offset < state_len,
"scan offset must always remain inside state length" "scan offset must always remain inside state length"

View File

@ -116,14 +116,8 @@ async fn handshake_baseline_auth_probe_streak_increments_per_ip() {
) )
.await; .await;
assert!(matches!(res, HandshakeResult::BadClient { .. })); assert!(matches!(res, HandshakeResult::BadClient { .. }));
assert_eq!( assert_eq!(auth_probe_fail_streak_for_testing_in_shared(shared.as_ref(), peer.ip()), Some(expected));
auth_probe_fail_streak_for_testing_in_shared(shared.as_ref(), peer.ip()), assert_eq!(auth_probe_fail_streak_for_testing_in_shared(shared.as_ref(), untouched_ip), None);
Some(expected)
);
assert_eq!(
auth_probe_fail_streak_for_testing_in_shared(shared.as_ref(), untouched_ip),
None
);
} }
} }
@ -155,8 +149,7 @@ fn handshake_baseline_repeated_probes_streak_monotonic() {
for _ in 0..100 { for _ in 0..100 {
auth_probe_record_failure_in(shared.as_ref(), ip, now); auth_probe_record_failure_in(shared.as_ref(), ip, now);
let current = let current = auth_probe_fail_streak_for_testing_in_shared(shared.as_ref(), ip).unwrap_or(0);
auth_probe_fail_streak_for_testing_in_shared(shared.as_ref(), ip).unwrap_or(0);
assert!(current >= prev, "streak must be monotonic"); assert!(current >= prev, "streak must be monotonic");
prev = current; prev = current;
} }
@ -180,16 +173,8 @@ fn handshake_baseline_throttled_ip_incurs_backoff_delay() {
let before_expiry = now + delay.saturating_sub(Duration::from_millis(1)); let before_expiry = now + delay.saturating_sub(Duration::from_millis(1));
let after_expiry = now + delay + Duration::from_millis(1); let after_expiry = now + delay + Duration::from_millis(1);
assert!(auth_probe_is_throttled_in( assert!(auth_probe_is_throttled_in(shared.as_ref(), ip, before_expiry));
shared.as_ref(), assert!(!auth_probe_is_throttled_in(shared.as_ref(), ip, after_expiry));
ip,
before_expiry
));
assert!(!auth_probe_is_throttled_in(
shared.as_ref(),
ip,
after_expiry
));
} }
#[tokio::test] #[tokio::test]
@ -227,10 +212,7 @@ async fn handshake_baseline_malformed_probe_frames_fail_closed_to_masking() {
.expect("malformed probe handling must complete in bounded time"); .expect("malformed probe handling must complete in bounded time");
assert!( assert!(
matches!( matches!(res, HandshakeResult::BadClient { .. } | HandshakeResult::Error(_)),
res,
HandshakeResult::BadClient { .. } | HandshakeResult::Error(_)
),
"malformed probe must fail closed" "malformed probe must fail closed"
); );
} }

View File

@ -332,13 +332,7 @@ async fn invalid_secret_warning_lock_contention_and_bound() {
b.wait().await; b.wait().await;
for i in 0..iterations_per_task { for i in 0..iterations_per_task {
let user_name = format!("contention_user_{}_{}", t, i); let user_name = format!("contention_user_{}_{}", t, i);
warn_invalid_secret_once_in( warn_invalid_secret_once_in(shared.as_ref(), &user_name, "invalid_hex", ACCESS_SECRET_BYTES, None);
shared.as_ref(),
&user_name,
"invalid_hex",
ACCESS_SECRET_BYTES,
None,
);
} }
})); }));
} }
@ -635,8 +629,7 @@ fn auth_probe_saturation_note_resets_retention_window() {
// This call may return false if backoff has elapsed, but it must not clear // This call may return false if backoff has elapsed, but it must not clear
// the saturation state because `later` refreshed last_seen. // the saturation state because `later` refreshed last_seen.
let _ = let _ = auth_probe_saturation_is_throttled_at_for_testing_in_shared(shared.as_ref(), check_time);
auth_probe_saturation_is_throttled_at_for_testing_in_shared(shared.as_ref(), check_time);
let guard = auth_probe_saturation_state_lock_for_testing_in_shared(shared.as_ref()); let guard = auth_probe_saturation_state_lock_for_testing_in_shared(shared.as_ref());
assert!( assert!(
guard.is_some(), guard.is_some(),

View File

@ -206,12 +206,7 @@ fn auth_probe_eviction_identical_timestamps_keeps_map_bounded() {
} }
let new_ip = IpAddr::V4(Ipv4Addr::new(192, 168, 21, 21)); let new_ip = IpAddr::V4(Ipv4Addr::new(192, 168, 21, 21));
auth_probe_record_failure_with_state_in( auth_probe_record_failure_with_state_in(shared.as_ref(), state, new_ip, same + Duration::from_millis(1));
shared.as_ref(),
state,
new_ip,
same + Duration::from_millis(1),
);
assert_eq!(state.len(), AUTH_PROBE_TRACK_MAX_ENTRIES); assert_eq!(state.len(), AUTH_PROBE_TRACK_MAX_ENTRIES);
assert!(state.contains_key(&new_ip)); assert!(state.contains_key(&new_ip));
@ -330,8 +325,7 @@ async fn saturation_grace_exhaustion_under_concurrency_keeps_peer_throttled() {
final_state.fail_streak final_state.fail_streak
>= AUTH_PROBE_BACKOFF_START_FAILS + AUTH_PROBE_SATURATION_GRACE_FAILS >= AUTH_PROBE_BACKOFF_START_FAILS + AUTH_PROBE_SATURATION_GRACE_FAILS
); );
assert!(auth_probe_should_apply_preauth_throttle_in( assert!(auth_probe_should_apply_preauth_throttle_in(shared.as_ref(),
shared.as_ref(),
peer_ip, peer_ip,
Instant::now() Instant::now()
)); ));

View File

@ -54,9 +54,7 @@ fn clear_auth_probe_state_clears_saturation_even_if_poisoned() {
poison_saturation_mutex(shared.as_ref()); poison_saturation_mutex(shared.as_ref());
auth_probe_note_saturation_in(shared.as_ref(), Instant::now()); auth_probe_note_saturation_in(shared.as_ref(), Instant::now());
assert!(auth_probe_saturation_is_throttled_for_testing_in_shared( assert!(auth_probe_saturation_is_throttled_for_testing_in_shared(shared.as_ref()));
shared.as_ref()
));
clear_auth_probe_state_for_testing_in_shared(shared.as_ref()); clear_auth_probe_state_for_testing_in_shared(shared.as_ref());
assert!( assert!(

View File

@ -1427,13 +1427,7 @@ fn invalid_secret_warning_cache_is_bounded() {
for idx in 0..(WARNED_SECRET_MAX_ENTRIES + 32) { for idx in 0..(WARNED_SECRET_MAX_ENTRIES + 32) {
let user = format!("warned_user_{idx}"); let user = format!("warned_user_{idx}");
warn_invalid_secret_once_in( warn_invalid_secret_once_in(shared.as_ref(), &user, "invalid_length", ACCESS_SECRET_BYTES, Some(idx));
shared.as_ref(),
&user,
"invalid_length",
ACCESS_SECRET_BYTES,
Some(idx),
);
} }
let warned = warned_secrets_for_testing_in_shared(shared.as_ref()); let warned = warned_secrets_for_testing_in_shared(shared.as_ref());
@ -1646,15 +1640,11 @@ fn unknown_sni_warn_cooldown_first_event_is_warn_and_repeated_events_are_info_un
"first unknown SNI event must be eligible for WARN emission" "first unknown SNI event must be eligible for WARN emission"
); );
assert!( assert!(
!should_emit_unknown_sni_warn_for_testing_in_shared( !should_emit_unknown_sni_warn_for_testing_in_shared(shared.as_ref(), now + Duration::from_secs(1)),
shared.as_ref(),
now + Duration::from_secs(1)
),
"events inside cooldown window must be demoted from WARN to INFO" "events inside cooldown window must be demoted from WARN to INFO"
); );
assert!( assert!(
should_emit_unknown_sni_warn_for_testing_in_shared( should_emit_unknown_sni_warn_for_testing_in_shared(shared.as_ref(),
shared.as_ref(),
now + Duration::from_secs(UNKNOWN_SNI_WARN_COOLDOWN_SECS) now + Duration::from_secs(UNKNOWN_SNI_WARN_COOLDOWN_SECS)
), ),
"once cooldown expires, next unknown SNI event must be WARN-eligible again" "once cooldown expires, next unknown SNI event must be WARN-eligible again"
@ -1735,12 +1725,7 @@ fn auth_probe_over_cap_churn_still_tracks_newcomer_after_round_limit() {
} }
let newcomer = IpAddr::V4(Ipv4Addr::new(203, 0, 114, 77)); let newcomer = IpAddr::V4(Ipv4Addr::new(203, 0, 114, 77));
auth_probe_record_failure_with_state_in( auth_probe_record_failure_with_state_in(shared.as_ref(), &state, newcomer, now + Duration::from_secs(1));
shared.as_ref(),
&state,
newcomer,
now + Duration::from_secs(1),
);
assert!( assert!(
state.get(&newcomer).is_some(), state.get(&newcomer).is_some(),
@ -1946,18 +1931,8 @@ fn auth_probe_ipv6_is_bucketed_by_prefix_64() {
let ip_a = IpAddr::V6("2001:db8:abcd:1234:1:2:3:4".parse().unwrap()); let ip_a = IpAddr::V6("2001:db8:abcd:1234:1:2:3:4".parse().unwrap());
let ip_b = IpAddr::V6("2001:db8:abcd:1234:ffff:eeee:dddd:cccc".parse().unwrap()); let ip_b = IpAddr::V6("2001:db8:abcd:1234:ffff:eeee:dddd:cccc".parse().unwrap());
auth_probe_record_failure_with_state_in( auth_probe_record_failure_with_state_in(shared.as_ref(), &state, normalize_auth_probe_ip(ip_a), now);
shared.as_ref(), auth_probe_record_failure_with_state_in(shared.as_ref(), &state, normalize_auth_probe_ip(ip_b), now);
&state,
normalize_auth_probe_ip(ip_a),
now,
);
auth_probe_record_failure_with_state_in(
shared.as_ref(),
&state,
normalize_auth_probe_ip(ip_b),
now,
);
let normalized = normalize_auth_probe_ip(ip_a); let normalized = normalize_auth_probe_ip(ip_a);
assert_eq!( assert_eq!(
@ -1981,18 +1956,8 @@ fn auth_probe_ipv6_different_prefixes_use_distinct_buckets() {
let ip_a = IpAddr::V6("2001:db8:1111:2222:1:2:3:4".parse().unwrap()); let ip_a = IpAddr::V6("2001:db8:1111:2222:1:2:3:4".parse().unwrap());
let ip_b = IpAddr::V6("2001:db8:1111:3333:1:2:3:4".parse().unwrap()); let ip_b = IpAddr::V6("2001:db8:1111:3333:1:2:3:4".parse().unwrap());
auth_probe_record_failure_with_state_in( auth_probe_record_failure_with_state_in(shared.as_ref(), &state, normalize_auth_probe_ip(ip_a), now);
shared.as_ref(), auth_probe_record_failure_with_state_in(shared.as_ref(), &state, normalize_auth_probe_ip(ip_b), now);
&state,
normalize_auth_probe_ip(ip_a),
now,
);
auth_probe_record_failure_with_state_in(
shared.as_ref(),
&state,
normalize_auth_probe_ip(ip_b),
now,
);
assert_eq!( assert_eq!(
state.len(), state.len(),
@ -2105,12 +2070,7 @@ fn auth_probe_round_limited_overcap_eviction_marks_saturation_and_keeps_newcomer
} }
let newcomer = IpAddr::V4(Ipv4Addr::new(203, 0, 113, 40)); let newcomer = IpAddr::V4(Ipv4Addr::new(203, 0, 113, 40));
auth_probe_record_failure_with_state_in( auth_probe_record_failure_with_state_in(shared.as_ref(), &state, newcomer, now + Duration::from_millis(1));
shared.as_ref(),
&state,
newcomer,
now + Duration::from_millis(1),
);
assert!( assert!(
state.get(&newcomer).is_some(), state.get(&newcomer).is_some(),
@ -2121,10 +2081,7 @@ fn auth_probe_round_limited_overcap_eviction_marks_saturation_and_keeps_newcomer
"high fail-streak sentinel must survive round-limited eviction" "high fail-streak sentinel must survive round-limited eviction"
); );
assert!( assert!(
auth_probe_saturation_is_throttled_at_for_testing_in_shared( auth_probe_saturation_is_throttled_at_for_testing_in_shared(shared.as_ref(), now + Duration::from_millis(1)),
shared.as_ref(),
now + Duration::from_millis(1)
),
"round-limited over-cap path must activate saturation throttle marker" "round-limited over-cap path must activate saturation throttle marker"
); );
} }
@ -2206,8 +2163,7 @@ fn stress_auth_probe_overcap_churn_does_not_starve_high_threat_sentinel_bucket()
((step >> 8) & 0xff) as u8, ((step >> 8) & 0xff) as u8,
(step & 0xff) as u8, (step & 0xff) as u8,
)); ));
auth_probe_record_failure_with_state_in( auth_probe_record_failure_with_state_in(shared.as_ref(),
shared.as_ref(),
&state, &state,
newcomer, newcomer,
base_now + Duration::from_millis(step as u64 + 1), base_now + Duration::from_millis(step as u64 + 1),
@ -2270,8 +2226,7 @@ fn light_fuzz_auth_probe_overcap_eviction_prefers_less_threatening_entries() {
((round >> 8) & 0xff) as u8, ((round >> 8) & 0xff) as u8,
(round & 0xff) as u8, (round & 0xff) as u8,
)); ));
auth_probe_record_failure_with_state_in( auth_probe_record_failure_with_state_in(shared.as_ref(),
shared.as_ref(),
&state, &state,
newcomer, newcomer,
now + Duration::from_millis(round as u64 + 1), now + Duration::from_millis(round as u64 + 1),
@ -3150,10 +3105,7 @@ async fn saturation_grace_boundary_still_admits_valid_tls_before_exhaustion() {
matches!(result, HandshakeResult::Success(_)), matches!(result, HandshakeResult::Success(_)),
"valid TLS should still pass while peer remains within saturation grace budget" "valid TLS should still pass while peer remains within saturation grace budget"
); );
assert_eq!( assert_eq!(auth_probe_fail_streak_for_testing_in_shared(shared.as_ref(), peer.ip()), None);
auth_probe_fail_streak_for_testing_in_shared(shared.as_ref(), peer.ip()),
None
);
} }
#[tokio::test] #[tokio::test]
@ -3219,10 +3171,7 @@ async fn saturation_grace_exhaustion_blocks_valid_tls_until_backoff_expires() {
matches!(allowed, HandshakeResult::Success(_)), matches!(allowed, HandshakeResult::Success(_)),
"valid TLS should recover after peer-specific pre-auth backoff has elapsed" "valid TLS should recover after peer-specific pre-auth backoff has elapsed"
); );
assert_eq!( assert_eq!(auth_probe_fail_streak_for_testing_in_shared(shared.as_ref(), peer.ip()), None);
auth_probe_fail_streak_for_testing_in_shared(shared.as_ref(), peer.ip()),
None
);
} }
#[tokio::test] #[tokio::test]

View File

@ -1,6 +1,6 @@
use super::*; use super::*;
use rand::SeedableRng;
use rand::rngs::StdRng; use rand::rngs::StdRng;
use rand::SeedableRng;
fn seeded_rng(seed: u64) -> StdRng { fn seeded_rng(seed: u64) -> StdRng {
StdRng::seed_from_u64(seed) StdRng::seed_from_u64(seed)
@ -57,10 +57,7 @@ fn masking_lognormal_degenerate_floor_eq_ceiling_returns_floor() {
let mut rng = seeded_rng(99); let mut rng = seeded_rng(99);
for _ in 0..100 { for _ in 0..100 {
let val = sample_lognormal_percentile_bounded(1000, 1000, &mut rng); let val = sample_lognormal_percentile_bounded(1000, 1000, &mut rng);
assert_eq!( assert_eq!(val, 1000, "floor == ceiling must always return exactly that value");
val, 1000,
"floor == ceiling must always return exactly that value"
);
} }
} }

View File

@ -7,22 +7,13 @@ fn middle_relay_baseline_public_api_idle_roundtrip_contract() {
clear_relay_idle_pressure_state_for_testing_in_shared(shared.as_ref()); clear_relay_idle_pressure_state_for_testing_in_shared(shared.as_ref());
assert!(mark_relay_idle_candidate_for_testing(shared.as_ref(), 7001)); assert!(mark_relay_idle_candidate_for_testing(shared.as_ref(), 7001));
assert_eq!( assert_eq!(oldest_relay_idle_candidate_for_testing(shared.as_ref()), Some(7001));
oldest_relay_idle_candidate_for_testing(shared.as_ref()),
Some(7001)
);
clear_relay_idle_candidate_for_testing(shared.as_ref(), 7001); clear_relay_idle_candidate_for_testing(shared.as_ref(), 7001);
assert_ne!( assert_ne!(oldest_relay_idle_candidate_for_testing(shared.as_ref()), Some(7001));
oldest_relay_idle_candidate_for_testing(shared.as_ref()),
Some(7001)
);
assert!(mark_relay_idle_candidate_for_testing(shared.as_ref(), 7001)); assert!(mark_relay_idle_candidate_for_testing(shared.as_ref(), 7001));
assert_eq!( assert_eq!(oldest_relay_idle_candidate_for_testing(shared.as_ref()), Some(7001));
oldest_relay_idle_candidate_for_testing(shared.as_ref()),
Some(7001)
);
clear_relay_idle_pressure_state_for_testing_in_shared(shared.as_ref()); clear_relay_idle_pressure_state_for_testing_in_shared(shared.as_ref());
} }
@ -35,12 +26,7 @@ fn middle_relay_baseline_public_api_desync_window_contract() {
let key = 0xDEAD_BEEF_0000_0001u64; let key = 0xDEAD_BEEF_0000_0001u64;
let t0 = Instant::now(); let t0 = Instant::now();
assert!(should_emit_full_desync_for_testing( assert!(should_emit_full_desync_for_testing(shared.as_ref(), key, false, t0));
shared.as_ref(),
key,
false,
t0
));
assert!(!should_emit_full_desync_for_testing( assert!(!should_emit_full_desync_for_testing(
shared.as_ref(), shared.as_ref(),
key, key,
@ -49,12 +35,7 @@ fn middle_relay_baseline_public_api_desync_window_contract() {
)); ));
let t1 = t0 + DESYNC_DEDUP_WINDOW + Duration::from_millis(10); let t1 = t0 + DESYNC_DEDUP_WINDOW + Duration::from_millis(10);
assert!(should_emit_full_desync_for_testing( assert!(should_emit_full_desync_for_testing(shared.as_ref(), key, false, t1));
shared.as_ref(),
key,
false,
t1
));
clear_desync_dedup_for_testing_in_shared(shared.as_ref()); clear_desync_dedup_for_testing_in_shared(shared.as_ref());
} }

View File

@ -13,12 +13,7 @@ fn desync_all_full_bypass_does_not_initialize_or_grow_dedup_cache() {
for i in 0..20_000u64 { for i in 0..20_000u64 {
assert!( assert!(
should_emit_full_desync_for_testing( should_emit_full_desync_for_testing(shared.as_ref(), 0xD35E_D000_0000_0000u64 ^ i, true, now),
shared.as_ref(),
0xD35E_D000_0000_0000u64 ^ i,
true,
now
),
"desync_all_full path must always emit" "desync_all_full path must always emit"
); );
} }
@ -42,12 +37,7 @@ fn desync_all_full_bypass_keeps_existing_dedup_entries_unchanged() {
let now = Instant::now(); let now = Instant::now();
for i in 0..2048u64 { for i in 0..2048u64 {
assert!( assert!(
should_emit_full_desync_for_testing( should_emit_full_desync_for_testing(shared.as_ref(), 0xF011_F000_0000_0000u64 ^ i, true, now),
shared.as_ref(),
0xF011_F000_0000_0000u64 ^ i,
true,
now
),
"desync_all_full must bypass suppression and dedup refresh" "desync_all_full must bypass suppression and dedup refresh"
); );
} }
@ -78,8 +68,7 @@ fn edge_all_full_burst_does_not_poison_later_false_path_tracking() {
let now = Instant::now(); let now = Instant::now();
for i in 0..8192u64 { for i in 0..8192u64 {
assert!(should_emit_full_desync_for_testing( assert!(should_emit_full_desync_for_testing(shared.as_ref(),
shared.as_ref(),
0xABCD_0000_0000_0000 ^ i, 0xABCD_0000_0000_0000 ^ i,
true, true,
now now
@ -113,12 +102,7 @@ fn adversarial_mixed_sequence_true_steps_never_change_cache_len() {
let flag_all_full = (seed & 0x1) == 1; let flag_all_full = (seed & 0x1) == 1;
let key = 0x7000_0000_0000_0000u64 ^ i ^ seed; let key = 0x7000_0000_0000_0000u64 ^ i ^ seed;
let before = desync_dedup_len_for_testing(shared.as_ref()); let before = desync_dedup_len_for_testing(shared.as_ref());
let _ = should_emit_full_desync_for_testing( let _ = should_emit_full_desync_for_testing(shared.as_ref(), key, flag_all_full, Instant::now());
shared.as_ref(),
key,
flag_all_full,
Instant::now(),
);
let after = desync_dedup_len_for_testing(shared.as_ref()); let after = desync_dedup_len_for_testing(shared.as_ref());
if flag_all_full { if flag_all_full {
@ -140,12 +124,7 @@ fn light_fuzz_all_full_mode_always_emits_and_stays_bounded() {
seed ^= seed >> 9; seed ^= seed >> 9;
seed ^= seed << 8; seed ^= seed << 8;
let key = seed ^ 0x55AA_55AA_55AA_55AAu64; let key = seed ^ 0x55AA_55AA_55AA_55AAu64;
assert!(should_emit_full_desync_for_testing( assert!(should_emit_full_desync_for_testing(shared.as_ref(), key, true, Instant::now()));
shared.as_ref(),
key,
true,
Instant::now()
));
} }
let after = desync_dedup_len_for_testing(shared.as_ref()); let after = desync_dedup_len_for_testing(shared.as_ref());

View File

@ -366,42 +366,23 @@ fn pressure_evicts_oldest_idle_candidate_with_deterministic_ordering() {
assert!(mark_relay_idle_candidate_for_testing(shared.as_ref(), 10)); assert!(mark_relay_idle_candidate_for_testing(shared.as_ref(), 10));
assert!(mark_relay_idle_candidate_for_testing(shared.as_ref(), 11)); assert!(mark_relay_idle_candidate_for_testing(shared.as_ref(), 11));
assert_eq!( assert_eq!(oldest_relay_idle_candidate_for_testing(shared.as_ref()), Some(10));
oldest_relay_idle_candidate_for_testing(shared.as_ref()),
Some(10)
);
note_relay_pressure_event_for_testing(shared.as_ref()); note_relay_pressure_event_for_testing(shared.as_ref());
let mut seen_for_newer = 0u64; let mut seen_for_newer = 0u64;
assert!( assert!(
!maybe_evict_idle_candidate_on_pressure_for_testing( !maybe_evict_idle_candidate_on_pressure_for_testing(shared.as_ref(), 11, &mut seen_for_newer, &stats),
shared.as_ref(),
11,
&mut seen_for_newer,
&stats
),
"newer idle candidate must not be evicted while older candidate exists" "newer idle candidate must not be evicted while older candidate exists"
); );
assert_eq!( assert_eq!(oldest_relay_idle_candidate_for_testing(shared.as_ref()), Some(10));
oldest_relay_idle_candidate_for_testing(shared.as_ref()),
Some(10)
);
let mut seen_for_oldest = 0u64; let mut seen_for_oldest = 0u64;
assert!( assert!(
maybe_evict_idle_candidate_on_pressure_for_testing( maybe_evict_idle_candidate_on_pressure_for_testing(shared.as_ref(), 10, &mut seen_for_oldest, &stats),
shared.as_ref(),
10,
&mut seen_for_oldest,
&stats
),
"oldest idle candidate must be evicted first under pressure" "oldest idle candidate must be evicted first under pressure"
); );
assert_eq!( assert_eq!(oldest_relay_idle_candidate_for_testing(shared.as_ref()), Some(11));
oldest_relay_idle_candidate_for_testing(shared.as_ref()),
Some(11)
);
assert_eq!(stats.get_relay_pressure_evict_total(), 1); assert_eq!(stats.get_relay_pressure_evict_total(), 1);
clear_relay_idle_pressure_state_for_testing_in_shared(shared.as_ref()); clear_relay_idle_pressure_state_for_testing_in_shared(shared.as_ref());
@ -421,10 +402,7 @@ fn pressure_does_not_evict_without_new_pressure_signal() {
"without new pressure signal, candidate must stay" "without new pressure signal, candidate must stay"
); );
assert_eq!(stats.get_relay_pressure_evict_total(), 0); assert_eq!(stats.get_relay_pressure_evict_total(), 0);
assert_eq!( assert_eq!(oldest_relay_idle_candidate_for_testing(shared.as_ref()), Some(21));
oldest_relay_idle_candidate_for_testing(shared.as_ref()),
Some(21)
);
clear_relay_idle_pressure_state_for_testing_in_shared(shared.as_ref()); clear_relay_idle_pressure_state_for_testing_in_shared(shared.as_ref());
} }
@ -437,10 +415,7 @@ fn stress_pressure_eviction_preserves_fifo_across_many_candidates() {
let mut seen_per_conn = std::collections::HashMap::new(); let mut seen_per_conn = std::collections::HashMap::new();
for conn_id in 1000u64..1064u64 { for conn_id in 1000u64..1064u64 {
assert!(mark_relay_idle_candidate_for_testing( assert!(mark_relay_idle_candidate_for_testing(shared.as_ref(), conn_id));
shared.as_ref(),
conn_id
));
seen_per_conn.insert(conn_id, 0u64); seen_per_conn.insert(conn_id, 0u64);
} }
@ -451,12 +426,7 @@ fn stress_pressure_eviction_preserves_fifo_across_many_candidates() {
.get(&expected) .get(&expected)
.expect("per-conn pressure cursor must exist"); .expect("per-conn pressure cursor must exist");
assert!( assert!(
maybe_evict_idle_candidate_on_pressure_for_testing( maybe_evict_idle_candidate_on_pressure_for_testing(shared.as_ref(), expected, &mut seen, &stats),
shared.as_ref(),
expected,
&mut seen,
&stats
),
"expected conn_id {expected} must be evicted next by deterministic FIFO ordering" "expected conn_id {expected} must be evicted next by deterministic FIFO ordering"
); );
seen_per_conn.insert(expected, seen); seen_per_conn.insert(expected, seen);
@ -466,10 +436,7 @@ fn stress_pressure_eviction_preserves_fifo_across_many_candidates() {
} else { } else {
Some(expected + 1) Some(expected + 1)
}; };
assert_eq!( assert_eq!(oldest_relay_idle_candidate_for_testing(shared.as_ref()), next);
oldest_relay_idle_candidate_for_testing(shared.as_ref()),
next
);
} }
assert_eq!(stats.get_relay_pressure_evict_total(), 64); assert_eq!(stats.get_relay_pressure_evict_total(), 64);
@ -493,24 +460,9 @@ fn blackhat_single_pressure_event_must_not_evict_more_than_one_candidate() {
// Single pressure event should authorize at most one eviction globally. // Single pressure event should authorize at most one eviction globally.
note_relay_pressure_event_for_testing(shared.as_ref()); note_relay_pressure_event_for_testing(shared.as_ref());
let evicted_301 = maybe_evict_idle_candidate_on_pressure_for_testing( let evicted_301 = maybe_evict_idle_candidate_on_pressure_for_testing(shared.as_ref(), 301, &mut seen_301, &stats);
shared.as_ref(), let evicted_302 = maybe_evict_idle_candidate_on_pressure_for_testing(shared.as_ref(), 302, &mut seen_302, &stats);
301, let evicted_303 = maybe_evict_idle_candidate_on_pressure_for_testing(shared.as_ref(), 303, &mut seen_303, &stats);
&mut seen_301,
&stats,
);
let evicted_302 = maybe_evict_idle_candidate_on_pressure_for_testing(
shared.as_ref(),
302,
&mut seen_302,
&stats,
);
let evicted_303 = maybe_evict_idle_candidate_on_pressure_for_testing(
shared.as_ref(),
303,
&mut seen_303,
&stats,
);
let evicted_total = [evicted_301, evicted_302, evicted_303] let evicted_total = [evicted_301, evicted_302, evicted_303]
.iter() .iter()
@ -540,22 +492,12 @@ fn blackhat_pressure_counter_must_track_global_budget_not_per_session_cursor() {
note_relay_pressure_event_for_testing(shared.as_ref()); note_relay_pressure_event_for_testing(shared.as_ref());
assert!( assert!(
maybe_evict_idle_candidate_on_pressure_for_testing( maybe_evict_idle_candidate_on_pressure_for_testing(shared.as_ref(), 401, &mut seen_oldest, &stats),
shared.as_ref(),
401,
&mut seen_oldest,
&stats
),
"oldest candidate must consume pressure budget first" "oldest candidate must consume pressure budget first"
); );
assert!( assert!(
!maybe_evict_idle_candidate_on_pressure_for_testing( !maybe_evict_idle_candidate_on_pressure_for_testing(shared.as_ref(), 402, &mut seen_next, &stats),
shared.as_ref(),
402,
&mut seen_next,
&stats
),
"next candidate must not consume the same pressure budget" "next candidate must not consume the same pressure budget"
); );
@ -580,12 +522,7 @@ fn blackhat_stale_pressure_before_idle_mark_must_not_trigger_eviction() {
let mut seen = 0u64; let mut seen = 0u64;
assert!( assert!(
!maybe_evict_idle_candidate_on_pressure_for_testing( !maybe_evict_idle_candidate_on_pressure_for_testing(shared.as_ref(), 501, &mut seen, &stats),
shared.as_ref(),
501,
&mut seen,
&stats
),
"stale pressure (before soft-idle mark) must not evict newly marked candidate" "stale pressure (before soft-idle mark) must not evict newly marked candidate"
); );
@ -608,24 +545,9 @@ fn blackhat_stale_pressure_must_not_evict_any_of_newly_marked_batch() {
let mut seen_513 = 0u64; let mut seen_513 = 0u64;
let evicted = [ let evicted = [
maybe_evict_idle_candidate_on_pressure_for_testing( maybe_evict_idle_candidate_on_pressure_for_testing(shared.as_ref(), 511, &mut seen_511, &stats),
shared.as_ref(), maybe_evict_idle_candidate_on_pressure_for_testing(shared.as_ref(), 512, &mut seen_512, &stats),
511, maybe_evict_idle_candidate_on_pressure_for_testing(shared.as_ref(), 513, &mut seen_513, &stats),
&mut seen_511,
&stats,
),
maybe_evict_idle_candidate_on_pressure_for_testing(
shared.as_ref(),
512,
&mut seen_512,
&stats,
),
maybe_evict_idle_candidate_on_pressure_for_testing(
shared.as_ref(),
513,
&mut seen_513,
&stats,
),
] ]
.iter() .iter()
.filter(|value| **value) .filter(|value| **value)
@ -650,12 +572,7 @@ fn blackhat_stale_pressure_seen_without_candidates_must_be_globally_invalidated(
// Session A observed pressure while there were no candidates. // Session A observed pressure while there were no candidates.
let mut seen_a = 0u64; let mut seen_a = 0u64;
assert!( assert!(
!maybe_evict_idle_candidate_on_pressure_for_testing( !maybe_evict_idle_candidate_on_pressure_for_testing(shared.as_ref(), 999_001, &mut seen_a, &stats),
shared.as_ref(),
999_001,
&mut seen_a,
&stats
),
"no candidate existed, so no eviction is possible" "no candidate existed, so no eviction is possible"
); );
@ -663,12 +580,7 @@ fn blackhat_stale_pressure_seen_without_candidates_must_be_globally_invalidated(
assert!(mark_relay_idle_candidate_for_testing(shared.as_ref(), 521)); assert!(mark_relay_idle_candidate_for_testing(shared.as_ref(), 521));
let mut seen_b = 0u64; let mut seen_b = 0u64;
assert!( assert!(
!maybe_evict_idle_candidate_on_pressure_for_testing( !maybe_evict_idle_candidate_on_pressure_for_testing(shared.as_ref(), 521, &mut seen_b, &stats),
shared.as_ref(),
521,
&mut seen_b,
&stats
),
"once pressure is observed with empty candidate set, it must not be replayed later" "once pressure is observed with empty candidate set, it must not be replayed later"
); );
@ -688,12 +600,7 @@ fn blackhat_stale_pressure_must_not_survive_candidate_churn() {
let mut seen = 0u64; let mut seen = 0u64;
assert!( assert!(
!maybe_evict_idle_candidate_on_pressure_for_testing( !maybe_evict_idle_candidate_on_pressure_for_testing(shared.as_ref(), 532, &mut seen, &stats),
shared.as_ref(),
532,
&mut seen,
&stats
),
"stale pressure must not survive clear+remark churn cycles" "stale pressure must not survive clear+remark churn cycles"
); );
@ -756,10 +663,7 @@ async fn integration_race_single_pressure_event_allows_at_most_one_eviction_unde
let mut seen_per_session = vec![0u64; sessions]; let mut seen_per_session = vec![0u64; sessions];
for conn_id in &conn_ids { for conn_id in &conn_ids {
assert!(mark_relay_idle_candidate_for_testing( assert!(mark_relay_idle_candidate_for_testing(shared.as_ref(), *conn_id));
shared.as_ref(),
*conn_id
));
} }
for round in 0..rounds { for round in 0..rounds {
@ -772,12 +676,8 @@ async fn integration_race_single_pressure_event_allows_at_most_one_eviction_unde
let stats = stats.clone(); let stats = stats.clone();
let shared = shared.clone(); let shared = shared.clone();
joins.push(tokio::spawn(async move { joins.push(tokio::spawn(async move {
let evicted = maybe_evict_idle_candidate_on_pressure_for_testing( let evicted =
shared.as_ref(), maybe_evict_idle_candidate_on_pressure_for_testing(shared.as_ref(), conn_id, &mut seen, stats.as_ref());
conn_id,
&mut seen,
stats.as_ref(),
);
(idx, conn_id, seen, evicted) (idx, conn_id, seen, evicted)
})); }));
} }
@ -829,10 +729,7 @@ async fn integration_race_burst_pressure_with_churn_preserves_empty_set_invalida
let mut seen_per_session = vec![0u64; sessions]; let mut seen_per_session = vec![0u64; sessions];
for conn_id in &conn_ids { for conn_id in &conn_ids {
assert!(mark_relay_idle_candidate_for_testing( assert!(mark_relay_idle_candidate_for_testing(shared.as_ref(), *conn_id));
shared.as_ref(),
*conn_id
));
} }
let mut expected_total_evictions = 0u64; let mut expected_total_evictions = 0u64;
@ -854,12 +751,8 @@ async fn integration_race_burst_pressure_with_churn_preserves_empty_set_invalida
let stats = stats.clone(); let stats = stats.clone();
let shared = shared.clone(); let shared = shared.clone();
joins.push(tokio::spawn(async move { joins.push(tokio::spawn(async move {
let evicted = maybe_evict_idle_candidate_on_pressure_for_testing( let evicted =
shared.as_ref(), maybe_evict_idle_candidate_on_pressure_for_testing(shared.as_ref(), conn_id, &mut seen, stats.as_ref());
conn_id,
&mut seen,
stats.as_ref(),
);
(idx, conn_id, seen, evicted) (idx, conn_id, seen, evicted)
})); }));
} }
@ -881,10 +774,7 @@ async fn integration_race_burst_pressure_with_churn_preserves_empty_set_invalida
"round {round}: empty candidate phase must not allow stale-pressure eviction" "round {round}: empty candidate phase must not allow stale-pressure eviction"
); );
for conn_id in &conn_ids { for conn_id in &conn_ids {
assert!(mark_relay_idle_candidate_for_testing( assert!(mark_relay_idle_candidate_for_testing(shared.as_ref(), *conn_id));
shared.as_ref(),
*conn_id
));
} }
} else { } else {
assert!( assert!(
@ -893,10 +783,7 @@ async fn integration_race_burst_pressure_with_churn_preserves_empty_set_invalida
); );
if let Some(conn_id) = evicted_conn { if let Some(conn_id) = evicted_conn {
expected_total_evictions = expected_total_evictions.saturating_add(1); expected_total_evictions = expected_total_evictions.saturating_add(1);
assert!(mark_relay_idle_candidate_for_testing( assert!(mark_relay_idle_candidate_for_testing(shared.as_ref(), conn_id));
shared.as_ref(),
conn_id
));
} }
} }
} }

View File

@ -25,10 +25,7 @@ fn blackhat_registry_poison_recovers_with_fail_closed_reset_and_pressure_account
// Helper lock must recover from poison, reset stale state, and continue. // Helper lock must recover from poison, reset stale state, and continue.
assert!(mark_relay_idle_candidate_for_testing(shared.as_ref(), 42)); assert!(mark_relay_idle_candidate_for_testing(shared.as_ref(), 42));
assert_eq!( assert_eq!(oldest_relay_idle_candidate_for_testing(shared.as_ref()), Some(42));
oldest_relay_idle_candidate_for_testing(shared.as_ref()),
Some(42)
);
let before = relay_pressure_event_seq_for_testing(shared.as_ref()); let before = relay_pressure_event_seq_for_testing(shared.as_ref());
note_relay_pressure_event_for_testing(shared.as_ref()); note_relay_pressure_event_for_testing(shared.as_ref());
@ -57,17 +54,11 @@ fn clear_state_helper_must_reset_poisoned_registry_for_deterministic_fifo_tests(
clear_relay_idle_pressure_state_for_testing_in_shared(shared.as_ref()); clear_relay_idle_pressure_state_for_testing_in_shared(shared.as_ref());
assert_eq!( assert_eq!(oldest_relay_idle_candidate_for_testing(shared.as_ref()), None);
oldest_relay_idle_candidate_for_testing(shared.as_ref()),
None
);
assert_eq!(relay_pressure_event_seq_for_testing(shared.as_ref()), 0); assert_eq!(relay_pressure_event_seq_for_testing(shared.as_ref()), 0);
assert!(mark_relay_idle_candidate_for_testing(shared.as_ref(), 7)); assert!(mark_relay_idle_candidate_for_testing(shared.as_ref(), 7));
assert_eq!( assert_eq!(oldest_relay_idle_candidate_for_testing(shared.as_ref()), Some(7));
oldest_relay_idle_candidate_for_testing(shared.as_ref()),
Some(7)
);
clear_relay_idle_pressure_state_for_testing_in_shared(shared.as_ref()); clear_relay_idle_pressure_state_for_testing_in_shared(shared.as_ref());
} }

View File

@ -1,10 +1,10 @@
use crate::proxy::client::handle_client_stream_with_shared;
use crate::proxy::handshake::{ use crate::proxy::handshake::{
auth_probe_fail_streak_for_testing_in_shared, auth_probe_is_throttled_for_testing_in_shared, auth_probe_fail_streak_for_testing_in_shared, auth_probe_is_throttled_for_testing_in_shared,
auth_probe_record_failure_for_testing, clear_auth_probe_state_for_testing_in_shared, auth_probe_record_failure_for_testing, clear_auth_probe_state_for_testing_in_shared,
clear_unknown_sni_warn_state_for_testing_in_shared, clear_warned_secrets_for_testing_in_shared, clear_unknown_sni_warn_state_for_testing_in_shared, clear_warned_secrets_for_testing_in_shared,
should_emit_unknown_sni_warn_for_testing_in_shared, warned_secrets_for_testing_in_shared, should_emit_unknown_sni_warn_for_testing_in_shared, warned_secrets_for_testing_in_shared,
}; };
use crate::proxy::client::handle_client_stream_with_shared;
use crate::proxy::middle_relay::{ use crate::proxy::middle_relay::{
clear_desync_dedup_for_testing_in_shared, clear_relay_idle_candidate_for_testing, clear_desync_dedup_for_testing_in_shared, clear_relay_idle_candidate_for_testing,
clear_relay_idle_pressure_state_for_testing_in_shared, mark_relay_idle_candidate_for_testing, clear_relay_idle_pressure_state_for_testing_in_shared, mark_relay_idle_candidate_for_testing,
@ -81,10 +81,7 @@ fn new_client_harness() -> ClientHarness {
} }
} }
async fn drive_invalid_mtproto_handshake( async fn drive_invalid_mtproto_handshake(shared: Arc<ProxySharedState>, peer: std::net::SocketAddr) {
shared: Arc<ProxySharedState>,
peer: std::net::SocketAddr,
) {
let harness = new_client_harness(); let harness = new_client_harness();
let (server_side, mut client_side) = duplex(4096); let (server_side, mut client_side) = duplex(4096);
let invalid = [0u8; 64]; let invalid = [0u8; 64];
@ -111,10 +108,7 @@ async fn drive_invalid_mtproto_handshake(
.write_all(&invalid) .write_all(&invalid)
.await .await
.expect("failed to write invalid handshake"); .expect("failed to write invalid handshake");
client_side client_side.shutdown().await.expect("failed to shutdown client");
.shutdown()
.await
.expect("failed to shutdown client");
let _ = tokio::time::timeout(Duration::from_secs(3), task) let _ = tokio::time::timeout(Duration::from_secs(3), task)
.await .await
.expect("client task timed out") .expect("client task timed out")
@ -134,10 +128,7 @@ fn proxy_shared_state_two_instances_do_not_share_auth_probe_state() {
auth_probe_fail_streak_for_testing_in_shared(a.as_ref(), ip), auth_probe_fail_streak_for_testing_in_shared(a.as_ref(), ip),
Some(1) Some(1)
); );
assert_eq!( assert_eq!(auth_probe_fail_streak_for_testing_in_shared(b.as_ref(), ip), None);
auth_probe_fail_streak_for_testing_in_shared(b.as_ref(), ip),
None
);
} }
#[test] #[test]
@ -148,18 +139,8 @@ fn proxy_shared_state_two_instances_do_not_share_desync_dedup() {
let now = Instant::now(); let now = Instant::now();
let key = 0xA5A5_u64; let key = 0xA5A5_u64;
assert!(should_emit_full_desync_for_testing( assert!(should_emit_full_desync_for_testing(a.as_ref(), key, false, now));
a.as_ref(), assert!(should_emit_full_desync_for_testing(b.as_ref(), key, false, now));
key,
false,
now
));
assert!(should_emit_full_desync_for_testing(
b.as_ref(),
key,
false,
now
));
} }
#[test] #[test]
@ -169,10 +150,7 @@ fn proxy_shared_state_two_instances_do_not_share_idle_registry() {
clear_relay_idle_pressure_state_for_testing_in_shared(a.as_ref()); clear_relay_idle_pressure_state_for_testing_in_shared(a.as_ref());
assert!(mark_relay_idle_candidate_for_testing(a.as_ref(), 111)); assert!(mark_relay_idle_candidate_for_testing(a.as_ref(), 111));
assert_eq!( assert_eq!(oldest_relay_idle_candidate_for_testing(a.as_ref()), Some(111));
oldest_relay_idle_candidate_for_testing(a.as_ref()),
Some(111)
);
assert_eq!(oldest_relay_idle_candidate_for_testing(b.as_ref()), None); assert_eq!(oldest_relay_idle_candidate_for_testing(b.as_ref()), None);
} }
@ -190,10 +168,7 @@ fn proxy_shared_state_reset_in_one_instance_does_not_affect_another() {
auth_probe_record_failure_for_testing(b.as_ref(), ip_b, now); auth_probe_record_failure_for_testing(b.as_ref(), ip_b, now);
clear_auth_probe_state_for_testing_in_shared(a.as_ref()); clear_auth_probe_state_for_testing_in_shared(a.as_ref());
assert_eq!( assert_eq!(auth_probe_fail_streak_for_testing_in_shared(a.as_ref(), ip_a), None);
auth_probe_fail_streak_for_testing_in_shared(a.as_ref(), ip_a),
None
);
assert_eq!( assert_eq!(
auth_probe_fail_streak_for_testing_in_shared(b.as_ref(), ip_b), auth_probe_fail_streak_for_testing_in_shared(b.as_ref(), ip_b),
Some(1) Some(1)
@ -216,14 +191,8 @@ fn proxy_shared_state_parallel_auth_probe_updates_stay_per_instance() {
auth_probe_record_failure_for_testing(b.as_ref(), ip, now + Duration::from_millis(1)); auth_probe_record_failure_for_testing(b.as_ref(), ip, now + Duration::from_millis(1));
} }
assert_eq!( assert_eq!(auth_probe_fail_streak_for_testing_in_shared(a.as_ref(), ip), Some(5));
auth_probe_fail_streak_for_testing_in_shared(a.as_ref(), ip), assert_eq!(auth_probe_fail_streak_for_testing_in_shared(b.as_ref(), ip), Some(3));
Some(5)
);
assert_eq!(
auth_probe_fail_streak_for_testing_in_shared(b.as_ref(), ip),
Some(3)
);
} }
#[tokio::test] #[tokio::test]
@ -348,14 +317,8 @@ fn proxy_shared_state_auth_saturation_does_not_bleed_across_instances() {
auth_probe_record_failure_for_testing(a.as_ref(), ip, future_now); auth_probe_record_failure_for_testing(a.as_ref(), ip, future_now);
} }
assert!(auth_probe_is_throttled_for_testing_in_shared( assert!(auth_probe_is_throttled_for_testing_in_shared(a.as_ref(), ip));
a.as_ref(), assert!(!auth_probe_is_throttled_for_testing_in_shared(b.as_ref(), ip));
ip
));
assert!(!auth_probe_is_throttled_for_testing_in_shared(
b.as_ref(),
ip
));
} }
#[test] #[test]
@ -385,10 +348,7 @@ fn proxy_shared_state_poison_clear_in_one_instance_does_not_affect_other_instanc
clear_auth_probe_state_for_testing_in_shared(a.as_ref()); clear_auth_probe_state_for_testing_in_shared(a.as_ref());
assert_eq!( assert_eq!(auth_probe_fail_streak_for_testing_in_shared(a.as_ref(), ip_a), None);
auth_probe_fail_streak_for_testing_in_shared(a.as_ref(), ip_a),
None
);
assert_eq!( assert_eq!(
auth_probe_fail_streak_for_testing_in_shared(b.as_ref(), ip_b), auth_probe_fail_streak_for_testing_in_shared(b.as_ref(), ip_b),
Some(1), Some(1),
@ -503,10 +463,7 @@ fn proxy_shared_state_warned_secret_clear_in_one_instance_does_not_clear_other()
clear_warned_secrets_for_testing_in_shared(a.as_ref()); clear_warned_secrets_for_testing_in_shared(a.as_ref());
clear_warned_secrets_for_testing_in_shared(b.as_ref()); clear_warned_secrets_for_testing_in_shared(b.as_ref());
let key = ( let key = ("clear-isolation-user".to_string(), "invalid_length".to_string());
"clear-isolation-user".to_string(),
"invalid_length".to_string(),
);
{ {
let warned_a = warned_secrets_for_testing_in_shared(a.as_ref()); let warned_a = warned_secrets_for_testing_in_shared(a.as_ref());
let mut guard_a = warned_a let mut guard_a = warned_a
@ -551,24 +508,14 @@ fn proxy_shared_state_desync_duplicate_suppression_is_instance_scoped() {
let now = Instant::now(); let now = Instant::now();
let key = 0xBEEF_0000_0000_0001u64; let key = 0xBEEF_0000_0000_0001u64;
assert!(should_emit_full_desync_for_testing( assert!(should_emit_full_desync_for_testing(a.as_ref(), key, false, now));
a.as_ref(),
key,
false,
now
));
assert!(!should_emit_full_desync_for_testing( assert!(!should_emit_full_desync_for_testing(
a.as_ref(), a.as_ref(),
key, key,
false, false,
now + Duration::from_millis(1) now + Duration::from_millis(1)
)); ));
assert!(should_emit_full_desync_for_testing( assert!(should_emit_full_desync_for_testing(b.as_ref(), key, false, now));
b.as_ref(),
key,
false,
now
));
} }
#[test] #[test]
@ -580,18 +527,8 @@ fn proxy_shared_state_desync_clear_in_one_instance_does_not_clear_other() {
let now = Instant::now(); let now = Instant::now();
let key = 0xCAFE_0000_0000_0001u64; let key = 0xCAFE_0000_0000_0001u64;
assert!(should_emit_full_desync_for_testing( assert!(should_emit_full_desync_for_testing(a.as_ref(), key, false, now));
a.as_ref(), assert!(should_emit_full_desync_for_testing(b.as_ref(), key, false, now));
key,
false,
now
));
assert!(should_emit_full_desync_for_testing(
b.as_ref(),
key,
false,
now
));
clear_desync_dedup_for_testing_in_shared(a.as_ref()); clear_desync_dedup_for_testing_in_shared(a.as_ref());
@ -621,10 +558,7 @@ fn proxy_shared_state_idle_candidate_clear_in_one_instance_does_not_affect_other
clear_relay_idle_candidate_for_testing(a.as_ref(), 1001); clear_relay_idle_candidate_for_testing(a.as_ref(), 1001);
assert_eq!(oldest_relay_idle_candidate_for_testing(a.as_ref()), None); assert_eq!(oldest_relay_idle_candidate_for_testing(a.as_ref()), None);
assert_eq!( assert_eq!(oldest_relay_idle_candidate_for_testing(b.as_ref()), Some(2002));
oldest_relay_idle_candidate_for_testing(b.as_ref()),
Some(2002)
);
} }
#[test] #[test]

View File

@ -1,17 +1,16 @@
use crate::proxy::handshake::{ use crate::proxy::handshake::{
auth_probe_fail_streak_for_testing_in_shared, auth_probe_record_failure_for_testing, auth_probe_fail_streak_for_testing_in_shared, auth_probe_record_failure_for_testing,
clear_auth_probe_state_for_testing_in_shared, clear_auth_probe_state_for_testing_in_shared, clear_unknown_sni_warn_state_for_testing_in_shared,
clear_unknown_sni_warn_state_for_testing_in_shared,
should_emit_unknown_sni_warn_for_testing_in_shared, should_emit_unknown_sni_warn_for_testing_in_shared,
}; };
use crate::proxy::middle_relay::{ use crate::proxy::middle_relay::{
clear_desync_dedup_for_testing_in_shared, clear_desync_dedup_for_testing_in_shared, clear_relay_idle_pressure_state_for_testing_in_shared,
clear_relay_idle_pressure_state_for_testing_in_shared, mark_relay_idle_candidate_for_testing, mark_relay_idle_candidate_for_testing, oldest_relay_idle_candidate_for_testing,
oldest_relay_idle_candidate_for_testing, should_emit_full_desync_for_testing, should_emit_full_desync_for_testing,
}; };
use crate::proxy::shared_state::ProxySharedState; use crate::proxy::shared_state::ProxySharedState;
use rand::RngExt;
use rand::SeedableRng; use rand::SeedableRng;
use rand::RngExt;
use rand::rngs::StdRng; use rand::rngs::StdRng;
use std::net::{IpAddr, Ipv4Addr}; use std::net::{IpAddr, Ipv4Addr};
use std::sync::Arc; use std::sync::Arc;
@ -100,14 +99,8 @@ async fn proxy_shared_state_dual_instance_same_ip_high_contention_no_counter_ble
handle.await.expect("task join failed"); handle.await.expect("task join failed");
} }
assert_eq!( assert_eq!(auth_probe_fail_streak_for_testing_in_shared(a.as_ref(), ip), Some(64));
auth_probe_fail_streak_for_testing_in_shared(a.as_ref(), ip), assert_eq!(auth_probe_fail_streak_for_testing_in_shared(b.as_ref(), ip), Some(64));
Some(64)
);
assert_eq!(
auth_probe_fail_streak_for_testing_in_shared(b.as_ref(), ip),
Some(64)
);
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 4)] #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
@ -190,7 +183,12 @@ async fn proxy_shared_state_seed_matrix_concurrency_isolation_no_counter_bleed()
clear_auth_probe_state_for_testing_in_shared(shared_a.as_ref()); clear_auth_probe_state_for_testing_in_shared(shared_a.as_ref());
clear_auth_probe_state_for_testing_in_shared(shared_b.as_ref()); clear_auth_probe_state_for_testing_in_shared(shared_b.as_ref());
let ip = IpAddr::V4(Ipv4Addr::new(198, 51, 100, rng.random_range(1_u8..=250_u8))); let ip = IpAddr::V4(Ipv4Addr::new(
198,
51,
100,
rng.random_range(1_u8..=250_u8),
));
let workers = rng.random_range(16_usize..=48_usize); let workers = rng.random_range(16_usize..=48_usize);
let rounds = rng.random_range(4_usize..=10_usize); let rounds = rng.random_range(4_usize..=10_usize);
@ -212,11 +210,7 @@ async fn proxy_shared_state_seed_matrix_concurrency_isolation_no_counter_bleed()
handles.push(tokio::spawn(async move { handles.push(tokio::spawn(async move {
start_a.wait().await; start_a.wait().await;
for _ in 0..a_ops { for _ in 0..a_ops {
auth_probe_record_failure_for_testing( auth_probe_record_failure_for_testing(shared_a.as_ref(), ip, Instant::now());
shared_a.as_ref(),
ip,
Instant::now(),
);
} }
})); }));
@ -225,11 +219,7 @@ async fn proxy_shared_state_seed_matrix_concurrency_isolation_no_counter_bleed()
handles.push(tokio::spawn(async move { handles.push(tokio::spawn(async move {
start_b.wait().await; start_b.wait().await;
for _ in 0..b_ops { for _ in 0..b_ops {
auth_probe_record_failure_for_testing( auth_probe_record_failure_for_testing(shared_b.as_ref(), ip, Instant::now());
shared_b.as_ref(),
ip,
Instant::now(),
);
} }
})); }));
} }

View File

@ -69,10 +69,7 @@ async fn relay_baseline_activity_timeout_fires_after_inactivity() {
.expect("relay must complete after inactivity timeout") .expect("relay must complete after inactivity timeout")
.expect("relay task must not panic"); .expect("relay task must not panic");
assert!( assert!(done.is_ok(), "relay must return Ok(()) after inactivity timeout");
done.is_ok(),
"relay must return Ok(()) after inactivity timeout"
);
} }
#[tokio::test] #[tokio::test]
@ -158,10 +155,7 @@ async fn relay_baseline_bidirectional_bytes_counted_symmetrically() {
.expect("relay task must not panic"); .expect("relay task must not panic");
assert!(done.is_ok()); assert!(done.is_ok());
assert_eq!( assert_eq!(stats.get_user_total_octets(user), (c2s.len() + s2c.len()) as u64);
stats.get_user_total_octets(user),
(c2s.len() + s2c.len()) as u64
);
} }
#[tokio::test] #[tokio::test]
@ -228,10 +222,7 @@ async fn relay_baseline_broken_pipe_midtransfer_returns_error() {
match done { match done {
Err(ProxyError::Io(err)) => { Err(ProxyError::Io(err)) => {
assert!( assert!(
matches!( matches!(err.kind(), io::ErrorKind::BrokenPipe | io::ErrorKind::ConnectionReset),
err.kind(),
io::ErrorKind::BrokenPipe | io::ErrorKind::ConnectionReset
),
"expected BrokenPipe/ConnectionReset, got {:?}", "expected BrokenPipe/ConnectionReset, got {:?}",
err.kind() err.kind()
); );

View File

@ -1,6 +1,6 @@
use crate::config::ProxyConfig; use crate::config::ProxyConfig;
use rand::SeedableRng;
use rand::rngs::StdRng; use rand::rngs::StdRng;
use rand::SeedableRng;
use std::io; use std::io;
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
@ -18,10 +18,7 @@ mod tests {
let arc = Arc::<AtomicUsize>::from_raw(data.cast::<AtomicUsize>()); let arc = Arc::<AtomicUsize>::from_raw(data.cast::<AtomicUsize>());
let cloned = Arc::clone(&arc); let cloned = Arc::clone(&arc);
let _ = Arc::into_raw(arc); let _ = Arc::into_raw(arc);
RawWaker::new( RawWaker::new(Arc::into_raw(cloned).cast::<()>(), &WAKE_COUNTER_WAKER_VTABLE)
Arc::into_raw(cloned).cast::<()>(),
&WAKE_COUNTER_WAKER_VTABLE,
)
} }
unsafe fn wake_counter_wake(data: *const ()) { unsafe fn wake_counter_wake(data: *const ()) {

View File

@ -1593,15 +1593,13 @@ impl Stats {
self.conntrack_delete_success_total.load(Ordering::Relaxed) self.conntrack_delete_success_total.load(Ordering::Relaxed)
} }
pub fn get_conntrack_delete_not_found_total(&self) -> u64 { pub fn get_conntrack_delete_not_found_total(&self) -> u64 {
self.conntrack_delete_not_found_total self.conntrack_delete_not_found_total.load(Ordering::Relaxed)
.load(Ordering::Relaxed)
} }
pub fn get_conntrack_delete_error_total(&self) -> u64 { pub fn get_conntrack_delete_error_total(&self) -> u64 {
self.conntrack_delete_error_total.load(Ordering::Relaxed) self.conntrack_delete_error_total.load(Ordering::Relaxed)
} }
pub fn get_conntrack_close_event_drop_total(&self) -> u64 { pub fn get_conntrack_close_event_drop_total(&self) -> u64 {
self.conntrack_close_event_drop_total self.conntrack_close_event_drop_total.load(Ordering::Relaxed)
.load(Ordering::Relaxed)
} }
pub fn get_me_keepalive_sent(&self) -> u64 { pub fn get_me_keepalive_sent(&self) -> u64 {
self.me_keepalive_sent.load(Ordering::Relaxed) self.me_keepalive_sent.load(Ordering::Relaxed)