Add regression coverage for ME routing, D2C padding, synlimit, and MSS bulk validation

Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
Alexey
2026-06-30 13:13:11 +03:00
parent 88d161a5e9
commit 81ae483201
9 changed files with 530 additions and 1 deletions
+34
View File
@@ -292,6 +292,10 @@ mod tests {
.any(|pair| pair[0].as_str() == key && pair[1].as_str() == value)
}
fn has_key(args: &[String], key: &str) -> bool {
args.iter().any(|arg| arg == key)
}
#[test]
fn iptables_rules_use_synfix_order_and_rejects() {
let target = test_rule(Some(IpAddr::V4(Ipv4Addr::new(203, 0, 113, 7))), 443);
@@ -320,4 +324,34 @@ mod tests {
assert!(has_pair(&rules[0], "--hl-lt", "65"));
assert!(has_pair(&rules[0], "-d", "::1"));
}
#[test]
fn iptables_missing_rule_errors_are_cleanup_benign() {
assert!(is_missing_command_or_iptables_rule(
"iptables is not available"
));
assert!(is_missing_command_or_iptables_rule(
"iptables: No chain/target/match by that name."
));
assert!(is_missing_command_or_iptables_rule(
"iptables: Chain TELEMT_SYNLIMIT does not exist."
));
assert!(is_missing_command_or_iptables_rule(
"Couldn't load target `TELEMT_SYNLIMIT': No such file or directory"
));
assert!(!is_missing_command_or_iptables_rule(
"iptables: Permission denied"
));
}
#[test]
fn iptables_wildcard_rule_omits_destination_match() {
let target = test_rule(None, 443);
let rules = iptables_synfix_rule_args(&target, 0, IpTablesFamily::V4);
for rule in rules {
assert!(!has_key(&rule, "-d"));
assert!(has_pair(&rule, "--dport", "443"));
}
}
}
+108
View File
@@ -124,3 +124,111 @@ pub(super) fn test_rule(ip: Option<IpAddr>, port: u16) -> SynLimitRule {
hashlimit_size: 32_768,
}
}
#[cfg(test)]
mod tests {
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use super::*;
use crate::config::ListenerConfig;
fn listener(ip: IpAddr, port: Option<u16>, synlimit: SynLimitMode) -> ListenerConfig {
ListenerConfig {
ip,
port,
client_mss: None,
synlimit,
synlimit_seconds: 60,
synlimit_hitcount: 48,
synlimit_burst: 1,
synlimit_ios_seconds: 1,
synlimit_ios_hitcount: 12,
synlimit_ios_burst: 24,
synlimit_hashlimit_expire_ms: 60_000,
synlimit_hashlimit_size: 32_768,
announce: None,
announce_ip: None,
proxy_protocol: None,
reuse_allow: false,
}
}
#[test]
fn synlimit_targets_deduplicate_and_use_legacy_port_fallback() {
let mut cfg = ProxyConfig::default();
cfg.server.port = 9443;
cfg.server.listeners = vec![
listener(
IpAddr::V4(Ipv4Addr::UNSPECIFIED),
None,
SynLimitMode::Iptables,
),
listener(
IpAddr::V4(Ipv4Addr::UNSPECIFIED),
None,
SynLimitMode::Iptables,
),
];
let targets = synlimit_targets(&cfg);
assert_eq!(targets.iptables_v4.len(), 1);
assert_eq!(targets.iptables_v4[0].ip, None);
assert_eq!(targets.iptables_v4[0].port, 9443);
assert!(targets.iptables_v6.is_empty());
assert!(targets.nft_v4.is_empty());
assert!(targets.nft_v6.is_empty());
}
#[test]
fn synlimit_targets_separate_backends_and_ip_families() {
let mut cfg = ProxyConfig::default();
cfg.server.listeners = vec![
listener(
IpAddr::V4(Ipv4Addr::new(203, 0, 113, 1)),
Some(443),
SynLimitMode::Iptables,
),
listener(
IpAddr::V6(Ipv6Addr::LOCALHOST),
Some(443),
SynLimitMode::Iptables,
),
listener(
IpAddr::V4(Ipv4Addr::new(203, 0, 113, 2)),
Some(444),
SynLimitMode::Nftables,
),
listener(
IpAddr::V6(Ipv6Addr::UNSPECIFIED),
Some(444),
SynLimitMode::Nftables,
),
];
let targets = synlimit_targets(&cfg);
assert_eq!(targets.iptables_v4.len(), 1);
assert_eq!(targets.iptables_v6.len(), 1);
assert_eq!(targets.nft_v4.len(), 1);
assert_eq!(targets.nft_v6.len(), 1);
assert_eq!(
targets.iptables_v4[0].ip,
Some(IpAddr::V4(Ipv4Addr::new(203, 0, 113, 1)))
);
assert_eq!(targets.iptables_v6[0].ip, Some(IpAddr::V6(Ipv6Addr::LOCALHOST)));
assert_eq!(
targets.nft_v4[0].ip,
Some(IpAddr::V4(Ipv4Addr::new(203, 0, 113, 2)))
);
assert_eq!(targets.nft_v6[0].ip, None);
}
#[test]
fn synlimit_rate_arg_uses_native_units_without_fractional_rates() {
assert_eq!(synlimit_rate_arg(1, 12), "12/second");
assert_eq!(synlimit_rate_arg(60, 48), "48/minute");
assert_eq!(synlimit_rate_arg(3600, 121), "121/hour");
assert_eq!(synlimit_rate_arg(86400, 241), "241/day");
}
}
+33
View File
@@ -259,4 +259,37 @@ mod tests {
assert!(script.contains("ip6 saddr limit rate over 12/second burst 24 packets"));
assert!(script.contains("ip6 saddr limit rate over 48/minute burst 1 packets"));
}
#[test]
fn nft_missing_table_errors_are_cleanup_benign() {
assert!(is_missing_command_or_nft_table("nft is not available"));
assert!(is_missing_command_or_nft_table(
"Error: No such file or directory"
));
assert!(!is_missing_command_or_nft_table(
"Error: Operation not permitted"
));
}
#[test]
fn nft_apply_plan_keeps_dual_stack_rules_in_inet_table() {
let v4_rule = test_rule(Some(IpAddr::V4(Ipv4Addr::new(203, 0, 113, 7))), 443);
let v6_rule = test_rule(Some(IpAddr::V6(Ipv6Addr::LOCALHOST)), 443);
let v4_rules = [v4_rule];
let v6_rules = [v6_rule];
let plans = nft_apply_plan(
NftTableFamilies {
inet: false,
ip: false,
ip6: false,
},
&v4_rules,
&v6_rules,
);
assert_eq!(plans.len(), 1);
assert_eq!(plans[0].family.as_str(), "inet");
assert_eq!(plans[0].v4_targets, v4_rules.as_slice());
assert_eq!(plans[0].v6_targets, v6_rules.as_slice());
}
}