ME Frame too large Fixes

Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
Alexey 2026-02-21 02:15:10 +03:00
parent 7da062e448
commit c9a043d8d5
No known key found for this signature in database
12 changed files with 189 additions and 95 deletions

View File

@ -110,6 +110,14 @@ pub(crate) fn default_reconnect_backoff_cap_ms() -> u64 {
30_000 30_000
} }
pub(crate) fn default_crypto_pending_buffer() -> usize {
256 * 1024
}
pub(crate) fn default_max_client_frame() -> usize {
16 * 1024 * 1024
}
// Custom deserializer helpers // Custom deserializer helpers
#[derive(Deserialize)] #[derive(Deserialize)]

View File

@ -172,6 +172,15 @@ pub struct GeneralConfig {
#[serde(default = "default_true")] #[serde(default = "default_true")]
pub me_keepalive_payload_random: bool, pub me_keepalive_payload_random: bool,
/// Max pending ciphertext buffer per client writer (bytes).
/// Controls FakeTLS backpressure vs throughput.
#[serde(default = "default_crypto_pending_buffer")]
pub crypto_pending_buffer: usize,
/// Maximum allowed client MTProto frame size (bytes).
#[serde(default = "default_max_client_frame")]
pub max_client_frame: usize,
/// Enable staggered warmup of extra ME writers. /// Enable staggered warmup of extra ME writers.
#[serde(default = "default_true")] #[serde(default = "default_true")]
pub me_warmup_stagger_enabled: bool, pub me_warmup_stagger_enabled: bool,
@ -251,6 +260,8 @@ impl Default for GeneralConfig {
log_level: LogLevel::Normal, log_level: LogLevel::Normal,
disable_colors: false, disable_colors: false,
links: LinksConfig::default(), links: LinksConfig::default(),
crypto_pending_buffer: default_crypto_pending_buffer(),
max_client_frame: default_max_client_frame(),
} }
} }
} }

View File

@ -1,6 +1,8 @@
//! Protocol constants and datacenter addresses //! Protocol constants and datacenter addresses
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use crate::crypto::SecureRandom;
use std::sync::LazyLock; use std::sync::LazyLock;
// ============= Telegram Datacenters ============= // ============= Telegram Datacenters =============
@ -151,7 +153,18 @@ pub const TLS_RECORD_ALERT: u8 = 0x15;
/// Maximum TLS record size /// Maximum TLS record size
pub const MAX_TLS_RECORD_SIZE: usize = 16384; pub const MAX_TLS_RECORD_SIZE: usize = 16384;
/// Maximum TLS chunk size (with overhead) /// Maximum TLS chunk size (with overhead)
pub const MAX_TLS_CHUNK_SIZE: usize = 16384 + 24; /// RFC 8446 §5.2 allows up to 16384 + 256 bytes of ciphertext
pub const MAX_TLS_CHUNK_SIZE: usize = 16384 + 256;
/// Generate padding length for Secure Intermediate protocol.
/// Total (data + padding) must not be divisible by 4 per MTProto spec.
pub fn secure_padding_len(data_len: usize, rng: &SecureRandom) -> usize {
if data_len % 4 == 0 {
(rng.range(3) + 1) as usize // 1-3
} else {
rng.range(4) as usize // 0-3
}
}
// ============= Timeouts ============= // ============= Timeouts =============

View File

