diff --git a/README.md b/README.md index ae848b4..4ae83c9 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ > > From June 5th, 2026: we are already analyzing the causes of a new wave of "malfunctions" > -> Telegram Clients TLS ClientHello has been banned by JA3 Fingerprint: we are already looking for ways to solve this problem +> Telegram Clients TLS ClientHello has been banned by JA4/JA4+ Fingerprint: we are already looking for ways to solve this problem > > You can try build your client with our Telegram Devlibrary - [tdlib-obf](https://github.com/telemt/tdlib-obf) diff --git a/docs/Config_params/CONFIG_PARAMS.en.md b/docs/Config_params/CONFIG_PARAMS.en.md index c508c87..d51259b 100644 --- a/docs/Config_params/CONFIG_PARAMS.en.md +++ b/docs/Config_params/CONFIG_PARAMS.en.md @@ -1806,6 +1806,7 @@ This document lists all configuration keys accepted by `config.toml`. | [`listen_unix_sock_perm`](#listen_unix_sock_perm) | `String` | — | `✘` | | [`listen_tcp`](#listen_tcp) | `bool` | — (auto) | `✘` | | [`client_mss`](#client_mss) | `String` | `""` | `✘` | +| [`client_mss_bulk`](#client_mss_bulk) | `String` | `""` | `✘` | | [`proxy_protocol`](#proxy_protocol) | `bool` | `false` | `✘` | | [`proxy_protocol_header_timeout_ms`](#proxy_protocol_header_timeout_ms) | `u64` | `500` | `✘` | | [`proxy_protocol_trusted_cidrs`](#proxy_protocol_trusted_cidrs) | `IpNetwork[]` | `[]` | `✘` | @@ -1898,6 +1899,16 @@ This document lists all configuration keys accepted by `config.toml`. [server] client_mss = "tspu" ``` +## client_mss_bulk + - **Constraints / validation**: `String`. Same grammar as [`client_mss`](#client_mss) (empty/omitted, presets `"extreme-low"`/`"tspu"`/`"2in8"`, or a decimal in `88..=4096`). + - **Description**: Optional bulk-phase MSS. When set, the low `client_mss` is applied only while the TLS handshake (including the DPI-inspected ServerHello) is sent; once the connection transitions to relaying, the client socket MSS is raised to `client_mss_bulk` for the bulk data phase. This keeps the anti-DPI handshake fragmentation but restores normal-size packets for payload, cutting outgoing packets-per-second by roughly the `client_mss` segment multiplier (e.g. ~10x with `"tspu"`). Useful on hosts whose abuse detection counts packets-per-second rather than bandwidth. When empty/omitted, the handshake MSS is kept for the whole connection (previous behavior). Linux only; a no-op elsewhere. + - **Example**: + + ```toml + [server] + client_mss = "tspu" + client_mss_bulk = "1400" + ``` ## proxy_protocol - **Constraints / validation**: `bool`. - **Description**: Enables HAProxy PROXY protocol parsing on incoming connections (PROXY v1/v2). When enabled, client source address is taken from the PROXY header. diff --git a/docs/Config_params/CONFIG_PARAMS.ru.md b/docs/Config_params/CONFIG_PARAMS.ru.md index 0be80fe..755e964 100644 --- a/docs/Config_params/CONFIG_PARAMS.ru.md +++ b/docs/Config_params/CONFIG_PARAMS.ru.md @@ -1808,6 +1808,7 @@ | [`listen_unix_sock_perm`](#listen_unix_sock_perm) | `String` | — | `✘` | | [`listen_tcp`](#listen_tcp) | `bool` | — (auto) | `✘` | | [`client_mss`](#client_mss) | `String` | `""` | `✘` | +| [`client_mss_bulk`](#client_mss_bulk) | `String` | `""` | `✘` | | [`proxy_protocol`](#proxy_protocol) | `bool` | `false` | `✘` | | [`proxy_protocol_header_timeout_ms`](#proxy_protocol_header_timeout_ms) | `u64` | `500` | `✘` | | [`proxy_protocol_trusted_cidrs`](#proxy_protocol_trusted_cidrs) | `IpNetwork[]` | `[]` | `✘` | @@ -1900,6 +1901,16 @@ [server] client_mss = "tspu" ``` +## client_mss_bulk + - **Ограничения / валидация**: `String`. Грамматика та же, что у [`client_mss`](#client_mss) (пусто/не задано, пресеты `"extreme-low"`/`"tspu"`/`"2in8"` либо десятичное число в диапазоне `88..=4096`). + - **Описание**: Необязательный MSS для bulk-фазы. Если задан, низкий `client_mss` применяется только на время TLS-handshake (включая инспектируемый DPI ServerHello); как только соединение переходит в фазу relay, MSS клиентского сокета поднимается до `client_mss_bulk` для передачи полезной нагрузки. Так сохраняется anti-DPI фрагментация handshake, но для данных возвращаются пакеты нормального размера — это снижает исходящий packets-per-second примерно во столько раз, каков segment multiplier у `client_mss` (например, ~10x для `"tspu"`). Полезно на хостингах, где abuse-детекция считает packets-per-second, а не полосу. Если пусто/не задано — MSS handshake сохраняется на всё соединение (прежнее поведение). Только Linux; на прочих платформах — no-op. + - **Пример**: + + ```toml + [server] + client_mss = "tspu" + client_mss_bulk = "1400" + ``` ## proxy_protocol - **Ограничения / валидация**: `bool`. - **Описание**: Включает поддержку разбора PROXY protocol от HAProxy (v1/v2) на входящих соединениях. При включении исходный IP клиента берётся из PROXY-заголовка. diff --git a/src/config/load.rs b/src/config/load.rs index a89d42f..568b619 100644 --- a/src/config/load.rs +++ b/src/config/load.rs @@ -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", diff --git a/src/config/types.rs b/src/config/types.rs index 6b95260..a00d12b 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -1527,6 +1527,15 @@ pub struct ServerConfig { #[serde(default)] pub client_mss: Option, + /// 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, + /// 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, 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, String> { + parse_client_mss(self.client_mss_bulk.as_deref()) + } } impl ListenerConfig { diff --git a/src/proxy/client.rs b/src/proxy/client.rs index a180e07..bd30e5b 100644 --- a/src/proxy/client.rs +++ b/src/proxy/client.rs @@ -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 = 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, diff --git a/src/transport/socket.rs b/src/transport/socket.rs index edcd626..2a6e762 100644 --- a/src/transport/socket.rs +++ b/src/transport/socket.rs @@ -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::() 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 {