mirror of
https://github.com/telemt/telemt.git
synced 2026-11-01 05:12:02 +03:00
Namespace synlimit netfilter rules per target set
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
+72
-19
@@ -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<Option<SynLimitNamespace>> = Mutex::new(None);
|
||||
|
||||
pub(crate) fn spawn_synlimit_controller(config_rx: watch::Receiver<Arc<ProxyConfig>>) {
|
||||
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<bool, String> {
|
||||
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<bool, String> {
|
||||
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<bool, String>
|
||||
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<bool, String>
|
||||
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<bool, String>
|
||||
}
|
||||
}
|
||||
|
||||
fn set_active_synlimit_namespace(next: Option<SynLimitNamespace>) -> Option<SynLimitNamespace> {
|
||||
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<SynLimitNamespace> {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user