@ -376,13 +376,9 @@ pub fn build_server_hello(
app_data_record.push(TLS_RECORD_APPLICATION); app_data_record.push(TLS_RECORD_APPLICATION);
app_data_record.extend_from_slice(&TLS_VERSION); app_data_record.extend_from_slice(&TLS_VERSION);
app_data_record.extend_from_slice(&(fake_cert_len as u16).to_be_bytes()); app_data_record.extend_from_slice(&(fake_cert_len as u16).to_be_bytes());
if fake_cert_len > 17 { // Fill ApplicationData with fully random bytes of desired length to avoid
app_data_record.extend_from_slice(&fake_cert[..fake_cert_len - 17]); // deterministic DPI fingerprints (fixed inner content type markers).
app_data_record.push(0x16); // inner content type marker
app_data_record.extend_from_slice(&rng.bytes(16)); // AEAD-like tag mimic
} else {
app_data_record.extend_from_slice(&fake_cert); app_data_record.extend_from_slice(&fake_cert);
}
// Combine all records // Combine all records
let mut response = Vec::with_capacity( let mut response = Vec::with_capacity(

View File

@ -178,8 +178,9 @@ async fn do_tg_handshake_static(
let (read_half, write_half) = stream.into_split(); let (read_half, write_half) = stream.into_split();
let max_pending = config.general.crypto_pending_buffer;
Ok(( Ok((
CryptoReader::new(read_half, tg_decryptor), CryptoReader::new(read_half, tg_decryptor),
CryptoWriter::new(write_half, tg_encryptor), CryptoWriter::new(write_half, tg_encryptor, max_pending),
)) ))
} }

View File

@ -264,9 +264,10 @@ where
"MTProto handshake successful" "MTProto handshake successful"
); );
let max_pending = config.general.crypto_pending_buffer;
return HandshakeResult::Success(( return HandshakeResult::Success((
CryptoReader::new(reader, decryptor), CryptoReader::new(reader, decryptor),
CryptoWriter::new(writer, encryptor), CryptoWriter::new(writer, encryptor, max_pending),
success, success,
)); ));
} }

View File

