From 451227da60404a231eabb9d4bf22ad7f688c7303 Mon Sep 17 00:00:00 2001 From: Alexey <247128645+axkurcom@users.noreply.github.com> Date: Wed, 1 Jul 2026 01:47:14 +0300 Subject: [PATCH] Namespace synlimit netfilter rules per target set Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com> --- src/synlimit_control/iptables.rs | 118 +++++++++++++++++++--------- src/synlimit_control/mod.rs | 91 +++++++++++++++++----- src/synlimit_control/model.rs | 128 +++++++++++++++++++++++++++++++ src/synlimit_control/nftables.rs | 72 +++++++++++------ 4 files changed, 327 insertions(+), 82 deletions(-) diff --git a/src/synlimit_control/iptables.rs b/src/synlimit_control/iptables.rs index a1bf7ce..e39097f 100644 --- a/src/synlimit_control/iptables.rs +++ b/src/synlimit_control/iptables.rs @@ -1,10 +1,8 @@ use std::net::IpAddr; use super::command::run_command; -use super::model::{SynLimitRule, SynLimitTargets, synlimit_rate_arg}; +use super::model::{SynLimitNamespace, SynLimitRule, SynLimitTargets, synlimit_rate_arg}; -const IPTABLES_CHAIN: &str = "TELEMT_SYNLIMIT"; -const IPTABLES_HASHLIMIT_PREFIX: &str = "TMT-SYN"; const IPV4_IOS_PACKET_LENGTH: u16 = 64; const IPV6_IOS_PACKET_LENGTH: u16 = 84; const IOS_TTL_LIMIT: u8 = 65; @@ -38,24 +36,41 @@ impl IpTablesFamily { } } -pub(super) async fn apply_synlimit_rules(targets: &SynLimitTargets) -> Result<(), String> { - apply_rules_for_binary("iptables", &targets.iptables_v4, IpTablesFamily::V4).await?; - apply_rules_for_binary("ip6tables", &targets.iptables_v6, IpTablesFamily::V6).await +pub(super) async fn apply_synlimit_rules( + targets: &SynLimitTargets, + namespace: &SynLimitNamespace, +) -> Result<(), String> { + apply_rules_for_binary( + "iptables", + &targets.iptables_v4, + IpTablesFamily::V4, + namespace, + ) + .await?; + apply_rules_for_binary( + "ip6tables", + &targets.iptables_v6, + IpTablesFamily::V6, + namespace, + ) + .await } async fn apply_rules_for_binary( binary: &str, targets: &[SynLimitRule], family: IpTablesFamily, + namespace: &SynLimitNamespace, ) -> Result<(), String> { if targets.is_empty() { return Ok(()); } - let _ = run_command(binary, &["-t", "filter", "-N", IPTABLES_CHAIN], None).await; - run_command(binary, &["-t", "filter", "-F", IPTABLES_CHAIN], None).await?; + let chain = namespace.iptables_chain.as_str(); + let _ = run_command(binary, &["-t", "filter", "-N", chain], None).await; + run_command(binary, &["-t", "filter", "-F", chain], None).await?; if run_command( binary, - &["-t", "filter", "-C", "INPUT", "-j", IPTABLES_CHAIN], + &["-t", "filter", "-C", "INPUT", "-j", chain], None, ) .await @@ -63,24 +78,19 @@ async fn apply_rules_for_binary( { run_command( binary, - &["-t", "filter", "-I", "INPUT", "1", "-j", IPTABLES_CHAIN], + &["-t", "filter", "-I", "INPUT", "1", "-j", chain], None, ) .await?; } for (idx, target) in targets.iter().enumerate() { - for rule in iptables_synfix_rule_args(target, idx, family) { + for rule in iptables_synfix_rule_args(target, idx, family, namespace) { let refs: Vec<&str> = rule.iter().map(String::as_str).collect(); run_command(binary, &refs, None).await?; } } - run_command( - binary, - &["-t", "filter", "-A", IPTABLES_CHAIN, "-j", "RETURN"], - None, - ) - .await?; + run_command(binary, &["-t", "filter", "-A", chain, "-j", "RETURN"], None).await?; Ok(()) } @@ -89,12 +99,13 @@ fn iptables_synfix_rule_args( target: &SynLimitRule, idx: usize, family: IpTablesFamily, + namespace: &SynLimitNamespace, ) -> Vec> { vec![ - iptables_ios_accept_rule_args(target, idx, family), - iptables_ios_reject_rule_args(target, family), - iptables_generic_accept_rule_args(target, idx, family), - iptables_generic_reject_rule_args(target), + iptables_ios_accept_rule_args(target, idx, family, namespace), + iptables_ios_reject_rule_args(target, family, namespace), + iptables_generic_accept_rule_args(target, idx, family, namespace), + iptables_generic_reject_rule_args(target, namespace), ] } @@ -102,12 +113,15 @@ fn iptables_ios_accept_rule_args( target: &SynLimitRule, idx: usize, family: IpTablesFamily, + namespace: &SynLimitNamespace, ) -> Vec { let hashlimit_name = format!( - "{IPTABLES_HASHLIMIT_PREFIX}-I{}-{idx}", + "{}-I{}-{idx}", + namespace.iptables_hashlimit_prefix, family.hashlimit_tag() ); - let mut args = iptables_base_rule_args(target.ip, target.port); + let mut args = + iptables_base_rule_args(namespace.iptables_chain.as_str(), target.ip, target.port); args.extend(iptables_ios_match_args(family)); args.extend(iptables_hashlimit_args( &hashlimit_name, @@ -121,8 +135,13 @@ fn iptables_ios_accept_rule_args( args } -fn iptables_ios_reject_rule_args(target: &SynLimitRule, family: IpTablesFamily) -> Vec { - let mut args = iptables_base_rule_args(target.ip, target.port); +fn iptables_ios_reject_rule_args( + target: &SynLimitRule, + family: IpTablesFamily, + namespace: &SynLimitNamespace, +) -> Vec { + let mut args = + iptables_base_rule_args(namespace.iptables_chain.as_str(), target.ip, target.port); args.extend(iptables_ios_match_args(family)); args.extend(iptables_reject_args()); args @@ -132,12 +151,15 @@ fn iptables_generic_accept_rule_args( target: &SynLimitRule, idx: usize, family: IpTablesFamily, + namespace: &SynLimitNamespace, ) -> Vec { let hashlimit_name = format!( - "{IPTABLES_HASHLIMIT_PREFIX}-G{}-{idx}", + "{}-G{}-{idx}", + namespace.iptables_hashlimit_prefix, family.hashlimit_tag() ); - let mut args = iptables_base_rule_args(target.ip, target.port); + let mut args = + iptables_base_rule_args(namespace.iptables_chain.as_str(), target.ip, target.port); args.extend(iptables_hashlimit_args( &hashlimit_name, target.generic_seconds, @@ -150,18 +172,22 @@ fn iptables_generic_accept_rule_args( args } -fn iptables_generic_reject_rule_args(target: &SynLimitRule) -> Vec { - let mut args = iptables_base_rule_args(target.ip, target.port); +fn iptables_generic_reject_rule_args( + target: &SynLimitRule, + namespace: &SynLimitNamespace, +) -> Vec { + let mut args = + iptables_base_rule_args(namespace.iptables_chain.as_str(), target.ip, target.port); args.extend(iptables_reject_args()); args } -fn iptables_base_rule_args(ip: Option, port: u16) -> Vec { +fn iptables_base_rule_args(chain: &str, ip: Option, port: u16) -> Vec { let mut args = vec![ "-t".to_string(), "filter".to_string(), "-A".to_string(), - IPTABLES_CHAIN.to_string(), + chain.to_string(), "-p".to_string(), "tcp".to_string(), "--syn".to_string(), @@ -226,13 +252,17 @@ fn iptables_reject_args() -> Vec { ] } -pub(super) async fn clear_rules_for_binary(binary: &str) -> Result { +pub(super) async fn clear_rules_for_binary( + binary: &str, + namespace: &SynLimitNamespace, +) -> Result { let mut errors = Vec::new(); let mut removed = false; + let chain = namespace.iptables_chain.as_str(); for _ in 0..8 { match run_command( binary, - &["-t", "filter", "-D", "INPUT", "-j", IPTABLES_CHAIN], + &["-t", "filter", "-D", "INPUT", "-j", chain], None, ) .await @@ -247,7 +277,7 @@ pub(super) async fn clear_rules_for_binary(binary: &str) -> Result } } } - match run_command(binary, &["-t", "filter", "-F", IPTABLES_CHAIN], None).await { + match run_command(binary, &["-t", "filter", "-F", chain], None).await { Ok(()) => { removed = true; } @@ -256,7 +286,7 @@ pub(super) async fn clear_rules_for_binary(binary: &str) -> Result errors.push(format!("{binary} flush chain failed: {error}")); } } - match run_command(binary, &["-t", "filter", "-X", IPTABLES_CHAIN], None).await { + match run_command(binary, &["-t", "filter", "-X", chain], None).await { Ok(()) => { removed = true; } @@ -296,12 +326,22 @@ mod tests { args.iter().any(|arg| arg == key) } + fn test_namespace() -> SynLimitNamespace { + SynLimitNamespace { + nft_table: "telemt_synlimit_test".to_string(), + iptables_chain: "TMT_SYN_TEST".to_string(), + iptables_hashlimit_prefix: "TMTTEST".to_string(), + } + } + #[test] fn iptables_rules_use_synfix_order_and_rejects() { let target = test_rule(Some(IpAddr::V4(Ipv4Addr::new(203, 0, 113, 7))), 443); - let rules = iptables_synfix_rule_args(&target, 0, IpTablesFamily::V4); + let namespace = test_namespace(); + let rules = iptables_synfix_rule_args(&target, 0, IpTablesFamily::V4, &namespace); assert_eq!(rules.len(), 4); + assert!(has_pair(&rules[0], "-A", "TMT_SYN_TEST")); assert!(has_pair(&rules[0], "--length", "64")); assert!(has_pair(&rules[0], "--ttl-lt", "65")); assert!(has_pair(&rules[0], "--hashlimit-upto", "12/second")); @@ -318,7 +358,8 @@ mod tests { #[test] fn ip6tables_rules_use_ipv6_hoplimit_classifier() { let target = test_rule(Some(IpAddr::V6(Ipv6Addr::LOCALHOST)), 443); - let rules = iptables_synfix_rule_args(&target, 0, IpTablesFamily::V6); + let namespace = test_namespace(); + let rules = iptables_synfix_rule_args(&target, 0, IpTablesFamily::V6, &namespace); assert!(has_pair(&rules[0], "--length", "84")); assert!(has_pair(&rules[0], "--hl-lt", "65")); @@ -347,7 +388,8 @@ mod tests { #[test] fn iptables_wildcard_rule_omits_destination_match() { let target = test_rule(None, 443); - let rules = iptables_synfix_rule_args(&target, 0, IpTablesFamily::V4); + let namespace = test_namespace(); + let rules = iptables_synfix_rule_args(&target, 0, IpTablesFamily::V4, &namespace); for rule in rules { assert!(!has_key(&rule, "-d")); diff --git a/src/synlimit_control/mod.rs b/src/synlimit_control/mod.rs index 5e53ad2..fb6c0c1 100644 --- a/src/synlimit_control/mod.rs +++ b/src/synlimit_control/mod.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use tokio::sync::watch; use tracing::warn; @@ -11,7 +11,9 @@ mod model; mod nftables; use self::command::has_cap_net_admin; -use self::model::synlimit_targets; +use self::model::{SynLimitNamespace, synlimit_namespace, synlimit_targets}; + +static ACTIVE_SYNLIMIT_NAMESPACE: Mutex> = Mutex::new(None); pub(crate) fn spawn_synlimit_controller(config_rx: watch::Receiver>) { if !cfg!(target_os = "linux") { @@ -39,7 +41,34 @@ async fn wait_for_config_channel_close_and_reconcile( } pub(crate) async fn reconcile_synlimit_rules(cfg: &ProxyConfig) { - match clear_synlimit_rules_all_backends().await { + let targets = synlimit_targets(cfg); + let namespace = synlimit_namespace(&targets); + if let Some(previous_namespace) = set_active_synlimit_namespace(namespace.clone()) { + match clear_synlimit_rules_for_namespace(&previous_namespace).await { + Ok(true) => { + warn!("Removed previous SYN limiter namespace before reconcile"); + } + Ok(false) => {} + Err(error) => { + warn!(error = %error, "Failed to clear previous SYN limiter namespace before reconcile"); + } + } + } + + if targets.is_empty() { + return; + } + let Some(namespace) = namespace else { + return; + }; + if !has_cap_net_admin() { + warn!( + "SYN limiter configured but CAP_NET_ADMIN is not available; netfilter rules not applied" + ); + return; + } + + match clear_synlimit_rules_for_namespace(&namespace).await { Ok(true) => { warn!("Removed stale SYN limiter rules left by a previous run before reconcile"); } @@ -49,37 +78,35 @@ pub(crate) async fn reconcile_synlimit_rules(cfg: &ProxyConfig) { } } - let targets = synlimit_targets(cfg); - if targets.is_empty() { - return; - } - if !has_cap_net_admin() { - warn!( - "SYN limiter configured but CAP_NET_ADMIN is not available; netfilter rules not applied" - ); - return; - } - if targets.has_iptables_targets() - && let Err(error) = iptables::apply_synlimit_rules(&targets).await + && let Err(error) = iptables::apply_synlimit_rules(&targets, &namespace).await { warn!(error = %error, "Failed to apply iptables SYN limiter rules"); } if targets.has_nft_targets() - && let Err(error) = nftables::apply_synlimit_rules(&targets).await + && let Err(error) = nftables::apply_synlimit_rules(&targets, &namespace).await { warn!(error = %error, "Failed to apply nftables SYN limiter rules"); } } pub(crate) async fn clear_synlimit_rules_all_backends() -> Result { + let Some(namespace) = take_active_synlimit_namespace() else { + return Ok(false); + }; + clear_synlimit_rules_for_namespace(&namespace).await +} + +async fn clear_synlimit_rules_for_namespace( + namespace: &SynLimitNamespace, +) -> Result { if !has_cap_net_admin() { return Ok(false); } let mut errors = Vec::new(); let mut removed = false; - match nftables::clear_rules_all_families().await { + match nftables::clear_rules_all_families(namespace).await { Ok(value) => { removed |= value; } @@ -87,7 +114,7 @@ pub(crate) async fn clear_synlimit_rules_all_backends() -> Result errors.push(error); } } - match iptables::clear_rules_for_binary("iptables").await { + match iptables::clear_rules_for_binary("iptables", namespace).await { Ok(value) => { removed |= value; } @@ -95,7 +122,7 @@ pub(crate) async fn clear_synlimit_rules_all_backends() -> Result errors.push(error); } } - match iptables::clear_rules_for_binary("ip6tables").await { + match iptables::clear_rules_for_binary("ip6tables", namespace).await { Ok(value) => { removed |= value; } @@ -111,6 +138,32 @@ pub(crate) async fn clear_synlimit_rules_all_backends() -> Result } } +fn set_active_synlimit_namespace(next: Option) -> Option { + match ACTIVE_SYNLIMIT_NAMESPACE.lock() { + Ok(mut active) => { + if *active == next { + None + } else { + std::mem::replace(&mut *active, next) + } + } + Err(error) => { + warn!(error = %error, "Failed to update active SYN limiter namespace"); + None + } + } +} + +fn take_active_synlimit_namespace() -> Option { + match ACTIVE_SYNLIMIT_NAMESPACE.lock() { + Ok(mut active) => active.take(), + Err(error) => { + warn!(error = %error, "Failed to read active SYN limiter namespace"); + None + } + } +} + fn has_synlimit_config(cfg: &ProxyConfig) -> bool { cfg.server .listeners diff --git a/src/synlimit_control/model.rs b/src/synlimit_control/model.rs index ced8ec5..193f1a5 100644 --- a/src/synlimit_control/model.rs +++ b/src/synlimit_control/model.rs @@ -17,6 +17,13 @@ pub(super) struct SynLimitRule { pub(super) hashlimit_size: u32, } +#[derive(Clone, Debug, Eq, PartialEq)] +pub(super) struct SynLimitNamespace { + pub(super) nft_table: String, + pub(super) iptables_chain: String, + pub(super) iptables_hashlimit_prefix: String, +} + #[derive(Default)] pub(super) struct SynLimitTargets { pub(super) iptables_v4: Vec, @@ -42,6 +49,44 @@ impl SynLimitTargets { } } +struct SynLimitNamespaceHasher { + value: u64, +} + +impl SynLimitNamespaceHasher { + const OFFSET: u64 = 0xcbf2_9ce4_8422_2325; + const PRIME: u64 = 0x0000_0100_0000_01b3; + + fn new() -> Self { + Self { + value: Self::OFFSET, + } + } + + fn write(&mut self, bytes: &[u8]) { + for byte in bytes { + self.value ^= u64::from(*byte); + self.value = self.value.wrapping_mul(Self::PRIME); + } + } + + fn write_u8(&mut self, value: u8) { + self.write(&[value]); + } + + fn write_u16(&mut self, value: u16) { + self.write(&value.to_le_bytes()); + } + + fn write_u32(&mut self, value: u32) { + self.write(&value.to_le_bytes()); + } + + fn finish(self) -> u64 { + self.value + } +} + pub(super) fn synlimit_targets(cfg: &ProxyConfig) -> SynLimitTargets { let mut iptables_v4 = BTreeSet::new(); let mut iptables_v6 = BTreeSet::new(); @@ -91,6 +136,64 @@ pub(super) fn synlimit_targets(cfg: &ProxyConfig) -> SynLimitTargets { } } +pub(super) fn synlimit_namespace(targets: &SynLimitTargets) -> Option { + if targets.is_empty() { + return None; + } + + let mut hasher = SynLimitNamespaceHasher::new(); + write_namespace_rule_group(&mut hasher, b"iptables-v4", &targets.iptables_v4); + write_namespace_rule_group(&mut hasher, b"iptables-v6", &targets.iptables_v6); + write_namespace_rule_group(&mut hasher, b"nft-v4", &targets.nft_v4); + write_namespace_rule_group(&mut hasher, b"nft-v6", &targets.nft_v6); + + let suffix = format!("{:016x}", hasher.finish()); + let iptables_suffix = &suffix[..12]; + let hashlimit_suffix = &suffix[..10]; + Some(SynLimitNamespace { + nft_table: format!("telemt_synlimit_{suffix}"), + iptables_chain: format!("TMT_SYN_{iptables_suffix}"), + iptables_hashlimit_prefix: format!("TMT{hashlimit_suffix}"), + }) +} + +fn write_namespace_rule_group( + hasher: &mut SynLimitNamespaceHasher, + group: &[u8], + rules: &[SynLimitRule], +) { + hasher.write(group); + hasher.write_u32(rules.len() as u32); + for rule in rules { + write_namespace_rule(hasher, rule); + } +} + +fn write_namespace_rule(hasher: &mut SynLimitNamespaceHasher, rule: &SynLimitRule) { + match rule.ip { + Some(IpAddr::V4(ip)) => { + hasher.write_u8(4); + hasher.write(&ip.octets()); + } + Some(IpAddr::V6(ip)) => { + hasher.write_u8(6); + hasher.write(&ip.octets()); + } + None => { + hasher.write_u8(0); + } + } + hasher.write_u16(rule.port); + hasher.write_u32(rule.generic_seconds); + hasher.write_u32(rule.generic_hitcount); + hasher.write_u32(rule.generic_burst); + hasher.write_u32(rule.ios_seconds); + hasher.write_u32(rule.ios_hitcount); + hasher.write_u32(rule.ios_burst); + hasher.write_u32(rule.hashlimit_expire_ms); + hasher.write_u32(rule.hashlimit_size); +} + pub(super) fn synlimit_rate_arg(seconds: u32, hitcount: u32) -> String { let seconds = u64::from(seconds.max(1)); let hitcount = u64::from(hitcount.max(1)); @@ -224,6 +327,31 @@ mod tests { assert_eq!(targets.nft_v6[0].ip, None); } + #[test] + fn synlimit_namespace_is_stable_and_changes_by_targets() { + let mut cfg = ProxyConfig::default(); + cfg.server.listeners = vec![listener( + IpAddr::V4(Ipv4Addr::new(203, 0, 113, 1)), + Some(443), + SynLimitMode::Nftables, + )]; + let first = synlimit_namespace(&synlimit_targets(&cfg)) + .expect("configured targets must have a namespace"); + let second = synlimit_namespace(&synlimit_targets(&cfg)) + .expect("configured targets must have a namespace"); + + cfg.server.listeners[0].port = Some(444); + let changed = synlimit_namespace(&synlimit_targets(&cfg)) + .expect("configured targets must have a namespace"); + + assert_eq!(first, second); + assert_ne!(first, changed); + assert!(first.nft_table.starts_with("telemt_synlimit_")); + assert!(first.iptables_chain.starts_with("TMT_SYN_")); + assert!(first.iptables_chain.len() <= 28); + assert!(first.iptables_hashlimit_prefix.starts_with("TMT")); + } + #[test] fn synlimit_rate_arg_uses_native_units_without_fractional_rates() { assert_eq!(synlimit_rate_arg(1, 12), "12/second"); diff --git a/src/synlimit_control/nftables.rs b/src/synlimit_control/nftables.rs index c8f8510..826e328 100644 --- a/src/synlimit_control/nftables.rs +++ b/src/synlimit_control/nftables.rs @@ -1,7 +1,6 @@ use super::command::{run_command, run_command_stdout}; -use super::model::{SynLimitRule, SynLimitTargets, synlimit_rate_arg}; +use super::model::{SynLimitNamespace, SynLimitRule, SynLimitTargets, synlimit_rate_arg}; -const NFT_TABLE: &str = "telemt_synlimit"; const NFT_CHAIN: &str = "input"; const NFT_INPUT_PRIORITY: i16 = -5; const IPV4_IOS_PACKET_LENGTH: u16 = 64; @@ -38,10 +37,13 @@ impl NftFamily { } } -pub(super) async fn apply_synlimit_rules(targets: &SynLimitTargets) -> Result<(), String> { +pub(super) async fn apply_synlimit_rules( + targets: &SynLimitTargets, + namespace: &SynLimitNamespace, +) -> Result<(), String> { let families = detect_nft_table_families().await; for plan in nft_apply_plan(families, &targets.nft_v4, &targets.nft_v6) { - let script = nft_synlimit_script(plan); + let script = nft_synlimit_script(plan, namespace); run_command("nft", &["-f", "-"], Some(script)).await?; } @@ -114,9 +116,13 @@ fn nft_apply_plan<'a>( Vec::new() } -fn nft_synlimit_script(plan: NftApplyPlan<'_>) -> String { +fn nft_synlimit_script(plan: NftApplyPlan<'_>, namespace: &SynLimitNamespace) -> String { let mut script = String::new(); - script.push_str(&format!("table {} {NFT_TABLE} {{\n", plan.family.as_str())); + script.push_str(&format!( + "table {} {} {{\n", + plan.family.as_str(), + namespace.nft_table + )); script.push_str(&format!(" chain {NFT_CHAIN} {{\n")); script.push_str(&format!( " type filter hook input priority {NFT_INPUT_PRIORITY}; policy accept;\n" @@ -186,16 +192,14 @@ fn push_nft_v6_rules(script: &mut String, target: &SynLimitRule, idx: usize) { )); } -pub(super) async fn clear_rules_all_families() -> Result { +pub(super) async fn clear_rules_all_families( + namespace: &SynLimitNamespace, +) -> Result { let mut errors = Vec::new(); let mut removed = false; + let table = namespace.nft_table.as_str(); for family in [NftFamily::Inet, NftFamily::Ip, NftFamily::Ip6] { - match run_command( - "nft", - &["delete", "table", family.as_str(), NFT_TABLE], - None, - ) - .await + match run_command("nft", &["delete", "table", family.as_str(), table], None).await { Ok(()) => { removed = true; @@ -203,8 +207,8 @@ pub(super) async fn clear_rules_all_families() -> Result { Err(error) if is_missing_command_or_nft_table(&error) => {} Err(error) => { errors.push(format!( - "nft delete table {} {NFT_TABLE} failed: {error}", - family.as_str() + "nft delete table {} {table} failed: {error}", + family.as_str(), )); } } @@ -228,15 +232,28 @@ mod tests { use super::*; use crate::synlimit_control::model::test_rule; + fn test_namespace(table: &str) -> SynLimitNamespace { + SynLimitNamespace { + nft_table: table.to_string(), + iptables_chain: "TMT_SYN_TEST".to_string(), + iptables_hashlimit_prefix: "TMTTEST".to_string(), + } + } + #[test] fn nft_script_uses_synfix_v4_rules_and_early_priority() { let rule = test_rule(Some(IpAddr::V4(Ipv4Addr::new(203, 0, 113, 7))), 443); - let script = nft_synlimit_script(NftApplyPlan { - family: NftFamily::Inet, - v4_targets: &[rule], - v6_targets: &[], - }); + let namespace = test_namespace("telemt_synlimit_test_a"); + let script = nft_synlimit_script( + NftApplyPlan { + family: NftFamily::Inet, + v4_targets: &[rule], + v6_targets: &[], + }, + &namespace, + ); + assert!(script.contains("table inet telemt_synlimit_test_a")); assert!(script.contains("type filter hook input priority -5; policy accept;")); assert!(script.contains("ip daddr 203.0.113.7")); assert!(script.contains("meta length 64 ip ttl < 65")); @@ -248,12 +265,17 @@ mod tests { #[test] fn nft_script_uses_ipv6_hoplimit_classifier() { let rule = test_rule(Some(IpAddr::V6(Ipv6Addr::LOCALHOST)), 443); - let script = nft_synlimit_script(NftApplyPlan { - family: NftFamily::Inet, - v4_targets: &[], - v6_targets: &[rule], - }); + let namespace = test_namespace("telemt_synlimit_test_b"); + let script = nft_synlimit_script( + NftApplyPlan { + family: NftFamily::Inet, + v4_targets: &[], + v6_targets: &[rule], + }, + &namespace, + ); + assert!(script.contains("table inet telemt_synlimit_test_b")); assert!(script.contains("ip6 daddr ::1")); assert!(script.contains("meta length 84 ip6 hoplimit < 65")); assert!(script.contains("ip6 saddr limit rate over 12/second burst 24 packets"));