mirror of
https://github.com/telemt/telemt.git
synced 2026-06-15 02:00:09 +07:00
SYN Limiter interval and hitcount in Config
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
@@ -54,6 +54,8 @@ const DEFAULT_CONNTRACK_CONTROL_ENABLED: bool = true;
|
|||||||
const DEFAULT_CONNTRACK_PRESSURE_HIGH_WATERMARK_PCT: u8 = 85;
|
const DEFAULT_CONNTRACK_PRESSURE_HIGH_WATERMARK_PCT: u8 = 85;
|
||||||
const DEFAULT_CONNTRACK_PRESSURE_LOW_WATERMARK_PCT: u8 = 70;
|
const DEFAULT_CONNTRACK_PRESSURE_LOW_WATERMARK_PCT: u8 = 70;
|
||||||
const DEFAULT_CONNTRACK_DELETE_BUDGET_PER_SEC: u64 = 4096;
|
const DEFAULT_CONNTRACK_DELETE_BUDGET_PER_SEC: u64 = 4096;
|
||||||
|
const DEFAULT_SYNLIMIT_SECONDS: u32 = 1;
|
||||||
|
const DEFAULT_SYNLIMIT_HITCOUNT: u32 = 1;
|
||||||
const DEFAULT_UPSTREAM_CONNECT_RETRY_ATTEMPTS: u32 = 2;
|
const DEFAULT_UPSTREAM_CONNECT_RETRY_ATTEMPTS: u32 = 2;
|
||||||
const DEFAULT_UPSTREAM_UNHEALTHY_FAIL_THRESHOLD: u32 = 5;
|
const DEFAULT_UPSTREAM_UNHEALTHY_FAIL_THRESHOLD: u32 = 5;
|
||||||
const DEFAULT_UPSTREAM_CONNECT_BUDGET_MS: u64 = 3000;
|
const DEFAULT_UPSTREAM_CONNECT_BUDGET_MS: u64 = 3000;
|
||||||
@@ -243,6 +245,14 @@ pub(crate) fn default_conntrack_delete_budget_per_sec() -> u64 {
|
|||||||
DEFAULT_CONNTRACK_DELETE_BUDGET_PER_SEC
|
DEFAULT_CONNTRACK_DELETE_BUDGET_PER_SEC
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn default_synlimit_seconds() -> u32 {
|
||||||
|
DEFAULT_SYNLIMIT_SECONDS
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn default_synlimit_hitcount() -> u32 {
|
||||||
|
DEFAULT_SYNLIMIT_HITCOUNT
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn default_prefer_4() -> u8 {
|
pub(crate) fn default_prefer_4() -> u8 {
|
||||||
4
|
4
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -347,6 +347,8 @@ const LISTENER_CONFIG_KEYS: &[&str] = &[
|
|||||||
"port",
|
"port",
|
||||||
"client_mss",
|
"client_mss",
|
||||||
"synlimit",
|
"synlimit",
|
||||||
|
"synlimit_seconds",
|
||||||
|
"synlimit_hitcount",
|
||||||
"announce",
|
"announce",
|
||||||
"announce_ip",
|
"announce_ip",
|
||||||
"proxy_protocol",
|
"proxy_protocol",
|
||||||
@@ -1949,6 +1951,16 @@ impl ProxyConfig {
|
|||||||
ProxyError::Config(format!("server.listeners[{idx}].client_mss {error}"))
|
ProxyError::Config(format!("server.listeners[{idx}].client_mss {error}"))
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
if listener.synlimit_seconds == 0 {
|
||||||
|
return Err(ProxyError::Config(format!(
|
||||||
|
"server.listeners[{idx}].synlimit_seconds must be > 0"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if listener.synlimit_hitcount == 0 {
|
||||||
|
return Err(ProxyError::Config(format!(
|
||||||
|
"server.listeners[{idx}].synlimit_hitcount must be > 0"
|
||||||
|
)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.server.accept_permit_timeout_ms > 60_000 {
|
if config.server.accept_permit_timeout_ms > 60_000 {
|
||||||
@@ -2188,6 +2200,8 @@ impl ProxyConfig {
|
|||||||
port: Some(config.server.port),
|
port: Some(config.server.port),
|
||||||
client_mss: None,
|
client_mss: None,
|
||||||
synlimit: SynLimitMode::default(),
|
synlimit: SynLimitMode::default(),
|
||||||
|
synlimit_seconds: default_synlimit_seconds(),
|
||||||
|
synlimit_hitcount: default_synlimit_hitcount(),
|
||||||
announce: None,
|
announce: None,
|
||||||
announce_ip: None,
|
announce_ip: None,
|
||||||
proxy_protocol: None,
|
proxy_protocol: None,
|
||||||
@@ -2202,6 +2216,8 @@ impl ProxyConfig {
|
|||||||
port: Some(config.server.port),
|
port: Some(config.server.port),
|
||||||
client_mss: None,
|
client_mss: None,
|
||||||
synlimit: SynLimitMode::default(),
|
synlimit: SynLimitMode::default(),
|
||||||
|
synlimit_seconds: default_synlimit_seconds(),
|
||||||
|
synlimit_hitcount: default_synlimit_hitcount(),
|
||||||
announce: None,
|
announce: None,
|
||||||
announce_ip: None,
|
announce_ip: None,
|
||||||
proxy_protocol: None,
|
proxy_protocol: None,
|
||||||
|
|||||||
@@ -2176,6 +2176,12 @@ pub struct ListenerConfig {
|
|||||||
/// Per-listener SYN limiter mode.
|
/// Per-listener SYN limiter mode.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub synlimit: SynLimitMode,
|
pub synlimit: SynLimitMode,
|
||||||
|
/// Iptables recent-match interval for the per-listener SYN limiter.
|
||||||
|
#[serde(default = "default_synlimit_seconds")]
|
||||||
|
pub synlimit_seconds: u32,
|
||||||
|
/// Iptables recent-match hit count for the per-listener SYN limiter.
|
||||||
|
#[serde(default = "default_synlimit_hitcount")]
|
||||||
|
pub synlimit_hitcount: u32,
|
||||||
/// IP address or hostname to announce in proxy links.
|
/// IP address or hostname to announce in proxy links.
|
||||||
/// Takes precedence over `announce_ip` if both are set.
|
/// Takes precedence over `announce_ip` if both are set.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|||||||
+21
-61
@@ -6,7 +6,7 @@ use std::sync::Arc;
|
|||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
use tracing::{debug, warn};
|
use tracing::warn;
|
||||||
|
|
||||||
use crate::config::{ProxyConfig, SynLimitMode};
|
use crate::config::{ProxyConfig, SynLimitMode};
|
||||||
|
|
||||||
@@ -19,8 +19,8 @@ const NFT_SET_V6: &str = "telemt_synlimit_v6";
|
|||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct SynLimitTargets {
|
struct SynLimitTargets {
|
||||||
iptables_v4: Vec<(Option<IpAddr>, u16)>,
|
iptables_v4: Vec<(Option<IpAddr>, u16, u32, u32)>,
|
||||||
iptables_v6: Vec<(Option<IpAddr>, u16)>,
|
iptables_v6: Vec<(Option<IpAddr>, u16, u32, u32)>,
|
||||||
nft_v4: Vec<(Option<IpAddr>, u16)>,
|
nft_v4: Vec<(Option<IpAddr>, u16)>,
|
||||||
nft_v6: Vec<(Option<IpAddr>, u16)>,
|
nft_v6: Vec<(Option<IpAddr>, u16)>,
|
||||||
}
|
}
|
||||||
@@ -45,14 +45,6 @@ struct NftApplyPlan<'a> {
|
|||||||
v6_targets: &'a [(Option<IpAddr>, u16)],
|
v6_targets: &'a [(Option<IpAddr>, u16)],
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SynLimitRuleGuard;
|
|
||||||
|
|
||||||
impl Drop for SynLimitRuleGuard {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
clear_synlimit_rules_all_backends_sync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SynLimitTargets {
|
impl SynLimitTargets {
|
||||||
fn is_empty(&self) -> bool {
|
fn is_empty(&self) -> bool {
|
||||||
self.iptables_v4.is_empty()
|
self.iptables_v4.is_empty()
|
||||||
@@ -89,7 +81,6 @@ pub(crate) fn spawn_synlimit_controller(config_rx: watch::Receiver<Arc<ProxyConf
|
|||||||
}
|
}
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let _guard = SynLimitRuleGuard;
|
|
||||||
wait_for_config_channel_close(config_rx).await;
|
wait_for_config_channel_close(config_rx).await;
|
||||||
clear_synlimit_rules_all_backends().await;
|
clear_synlimit_rules_all_backends().await;
|
||||||
});
|
});
|
||||||
@@ -153,13 +144,15 @@ fn synlimit_targets(cfg: &ProxyConfig) -> SynLimitTargets {
|
|||||||
}
|
}
|
||||||
let port = listener.port.unwrap_or(cfg.server.port);
|
let port = listener.port.unwrap_or(cfg.server.port);
|
||||||
let ip = (!listener.ip.is_unspecified()).then_some(listener.ip);
|
let ip = (!listener.ip.is_unspecified()).then_some(listener.ip);
|
||||||
|
let seconds = listener.synlimit_seconds;
|
||||||
|
let hitcount = listener.synlimit_hitcount;
|
||||||
|
|
||||||
match (backend, listener.ip.is_ipv4()) {
|
match (backend, listener.ip.is_ipv4()) {
|
||||||
(SynLimitMode::Iptables, true) => {
|
(SynLimitMode::Iptables, true) => {
|
||||||
iptables_v4.insert((ip, port));
|
iptables_v4.insert((ip, port, seconds, hitcount));
|
||||||
}
|
}
|
||||||
(SynLimitMode::Iptables, false) => {
|
(SynLimitMode::Iptables, false) => {
|
||||||
iptables_v6.insert((ip, port));
|
iptables_v6.insert((ip, port, seconds, hitcount));
|
||||||
}
|
}
|
||||||
(SynLimitMode::Nftables, true) => {
|
(SynLimitMode::Nftables, true) => {
|
||||||
nft_v4.insert((ip, port));
|
nft_v4.insert((ip, port));
|
||||||
@@ -186,7 +179,7 @@ async fn apply_iptables_synlimit_rules(targets: &SynLimitTargets) -> Result<(),
|
|||||||
|
|
||||||
async fn apply_iptables_synlimit_rules_for_binary(
|
async fn apply_iptables_synlimit_rules_for_binary(
|
||||||
binary: &str,
|
binary: &str,
|
||||||
targets: &[(Option<IpAddr>, u16)],
|
targets: &[(Option<IpAddr>, u16, u32, u32)],
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
if targets.is_empty() {
|
if targets.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -213,9 +206,11 @@ async fn apply_iptables_synlimit_rules_for_binary(
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (ip, port) in targets {
|
for (ip, port, seconds, hitcount) in targets {
|
||||||
let drop_args = iptables_synlimit_rule_args(ip, *port, "--rcheck", "DROP");
|
let drop_args =
|
||||||
let accept_args = iptables_synlimit_rule_args(ip, *port, "--set", "ACCEPT");
|
iptables_synlimit_rule_args(ip, *port, *seconds, *hitcount, "--rcheck", "DROP");
|
||||||
|
let accept_args =
|
||||||
|
iptables_synlimit_rule_args(ip, *port, *seconds, *hitcount, "--set", "ACCEPT");
|
||||||
let drop_refs: Vec<&str> = drop_args.iter().map(String::as_str).collect();
|
let drop_refs: Vec<&str> = drop_args.iter().map(String::as_str).collect();
|
||||||
let accept_refs: Vec<&str> = accept_args.iter().map(String::as_str).collect();
|
let accept_refs: Vec<&str> = accept_args.iter().map(String::as_str).collect();
|
||||||
run_command(binary, &drop_refs, None).await?;
|
run_command(binary, &drop_refs, None).await?;
|
||||||
@@ -228,6 +223,8 @@ async fn apply_iptables_synlimit_rules_for_binary(
|
|||||||
fn iptables_synlimit_rule_args(
|
fn iptables_synlimit_rule_args(
|
||||||
ip: &Option<IpAddr>,
|
ip: &Option<IpAddr>,
|
||||||
port: u16,
|
port: u16,
|
||||||
|
seconds: u32,
|
||||||
|
hitcount: u32,
|
||||||
recent_op: &str,
|
recent_op: &str,
|
||||||
verdict: &str,
|
verdict: &str,
|
||||||
) -> Vec<String> {
|
) -> Vec<String> {
|
||||||
@@ -254,7 +251,12 @@ fn iptables_synlimit_rule_args(
|
|||||||
recent_op.to_string(),
|
recent_op.to_string(),
|
||||||
]);
|
]);
|
||||||
if recent_op == "--rcheck" {
|
if recent_op == "--rcheck" {
|
||||||
args.extend(["--seconds".to_string(), "1".to_string()]);
|
args.extend([
|
||||||
|
"--seconds".to_string(),
|
||||||
|
seconds.to_string(),
|
||||||
|
"--hitcount".to_string(),
|
||||||
|
hitcount.to_string(),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
args.extend(["-j".to_string(), verdict.to_string()]);
|
args.extend(["-j".to_string(), verdict.to_string()]);
|
||||||
args
|
args
|
||||||
@@ -507,45 +509,3 @@ fn has_cap_net_admin() -> bool {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_synlimit_rules_all_backends_sync() {
|
|
||||||
run_command_sync("nft", &["delete", "table", "inet", NFT_TABLE]);
|
|
||||||
run_command_sync("nft", &["delete", "table", "ip", NFT_TABLE]);
|
|
||||||
run_command_sync("nft", &["delete", "table", "ip6", NFT_TABLE]);
|
|
||||||
clear_iptables_synlimit_rules_for_binary_sync("iptables");
|
|
||||||
clear_iptables_synlimit_rules_for_binary_sync("ip6tables");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear_iptables_synlimit_rules_for_binary_sync(binary: &str) {
|
|
||||||
if !command_exists(binary) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for _ in 0..8 {
|
|
||||||
if !run_command_sync(
|
|
||||||
binary,
|
|
||||||
&["-t", "filter", "-D", "INPUT", "-j", IPTABLES_CHAIN],
|
|
||||||
) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
run_command_sync(binary, &["-t", "filter", "-F", IPTABLES_CHAIN]);
|
|
||||||
run_command_sync(binary, &["-t", "filter", "-X", IPTABLES_CHAIN]);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_command_sync(binary: &str, args: &[&str]) -> bool {
|
|
||||||
if !command_exists(binary) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
match std::process::Command::new(binary)
|
|
||||||
.args(args)
|
|
||||||
.stdout(std::process::Stdio::null())
|
|
||||||
.stderr(std::process::Stdio::null())
|
|
||||||
.status()
|
|
||||||
{
|
|
||||||
Ok(status) => status.success(),
|
|
||||||
Err(error) => {
|
|
||||||
debug!(binary, error = %error, "SYN limiter cleanup command failed to spawn");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user