Hardened listener reload + config persistence + SYN Limit startup safety

This commit is contained in:
Alexey
2026-08-22 13:45:57 +03:00
parent 96d467a400
commit 189e10800a
28 changed files with 2749 additions and 1900 deletions
+13 -3
View File
@@ -1,4 +1,4 @@
use std::collections::BTreeSet;
use std::collections::{BTreeMap, BTreeSet};
use std::hash::{DefaultHasher, Hash, Hasher};
use std::path::{Path, PathBuf};
@@ -27,6 +27,8 @@ pub(super) fn preprocess_includes(
base_dir: &Path,
depth: u8,
source_files: &mut BTreeSet<PathBuf>,
source_contents: &mut BTreeMap<PathBuf, String>,
source_overrides: &BTreeMap<PathBuf, String>,
) -> Result<String> {
if depth > 10 {
return Err(ProxyError::Config("Include depth > 10".into()));
@@ -39,15 +41,23 @@ pub(super) fn preprocess_includes(
if let Some(rest) = rest.strip_prefix('=') {
let path_str = rest.trim().trim_matches('"');
let resolved = base_dir.join(path_str);
source_files.insert(normalize_config_path(&resolved));
let included = std::fs::read_to_string(&resolved)
let normalized = normalize_config_path(&resolved);
source_files.insert(normalized.clone());
let included = source_overrides
.get(&normalized)
.cloned()
.map(Ok)
.unwrap_or_else(|| std::fs::read_to_string(&resolved))
.map_err(|e| ProxyError::Config(e.to_string()))?;
source_contents.insert(normalized, included.clone());
let included_dir = resolved.parent().unwrap_or(base_dir);
output.push_str(&preprocess_includes(
&included,
included_dir,
depth + 1,
source_files,
source_contents,
source_overrides,
)?);
output.push('\n');
continue;
+74 -1
View File
@@ -3,7 +3,9 @@ use tracing::warn;
use crate::error::{ProxyError, Result};
use super::super::types::{LoggingConfig, LoggingDestination, NetworkConfig, UpstreamType};
use super::super::types::{
LoggingConfig, LoggingDestination, NetworkConfig, SynLimitMode, UpstreamType,
};
use super::ProxyConfig;
pub(super) fn validate_network_cfg(net: &mut NetworkConfig) -> Result<()> {
@@ -90,6 +92,77 @@ pub(super) fn validate_upstreams(config: &ProxyConfig) -> Result<()> {
Ok(())
}
pub(super) fn validate_listener_runtime_profiles(config: &ProxyConfig) -> Result<()> {
for (index, listener) in config.server.listeners.iter().enumerate() {
let supported = if cfg!(target_os = "linux") {
matches!(
listener.synlimit,
SynLimitMode::Off | SynLimitMode::Iptables | SynLimitMode::Nftables
)
} else if cfg!(target_os = "freebsd") {
matches!(listener.synlimit, SynLimitMode::Off | SynLimitMode::Pf)
} else {
listener.synlimit == SynLimitMode::Off
};
if !supported {
let backend = match listener.synlimit {
SynLimitMode::Off => "off",
SynLimitMode::Iptables => "iptables",
SynLimitMode::Nftables => "nftables",
SynLimitMode::Pf => "pf",
};
let supported = if cfg!(target_os = "linux") {
"off, iptables, nftables"
} else if cfg!(target_os = "freebsd") {
"off, pf"
} else {
"off"
};
return Err(ProxyError::Config(format!(
"server.listeners[{index}].synlimit backend {backend} is unsupported on this platform; supported values: {supported}"
)));
}
}
let Some(bulk_mss) = config
.server
.client_mss_bulk_value()
.map_err(|error| ProxyError::Config(format!("server.client_mss_bulk {error}")))?
else {
return Ok(());
};
if !cfg!(target_os = "linux") {
return Err(ProxyError::Config(
"server.client_mss_bulk is supported only on Linux".to_string(),
));
}
let mut participants = 0usize;
for (index, listener) in config.server.listeners.iter().enumerate() {
let handshake_mss = listener
.effective_client_mss(&config.server)
.map_err(|error| {
ProxyError::Config(format!("server.listeners[{index}].client_mss {error}"))
})?;
let Some(handshake_mss) = handshake_mss else {
continue;
};
participants = participants.saturating_add(1);
if bulk_mss <= handshake_mss {
return Err(ProxyError::Config(format!(
"server.client_mss_bulk ({bulk_mss}) must be greater than the effective handshake MSS ({handshake_mss}) for server.listeners[{index}]"
)));
}
}
if participants == 0 {
return Err(ProxyError::Config(
"server.client_mss_bulk requires an effective client_mss on at least one listener"
.to_string(),
));
}
Ok(())
}
pub(super) fn normalize_upstream_family_policy(config: &mut ProxyConfig) {
for (idx, upstream) in config.upstreams.iter_mut().enumerate() {
if matches!(upstream.ipv4, Some(false)) && upstream.prefer == Some(4) {