mirror of
https://github.com/telemt/telemt.git
synced 2026-07-02 15:51:10 +03:00
Merge pull request #847 from AndreyOsipuk/feat/client-mss-relay
feat(server): client_mss_bulk — fragment only the handshake, restore MSS for bulk data (cuts pps)
This commit is contained in:
@@ -300,6 +300,7 @@ const SERVER_CONFIG_KEYS: &[&str] = &[
|
||||
"listen_unix_sock_perm",
|
||||
"listen_tcp",
|
||||
"client_mss",
|
||||
"client_mss_bulk",
|
||||
"proxy_protocol",
|
||||
"proxy_protocol_header_timeout_ms",
|
||||
"proxy_protocol_trusted_cidrs",
|
||||
|
||||
@@ -1527,6 +1527,15 @@ pub struct ServerConfig {
|
||||
#[serde(default)]
|
||||
pub client_mss: Option<String>,
|
||||
|
||||
/// Client-facing TCP MSS to switch to AFTER the TLS handshake (ServerHello)
|
||||
/// is sent. Lets `client_mss` fragment ONLY the handshake (the DPI-inspected
|
||||
/// part) while the bulk transfer uses normal-size packets — avoids the ~10x
|
||||
/// packets-per-second blowup that triggers anti-DDoS abuse blocks on
|
||||
/// pps-policing hosts. Empty/omitted = keep the handshake MSS for the whole
|
||||
/// connection (previous behavior). Same preset/int grammar as `client_mss`.
|
||||
#[serde(default)]
|
||||
pub client_mss_bulk: Option<String>,
|
||||
|
||||
/// Accept HAProxy PROXY protocol headers on incoming connections.
|
||||
/// When enabled, real client IPs are extracted from PROXY v1/v2 headers.
|
||||
#[serde(default)]
|
||||
@@ -1594,6 +1603,7 @@ impl Default for ServerConfig {
|
||||
listen_unix_sock_perm: None,
|
||||
listen_tcp: None,
|
||||
client_mss: None,
|
||||
client_mss_bulk: None,
|
||||
proxy_protocol: false,
|
||||
proxy_protocol_header_timeout_ms: default_proxy_protocol_header_timeout_ms(),
|
||||
proxy_protocol_trusted_cidrs: default_proxy_protocol_trusted_cidrs(),
|
||||
@@ -2218,6 +2228,11 @@ impl ServerConfig {
|
||||
pub fn client_mss_value(&self) -> std::result::Result<Option<u16>, String> {
|
||||
parse_client_mss(self.client_mss.as_deref())
|
||||
}
|
||||
|
||||
/// Resolves the post-handshake (bulk transfer) client MSS, if configured.
|
||||
pub fn client_mss_bulk_value(&self) -> std::result::Result<Option<u16>, String> {
|
||||
parse_client_mss(self.client_mss_bulk.as_deref())
|
||||
}
|
||||
}
|
||||
|
||||
impl ListenerConfig {
|
||||
|
||||
@@ -1105,6 +1105,12 @@ impl RunningClientHandler {
|
||||
#[cfg(unix)]
|
||||
let raw_fd = self.raw_fd;
|
||||
let rst_on_close = self.rst_on_close;
|
||||
// MSS for the bulk data phase: once the handshake (incl. ServerHello) is
|
||||
// sent, restore a normal MSS so only the handshake stays fragmented by the
|
||||
// low listener `client_mss`. Cuts pps ~10x (anti-DDoS abuse on pps-policing
|
||||
// hosts like FastVPS). None = keep handshake MSS for the whole connection.
|
||||
#[cfg(unix)]
|
||||
let bulk_mss: Option<u16> = self.config.server.client_mss_bulk_value().ok().flatten();
|
||||
|
||||
let outcome = match self.do_handshake().await? {
|
||||
Some(outcome) => outcome,
|
||||
@@ -1118,6 +1124,14 @@ impl RunningClientHandler {
|
||||
if matches!(rst_on_close, crate::config::RstOnCloseMode::Errors) {
|
||||
let _ = crate::transport::socket::clear_linger_fd(raw_fd);
|
||||
}
|
||||
// Handshake (ServerHello) done — raise MSS for bulk transfer.
|
||||
#[cfg(unix)]
|
||||
if let Some(mss) = bulk_mss {
|
||||
if let Err(e) = crate::transport::socket::set_tcp_mss_fd(raw_fd, u32::from(mss))
|
||||
{
|
||||
debug!(error = %e, "Failed to raise bulk MSS; keeping handshake MSS");
|
||||
}
|
||||
}
|
||||
fut.await
|
||||
}
|
||||
HandshakeOutcome::NeedsMasking(fut) => fut.await,
|
||||
|
||||
@@ -125,6 +125,39 @@ pub fn clear_linger_fd(fd: std::os::unix::io::RawFd) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Raise the TCP MSS on an already-accepted connection's fd. Used to fragment
|
||||
/// ONLY the TLS handshake (via a low listener MSS) and then restore a normal MSS
|
||||
/// for the bulk (post-handshake) data phase — cuts packets-per-second ~10x without losing the
|
||||
/// DPI evasion that the fragmented ServerHello provides. No-op safe: errors are
|
||||
/// returned to the caller, which logs and continues with the handshake MSS.
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn set_tcp_mss_fd(fd: std::os::unix::io::RawFd, mss: u32) -> Result<()> {
|
||||
use std::io::Error;
|
||||
let mss = i32::try_from(mss)
|
||||
.map_err(|_| Error::new(std::io::ErrorKind::InvalidInput, "bulk MSS out of range"))?;
|
||||
// Direct setsockopt(TCP_MAXSEG) — same pattern as the TCP_USER_TIMEOUT call
|
||||
// above; avoids socket2 method-name drift across versions.
|
||||
let rc = unsafe {
|
||||
libc::setsockopt(
|
||||
fd,
|
||||
libc::IPPROTO_TCP,
|
||||
libc::TCP_MAXSEG,
|
||||
&mss as *const libc::c_int as *const libc::c_void,
|
||||
std::mem::size_of::<libc::c_int>() as libc::socklen_t,
|
||||
)
|
||||
};
|
||||
if rc != 0 {
|
||||
return Err(Error::last_os_error());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Non-Linux stub: MSS shaping only on Linux (TCP_MAXSEG).
|
||||
#[cfg(all(unix, not(target_os = "linux")))]
|
||||
pub fn set_tcp_mss_fd(_fd: std::os::unix::io::RawFd, _mss: u32) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a new TCP socket for outgoing connections
|
||||
#[allow(dead_code)]
|
||||
pub fn create_outgoing_socket(addr: SocketAddr) -> Result<Socket> {
|
||||
|
||||
Reference in New Issue
Block a user