backpressures fixes and recieve/send sync

This commit is contained in:
2026-04-14 16:59:36 +07:00
parent a6ca710954
commit fac16c5296
4 changed files with 104 additions and 78 deletions
+30 -22
View File
@@ -16,11 +16,10 @@ use crate::{
rawcast::{LocalProtocol, RawCastAdapter, RawCastFrame},
tlseng::{BrowserProfile, ServerProfile},
};
use bytes::{Bytes, BytesMut};
use bytes::{Buf, Bytes, BytesMut};
use dashmap::DashMap;
use netrunner_logger::{debug, error, info, warn};
use rand::Rng;
use socket2::{Domain, Protocol, Socket, Type};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::{
@@ -95,20 +94,21 @@ impl ClientHandler {
let mut addrs = tokio::net::lookup_host(remote_proxy_addr)
.await
.map_err(|e| format!("DNS resolution failed: {}", e))?;
let addr = addrs
.next()
.ok_or_else(|| format!("No IPs found for {}", remote_proxy_addr))?;
let domain = if addr.is_ipv4() {
Domain::IPV4
// 🔥 ФИКС: Используем Tokio TcpSocket вместо socket2.
// Это позволяет корректно отменять подключение при зависании ОС.
let socket = if addr.is_ipv4() {
tokio::net::TcpSocket::new_v4()
} else {
Domain::IPV6
};
let socket =
Socket::new(domain, Type::STREAM, Some(Protocol::TCP)).map_err(|e| e.to_string())?;
tokio::net::TcpSocket::new_v6()
}
.map_err(|e| e.to_string())?;
socket.set_nonblocking(true).map_err(|e| e.to_string())?;
socket.set_nodelay(true).map_err(|e| e.to_string())?;
socket.set_nodelay(true).unwrap_or_default();
#[cfg(any(
target_os = "linux",
@@ -117,7 +117,7 @@ impl ClientHandler {
target_os = "macos"
))]
unsafe {
use std::os::fd::AsRawFd;
use std::os::unix::io::AsRawFd;
let lowat: libc::c_int = 16384;
let _ = libc::setsockopt(
socket.as_raw_fd(),
@@ -128,9 +128,12 @@ impl ClientHandler {
);
}
let _ = socket.connect(&addr.into());
let std_stream: std::net::TcpStream = socket.into();
let stream = TcpStream::from_std(std_stream).map_err(|e| e.to_string())?;
// 🔥 ФИКС: Добавляем Timeout на подключение (5 секунд)!
// Если при смене сети пакеты идут в никуда, мы быстро прерываем попытку и пробуем снова.
let stream = tokio::time::timeout(std::time::Duration::from_secs(5), socket.connect(addr))
.await
.map_err(|_| "Connection timed out (Network changed?)".to_string())?
.map_err(|e| e.to_string())?;
let mut conn = Connection::new(stream);
let mut session_keys = SessionKeys::new(true);
@@ -151,13 +154,18 @@ impl ClientHandler {
break;
}
Ok(None) => {
let n = conn
.inbound
.read_buf(&mut conn.read_buf)
.await
.map_err(|e| e.to_string())?;
if n == 0 {
return Err(format!("EOF on {}", leg_name));
// Также добавляем Timeout на чтение ответа от сервера
let res = tokio::time::timeout(
std::time::Duration::from_secs(5),
conn.inbound.read_buf(&mut conn.read_buf),
)
.await;
match res {
Ok(Ok(0)) => return Err(format!("EOF on {}", leg_name)),
Ok(Ok(_)) => continue,
Ok(Err(e)) => return Err(format!("Read error: {}", e)),
Err(_) => return Err("Handshake read timeout".into()),
}
}
Err(e) => return Err(format!("TLS error on {}: {:?}", leg_name, e)),
@@ -221,7 +229,7 @@ impl ClientHandler {
tokio::time::sleep(LEG_STAGGER_DELAY * id).await;
loop {
if let Err(e) = Self::establish_leg(&addr, id, m.clone(), &sid).await {
error!("Leg {} disconnected: {}. Reconnecting in 3s...", id, e);
error!("Leg {} disconnected: {}. Reconnecting in 2s...", id, e);
tokio::time::sleep(LEG_RECONNECT_DELAY).await;
}
}
+54 -30
View File
@@ -52,7 +52,6 @@ impl IdGenerator {
}
}
// 🔥 Клонируем сообщения для Retry-логики
#[derive(Clone)]
pub struct MuxMessage {
pub stream_id: u32,
@@ -108,8 +107,6 @@ impl Muxer {
pub fn remove_leg(&self, leg_id: u32) {
self.legs.remove(&leg_id);
// Мы не чистим stream_bindings полностью,
// балансировщик сам переназначит мертвые привязки
info!(
leg_id,
"MUXER: Leg removed, streams will re-balance dynamically"
@@ -125,7 +122,6 @@ impl Muxer {
return None;
}
// Если привязка есть и нога жива — используем её
if let Some(leg_id_ref) = self.stream_bindings.get(&stream_id) {
let leg_id = *leg_id_ref;
if let Some(leg) = self.legs.get(&leg_id) {
@@ -133,8 +129,6 @@ impl Muxer {
}
}
// 🔥 ФИКС: Убрано разделение на TCP/UDP ноги.
// Все физические ноги у нас TCP, поэтому мы балансируем трафик по всем ногам!
let mut candidates: Vec<(u32, MuxLeg)> = self
.legs
.iter()
@@ -155,7 +149,6 @@ impl Muxer {
let pool_size = std::cmp::min(candidates.len(), MUXER_POOL_SIZE);
let (selected_id, selected_leg) = candidates[stream_id as usize % pool_size].clone();
// Обновляем привязку
self.stream_bindings.insert(stream_id, selected_id);
Some((selected_id, selected_leg))
@@ -185,7 +178,6 @@ impl Muxer {
}
}
// 🔥 ФИКС: Добавлена логика повторной отправки (Retry) при обрыве связи!
pub async fn send_to_network(&self, message: MuxMessage) -> Result<(), String> {
let size = message.data.len() as u64;
let mut attempts = 0;
@@ -209,21 +201,42 @@ impl Muxer {
_ => &leg.data_tx,
};
if target_tx.send(message.clone()).await.is_err() {
// Нога умерла! Выкидываем её и пробуем отправить пакет в ДРУГУЮ ногу (continue)
self.remove_leg(leg_id);
continue;
// 🔥 ФИКС: Если канал данных забит, дропаем пакет, чтобы не ломать туннель
match target_tx.try_send(message.clone()) {
Ok(_) => {
leg.stats.tx_bytes.fetch_add(size, Ordering::Relaxed);
if let Some(stream_ref) = self.streams.get(&message.stream_id) {
stream_ref
.value()
.1
.tx_bytes
.fetch_add(size, Ordering::Relaxed);
}
return Ok(());
}
Err(tokio::sync::mpsc::error::TrySendError::Full(_)) => {
if matches!(message.frame_type, FrameType::Data | FrameType::UdpData) {
// Канал данных физически забит (слабый интернет). СБРАСЫВАЕМ пакет.
// TCP smoltcp на клиенте не получит ACK и сделает ретрансмиссию.
trace!(
message.stream_id,
"Physical TX full! Dropped packet to prevent deadlock."
);
return Ok(());
} else {
// Контрольные фреймы (Connect, Close, Ping) обязательны к доставке.
if target_tx.send(message.clone()).await.is_err() {
self.remove_leg(leg_id);
continue;
}
return Ok(());
}
}
Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => {
self.remove_leg(leg_id);
continue;
}
}
leg.stats.tx_bytes.fetch_add(size, Ordering::Relaxed);
if let Some(stream_ref) = self.streams.get(&message.stream_id) {
stream_ref
.value()
.1
.tx_bytes
.fetch_add(size, Ordering::Relaxed);
}
return Ok(());
}
}
@@ -268,7 +281,6 @@ impl Muxer {
pub fn remove_stream(&self, stream_id: u32) {
self.streams.remove(&stream_id);
self.stream_bindings.remove(&stream_id);
trace!(stream_id, "MUXER: Stream and bindings removed");
}
pub async fn dispatch_to_local(&self, stream_id: u32, data: Bytes) {
@@ -276,12 +288,26 @@ impl Muxer {
.streams
.get(&stream_id)
.map(|s| (s.value().0.clone(), s.value().1.clone()));
if let Some((tx, stats)) = stream_opt {
let size = data.len() as u64;
if tx.send(data).await.is_err() {
self.remove_stream(stream_id);
} else {
stats.rx_bytes.fetch_add(size, Ordering::Relaxed);
// 🔥 ФИКС: Используем try_send вместо await!
// Если локальный сокет тупит, а очередь полна (2MB), мы сбрасываем пакет.
// Внешний сервер увидит потерю пакета и замедлит передачу.
match tx.try_send(data) {
Ok(_) => {
stats.rx_bytes.fetch_add(size, Ordering::Relaxed);
}
Err(tokio::sync::mpsc::error::TrySendError::Full(_)) => {
trace!(
stream_id,
"Local RX queue full. Dropped packet for inner-TCP backpressure."
);
}
Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => {
self.remove_stream(stream_id);
}
}
}
}
@@ -326,9 +352,7 @@ impl Muxer {
}
match tokio::time::timeout(HEALTH_CHECK_TIMEOUT, probe_rx.recv()).await {
Ok(Some(_)) => {
debug!(leg_id, "✅ Leg Health Check OK");
}
Ok(Some(_)) => debug!(leg_id, "✅ Leg Health Check OK"),
_ => {
warn!(leg_id, "❌ Leg Health Check Timeout - Evicting leg");
self.remove_leg(leg_id);
+15 -20
View File
@@ -1,37 +1,32 @@
use std::time::Duration;
// --- Лимиты системы ---
pub const MAX_SOCKETS: usize = 2048; // Лимит сокетов в SocketSet (Anti-DDoS)
pub const MAX_TUNNEL_LEGS: u32 = 6; // Максимальное кол-во физических соединений (Legs)
pub const MUXER_POOL_SIZE: usize = 3; // Из скольких лучших Leg-ов выбирать для балансировки
pub const MAX_SOCKETS: usize = 2048;
pub const MAX_TUNNEL_LEGS: u32 = 6;
pub const MUXER_POOL_SIZE: usize = 3;
// --- Таймауты ---
pub const TCP_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(20); // Время на установку SYN/ACK
pub const UDP_IDLE_TIMEOUT: Duration = Duration::from_secs(60); // Смерть UDP сессии без данных
pub const GLOBAL_IDLE_TIMEOUT: Duration = Duration::from_secs(120); // Очистка Tracker-ом
pub const HEALTH_CHECK_INTERVAL: Duration = Duration::from_secs(7); // Частота пинга Leg-ов
pub const HEALTH_CHECK_TIMEOUT: Duration = Duration::from_secs(10);
pub const LEG_RECONNECT_DELAY: Duration = Duration::from_secs(3); // Пауза перед реконнектом Leg
pub const BRIDGE_IDLE_TIMEOUT: Duration = Duration::from_secs(30); // Таймаут задач-бриджей
pub const TCP_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(20);
pub const UDP_IDLE_TIMEOUT: Duration = Duration::from_secs(60);
pub const GLOBAL_IDLE_TIMEOUT: Duration = Duration::from_secs(120);
// 🔥 ФИКС: Ускоряем обнаружение мертвой сети при переключении Wi-Fi -> LTE
pub const HEALTH_CHECK_INTERVAL: Duration = Duration::from_secs(3); // Было 7
pub const HEALTH_CHECK_TIMEOUT: Duration = Duration::from_secs(4); // Было 10
pub const LEG_RECONNECT_DELAY: Duration = Duration::from_secs(2); // Было 3
pub const BRIDGE_IDLE_TIMEOUT: Duration = Duration::from_secs(30);
// --- Сетевые порты ---
pub const DNS_PORT: u16 = 53;
pub const HTTP_PORT: u16 = 80;
pub const HTTPS_PORT: u16 = 443;
pub const NETBIOS_PORTS: [u16; 2] = [137, 138];
// --- Настройки безопасности ---
pub const AUTH_TIME_STEP: u64 = 60; // Шаг генерации токена (секунды)
pub const AUTH_WINDOW_SIZE: u64 = 2; // Допуск шагов времени (current +/- 2)
pub const AUTH_TIME_STEP: u64 = 60;
pub const AUTH_WINDOW_SIZE: u64 = 2;
// --- Настройки Multipath (Legs) ---
pub const LEG_STAGGER_DELAY: Duration = Duration::from_millis(1500);
pub const LEG_STAGGER_DELAY: Duration = Duration::from_millis(1000); // Чуть ускорили старт
pub const TOPOLOGY_PRINT_INTERVAL: Duration = Duration::from_secs(15);
// --- Настройки Stealth Fallback (Маскировка) ---
pub const STEALTH_FALLBACK_HOST: &str = "ubuntu.com:443";
pub const FALLBACK_CONNECT_TIMEOUT: Duration = Duration::from_secs(5);
// --- Таймауты Handshake ---
pub const TLS_HELLO_TIMEOUT: Duration = Duration::from_secs(10);
pub const SECURE_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(20);