allocation optimization and in place crypt

This commit is contained in:
2026-06-05 00:22:38 +07:00
parent 6cd4d001ad
commit 05696a836a
10 changed files with 234 additions and 183 deletions
+14 -20
View File
@@ -30,30 +30,24 @@ impl NetworkConfig {
pub fn new(system_mtu: usize) -> Self {
Self {
mtu: system_mtu,
// Для сервера 64KB ок, для клиента поднимем до 256KB, чтобы не тормозить чтение с TUN
connection_buf_size: 256 * 1024,
connection_buf_size: 128 * 1024, // Уменьшили с 256KB
tcp_buffer_size: 128 * 1024, // Уменьшили с 256KB
udp_buffer_size: 64 * 1024, // Уменьшили со 128KB
tcp_chunk_size: 1024 * 1024 * 64,
// 256KB — это золотая середина. Позволяет держать ~10Мбит на стрим при пинге 200мс.
tcp_buffer_size: 256 * 1024,
udp_buffer_size: 128 * 1024,
tcp_chunk_size: system_mtu - 100,
channel_capacity: 512,
// Емкость каналов: 512 пакетов (~700КБ).
// Этого достаточно, чтобы сгладить лаги радио-эфира на телефоне.
channel_capacity: 8192,
// Окна smoltcp (уменьшаем, чтобы не создавать огромные очереди)
tcp_rx_heavy: 128 * 1024,
tcp_tx_heavy: 128 * 1024,
// Окна smoltcp (важно для Download)
// Увеличиваем до 512KB для тяжелых профилей
tcp_rx_heavy: 256 * 1024,
tcp_tx_heavy: 256 * 1024,
tcp_rx_light: 32 * 1024, // Уменьшили с 64KB
tcp_tx_light: 32 * 1024,
tcp_rx_light: 64 * 1024,
tcp_tx_light: 64 * 1024,
udp_buf_heavy: 256 * 1024,
udp_meta_heavy: 1024, // Больше метаданных для мелких UDP пакетов
udp_buf_light: 32 * 1024,
udp_meta_light: 64,
udp_buf_heavy: 128 * 1024,
udp_meta_heavy: 512,
udp_buf_light: 16 * 1024,
udp_meta_light: 32,
}
}
+70 -51
View File
@@ -68,6 +68,16 @@ impl TunnelEngine {
|| self.rx_codec.is_none()
|| self.tx_codec.is_none()
{
// 💡 ИСПРАВЛЕНИЕ 2: Если это Сервер (remote_addr пуст), он НЕ должен делать реконнект.
// Мертвая лега должна просто завершиться и удалиться из памяти.
if self.remote_addr.is_empty() {
info!(
"Server leg {} dropped, shutting down engine task",
self.leg_id
);
return Ok(());
}
self.leg_status = LegStatus::Reconnecting;
match self.attempt_reconnect().await {
Ok((new_in, new_out, new_rx, new_tx)) => {
@@ -91,7 +101,6 @@ impl TunnelEngine {
let outbound = self.outbound.take().unwrap();
let read_buf = std::mem::take(&mut self.read_buf);
// 💡 ИЗМЕНЕНО: Извлекаем кодеки и каналы из структуры во владение
let mut rx_codec = self.rx_codec.take().unwrap();
let mut tx_codec = self.tx_codec.take().unwrap();
let mut control_rx = self.control_rx.take().expect("control_rx is missing");
@@ -106,11 +115,10 @@ impl TunnelEngine {
let token_reader = token.clone();
let token_writer = token.clone();
// ЧИТАЮЩАЯ ЗАДАЧА
// ЧИТАЮЩАЯ ЗАДАЧА (Остается без изменений)
let mut reader_handle = tokio::spawn(async move {
let mut read_buf = read_buf;
let mut inbound = inbound;
const MAX_BUFFER_SIZE: usize = 1024 * 1024; // 1 MB
loop {
@@ -119,7 +127,7 @@ impl TunnelEngine {
return Err(AppError::new(
ERR_INFRA_TIMEOUT,
"Переполнение буфера",
"Read buffer exceeded MAX_BUFFER_SIZE",
"OOM Protection",
));
}
@@ -134,29 +142,15 @@ impl TunnelEngine {
break;
}
res = inbound.read_buf(&mut read_buf) => {
let n = res.map_err(|e| {
AppError::new(
ERR_INFRA_TIMEOUT,
"Сбой сети",
e.to_string(),
)
})?;
let n = res.map_err(|e| AppError::new(ERR_INFRA_TIMEOUT, "Сбой сети", e.to_string()))?;
if n == 0 {
if read_buf.is_empty() {
info!("Connection closed by peer (Clean EOF)");
} else {
error!("Connection abruptly closed by peer (Incomplete frame: {} bytes left)", read_buf.len());
}
// Возвращаем rx_codec обратно при EOF
info!("Connection closed by peer (Clean EOF)");
return Ok::<_, AppError>((true, read_buf, rx_codec));
}
muxer.record_leg_rx(leg_id, n as u64);
let mut frames = Vec::new();
// 💡 ИЗМЕНЕНО: Вызов decode_inbound напрямую без блокировок
loop {
match rx_codec.decode_inbound(&mut read_buf) {
Ok(Some(frame)) => frames.push(frame),
@@ -164,19 +158,9 @@ impl TunnelEngine {
Err(e) => {
if e.action == ErrorAction::Wait { break; }
if e.action == ErrorAction::Drop {
error!("CRITICAL: Crypto tampering or sync lost. Hard dropping tunnel!");
return Err(AppError::new(
ERR_NET_TLS_TAMPER,
"Критическая ошибка шифрования",
"Crypto drop",
));
return Err(AppError::new(ERR_NET_TLS_TAMPER, "Ошибка шифрования", "Crypto drop"));
}
error!(error = ?e, "Codec inbound failed");
return Err(AppError::new(
ERR_NET_TLS_TAMPER,
"Ошибка декодирования",
format!("Codec error: {:?}", e),
));
return Err(AppError::new(ERR_NET_TLS_TAMPER, "Сбой кодека", format!("{:?}", e)));
}
}
}
@@ -184,9 +168,7 @@ impl TunnelEngine {
for frame in frames {
if frame.header.frame_type == FrameType::Heartbeat {
let m = muxer.clone();
tokio::spawn(async move {
m.record_pong(leg_id).await;
});
tokio::spawn(async move { m.record_pong(leg_id).await; });
}
handler.handle(frame).await;
}
@@ -201,18 +183,15 @@ impl TunnelEngine {
let mut outbound = outbound;
let mut heartbeat = tokio::time::interval(HEALTH_CHECK_INTERVAL);
let mut pending_data: Option<MuxMessage> = None;
const INTERLEAVE_CHUNK: usize = 16384;
loop {
tokio::select! {
biased;
_ = token_writer.cancelled() => break,
msg_opt = control_rx.recv() => {
if let Some(msg) = msg_opt {
// 💡 ИЗМЕНЕНО: Передаем &mut tx_codec напрямую, никаких .lock().await!
if let Err(e) = Self::handle_outbound(&mut outbound, &mut tx_codec, msg).await {
return Err((e, control_rx, data_rx, tx_codec));
}
} else { break; }
}
_ = heartbeat.tick() => {
muxer_pong.record_ping_sent(leg_id);
let msg = MuxMessage { stream_id: 0, frame_type: FrameType::Heartbeat, data: Bytes::new() };
@@ -220,16 +199,48 @@ impl TunnelEngine {
return Err((e, control_rx, data_rx, tx_codec));
}
}
msg_opt = data_rx.recv() => {
msg_opt = control_rx.recv() => {
if let Some(msg) = msg_opt {
if let Err(e) = Self::handle_outbound(&mut outbound, &mut tx_codec, msg).await {
return Err((e, control_rx, data_rx, tx_codec));
}
} else { break; }
}
// 💡 ИСПРАВЛЕНИЕ 1: Мгновенно заходим в эту ветку, если есть данные
_ = std::future::ready(()), if pending_data.is_some() => {
let mut msg = pending_data.take().unwrap();
let chunk_size = std::cmp::min(msg.data.len(), INTERLEAVE_CHUNK);
let chunk_data = msg.data.split_to(chunk_size);
let chunk_msg = MuxMessage {
stream_id: msg.stream_id,
frame_type: msg.frame_type.clone(),
data: chunk_data,
};
if let Err(e) = Self::handle_outbound(&mut outbound, &mut tx_codec, chunk_msg).await {
return Err((e, control_rx, data_rx, tx_codec));
}
if !msg.data.is_empty() {
pending_data = Some(msg);
}
// 💡 ИСПРАВЛЕНИЕ 1.2: Вызываем yield ЗДЕСЬ. Это заставит планировщик
// проверить пинги и контрольные пакеты перед отправкой следующего куска.
tokio::task::yield_now().await;
}
msg_opt = data_rx.recv(), if pending_data.is_none() => {
if let Some(msg) = msg_opt {
pending_data = Some(msg);
} else { break; }
}
}
}
// Возвращаем receiver'ы и tx_codec обратно при корректном завершении
Ok::<
_,
(
@@ -246,7 +257,7 @@ impl TunnelEngine {
match res_reader {
Ok(Ok((is_eof, r_buf, returned_rx_codec))) => {
self.read_buf = r_buf;
self.rx_codec = Some(returned_rx_codec); // Восстанавливаем
self.rx_codec = Some(returned_rx_codec);
if is_eof {
token.cancel();
let w_res = writer_handle.await.unwrap();
@@ -288,16 +299,23 @@ impl TunnelEngine {
};
token.cancel();
reader_handle.abort();
writer_handle.abort();
if let Err(e) = &res {
if let Err(e) = res {
error!("TunnelEngine critical failure: {}", e);
return res;
} else {
return Err(e);
}
// 💡 ИСПРАВЛЕНИЕ 2.2: И здесь тоже, если сервер словил EOF, он не должен идти на реконнект.
if self.remote_addr.is_empty() {
return Ok(());
}
info!("Tunnel iteration finished, preparing to reconnect...");
continue;
}
}
// 💡 ИЗМЕНЕНО: Принимает &mut TxCodec, синхронное и сверхбыстрое шифрование
async fn handle_outbound(
outbound: &mut OwnedWriteHalf,
@@ -342,8 +360,9 @@ impl TunnelEngine {
for pkt in packets {
let write_future = outbound.write_all(&pkt);
// 💡 ИЗМЕНЕНО: Увеличен таймаут отправки до 20 секунд для совместимости с агрессивным BBR
if let Err(_) =
tokio::time::timeout(std::time::Duration::from_secs(10), write_future).await
tokio::time::timeout(std::time::Duration::from_secs(20), write_future).await
{
error!(stream_id, "🔥 Physical leg STUCK on write. Killing leg.");
return Err(AppError::new(
+63 -42
View File
@@ -150,11 +150,13 @@ impl Muxer {
}
fn select_leg(&self, stream_id: u32) -> Option<MuxLeg> {
// 1. Читаем кэш (это Arc, поэтому clone здесь — это просто инкремент счетчика, не копирование данных)
let legs = self.active_legs_cache.read().unwrap().clone();
if legs.is_empty() {
return None;
}
// 2. Если поток уже привязан к леге, используем её (Sticky Connection)
if let Some(leg_id_ref) = self.stream_bindings.get(&stream_id) {
let leg_id = *leg_id_ref;
if let Some(leg) = legs.iter().find(|l| l.id == leg_id) {
@@ -162,22 +164,31 @@ impl Muxer {
}
}
let mut candidates = (*legs).clone();
candidates.sort_by(|a, b| {
let score_a =
a.stats.rtt_ms.load(Ordering::Relaxed) as f64 + (a.congestion_factor() * 2000.0);
let score_b =
b.stats.rtt_ms.load(Ordering::Relaxed) as f64 + (b.congestion_factor() * 2000.0);
score_a
.partial_cmp(&score_b)
.unwrap_or(std::cmp::Ordering::Equal)
});
// 3. O(N) поиск лучшей леги без сортировки всего вектора
// Мы берем подмножество (pool) и сразу ищем в нем минимум
let pool_size = std::cmp::min(legs.len(), MUXER_POOL_SIZE);
let pool_size = std::cmp::min(candidates.len(), MUXER_POOL_SIZE);
let selected_leg = candidates[stream_id as usize % pool_size].clone();
// Используем min_by, чтобы найти лучший вариант за один проход
let selected_leg = legs
.iter()
.take(pool_size) // Берем только пул
.min_by(|a, b| {
let score_a = a.stats.rtt_ms.load(Ordering::Relaxed) as f64
+ (a.congestion_factor() * 2000.0);
let score_b = b.stats.rtt_ms.load(Ordering::Relaxed) as f64
+ (b.congestion_factor() * 2000.0);
score_a
.partial_cmp(&score_b)
.unwrap_or(std::cmp::Ordering::Equal)
})
.cloned();
self.stream_bindings.insert(stream_id, selected_leg.id);
Some(selected_leg)
if let Some(leg) = selected_leg {
self.stream_bindings.insert(stream_id, leg.id);
return Some(leg);
}
None
}
pub fn record_ping_sent(&self, leg_id: u32) {
@@ -215,17 +226,12 @@ impl Muxer {
};
let is_data = matches!(message.frame_type, FrameType::Data | FrameType::UdpData);
let target_tx = if is_data {
&leg.data_tx
} else {
&leg.control_tx
};
let stream_id = message.stream_id;
let size = message.data.len() as u64;
if is_data {
match target_tx.try_send(message) {
// 💡 ДАННЫЕ: Используем .send().await для создания Backpressure
match leg.data_tx.send(message).await {
Ok(_) => {
leg.stats.tx_bytes.fetch_add(size, Ordering::Relaxed);
if let Some(stream_ref) = self.streams.get(&stream_id) {
@@ -237,24 +243,37 @@ impl Muxer {
}
Ok(())
}
Err(tokio::sync::mpsc::error::TrySendError::Full(_)) => Ok(()), // HoL Blocking eliminated
Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => {
Err(_) => {
self.remove_leg(leg.id, &leg.control_tx);
Err(AppError::new(ERR_INFRA_TIMEOUT, "Обрыв", "Leg closed"))
}
}
} else {
match tokio::time::timeout(std::time::Duration::from_secs(2), target_tx.send(message))
.await
{
Ok(Ok(_)) => Ok(()),
_ => {
// 💡 КОНТРОЛЬ: Используем .try_send() для мгновенной приоритетной отправки (Non-blocking)
match leg.control_tx.try_send(message) {
Ok(_) => {
leg.stats.tx_bytes.fetch_add(size, Ordering::Relaxed);
if let Some(stream_ref) = self.streams.get(&stream_id) {
stream_ref
.value()
.1
.tx_bytes
.fetch_add(size, Ordering::Relaxed);
}
Ok(())
}
Err(tokio::sync::mpsc::error::TrySendError::Full(_)) => {
// Канал контроля не должен забиваться. Если это произошло, пакет сбрасывается,
// чтобы предотвратить зависание критических задач.
netrunner_logger::warn!(
stream_id,
"Control queue FULL! Dropping control frame to avoid deadlock."
);
Ok(())
}
Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => {
self.remove_leg(leg.id, &leg.control_tx);
Err(AppError::new(
ERR_INFRA_TIMEOUT,
"Таймаут",
"Control send timeout",
))
Err(AppError::new(ERR_INFRA_TIMEOUT, "Обрыв", "Leg closed"))
}
}
}
@@ -311,16 +330,18 @@ impl Muxer {
#[inline]
pub fn dispatch_to_local(&self, stream_id: u32, data: Bytes) {
if let Some(stream_ref) = self.streams.get(&stream_id) {
let tx = &stream_ref.value().0;
let stats = &stream_ref.value().1;
let size = data.len() as u64;
// Вычисляем размер здесь, так как переменная size не была определена
let size = data.len() as u64;
match tx.try_send(data) {
Ok(_) => {
stats.rx_bytes.fetch_add(size, Ordering::Relaxed);
}
Err(_) => {} // Silent drop to avoid slowing down Reader Task
let tx_and_stats = self.streams.get(&stream_id).map(|s| {
let val = s.value();
(val.0.clone(), val.1.clone()) // Клонируем Arc и Sender
});
if let Some((tx, stats)) = tx_and_stats {
// Используем .try_send() для неблокирующей доставки в локальный поток
if tx.try_send(data).is_ok() {
stats.rx_bytes.fetch_add(size, Ordering::Relaxed);
}
}
}
+2 -2
View File
@@ -1,11 +1,11 @@
use std::time::Duration;
pub const MAX_SOCKETS: usize = 64;
pub const MAX_SOCKETS: usize = 256;
pub const MAX_TUNNEL_LEGS: u32 = 4;
pub const MUXER_POOL_SIZE: usize = 3;
pub const TCP_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(20);
pub const UDP_IDLE_TIMEOUT: Duration = Duration::from_secs(60);
pub const UDP_IDLE_TIMEOUT: Duration = Duration::from_secs(15);
pub const GLOBAL_IDLE_TIMEOUT: Duration = Duration::from_secs(120);
// 🔥 ФИКС: Ускоряем обнаружение мертвой сети при переключении Wi-Fi -> LTE