mirror of
https://github.com/telemt/telemt.git
synced 2026-06-18 17:08:29 +03:00
MSS Tuning with config
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
@@ -312,6 +312,7 @@ fn listeners_equal(
|
||||
lhs.iter().zip(rhs.iter()).all(|(a, b)| {
|
||||
a.ip == b.ip
|
||||
&& a.port == b.port
|
||||
&& a.client_mss == b.client_mss
|
||||
&& a.announce == b.announce
|
||||
&& a.announce_ip == b.announce_ip
|
||||
&& a.proxy_protocol == b.proxy_protocol
|
||||
@@ -608,6 +609,7 @@ fn warn_non_hot_changes(old: &ProxyConfig, new: &ProxyConfig, non_hot_changed: b
|
||||
|| old.server.listen_addr_ipv4 != new.server.listen_addr_ipv4
|
||||
|| old.server.listen_addr_ipv6 != new.server.listen_addr_ipv6
|
||||
|| old.server.listen_tcp != new.server.listen_tcp
|
||||
|| old.server.client_mss != new.server.client_mss
|
||||
|| old.server.listen_unix_sock != new.server.listen_unix_sock
|
||||
|| old.server.listen_unix_sock_perm != new.server.listen_unix_sock_perm
|
||||
{
|
||||
|
||||
@@ -299,6 +299,7 @@ const SERVER_CONFIG_KEYS: &[&str] = &[
|
||||
"listen_unix_sock",
|
||||
"listen_unix_sock_perm",
|
||||
"listen_tcp",
|
||||
"client_mss",
|
||||
"proxy_protocol",
|
||||
"proxy_protocol_header_timeout_ms",
|
||||
"proxy_protocol_trusted_cidrs",
|
||||
@@ -344,6 +345,7 @@ const CONNTRACK_CONTROL_CONFIG_KEYS: &[&str] = &[
|
||||
const LISTENER_CONFIG_KEYS: &[&str] = &[
|
||||
"ip",
|
||||
"port",
|
||||
"client_mss",
|
||||
"announce",
|
||||
"announce_ip",
|
||||
"proxy_protocol",
|
||||
@@ -1933,6 +1935,18 @@ impl ProxyConfig {
|
||||
));
|
||||
}
|
||||
|
||||
config
|
||||
.server
|
||||
.client_mss_value()
|
||||
.map_err(|error| ProxyError::Config(format!("server.client_mss {error}")))?;
|
||||
for (idx, listener) in config.server.listeners.iter().enumerate() {
|
||||
if listener.client_mss.is_some() {
|
||||
listener.effective_client_mss(&config.server).map_err(|error| {
|
||||
ProxyError::Config(format!("server.listeners[{idx}].client_mss {error}"))
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
if config.server.accept_permit_timeout_ms > 60_000 {
|
||||
return Err(ProxyError::Config(
|
||||
"server.accept_permit_timeout_ms must be within [0, 60000]".to_string(),
|
||||
@@ -2173,6 +2187,7 @@ impl ProxyConfig {
|
||||
config.server.listeners.push(ListenerConfig {
|
||||
ip: ipv4,
|
||||
port: Some(config.server.port),
|
||||
client_mss: None,
|
||||
announce: None,
|
||||
announce_ip: None,
|
||||
proxy_protocol: None,
|
||||
@@ -2185,6 +2200,7 @@ impl ProxyConfig {
|
||||
config.server.listeners.push(ListenerConfig {
|
||||
ip: ipv6,
|
||||
port: Some(config.server.port),
|
||||
client_mss: None,
|
||||
announce: None,
|
||||
announce_ip: None,
|
||||
proxy_protocol: None,
|
||||
@@ -2460,6 +2476,7 @@ mod tests {
|
||||
assert_eq!(cfg.general.update_every, default_update_every());
|
||||
assert_eq!(cfg.server.listen_addr_ipv4, default_listen_addr_ipv4());
|
||||
assert_eq!(cfg.server.listen_addr_ipv6, default_listen_addr_ipv6_opt());
|
||||
assert_eq!(cfg.server.client_mss_value(), Ok(None));
|
||||
assert_eq!(
|
||||
cfg.server.proxy_protocol_trusted_cidrs,
|
||||
default_proxy_protocol_trusted_cidrs()
|
||||
@@ -3787,6 +3804,153 @@ mod tests {
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_mss_presets_and_listener_override_are_resolved() {
|
||||
let toml = r#"
|
||||
[server]
|
||||
client_mss = "tspu"
|
||||
|
||||
[[server.listeners]]
|
||||
ip = "127.0.0.1"
|
||||
port = 1443
|
||||
|
||||
[[server.listeners]]
|
||||
ip = "127.0.0.2"
|
||||
port = 1444
|
||||
client_mss = "2in8"
|
||||
|
||||
[[server.listeners]]
|
||||
ip = "127.0.0.3"
|
||||
port = 1445
|
||||
client_mss = ""
|
||||
|
||||
[[server.listeners]]
|
||||
ip = "127.0.0.4"
|
||||
port = 1446
|
||||
client_mss = "extreme-low"
|
||||
|
||||
[censorship]
|
||||
tls_domain = "example.com"
|
||||
|
||||
[access.users]
|
||||
user = "00000000000000000000000000000000"
|
||||
"#;
|
||||
let dir = std::env::temp_dir();
|
||||
let path = dir.join("telemt_client_mss_valid_test.toml");
|
||||
std::fs::write(&path, toml).unwrap();
|
||||
let cfg = ProxyConfig::load(&path).unwrap();
|
||||
|
||||
assert_eq!(cfg.server.client_mss_value(), Ok(Some(92)));
|
||||
assert_eq!(
|
||||
cfg.server.listeners[0].effective_client_mss(&cfg.server),
|
||||
Ok(Some(92))
|
||||
);
|
||||
assert_eq!(
|
||||
cfg.server.listeners[1].effective_client_mss(&cfg.server),
|
||||
Ok(Some(256))
|
||||
);
|
||||
assert_eq!(
|
||||
cfg.server.listeners[2].effective_client_mss(&cfg.server),
|
||||
Ok(None)
|
||||
);
|
||||
assert_eq!(
|
||||
cfg.server.listeners[3].effective_client_mss(&cfg.server),
|
||||
Ok(Some(88))
|
||||
);
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_mss_custom_value_is_accepted() {
|
||||
let toml = r#"
|
||||
[server]
|
||||
client_mss = "4096"
|
||||
|
||||
[censorship]
|
||||
tls_domain = "example.com"
|
||||
|
||||
[access.users]
|
||||
user = "00000000000000000000000000000000"
|
||||
"#;
|
||||
let dir = std::env::temp_dir();
|
||||
let path = dir.join("telemt_client_mss_custom_valid_test.toml");
|
||||
std::fs::write(&path, toml).unwrap();
|
||||
let cfg = ProxyConfig::load(&path).unwrap();
|
||||
|
||||
assert_eq!(cfg.server.client_mss_value(), Ok(Some(4096)));
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_mss_out_of_range_is_rejected() {
|
||||
for value in ["87", "4097"] {
|
||||
let toml = format!(
|
||||
r#"
|
||||
[server]
|
||||
client_mss = "{value}"
|
||||
|
||||
[censorship]
|
||||
tls_domain = "example.com"
|
||||
|
||||
[access.users]
|
||||
user = "00000000000000000000000000000000"
|
||||
"#
|
||||
);
|
||||
let dir = std::env::temp_dir();
|
||||
let path = dir.join(format!("telemt_client_mss_out_of_range_{value}_test.toml"));
|
||||
std::fs::write(&path, toml).unwrap();
|
||||
let err = ProxyConfig::load(&path).unwrap_err().to_string();
|
||||
|
||||
assert!(err.contains("server.client_mss custom value must be within [88, 4096]"));
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_mss_unquoted_number_is_rejected() {
|
||||
let toml = r#"
|
||||
[server]
|
||||
client_mss = 256
|
||||
|
||||
[censorship]
|
||||
tls_domain = "example.com"
|
||||
|
||||
[access.users]
|
||||
user = "00000000000000000000000000000000"
|
||||
"#;
|
||||
let dir = std::env::temp_dir();
|
||||
let path = dir.join("telemt_client_mss_unquoted_number_test.toml");
|
||||
std::fs::write(&path, toml).unwrap();
|
||||
let err = ProxyConfig::load(&path).unwrap_err().to_string();
|
||||
|
||||
assert!(err.contains("client_mss"));
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn listener_client_mss_invalid_preset_is_rejected() {
|
||||
let toml = r#"
|
||||
[[server.listeners]]
|
||||
ip = "127.0.0.1"
|
||||
port = 1443
|
||||
client_mss = "tiny"
|
||||
|
||||
[censorship]
|
||||
tls_domain = "example.com"
|
||||
|
||||
[access.users]
|
||||
user = "00000000000000000000000000000000"
|
||||
"#;
|
||||
let dir = std::env::temp_dir();
|
||||
let path = dir.join("telemt_listener_client_mss_invalid_test.toml");
|
||||
std::fs::write(&path, toml).unwrap();
|
||||
let err = ProxyConfig::load(&path).unwrap_err().to_string();
|
||||
|
||||
assert!(err.contains("server.listeners[0].client_mss"));
|
||||
assert!(err.contains("must be \"\", extreme-low, tspu, 2in8"));
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api_runtime_edge_cache_ttl_out_of_range_is_rejected() {
|
||||
let toml = r#"
|
||||
|
||||
@@ -1451,6 +1451,11 @@ pub struct ServerConfig {
|
||||
#[serde(default)]
|
||||
pub listen_tcp: Option<bool>,
|
||||
|
||||
/// Client-facing TCP MSS preset or custom value for all TCP listeners.
|
||||
/// Empty string or omitted value keeps the kernel default.
|
||||
#[serde(default)]
|
||||
pub client_mss: Option<String>,
|
||||
|
||||
/// Accept HAProxy PROXY protocol headers on incoming connections.
|
||||
/// When enabled, real client IPs are extracted from PROXY v1/v2 headers.
|
||||
#[serde(default)]
|
||||
@@ -1517,6 +1522,7 @@ impl Default for ServerConfig {
|
||||
listen_unix_sock: None,
|
||||
listen_unix_sock_perm: None,
|
||||
listen_tcp: None,
|
||||
client_mss: None,
|
||||
proxy_protocol: false,
|
||||
proxy_protocol_header_timeout_ms: default_proxy_protocol_header_timeout_ms(),
|
||||
proxy_protocol_trusted_cidrs: default_proxy_protocol_trusted_cidrs(),
|
||||
@@ -2087,6 +2093,10 @@ pub struct ListenerConfig {
|
||||
/// Per-listener TCP port. If omitted, falls back to legacy `server.port`.
|
||||
#[serde(default)]
|
||||
pub port: Option<u16>,
|
||||
/// Per-listener client-facing TCP MSS preset or custom value.
|
||||
/// Empty string disables MSS shaping for this listener.
|
||||
#[serde(default)]
|
||||
pub client_mss: Option<String>,
|
||||
/// IP address or hostname to announce in proxy links.
|
||||
/// Takes precedence over `announce_ip` if both are set.
|
||||
#[serde(default)]
|
||||
@@ -2104,6 +2114,64 @@ pub struct ListenerConfig {
|
||||
pub reuse_allow: bool,
|
||||
}
|
||||
|
||||
/// Client-facing TCP MSS preset for extreme-low fragmentation profiles.
|
||||
pub const CLIENT_MSS_EXTREME_LOW: u16 = 88;
|
||||
/// Client-facing TCP MSS preset matching TSPU-oriented deployments.
|
||||
pub const CLIENT_MSS_TSPU: u16 = 92;
|
||||
/// Client-facing TCP MSS preset for 2-in-8 segment shaping.
|
||||
pub const CLIENT_MSS_2IN8: u16 = 256;
|
||||
/// Minimum accepted custom client-facing TCP MSS value.
|
||||
pub const CLIENT_MSS_MIN: u16 = CLIENT_MSS_EXTREME_LOW;
|
||||
/// Maximum accepted custom client-facing TCP MSS value.
|
||||
pub const CLIENT_MSS_MAX: u16 = 4096;
|
||||
|
||||
impl ServerConfig {
|
||||
/// Resolves the global client-facing TCP MSS setting.
|
||||
pub fn client_mss_value(&self) -> std::result::Result<Option<u16>, String> {
|
||||
parse_client_mss(self.client_mss.as_deref())
|
||||
}
|
||||
}
|
||||
|
||||
impl ListenerConfig {
|
||||
/// Resolves the listener MSS override, falling back to the global server value.
|
||||
pub fn effective_client_mss(
|
||||
&self,
|
||||
server: &ServerConfig,
|
||||
) -> std::result::Result<Option<u16>, String> {
|
||||
match self.client_mss.as_deref() {
|
||||
Some(value) => parse_client_mss(Some(value)),
|
||||
None => server.client_mss_value(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_client_mss(raw: Option<&str>) -> std::result::Result<Option<u16>, String> {
|
||||
let Some(raw) = raw else {
|
||||
return Ok(None);
|
||||
};
|
||||
let value = raw.trim();
|
||||
if value.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
match value.to_ascii_lowercase().as_str() {
|
||||
"extreme-low" => return Ok(Some(CLIENT_MSS_EXTREME_LOW)),
|
||||
"tspu" => return Ok(Some(CLIENT_MSS_TSPU)),
|
||||
"2in8" => return Ok(Some(CLIENT_MSS_2IN8)),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let parsed = value
|
||||
.parse::<u16>()
|
||||
.map_err(|_| "must be \"\", extreme-low, tspu, 2in8, or a decimal value".to_string())?;
|
||||
if !(CLIENT_MSS_MIN..=CLIENT_MSS_MAX).contains(&parsed) {
|
||||
return Err(format!(
|
||||
"custom value must be within [{CLIENT_MSS_MIN}, {CLIENT_MSS_MAX}]"
|
||||
));
|
||||
}
|
||||
Ok(Some(parsed))
|
||||
}
|
||||
|
||||
// ============= ShowLink =============
|
||||
|
||||
/// Controls which users' proxy links are displayed at startup.
|
||||
|
||||
Reference in New Issue
Block a user