@ -2,12 +2,13 @@ use std::net::SocketAddr;
use std::sync::Arc; use std::sync::Arc;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use tracing::{debug, info, trace}; use tokio::sync::oneshot;
use tracing::{debug, info, trace, warn};
use crate::config::ProxyConfig; use crate::config::ProxyConfig;
use crate::crypto::SecureRandom; use crate::crypto::SecureRandom;
use crate::error::{ProxyError, Result}; use crate::error::{ProxyError, Result};
use crate::protocol::constants::*; use crate::protocol::constants::{*, secure_padding_len};
use crate::proxy::handshake::HandshakeSuccess; use crate::proxy::handshake::HandshakeSuccess;
use crate::stats::Stats; use crate::stats::Stats;
use crate::stream::{BufferPool, CryptoReader, CryptoWriter}; use crate::stream::{BufferPool, CryptoReader, CryptoWriter};
@ -15,11 +16,11 @@ use crate::transport::middle_proxy::{MePool, MeResponse, proto_flags_for_tag};
pub(crate) async fn handle_via_middle_proxy<R, W>( pub(crate) async fn handle_via_middle_proxy<R, W>(
mut crypto_reader: CryptoReader<R>, mut crypto_reader: CryptoReader<R>,
mut crypto_writer: CryptoWriter<W>, crypto_writer: CryptoWriter<W>,
success: HandshakeSuccess, success: HandshakeSuccess,
me_pool: Arc<MePool>, me_pool: Arc<MePool>,
stats: Arc<Stats>, stats: Arc<Stats>,
_config: Arc<ProxyConfig>, config: Arc<ProxyConfig>,
_buffer_pool: Arc<BufferPool>, _buffer_pool: Arc<BufferPool>,
local_addr: SocketAddr, local_addr: SocketAddr,
rng: Arc<SecureRandom>, rng: Arc<SecureRandom>,
@ -41,7 +42,7 @@ where
"Routing via Middle-End" "Routing via Middle-End"
); );
let (conn_id, mut me_rx) = me_pool.registry().register().await; let (conn_id, me_rx) = me_pool.registry().register().await;
stats.increment_user_connects(&user); stats.increment_user_connects(&user);
stats.increment_user_curr_connects(&user); stats.increment_user_curr_connects(&user);
@ -56,10 +57,49 @@ where
let translated_local_addr = me_pool.translate_our_addr(local_addr); let translated_local_addr = me_pool.translate_our_addr(local_addr);
let result: Result<()> = loop { let frame_limit = config.general.max_client_frame;
let (stop_tx, mut stop_rx) = oneshot::channel::<()>();
let mut me_rx_task = me_rx;
let stats_clone = stats.clone();
let rng_clone = rng.clone();
let user_clone = user.clone();
let me_writer = tokio::spawn(async move {
let mut writer = crypto_writer;
loop {
tokio::select! { tokio::select! {
client_frame = read_client_payload(&mut crypto_reader, proto_tag) => { msg = me_rx_task.recv() => {
match client_frame { match msg {
Some(MeResponse::Data { flags, data }) => {
trace!(conn_id, bytes = data.len(), flags, "ME->C data");
stats_clone.add_user_octets_to(&user_clone, data.len() as u64);
write_client_payload(&mut writer, proto_tag, flags, &data, rng_clone.as_ref()).await?;
}
Some(MeResponse::Ack(confirm)) => {
trace!(conn_id, confirm, "ME->C quickack");
write_client_ack(&mut writer, proto_tag, confirm).await?;
}
Some(MeResponse::Close) => {
debug!(conn_id, "ME sent close");
return Ok(());
}
None => {
debug!(conn_id, "ME channel closed");
return Err(ProxyError::Proxy("ME connection lost".into()));
}
}
}
_ = &mut stop_rx => {
debug!(conn_id, "ME writer stop signal");
return Ok(());
}
}
}
});
let mut main_result: Result<()> = Ok(());
loop {
match read_client_payload(&mut crypto_reader, proto_tag, frame_limit, &user).await {
Ok(Some((payload, quickack))) => { Ok(Some((payload, quickack))) => {
trace!(conn_id, bytes = payload.len(), "C->ME frame"); trace!(conn_id, bytes = payload.len(), "C->ME frame");
stats.add_user_octets_from(&user, payload.len() as u64); stats.add_user_octets_from(&user, payload.len() as u64);
@ -70,45 +110,37 @@ where
if payload.len() >= 8 && payload[..8].iter().all(|b| *b == 0) { if payload.len() >= 8 && payload[..8].iter().all(|b| *b == 0) {
flags |= RPC_FLAG_NOT_ENCRYPTED; flags |= RPC_FLAG_NOT_ENCRYPTED;
} }
me_pool.send_proxy_req( if let Err(e) = me_pool.send_proxy_req(
conn_id, conn_id,
success.dc_idx, success.dc_idx,
peer, peer,
translated_local_addr, translated_local_addr,
&payload, &payload,
flags, flags,
).await?; ).await {
main_result = Err(e);
break;
}
} }
Ok(None) => { Ok(None) => {
debug!(conn_id, "Client EOF"); debug!(conn_id, "Client EOF");
let _ = me_pool.send_close(conn_id).await; let _ = me_pool.send_close(conn_id).await;
break Ok(()); break;
}
Err(e) => break Err(e),
}
}
me_msg = me_rx.recv() => {
match me_msg {
Some(MeResponse::Data { flags, data }) => {
trace!(conn_id, bytes = data.len(), flags, "ME->C data");
stats.add_user_octets_to(&user, data.len() as u64);
write_client_payload(&mut crypto_writer, proto_tag, flags, &data, rng.as_ref()).await?;
}
Some(MeResponse::Ack(confirm)) => {
trace!(conn_id, confirm, "ME->C quickack");
write_client_ack(&mut crypto_writer, proto_tag, confirm).await?;
}
Some(MeResponse::Close) => {
debug!(conn_id, "ME sent close");
break Ok(());
}
None => {
debug!(conn_id, "ME channel closed");
break Err(ProxyError::Proxy("ME connection lost".into()));
} }
Err(e) => {
main_result = Err(e);
break;
} }
} }
} }
let _ = stop_tx.send(());
let writer_result = me_writer.await.unwrap_or_else(|e| Err(ProxyError::Proxy(format!("ME writer join error: {e}"))));
let result = match (main_result, writer_result) {
(Ok(()), Ok(())) => Ok(()),
(Err(e), _) => Err(e),
(_, Err(e)) => Err(e),
}; };
debug!(user = %user, conn_id, "ME relay cleanup"); debug!(user = %user, conn_id, "ME relay cleanup");
@ -120,6 +152,8 @@ where
async fn read_client_payload<R>( async fn read_client_payload<R>(
client_reader: &mut CryptoReader<R>, client_reader: &mut CryptoReader<R>,
proto_tag: ProtoTag, proto_tag: ProtoTag,
max_frame: usize,
user: &str,
) -> Result<Option<(Vec<u8>, bool)>> ) -> Result<Option<(Vec<u8>, bool)>>
where where
R: AsyncRead + Unpin + Send + 'static, R: AsyncRead + Unpin + Send + 'static,
@ -162,8 +196,15 @@ where
} }
}; };
if len > 16 * 1024 * 1024 { if len > max_frame {
return Err(ProxyError::Proxy(format!("Frame too large: {len}"))); warn!(
user = %user,
raw_len = len,
raw_len_hex = format_args!("0x{:08x}", len),
proto = ?proto_tag,
"Frame too large — possible crypto desync or TLS record error"
);
return Err(ProxyError::Proxy(format!("Frame too large: {len} (max {max_frame})")));
} }
let mut payload = vec![0u8; len]; let mut payload = vec![0u8; len];
@ -237,7 +278,7 @@ where
} }
ProtoTag::Intermediate | ProtoTag::Secure => { ProtoTag::Intermediate | ProtoTag::Secure => {
let padding_len = if proto_tag == ProtoTag::Secure { let padding_len = if proto_tag == ProtoTag::Secure {
(rng.bytes(1)[0] % 4) as usize secure_padding_len(data.len(), rng)
} else { } else {
0 0
}; };

View File

@ -34,7 +34,7 @@
//! └────────────────────────────────────────┘ //! └────────────────────────────────────────┘
//! //!
//! Backpressure //! Backpressure
//! - pending ciphertext buffer is bounded (MAX_PENDING_WRITE) //! - pending ciphertext buffer is bounded (configurable per connection)
//! - pending is full and upstream is pending //! - pending is full and upstream is pending
//! -> poll_write returns Poll::Pending //! -> poll_write returns Poll::Pending
//! -> do not accept any plaintext //! -> do not accept any plaintext
@ -62,10 +62,9 @@ use super::state::{StreamState, YieldBuffer};
// ============= Constants ============= // ============= Constants =============
/// Maximum size for pending ciphertext buffer (bounded backpressure). /// Default size for pending ciphertext buffer (bounded backpressure).
/// Reduced to 64KB to prevent bufferbloat on mobile networks. /// Actual limit is supplied at runtime from configuration.
/// 512KB was causing high latency on 3G/LTE connections. const DEFAULT_MAX_PENDING_WRITE: usize = 64 * 1024;
const MAX_PENDING_WRITE: usize = 64 * 1024;
/// Default read buffer capacity (reader mostly decrypts in-place into caller buffer). /// Default read buffer capacity (reader mostly decrypts in-place into caller buffer).
const DEFAULT_READ_CAPACITY: usize = 16 * 1024; const DEFAULT_READ_CAPACITY: usize = 16 * 1024;
@ -427,15 +426,22 @@ pub struct CryptoWriter<W> {
encryptor: AesCtr, encryptor: AesCtr,
state: CryptoWriterState, state: CryptoWriterState,
scratch: BytesMut, scratch: BytesMut,
max_pending_write: usize,
} }
impl<W> CryptoWriter<W> { impl<W> CryptoWriter<W> {
pub fn new(upstream: W, encryptor: AesCtr) -> Self { pub fn new(upstream: W, encryptor: AesCtr, max_pending_write: usize) -> Self {
let max_pending = if max_pending_write == 0 {
DEFAULT_MAX_PENDING_WRITE
} else {
max_pending_write
};
Self { Self {
upstream, upstream,
encryptor, encryptor,
state: CryptoWriterState::Idle, state: CryptoWriterState::Idle,
scratch: BytesMut::with_capacity(16 * 1024), scratch: BytesMut::with_capacity(16 * 1024),
max_pending_write: max_pending.max(4 * 1024),
} }
} }
@ -484,10 +490,10 @@ impl<W> CryptoWriter<W> {
} }
/// Ensure we are in Flushing state and return mutable pending buffer. /// Ensure we are in Flushing state and return mutable pending buffer.
fn ensure_pending<'a>(state: &'a mut CryptoWriterState) -> &'a mut PendingCiphertext { fn ensure_pending<'a>(state: &'a mut CryptoWriterState, max_pending: usize) -> &'a mut PendingCiphertext {
if matches!(state, CryptoWriterState::Idle) { if matches!(state, CryptoWriterState::Idle) {
*state = CryptoWriterState::Flushing { *state = CryptoWriterState::Flushing {
pending: PendingCiphertext::new(MAX_PENDING_WRITE), pending: PendingCiphertext::new(max_pending),
}; };
} }
@ -498,14 +504,14 @@ impl<W> CryptoWriter<W> {
} }
/// Select how many plaintext bytes can be accepted in buffering path /// Select how many plaintext bytes can be accepted in buffering path
fn select_to_accept_for_buffering(state: &CryptoWriterState, buf_len: usize) -> usize { fn select_to_accept_for_buffering(state: &CryptoWriterState, buf_len: usize, max_pending: usize) -> usize {
if buf_len == 0 { if buf_len == 0 {
return 0; return 0;
} }
match state { match state {
CryptoWriterState::Flushing { pending } => buf_len.min(pending.remaining_capacity()), CryptoWriterState::Flushing { pending } => buf_len.min(pending.remaining_capacity()),
CryptoWriterState::Idle => buf_len.min(MAX_PENDING_WRITE), CryptoWriterState::Idle => buf_len.min(max_pending),
CryptoWriterState::Poisoned { .. } => 0, CryptoWriterState::Poisoned { .. } => 0,
} }
} }
@ -603,7 +609,7 @@ impl<W: AsyncWrite + Unpin> AsyncWrite for CryptoWriter<W> {
Poll::Pending => { Poll::Pending => {
// Upstream blocked. Apply ideal backpressure // Upstream blocked. Apply ideal backpressure
let to_accept = let to_accept =
Self::select_to_accept_for_buffering(&this.state, buf.len()); Self::select_to_accept_for_buffering(&this.state, buf.len(), this.max_pending_write);
if to_accept == 0 { if to_accept == 0 {
trace!( trace!(
@ -618,7 +624,7 @@ impl<W: AsyncWrite + Unpin> AsyncWrite for CryptoWriter<W> {
// Disjoint borrows // Disjoint borrows
let encryptor = &mut this.encryptor; let encryptor = &mut this.encryptor;
let pending = Self::ensure_pending(&mut this.state); let pending = Self::ensure_pending(&mut this.state, this.max_pending_write);
if let Err(e) = pending.push_encrypted(encryptor, plaintext) { if let Err(e) = pending.push_encrypted(encryptor, plaintext) {
if e.kind() == ErrorKind::WouldBlock { if e.kind() == ErrorKind::WouldBlock {
@ -635,7 +641,7 @@ impl<W: AsyncWrite + Unpin> AsyncWrite for CryptoWriter<W> {
// 2) Fast path: pending empty -> write-through // 2) Fast path: pending empty -> write-through
debug_assert!(matches!(this.state, CryptoWriterState::Idle)); debug_assert!(matches!(this.state, CryptoWriterState::Idle));
let to_accept = buf.len().min(MAX_PENDING_WRITE); let to_accept = buf.len().min(this.max_pending_write);
let plaintext = &buf[..to_accept]; let plaintext = &buf[..to_accept];
Self::encrypt_into_scratch(&mut this.encryptor, &mut this.scratch, plaintext); Self::encrypt_into_scratch(&mut this.encryptor, &mut this.scratch, plaintext);
@ -645,7 +651,7 @@ impl<W: AsyncWrite + Unpin> AsyncWrite for CryptoWriter<W> {
// Upstream blocked: buffer FULL ciphertext for accepted bytes. // Upstream blocked: buffer FULL ciphertext for accepted bytes.
let ciphertext = std::mem::take(&mut this.scratch); let ciphertext = std::mem::take(&mut this.scratch);
let pending = Self::ensure_pending(&mut this.state); let pending = Self::ensure_pending(&mut this.state, this.max_pending_write);
pending.replace_with(ciphertext); pending.replace_with(ciphertext);
Poll::Ready(Ok(to_accept)) Poll::Ready(Ok(to_accept))
@ -672,7 +678,7 @@ impl<W: AsyncWrite + Unpin> AsyncWrite for CryptoWriter<W> {
let remainder = this.scratch.split_off(n); let remainder = this.scratch.split_off(n);
this.scratch.clear(); this.scratch.clear();
let pending = Self::ensure_pending(&mut this.state); let pending = Self::ensure_pending(&mut this.state, this.max_pending_write);
pending.replace_with(remainder); pending.replace_with(remainder);
Poll::Ready(Ok(to_accept)) Poll::Ready(Ok(to_accept))

View File

@ -267,8 +267,8 @@ impl<W: AsyncWrite + Unpin> SecureIntermediateFrameWriter<W> {
return Ok(()); return Ok(());
} }
// Add random padding (0-3 bytes) // Add padding so total length is never divisible by 4 (MTProto Secure)
let padding_len = self.rng.range(4); let padding_len = secure_padding_len(data.len(), &self.rng);
let padding = self.rng.bytes(padding_len); let padding = self.rng.bytes(padding_len);
let total_len = data.len() + padding_len; let total_len = data.len() + padding_len;

View File

@ -25,7 +25,8 @@
//! - However, the on-the-wire record length can exceed 16384 because TLS 1.3 //! - However, the on-the-wire record length can exceed 16384 because TLS 1.3
//! uses AEAD and can include tag/overhead/padding. //! uses AEAD and can include tag/overhead/padding.
//! - Telegram FakeTLS clients (notably iOS) may send Application Data records //! - Telegram FakeTLS clients (notably iOS) may send Application Data records
//! with length up to 16384 + 24 bytes. We accept that as MAX_TLS_CHUNK_SIZE. //! with length up to 16384 + 256 bytes (RFC 8446 §5.2). We accept that as
//! MAX_TLS_CHUNK_SIZE.
//! //!
//! If you reject those (e.g. validate length <= 16384), you will see errors like: //! If you reject those (e.g. validate length <= 16384), you will see errors like:
//! "TLS record too large: 16408 bytes" //! "TLS record too large: 16408 bytes"
@ -52,9 +53,8 @@ use super::state::{StreamState, HeaderBuffer, YieldBuffer, WriteBuffer};
const TLS_HEADER_SIZE: usize = 5; const TLS_HEADER_SIZE: usize = 5;
/// Maximum TLS fragment size we emit for Application Data. /// Maximum TLS fragment size we emit for Application Data.
/// Real TLS 1.3 ciphertexts often add ~16-24 bytes AEAD overhead, so to mimic /// Real TLS 1.3 allows up to 16384 + 256 bytes of ciphertext (incl. tag).
/// on-the-wire record sizes we allow up to 16384 + 24 bytes of plaintext. const MAX_TLS_PAYLOAD: usize = 16384 + 256;
const MAX_TLS_PAYLOAD: usize = 16384 + 24;
/// Maximum pending write buffer for one record remainder. /// Maximum pending write buffer for one record remainder.
/// Note: we never queue unlimited amount of data here; state holds at most one record. /// Note: we never queue unlimited amount of data here; state holds at most one record.
@ -91,7 +91,7 @@ impl TlsRecordHeader {
/// - We accept TLS 1.0 header version for ClientHello-like records (0x03 0x01), /// - We accept TLS 1.0 header version for ClientHello-like records (0x03 0x01),
/// and TLS 1.2/1.3 style version bytes for the rest (we use TLS_VERSION = 0x03 0x03). /// and TLS 1.2/1.3 style version bytes for the rest (we use TLS_VERSION = 0x03 0x03).
/// - For Application Data, Telegram FakeTLS may send payload length up to /// - For Application Data, Telegram FakeTLS may send payload length up to
/// MAX_TLS_CHUNK_SIZE (16384 + 24). /// MAX_TLS_CHUNK_SIZE (16384 + 256).
/// - For other record types we keep stricter bounds to avoid memory abuse. /// - For other record types we keep stricter bounds to avoid memory abuse.
fn validate(&self) -> Result<()> { fn validate(&self) -> Result<()> {
// Version: accept TLS 1.0 header (ClientHello quirk) and TLS_VERSION (0x0303). // Version: accept TLS 1.0 header (ClientHello quirk) and TLS_VERSION (0x0303).
@ -105,7 +105,7 @@ impl TlsRecordHeader {
let len = self.length as usize; let len = self.length as usize;
// Length checks depend on record type. // Length checks depend on record type.
// Telegram FakeTLS: ApplicationData length may be 16384 + 24. // Telegram FakeTLS: ApplicationData length may be 16384 + 256.
match self.record_type { match self.record_type {
TLS_RECORD_APPLICATION => { TLS_RECORD_APPLICATION => {
if len > MAX_TLS_CHUNK_SIZE { if len > MAX_TLS_CHUNK_SIZE {
@ -755,9 +755,6 @@ impl<W: AsyncWrite + Unpin> AsyncWrite for FakeTlsWriter<W> {
payload_size: chunk_size, payload_size: chunk_size,
}; };
// Wake to retry flushing soon.
cx.waker().wake_by_ref();
Poll::Ready(Ok(chunk_size)) Poll::Ready(Ok(chunk_size))
} }
} }

View File

@ -72,7 +72,27 @@ impl TlsFrontCache {
continue; continue;
} }
if let Ok(data) = tokio::fs::read(entry.path()).await { if let Ok(data) = tokio::fs::read(entry.path()).await {
if let Ok(cached) = serde_json::from_slice::<CachedTlsData>(&data) { if let Ok(mut cached) = serde_json::from_slice::<CachedTlsData>(&data) {
if cached.domain.is_empty()
|| cached.domain.len() > 255
|| !cached.domain.chars().all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-')
{
warn!(file = %name, "Skipping TLS cache entry with invalid domain");
continue;
}
// fetched_at is skipped during deserialization; approximate with file mtime if available.
if let Ok(meta) = entry.metadata().await {
if let Ok(modified) = meta.modified() {
cached.fetched_at = modified;
}
}
// Drop entries older than 72h
if let Ok(age) = cached.fetched_at.elapsed() {
if age > Duration::from_secs(72 * 3600) {
warn!(domain = %cached.domain, "Skipping stale TLS cache entry (>72h)");
continue;
}
}
let domain = cached.domain.clone(); let domain = cached.domain.clone();
self.set(&domain, cached).await; self.set(&domain, cached).await;
loaded += 1; loaded += 1;