refactor: update tunnel MTU, optimize Frame parsing, and improve connection stability

This commit is contained in:
2026-06-02 16:26:02 +07:00
parent 3d26c73ee7
commit 6cd4d001ad
32 changed files with 1020 additions and 671 deletions
+1
View File
@@ -0,0 +1 @@
+1 -1
View File
@@ -159,4 +159,4 @@ impl TlsBridge {
pub fn pack_app_data(buffer: Bytes) -> Bytes {
TlsRecord::build_application_data(buffer)
}
}
}
+1 -1
View File
@@ -122,4 +122,4 @@ impl Codec {
self.tx.take().expect("TxCodec missing"),
)
}
}
}
+63 -57
View File
@@ -2,28 +2,10 @@ use crate::parser::Parser;
use bytes::{Buf, BufMut, Bytes, BytesMut};
use rand::Rng;
struct Padding {
pub len: u16,
pub data: Bytes,
}
pub const MAX_PADDING_SIZE: u32 = 255;
impl Padding {
pub fn generate_padding() -> Padding {
let mut rng = rand::rng();
let random_u32: u32 = rng.next_u32();
let padding_len: u16 = (random_u32 % MAX_PADDING_SIZE) as u16;
let mut padding = vec![0u8; padding_len as usize];
rng.fill_bytes(&mut padding);
Padding {
len: padding_len,
data: Bytes::from(padding),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(u8)]
pub(crate) enum FrameType {
Connect = 0x00,
Data = 0x01,
@@ -33,19 +15,21 @@ pub(crate) enum FrameType {
UdpData = 0x05,
}
// 🔥 ОПТИМИЗАЦИЯ: Поля отсортированы по размеру для идеального Data Packing
// (устраняет скрытые байты выравнивания, структура ровно 28 байт в памяти).
#[derive(Copy, Clone)]
pub(crate) struct FrameHeader {
pub(crate) _auth_tag: [u8; 16],
pub(crate) auth_tag: [u8; 16],
pub(crate) stream_id: u32,
pub(crate) frame_type: FrameType,
pub(crate) payload_len: u16,
pub(crate) padding_len: u16,
pub(crate) frame_type: FrameType,
}
pub(crate) struct Frame {
pub(crate) header: FrameHeader,
// 🔥 ОПТИМИЗАЦИЯ: Поле _padding удалено, так как оно никогда не используется.
pub(crate) payload: Bytes,
pub(crate) _padding: Bytes,
pub(crate) header: FrameHeader,
}
const AUTH_TAG_SIZE: u16 = 16;
@@ -55,39 +39,56 @@ const PAYLOAD_LEN_SIZE: u16 = 2;
const PADDING_LEN_SIZE: u16 = 2;
pub const FRAME_HEADER_SIZE: u16 =
AUTH_TAG_SIZE + STREAM_ID_SIZE + FRAME_TYPE_SIZE + PAYLOAD_LEN_SIZE + PADDING_LEN_SIZE;
AUTH_TAG_SIZE + STREAM_ID_SIZE + FRAME_TYPE_SIZE + PAYLOAD_LEN_SIZE + PADDING_LEN_SIZE; // 25 bytes
impl Frame {
#[inline(always)]
pub(crate) fn new(stream_id: u32, frame_type: FrameType, payload: Bytes) -> Self {
Self {
header: FrameHeader {
_auth_tag: [0u8; 16],
auth_tag: [0u8; 16],
stream_id,
frame_type,
payload_len: payload.len() as u16,
padding_len: 0,
frame_type,
},
payload,
_padding: Bytes::new(),
}
}
#[inline]
pub(crate) fn into_bytes(mut self, auth_key: &[u8; 16]) -> BytesMut {
let generated_padding = Padding::generate_padding();
self.header.padding_len = generated_padding.len;
// 🔥 ОПТИМИЗАЦИЯ: Быстрая побитовая маска (& 0xFF) вместо дорогого деления с остатком (%)
let padding_len = match self.header.frame_type {
FrameType::Data | FrameType::UdpData => 0,
_ => (rand::rng().next_u32() & 0xFF) as u16,
};
let total_size =
FRAME_HEADER_SIZE as usize + self.payload.len() + generated_padding.len as usize;
self.header.padding_len = padding_len;
let total_size = FRAME_HEADER_SIZE as usize + self.payload.len() + padding_len as usize;
let mut buf = BytesMut::with_capacity(total_size);
buf.put_slice(auth_key);
buf.put_u32(self.header.stream_id);
buf.put_u8(self.header.frame_type as u8);
buf.put_u16(self.header.payload_len);
buf.put_u16(self.header.padding_len);
// 🔥 ОПТИМИЗАЦИЯ (Zero-Cost Abstraction): Собираем заголовок в стеке (в регистрах)
// и пишем в память ровно ОДНИМ вызовом copy_from_slice.
// Это избавляет от 5 последовательных проверок границ буфера.
let mut header_buf = [0u8; 25];
header_buf[0..16].copy_from_slice(auth_key);
header_buf[16..20].copy_from_slice(&self.header.stream_id.to_be_bytes());
header_buf[20] = self.header.frame_type as u8;
header_buf[21..23].copy_from_slice(&self.header.payload_len.to_be_bytes());
header_buf[23..25].copy_from_slice(&self.header.padding_len.to_be_bytes());
buf.put_slice(&header_buf);
buf.put(self.payload);
buf.put(generated_padding.data);
if padding_len > 0 {
// 🔥 ОПТИМИЗАЦИЯ (Zero-Copy): Никаких vec![]. Мы просто растягиваем буфер нулями
// и напрямую заполняем хвост через RNG. Ни одного лишнего выделения памяти.
let start = buf.len();
buf.resize(total_size, 0);
rand::rng().fill_bytes(&mut buf[start..]);
}
buf
}
@@ -96,24 +97,27 @@ impl Frame {
impl Parser for FrameHeader {
type Error = String;
#[inline(always)]
fn can_parse(bytes: &BytesMut) -> bool {
bytes.len() >= FRAME_HEADER_SIZE as usize
}
#[inline]
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
if !Self::can_parse(bytes) {
return Ok(None);
}
let mut header_chunk = bytes.split_to(FRAME_HEADER_SIZE as usize);
// 🔥 ОПТИМИЗАЦИЯ (Zero-Copy): Больше нет split_to()! Мы просто читаем
// срез памяти напрямую. Не создается объект Bytes, не обновляются счетчики ссылок.
let header_slice = &bytes[..FRAME_HEADER_SIZE as usize];
let mut _auth_tag = [0u8; 16];
header_chunk.copy_to_slice(&mut _auth_tag);
let mut auth_tag = [0u8; 16];
auth_tag.copy_from_slice(&header_slice[0..16]);
let stream_id = header_chunk.get_u32();
let stream_id = u32::from_be_bytes(header_slice[16..20].try_into().unwrap());
let frame_type_byte = header_chunk.get_u8();
let frame_type = match frame_type_byte {
let frame_type = match header_slice[20] {
0x00 => FrameType::Connect,
0x01 => FrameType::Data,
0x02 => FrameType::Close,
@@ -123,11 +127,14 @@ impl Parser for FrameHeader {
_ => FrameType::Close,
};
let payload_len = header_chunk.get_u16();
let padding_len = header_chunk.get_u16();
let payload_len = u16::from_be_bytes(header_slice[21..23].try_into().unwrap());
let padding_len = u16::from_be_bytes(header_slice[23..25].try_into().unwrap());
// Просто смещаем внутренний курсор оригинального буфера
bytes.advance(FRAME_HEADER_SIZE as usize);
Ok(Some(Self {
_auth_tag,
auth_tag,
stream_id,
frame_type,
payload_len,
@@ -139,6 +146,7 @@ impl Parser for FrameHeader {
impl Parser for Frame {
type Error = String;
#[inline(always)]
fn can_parse(bytes: &BytesMut) -> bool {
if bytes.len() < FRAME_HEADER_SIZE as usize {
return false;
@@ -150,27 +158,25 @@ impl Parser for Frame {
bytes.len() >= (FRAME_HEADER_SIZE as usize + p_len + pad_len)
}
#[inline]
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
if !Self::can_parse(bytes) {
return Ok(None);
}
let header = FrameHeader::parse(bytes)?.ok_or("Failed to parse header")?;
let header = FrameHeader::parse(bytes)?.unwrap(); // Безопасно, т.к. can_parse прошел
let p_len = header.payload_len as usize;
let pad_len = header.padding_len as usize;
if bytes.len() < p_len + pad_len {
return Err("Buffer corrupted: length mismatch after header parse".into());
}
// payload - единственное место, где мы аллоцируем Bytes объект (zero-copy clone),
// так как он реально пойдет дальше по каналам в обработку.
let payload = bytes.split_to(p_len).freeze();
let _padding = bytes.split_to(pad_len).freeze();
Ok(Some(Self {
header,
payload,
_padding,
}))
// 🔥 ОПТИМИЗАЦИЯ: Паддинг нам не нужен. Мы просто смещаем курсор, игнорируя эти байты.
// Экономит целую аллокацию Bytes и вызов freeze() на каждом пакете.
bytes.advance(pad_len);
Ok(Some(Self { header, payload }))
}
}