mirror of
https://github.com/telemt/telemt.git
synced 2026-04-18 19:14:09 +03:00
ME Frame too large Fixes
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
@@ -34,7 +34,7 @@
|
||||
//! └────────────────────────────────────────┘
|
||||
//!
|
||||
//! Backpressure
|
||||
//! - pending ciphertext buffer is bounded (MAX_PENDING_WRITE)
|
||||
//! - pending ciphertext buffer is bounded (configurable per connection)
|
||||
//! - pending is full and upstream is pending
|
||||
//! -> poll_write returns Poll::Pending
|
||||
//! -> do not accept any plaintext
|
||||
@@ -62,10 +62,9 @@ use super::state::{StreamState, YieldBuffer};
|
||||
|
||||
// ============= Constants =============
|
||||
|
||||
/// Maximum size for pending ciphertext buffer (bounded backpressure).
|
||||
/// Reduced to 64KB to prevent bufferbloat on mobile networks.
|
||||
/// 512KB was causing high latency on 3G/LTE connections.
|
||||
const MAX_PENDING_WRITE: usize = 64 * 1024;
|
||||
/// Default size for pending ciphertext buffer (bounded backpressure).
|
||||
/// Actual limit is supplied at runtime from configuration.
|
||||
const DEFAULT_MAX_PENDING_WRITE: usize = 64 * 1024;
|
||||
|
||||
/// Default read buffer capacity (reader mostly decrypts in-place into caller buffer).
|
||||
const DEFAULT_READ_CAPACITY: usize = 16 * 1024;
|
||||
@@ -427,15 +426,22 @@ pub struct CryptoWriter<W> {
|
||||
encryptor: AesCtr,
|
||||
state: CryptoWriterState,
|
||||
scratch: BytesMut,
|
||||
max_pending_write: usize,
|
||||
}
|
||||
|
||||
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 {
|
||||
upstream,
|
||||
encryptor,
|
||||
state: CryptoWriterState::Idle,
|
||||
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.
|
||||
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) {
|
||||
*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
|
||||
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 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
match state {
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -603,7 +609,7 @@ impl<W: AsyncWrite + Unpin> AsyncWrite for CryptoWriter<W> {
|
||||
Poll::Pending => {
|
||||
// Upstream blocked. Apply ideal backpressure
|
||||
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 {
|
||||
trace!(
|
||||
@@ -618,7 +624,7 @@ impl<W: AsyncWrite + Unpin> AsyncWrite for CryptoWriter<W> {
|
||||
|
||||
// Disjoint borrows
|
||||
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 e.kind() == ErrorKind::WouldBlock {
|
||||
@@ -635,7 +641,7 @@ impl<W: AsyncWrite + Unpin> AsyncWrite for CryptoWriter<W> {
|
||||
// 2) Fast path: pending empty -> write-through
|
||||
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];
|
||||
|
||||
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.
|
||||
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);
|
||||
|
||||
Poll::Ready(Ok(to_accept))
|
||||
@@ -672,7 +678,7 @@ impl<W: AsyncWrite + Unpin> AsyncWrite for CryptoWriter<W> {
|
||||
let remainder = this.scratch.split_off(n);
|
||||
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);
|
||||
|
||||
Poll::Ready(Ok(to_accept))
|
||||
@@ -767,4 +773,4 @@ impl<S: AsyncWrite + Unpin> AsyncWrite for PassthroughStream<S> {
|
||||
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
|
||||
Pin::new(&mut self.inner).poll_shutdown(cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,8 +267,8 @@ impl<W: AsyncWrite + Unpin> SecureIntermediateFrameWriter<W> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Add random padding (0-3 bytes)
|
||||
let padding_len = self.rng.range(4);
|
||||
// Add padding so total length is never divisible by 4 (MTProto Secure)
|
||||
let padding_len = secure_padding_len(data.len(), &self.rng);
|
||||
let padding = self.rng.bytes(padding_len);
|
||||
|
||||
let total_len = data.len() + padding_len;
|
||||
@@ -585,4 +585,4 @@ mod tests {
|
||||
let (received, _) = reader.read_frame().await.unwrap();
|
||||
assert_eq!(&received[..], &data[..]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,4 +40,4 @@ pub use frame_stream::{
|
||||
SecureIntermediateFrameReader, SecureIntermediateFrameWriter,
|
||||
MtprotoFrameReader, MtprotoFrameWriter,
|
||||
FrameReaderKind, FrameWriterKind,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
//! - However, the on-the-wire record length can exceed 16384 because TLS 1.3
|
||||
//! uses AEAD and can include tag/overhead/padding.
|
||||
//! - 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:
|
||||
//! "TLS record too large: 16408 bytes"
|
||||
@@ -52,9 +53,8 @@ use super::state::{StreamState, HeaderBuffer, YieldBuffer, WriteBuffer};
|
||||
const TLS_HEADER_SIZE: usize = 5;
|
||||
|
||||
/// Maximum TLS fragment size we emit for Application Data.
|
||||
/// Real TLS 1.3 ciphertexts often add ~16-24 bytes AEAD overhead, so to mimic
|
||||
/// on-the-wire record sizes we allow up to 16384 + 24 bytes of plaintext.
|
||||
const MAX_TLS_PAYLOAD: usize = 16384 + 24;
|
||||
/// Real TLS 1.3 allows up to 16384 + 256 bytes of ciphertext (incl. tag).
|
||||
const MAX_TLS_PAYLOAD: usize = 16384 + 256;
|
||||
|
||||
/// Maximum pending write buffer for one record remainder.
|
||||
/// 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),
|
||||
/// 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
|
||||
/// MAX_TLS_CHUNK_SIZE (16384 + 24).
|
||||
/// MAX_TLS_CHUNK_SIZE (16384 + 256).
|
||||
/// - For other record types we keep stricter bounds to avoid memory abuse.
|
||||
fn validate(&self) -> Result<()> {
|
||||
// Version: accept TLS 1.0 header (ClientHello quirk) and TLS_VERSION (0x0303).
|
||||
@@ -105,7 +105,7 @@ impl TlsRecordHeader {
|
||||
let len = self.length as usize;
|
||||
|
||||
// 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 {
|
||||
TLS_RECORD_APPLICATION => {
|
||||
if len > MAX_TLS_CHUNK_SIZE {
|
||||
@@ -755,9 +755,6 @@ impl<W: AsyncWrite + Unpin> AsyncWrite for FakeTlsWriter<W> {
|
||||
payload_size: chunk_size,
|
||||
};
|
||||
|
||||
// Wake to retry flushing soon.
|
||||
cx.waker().wake_by_ref();
|
||||
|
||||
Poll::Ready(Ok(chunk_size))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user