refactor: update tunnel MTU, optimize Frame parsing, and improve connection stability
This commit is contained in:
@@ -40,7 +40,7 @@ impl NetworkConfig {
|
||||
|
||||
// Емкость каналов: 512 пакетов (~700КБ).
|
||||
// Этого достаточно, чтобы сгладить лаги радио-эфира на телефоне.
|
||||
channel_capacity: 2048,
|
||||
channel_capacity: 8192,
|
||||
|
||||
// Окна smoltcp (важно для Download)
|
||||
// Увеличиваем до 512KB для тяжелых профилей
|
||||
|
||||
@@ -73,8 +73,6 @@ impl SessionManager {
|
||||
for entry in self.sessions.iter() {
|
||||
let session_id = entry.key();
|
||||
let muxer = entry.value();
|
||||
|
||||
// Вызываем уже существующий метод печати дерева у Muxer
|
||||
muxer.print_topology_tree();
|
||||
}
|
||||
info!("📊 ---------------------------------------");
|
||||
@@ -111,6 +109,102 @@ impl ClientHandler {
|
||||
socket.local_addr().ok().map(|a| a.ip())
|
||||
}
|
||||
|
||||
pub async fn perform_handshake(
|
||||
mut stream: tokio::net::TcpStream,
|
||||
session_id: &str,
|
||||
leg_id: u32,
|
||||
) -> Result<
|
||||
(
|
||||
OwnedReadHalf,
|
||||
OwnedWriteHalf,
|
||||
crate::nrxp::RxCodec,
|
||||
crate::nrxp::TxCodec,
|
||||
),
|
||||
AppError,
|
||||
> {
|
||||
stream.set_nodelay(true).unwrap_or_default();
|
||||
let mut conn = Connection::new(stream);
|
||||
let mut session_keys = SessionKeys::new(true);
|
||||
let ch =
|
||||
TlsBridge::wrap_client_hello(&BrowserProfile::CHROME_131, "ubuntu.com", &session_keys);
|
||||
|
||||
conn.outbound
|
||||
.write_all(&ch)
|
||||
.await
|
||||
.map_err(|e| AppError::new(ERR_INFRA_TIMEOUT, "Сбой сети", e.to_string()))?;
|
||||
|
||||
loop {
|
||||
match TlsBridge::unpack_handshake(&mut conn.read_buf) {
|
||||
Ok(Some(msg)) => {
|
||||
session_keys.update_keys(msg.random(), msg.extensions(), false)?;
|
||||
break;
|
||||
}
|
||||
Ok(None) => {
|
||||
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(AppError::new(
|
||||
ERR_INFRA_TIMEOUT,
|
||||
"Разрыв соединения",
|
||||
format!("EOF on handshake"),
|
||||
))
|
||||
}
|
||||
Ok(Ok(_)) => continue,
|
||||
Ok(Err(e)) => {
|
||||
return Err(AppError::new(
|
||||
ERR_INFRA_TIMEOUT,
|
||||
"Ошибка чтения",
|
||||
e.to_string(),
|
||||
))
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(AppError::new(
|
||||
ERR_INFRA_TIMEOUT,
|
||||
"Таймаут handshake",
|
||||
"Handshake read timeout",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(AppError::new(
|
||||
ERR_NET_TLS_TAMPER,
|
||||
"Ошибка TLS",
|
||||
format!("TLS error: {:?}", e.stage),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (tx_key, tx_iv, rx_key, rx_iv) = session_keys.get_aead_parameters();
|
||||
let mut cipher = ChaChaCipher::new();
|
||||
cipher.set_keys(tx_key, tx_iv, rx_key, rx_iv);
|
||||
let codec = Codec::new(cipher, session_keys.get_auth_key());
|
||||
let (rx_codec, mut tx_codec) = codec.split();
|
||||
|
||||
let auth_payload = Bytes::from(format!("{}:{}", session_id, leg_id));
|
||||
let encrypted_auth = tx_codec
|
||||
.encode_frame(0, FrameType::Heartbeat, auth_payload)
|
||||
.map_err(|e| {
|
||||
AppError::new(
|
||||
ERR_NET_TLS_TAMPER,
|
||||
"Сбой шифрования",
|
||||
format!("Failed to encrypt Auth: {:?}", e),
|
||||
)
|
||||
})?;
|
||||
|
||||
conn.outbound
|
||||
.write_all(&encrypted_auth)
|
||||
.await
|
||||
.map_err(|e| AppError::new(ERR_INFRA_TIMEOUT, "Сбой отправки", e.to_string()))?;
|
||||
|
||||
Ok((conn.inbound, conn.outbound, rx_codec, tx_codec))
|
||||
}
|
||||
|
||||
async fn establish_leg(
|
||||
remote_proxy_addr: &str,
|
||||
leg_id: u32,
|
||||
@@ -149,86 +243,8 @@ impl ClientHandler {
|
||||
})?
|
||||
.map_err(|e| AppError::new(ERR_INFRA_TIMEOUT, "Сбой сокета", e.to_string()))?;
|
||||
|
||||
stream.set_nodelay(true).unwrap_or_default();
|
||||
|
||||
let mut conn = Connection::new(stream);
|
||||
let mut session_keys = SessionKeys::new(true);
|
||||
let ch =
|
||||
TlsBridge::wrap_client_hello(&BrowserProfile::CHROME_131, "ubuntu.com", &session_keys);
|
||||
|
||||
conn.outbound
|
||||
.write_all(&ch)
|
||||
.await
|
||||
.map_err(|e| AppError::new(ERR_INFRA_TIMEOUT, "Сбой сети", e.to_string()))?;
|
||||
|
||||
loop {
|
||||
match TlsBridge::unpack_handshake(&mut conn.read_buf) {
|
||||
Ok(Some(msg)) => {
|
||||
session_keys.update_keys(msg.random(), msg.extensions(), false)?;
|
||||
break;
|
||||
}
|
||||
Ok(None) => {
|
||||
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(AppError::new(
|
||||
ERR_INFRA_TIMEOUT,
|
||||
"Разрыв соединения",
|
||||
format!("EOF on {}", leg_name),
|
||||
))
|
||||
}
|
||||
Ok(Ok(_)) => continue,
|
||||
Ok(Err(e)) => {
|
||||
return Err(AppError::new(
|
||||
ERR_INFRA_TIMEOUT,
|
||||
"Ошибка чтения",
|
||||
e.to_string(),
|
||||
))
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(AppError::new(
|
||||
ERR_INFRA_TIMEOUT,
|
||||
"Таймаут handshake",
|
||||
"Handshake read timeout",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(AppError::new(
|
||||
ERR_NET_TLS_TAMPER,
|
||||
"Ошибка TLS",
|
||||
format!("TLS error on {}: {:?}", leg_name, e.stage),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (tx_key, tx_iv, rx_key, rx_iv) = session_keys.get_aead_parameters();
|
||||
let mut cipher = ChaChaCipher::new();
|
||||
cipher.set_keys(tx_key, tx_iv, rx_key, rx_iv);
|
||||
let codec = Codec::new(cipher, session_keys.get_auth_key());
|
||||
let (rx_codec, mut tx_codec) = codec.split();
|
||||
|
||||
let auth_payload = Bytes::from(format!("{}:{}", session_id, leg_id));
|
||||
let encrypted_auth = tx_codec
|
||||
.encode_frame(0, FrameType::Heartbeat, auth_payload)
|
||||
.map_err(|e| {
|
||||
AppError::new(
|
||||
ERR_NET_TLS_TAMPER,
|
||||
"Сбой шифрования",
|
||||
format!("Failed to encrypt Auth: {:?}", e),
|
||||
)
|
||||
})?;
|
||||
|
||||
conn.outbound
|
||||
.write_all(&encrypted_auth)
|
||||
.await
|
||||
.map_err(|e| AppError::new(ERR_INFRA_TIMEOUT, "Сбой отправки", e.to_string()))?;
|
||||
let (inbound, outbound, rx_codec, tx_codec) =
|
||||
Self::perform_handshake(stream, session_id, leg_id).await?;
|
||||
|
||||
let cap = NetworkConfig::global().channel_capacity;
|
||||
let (control_tx, control_rx) = mpsc::channel::<MuxMessage>(cap);
|
||||
@@ -240,15 +256,19 @@ impl ClientHandler {
|
||||
let handler = Arc::new(StreamHandler::new(muxer.clone(), None));
|
||||
let engine = TunnelEngine {
|
||||
leg_id,
|
||||
inbound: conn.inbound,
|
||||
outbound: conn.outbound,
|
||||
rx_codec,
|
||||
tx_codec,
|
||||
read_buf: conn.read_buf,
|
||||
control_rx,
|
||||
data_rx,
|
||||
inbound: Some(inbound),
|
||||
outbound: Some(outbound),
|
||||
// 💡 ИЗМЕНЕНО: Передаем кодеки без Arc<Mutex>
|
||||
rx_codec: Some(rx_codec),
|
||||
tx_codec: Some(tx_codec),
|
||||
read_buf: BytesMut::with_capacity(NetworkConfig::global().connection_buf_size),
|
||||
control_rx: Some(control_rx),
|
||||
data_rx: Some(data_rx),
|
||||
handler,
|
||||
muxer: muxer.clone(),
|
||||
remote_addr: remote_proxy_addr.to_string(),
|
||||
session_id: session_id.to_string(),
|
||||
leg_status: crate::net::connection::engine::LegStatus::Active,
|
||||
};
|
||||
|
||||
let run_result = engine.run().await;
|
||||
@@ -272,6 +292,7 @@ impl ClientHandler {
|
||||
let registry: Arc<DashMap<u32, (u64, Ipv4Addr, u16, LocalProtocol)>> =
|
||||
Arc::new(DashMap::new());
|
||||
let local_to_global: Arc<DashMap<u64, u32>> = Arc::new(DashMap::new());
|
||||
let local_to_upload_tx: Arc<DashMap<u64, mpsc::Sender<Bytes>>> = Arc::new(DashMap::new());
|
||||
|
||||
let watcher_muxer = muxer.clone();
|
||||
tokio::spawn(async move {
|
||||
@@ -281,8 +302,6 @@ impl ClientHandler {
|
||||
interval.tick().await;
|
||||
let current_ip = Self::get_local_ip();
|
||||
if current_ip != last_ip {
|
||||
// ФИКС: Сбрасываем туннели только при реальном переходе (например, Wi-Fi на LTE)
|
||||
// Игнорируем кратковременную потерю сети (None), TCP сам справится с задержкой
|
||||
if current_ip.is_some() && last_ip.is_some() {
|
||||
netrunner_logger::warn!(
|
||||
"🌐 Network Change Detected: {:?} -> {:?}",
|
||||
@@ -346,12 +365,14 @@ impl ClientHandler {
|
||||
);
|
||||
|
||||
let cap = NetworkConfig::global().channel_capacity;
|
||||
|
||||
let (v_tx, mut v_rx) = mpsc::channel::<Bytes>(cap);
|
||||
muxer_inner.register_stream(global_stream_id, v_tx);
|
||||
|
||||
let tx_to_tun = tx_to_engine.clone();
|
||||
let reg = registry.clone();
|
||||
let l2g = local_to_global.clone(); // 🔥 Клонируем для очистки
|
||||
let l2g = local_to_global.clone();
|
||||
let up_tx_map = local_to_upload_tx.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
while let Some(back_payload) = v_rx.recv().await {
|
||||
@@ -379,12 +400,11 @@ impl ClientHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// 🔥 ФИКС УТЕЧКИ ПАМЯТИ: Сборщик мусора
|
||||
// Если цикл завершился (Muxer удалил v_tx), стираем мертвые сессии
|
||||
if let Some((_, (orig_local_id, _, _, _))) =
|
||||
reg.remove(&global_stream_id)
|
||||
{
|
||||
l2g.remove(&orig_local_id);
|
||||
up_tx_map.remove(&orig_local_id);
|
||||
debug!(
|
||||
global_stream_id,
|
||||
"🧹 Garbage Collector: Cleaned up dead registry stream"
|
||||
@@ -392,31 +412,54 @@ impl ClientHandler {
|
||||
}
|
||||
});
|
||||
|
||||
let _ = muxer_inner
|
||||
.send_control(global_stream_id, f_type, payload)
|
||||
.await;
|
||||
let (up_tx, mut up_rx) = mpsc::channel::<Bytes>(cap);
|
||||
local_to_upload_tx.insert(local_socket_id, up_tx);
|
||||
|
||||
let m_clone = muxer_inner.clone();
|
||||
let is_udp = raw_frame.protocol == LocalProtocol::Udp;
|
||||
|
||||
tokio::spawn(async move {
|
||||
let _ = m_clone
|
||||
.send_control(global_stream_id, f_type, payload)
|
||||
.await;
|
||||
|
||||
while let Some(data_payload) = up_rx.recv().await {
|
||||
let _ = m_clone
|
||||
.send_data_safe(global_stream_id, data_payload, is_udp)
|
||||
.await;
|
||||
}
|
||||
});
|
||||
}
|
||||
FrameType::Data | FrameType::UdpData => {
|
||||
let global_id = local_to_global.get(&local_socket_id).map(|id| *id);
|
||||
|
||||
if let Some(id) = global_id {
|
||||
let _ = muxer_inner
|
||||
.send_data_safe(
|
||||
id,
|
||||
payload,
|
||||
raw_frame.protocol == LocalProtocol::Udp,
|
||||
)
|
||||
.await;
|
||||
if let Some(up_tx) = local_to_upload_tx.get(&local_socket_id) {
|
||||
if let Err(mpsc::error::TrySendError::Full(_)) =
|
||||
up_tx.try_send(payload)
|
||||
{
|
||||
netrunner_logger::trace!(
|
||||
local_socket_id,
|
||||
"⚠️ Upload stream buffer full, dropping frame (TCP will retransmit)"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
FrameType::Close => {
|
||||
if let Some(kv) = local_to_global.remove(&local_socket_id) {
|
||||
let global_stream_id = kv.1;
|
||||
let _ = muxer_inner
|
||||
.send_control(global_stream_id, FrameType::Close, Bytes::new())
|
||||
.await;
|
||||
muxer_inner.remove_stream(global_stream_id);
|
||||
|
||||
let m_clone = muxer_inner.clone();
|
||||
tokio::spawn(async move {
|
||||
let _ = m_clone
|
||||
.send_control(
|
||||
global_stream_id,
|
||||
FrameType::Close,
|
||||
Bytes::new(),
|
||||
)
|
||||
.await;
|
||||
m_clone.remove_stream(global_stream_id);
|
||||
});
|
||||
|
||||
registry.remove(&global_stream_id);
|
||||
local_to_upload_tx.remove(&local_socket_id);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -614,17 +657,23 @@ impl TunnelHandler for ServerHandler {
|
||||
});
|
||||
let handler = Arc::new(StreamHandler::new(muxer.clone(), Some(opener)));
|
||||
|
||||
let log_session_id = session_id.clone();
|
||||
|
||||
let engine = TunnelEngine {
|
||||
leg_id,
|
||||
inbound,
|
||||
outbound,
|
||||
rx_codec,
|
||||
tx_codec,
|
||||
inbound: Some(inbound),
|
||||
outbound: Some(outbound),
|
||||
// 💡 ИЗМЕНЕНО: Передаем кодеки без Arc<Mutex>
|
||||
rx_codec: Some(rx_codec),
|
||||
tx_codec: Some(tx_codec),
|
||||
read_buf,
|
||||
control_rx,
|
||||
data_rx,
|
||||
control_rx: Some(control_rx),
|
||||
data_rx: Some(data_rx),
|
||||
handler,
|
||||
muxer: muxer.clone(),
|
||||
remote_addr: String::new(),
|
||||
session_id,
|
||||
leg_status: crate::net::connection::engine::LegStatus::Active,
|
||||
};
|
||||
|
||||
let res = engine.run().await;
|
||||
@@ -633,7 +682,7 @@ impl TunnelHandler for ServerHandler {
|
||||
|
||||
if muxer.active_legs_count() == 0 {
|
||||
let sm = self.session_manager.clone();
|
||||
let sid = session_id.clone();
|
||||
let sid = log_session_id;
|
||||
let m = muxer.clone();
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(120)).await;
|
||||
|
||||
+275
-158
@@ -1,7 +1,9 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use netrunner_logger::{debug, error, info, AppError, ERR_INFRA_TIMEOUT, ERR_NET_TLS_TAMPER};
|
||||
use netrunner_logger::{
|
||||
error, info, AppError, ERR_INFRA_TIMEOUT, ERR_NET_TLS_TAMPER, ERR_SYS_PANIC,
|
||||
};
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
net::tcp::{OwnedReadHalf, OwnedWriteHalf},
|
||||
@@ -18,170 +20,285 @@ use crate::{
|
||||
nrxp::{ErrorAction, FrameType, RxCodec, TxCodec},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum LegStatus {
|
||||
Active,
|
||||
Reconnecting,
|
||||
}
|
||||
|
||||
pub(crate) struct TunnelEngine {
|
||||
pub inbound: OwnedReadHalf,
|
||||
pub outbound: OwnedWriteHalf,
|
||||
pub rx_codec: RxCodec,
|
||||
pub tx_codec: TxCodec,
|
||||
pub inbound: Option<OwnedReadHalf>,
|
||||
pub outbound: Option<OwnedWriteHalf>,
|
||||
pub remote_addr: String,
|
||||
pub session_id: String,
|
||||
pub leg_status: LegStatus,
|
||||
// 💡 ИЗМЕНЕНО: Кодеки теперь хранятся как Option без Arc/Mutex
|
||||
pub rx_codec: Option<RxCodec>,
|
||||
pub tx_codec: Option<TxCodec>,
|
||||
pub read_buf: BytesMut,
|
||||
pub control_rx: Receiver<MuxMessage>,
|
||||
pub data_rx: Receiver<MuxMessage>,
|
||||
pub control_rx: Option<Receiver<MuxMessage>>,
|
||||
pub data_rx: Option<Receiver<MuxMessage>>,
|
||||
pub handler: Arc<StreamHandler>,
|
||||
pub leg_id: u32,
|
||||
pub muxer: Arc<crate::net::connection::muxer::Muxer>,
|
||||
}
|
||||
|
||||
impl TunnelEngine {
|
||||
#[instrument(skip_all, fields(leg_id = self.leg_id))]
|
||||
pub async fn run(self) -> Result<(), AppError> {
|
||||
let inbound = self.inbound;
|
||||
let outbound = self.outbound;
|
||||
let read_buf = self.read_buf;
|
||||
pub async fn attempt_reconnect(
|
||||
&mut self,
|
||||
) -> Result<(OwnedReadHalf, OwnedWriteHalf, RxCodec, TxCodec), AppError> {
|
||||
info!("🔄 Attempting reconnect to {}", self.remote_addr);
|
||||
let stream = tokio::time::timeout(
|
||||
tokio::time::Duration::from_secs(5),
|
||||
tokio::net::TcpStream::connect(&self.remote_addr),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| AppError::new(ERR_INFRA_TIMEOUT, "Сбой сети", "Reconnect timeout"))?
|
||||
.map_err(|e| AppError::new(ERR_INFRA_TIMEOUT, "Сбой сети", e.to_string()))?;
|
||||
|
||||
let mut rx_codec = self.rx_codec;
|
||||
let mut tx_codec = self.tx_codec;
|
||||
|
||||
let mut control_rx = self.control_rx;
|
||||
let mut data_rx = self.data_rx;
|
||||
let handler = self.handler;
|
||||
|
||||
let leg_id = self.leg_id;
|
||||
let muxer = self.muxer.clone();
|
||||
let muxer_pong = muxer.clone();
|
||||
|
||||
let token = CancellationToken::new();
|
||||
let token_reader = token.clone();
|
||||
let token_writer = token.clone();
|
||||
|
||||
let reader_handle = tokio::spawn(async move {
|
||||
let mut read_buf = read_buf;
|
||||
let mut inbound = inbound;
|
||||
|
||||
loop {
|
||||
if read_buf.is_empty() {
|
||||
read_buf.clear();
|
||||
}
|
||||
read_buf.reserve(16384);
|
||||
|
||||
tokio::select! {
|
||||
_ = token_reader.cancelled() => {
|
||||
info!("Reader Task: Shutdown signal received.");
|
||||
break;
|
||||
}
|
||||
res = inbound.read_buf(&mut read_buf) => {
|
||||
let n = res.map_err(|e| {
|
||||
netrunner_logger::AppError::new(
|
||||
netrunner_logger::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());
|
||||
}
|
||||
return Err(netrunner_logger::AppError::new(
|
||||
netrunner_logger::ERR_INFRA_TIMEOUT,
|
||||
"Соединение разорвано",
|
||||
"EOF occurred during read",
|
||||
));
|
||||
}
|
||||
|
||||
muxer.record_leg_rx(leg_id, n as u64);
|
||||
|
||||
let mut frames = Vec::new();
|
||||
|
||||
loop {
|
||||
match rx_codec.decode_inbound(&mut read_buf) {
|
||||
Ok(Some(frame)) => frames.push(frame),
|
||||
Ok(None) => break,
|
||||
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(netrunner_logger::AppError::new(
|
||||
netrunner_logger::ERR_NET_TLS_TAMPER,
|
||||
"Критическая ошибка шифрования",
|
||||
"Crypto drop",
|
||||
));
|
||||
}
|
||||
error!(error = ?e, "Codec inbound failed");
|
||||
return Err(netrunner_logger::AppError::new(
|
||||
netrunner_logger::ERR_NET_TLS_TAMPER,
|
||||
"Ошибка декодирования",
|
||||
format!("Codec error: {:?}", e),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
handler.handle(frame).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok::<(), AppError>(())
|
||||
});
|
||||
|
||||
let writer_handle = tokio::spawn(async move {
|
||||
let mut outbound = outbound;
|
||||
let mut heartbeat = tokio::time::interval(HEALTH_CHECK_INTERVAL);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = token_writer.cancelled() => break,
|
||||
msg_opt = control_rx.recv() => {
|
||||
if let Some(msg) = msg_opt {
|
||||
Self::handle_outbound(&mut outbound, &mut tx_codec, msg).await?;
|
||||
} else { break; }
|
||||
}
|
||||
_ = heartbeat.tick() => {
|
||||
muxer_pong.record_ping_sent(leg_id);
|
||||
let msg = MuxMessage { stream_id: 0, frame_type: FrameType::Heartbeat, data: Bytes::new() };
|
||||
Self::handle_outbound(&mut outbound, &mut tx_codec, msg).await?;
|
||||
}
|
||||
msg_opt = data_rx.recv() => {
|
||||
if let Some(msg) = msg_opt {
|
||||
Self::handle_outbound(&mut outbound, &mut tx_codec, msg).await?;
|
||||
} else { break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok::<(), AppError>(())
|
||||
});
|
||||
|
||||
let res: Result<(), netrunner_logger::AppError> = tokio::select! {
|
||||
res = reader_handle => res.unwrap_or_else(|e| Err(netrunner_logger::AppError::new(
|
||||
netrunner_logger::ERR_SYS_PANIC,
|
||||
"Сбой",
|
||||
format!("Reader panic: {}", e)
|
||||
))),
|
||||
res = writer_handle => res.unwrap_or_else(|e| Err(netrunner_logger::AppError::new(
|
||||
netrunner_logger::ERR_SYS_PANIC,
|
||||
"Сбой",
|
||||
format!("Writer panic: {}", e)
|
||||
))),
|
||||
};
|
||||
|
||||
token.cancel();
|
||||
|
||||
if let Err(e) = &res {
|
||||
error!("TunnelEngine critical failure: {}", e);
|
||||
}
|
||||
res
|
||||
crate::net::ClientHandler::perform_handshake(stream, &self.session_id, self.leg_id).await
|
||||
}
|
||||
|
||||
#[instrument(skip_all, fields(leg_id = self.leg_id))]
|
||||
pub async fn run(mut self) -> Result<(), AppError> {
|
||||
loop {
|
||||
// Проверяем наличие всех необходимых ресурсов
|
||||
if self.inbound.is_none()
|
||||
|| self.outbound.is_none()
|
||||
|| self.rx_codec.is_none()
|
||||
|| self.tx_codec.is_none()
|
||||
{
|
||||
self.leg_status = LegStatus::Reconnecting;
|
||||
match self.attempt_reconnect().await {
|
||||
Ok((new_in, new_out, new_rx, new_tx)) => {
|
||||
self.inbound = Some(new_in);
|
||||
self.outbound = Some(new_out);
|
||||
self.rx_codec = Some(new_rx);
|
||||
self.tx_codec = Some(new_tx);
|
||||
self.leg_status = LegStatus::Active;
|
||||
info!("✅ Leg {} reconnected successfully", self.leg_id);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Reconnect failed for leg {}: {}", self.leg_id, e);
|
||||
let jitter = rand::random::<u64>() % 1000;
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(2000 + jitter)).await;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let inbound = self.inbound.take().unwrap();
|
||||
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");
|
||||
let mut data_rx = self.data_rx.take().expect("data_rx is missing");
|
||||
|
||||
let handler = self.handler.clone();
|
||||
let leg_id = self.leg_id;
|
||||
let muxer = self.muxer.clone();
|
||||
let muxer_pong = self.muxer.clone();
|
||||
|
||||
let token = CancellationToken::new();
|
||||
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 {
|
||||
if read_buf.len() > MAX_BUFFER_SIZE {
|
||||
error!("CRITICAL: Read buffer exceeded 1MB (OOM Protection). Dropping connection!");
|
||||
return Err(AppError::new(
|
||||
ERR_INFRA_TIMEOUT,
|
||||
"Переполнение буфера",
|
||||
"Read buffer exceeded MAX_BUFFER_SIZE",
|
||||
));
|
||||
}
|
||||
|
||||
if read_buf.is_empty() {
|
||||
read_buf.clear();
|
||||
}
|
||||
read_buf.reserve(16384);
|
||||
|
||||
tokio::select! {
|
||||
_ = token_reader.cancelled() => {
|
||||
info!("Reader Task: Shutdown signal received.");
|
||||
break;
|
||||
}
|
||||
res = inbound.read_buf(&mut read_buf) => {
|
||||
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
|
||||
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),
|
||||
Ok(None) => break,
|
||||
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",
|
||||
));
|
||||
}
|
||||
error!(error = ?e, "Codec inbound failed");
|
||||
return Err(AppError::new(
|
||||
ERR_NET_TLS_TAMPER,
|
||||
"Ошибка декодирования",
|
||||
format!("Codec error: {:?}", e),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
handler.handle(frame).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok::<_, AppError>((false, read_buf, rx_codec))
|
||||
});
|
||||
|
||||
// ПИШУЩАЯ ЗАДАЧА
|
||||
let mut writer_handle = tokio::spawn(async move {
|
||||
let mut outbound = outbound;
|
||||
let mut heartbeat = tokio::time::interval(HEALTH_CHECK_INTERVAL);
|
||||
|
||||
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() };
|
||||
if let Err(e) = Self::handle_outbound(&mut outbound, &mut tx_codec, msg).await {
|
||||
return Err((e, control_rx, data_rx, tx_codec));
|
||||
}
|
||||
}
|
||||
msg_opt = data_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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
// Возвращаем receiver'ы и tx_codec обратно при корректном завершении
|
||||
Ok::<
|
||||
_,
|
||||
(
|
||||
AppError,
|
||||
Receiver<MuxMessage>,
|
||||
Receiver<MuxMessage>,
|
||||
TxCodec,
|
||||
),
|
||||
>((control_rx, data_rx, tx_codec))
|
||||
});
|
||||
|
||||
let res: Result<(), AppError> = tokio::select! {
|
||||
res_reader = &mut reader_handle => {
|
||||
match res_reader {
|
||||
Ok(Ok((is_eof, r_buf, returned_rx_codec))) => {
|
||||
self.read_buf = r_buf;
|
||||
self.rx_codec = Some(returned_rx_codec); // Восстанавливаем
|
||||
if is_eof {
|
||||
token.cancel();
|
||||
let w_res = writer_handle.await.unwrap();
|
||||
let (c_rx, d_rx, returned_tx_codec) = match w_res {
|
||||
Ok((c, d, t)) => (c, d, t),
|
||||
Err((_, c, d, t)) => (c, d, t),
|
||||
};
|
||||
self.control_rx = Some(c_rx);
|
||||
self.data_rx = Some(d_rx);
|
||||
self.tx_codec = Some(returned_tx_codec);
|
||||
|
||||
self.inbound = None;
|
||||
self.outbound = None;
|
||||
continue;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
Ok(Err(e)) => Err(e),
|
||||
Err(e) => Err(AppError::new(ERR_SYS_PANIC, "Сбой", format!("Reader panic: {}", e))),
|
||||
}
|
||||
},
|
||||
res_writer = &mut writer_handle => {
|
||||
match res_writer {
|
||||
Ok(Ok((c_rx, d_rx, returned_tx_codec))) => {
|
||||
self.control_rx = Some(c_rx);
|
||||
self.data_rx = Some(d_rx);
|
||||
self.tx_codec = Some(returned_tx_codec);
|
||||
Ok(())
|
||||
}
|
||||
Ok(Err((e, c_rx, d_rx, returned_tx_codec))) => {
|
||||
self.control_rx = Some(c_rx);
|
||||
self.data_rx = Some(d_rx);
|
||||
self.tx_codec = Some(returned_tx_codec);
|
||||
Err(e)
|
||||
}
|
||||
Err(e) => Err(AppError::new(ERR_SYS_PANIC, "Сбой", format!("Writer panic: {}", e))),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
token.cancel();
|
||||
|
||||
if let Err(e) = &res {
|
||||
error!("TunnelEngine critical failure: {}", e);
|
||||
return res;
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 💡 ИЗМЕНЕНО: Принимает &mut TxCodec, синхронное и сверхбыстрое шифрование
|
||||
async fn handle_outbound(
|
||||
outbound: &mut OwnedWriteHalf,
|
||||
tx_codec: &mut TxCodec,
|
||||
@@ -201,8 +318,8 @@ impl TunnelEngine {
|
||||
Ok(pkt) => packets.push(pkt),
|
||||
Err(e) => {
|
||||
error!(stream_id, error = ?e, "Encryption failed for TCP chunk");
|
||||
return Err(netrunner_logger::AppError::new(
|
||||
netrunner_logger::ERR_NET_TLS_TAMPER,
|
||||
return Err(AppError::new(
|
||||
ERR_NET_TLS_TAMPER,
|
||||
"Ошибка шифрования пакета",
|
||||
format!("Encryption error: {:?}", e),
|
||||
));
|
||||
@@ -214,8 +331,8 @@ impl TunnelEngine {
|
||||
Ok(pkt) => packets.push(pkt),
|
||||
Err(e) => {
|
||||
error!(stream_id, error = ?e, "Encryption failed for control/udp frame");
|
||||
return Err(netrunner_logger::AppError::new(
|
||||
netrunner_logger::ERR_NET_TLS_TAMPER,
|
||||
return Err(AppError::new(
|
||||
ERR_NET_TLS_TAMPER,
|
||||
"Ошибка шифрования пакета",
|
||||
format!("Encryption error: {:?}", e),
|
||||
));
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::net::{TcpStream, UdpSocket};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::net::{
|
||||
connection::{
|
||||
@@ -19,44 +20,66 @@ pub struct RemoteOpener {
|
||||
}
|
||||
|
||||
impl RemoteOpener {
|
||||
pub async fn open_tcp(&self, stream_id: u32, target: String, v_rx: mpsc::Receiver<Bytes>) {
|
||||
pub async fn open_tcp(
|
||||
&self,
|
||||
stream_id: u32,
|
||||
target: String,
|
||||
v_rx: mpsc::Receiver<Bytes>,
|
||||
token: CancellationToken,
|
||||
) {
|
||||
let muxer = self.muxer.clone();
|
||||
tokio::spawn(async move {
|
||||
info!(stream_id, "🌐 [Remote] Connecting to {}", target);
|
||||
let start = Instant::now();
|
||||
|
||||
let connect_res =
|
||||
tokio::time::timeout(Duration::from_secs(7), TcpStream::connect(&target)).await;
|
||||
|
||||
match connect_res {
|
||||
Ok(Ok(stream)) => {
|
||||
info!(stream_id, "✅ [Remote] Connected in {:?}", start.elapsed());
|
||||
let (r, w) = stream.into_split();
|
||||
run_tcp_bridge(stream_id, r, w, muxer.clone(), v_rx).await;
|
||||
tokio::select! {
|
||||
_ = token.cancelled() => {
|
||||
debug!(stream_id, "🔪 Target connection cancelled by Eviction");
|
||||
return;
|
||||
}
|
||||
_ => {
|
||||
error!(
|
||||
stream_id,
|
||||
"❌ [Remote] Target connection failed: {}", target
|
||||
);
|
||||
let _ = muxer
|
||||
.send_control(stream_id, FrameType::Close, Bytes::new())
|
||||
.await;
|
||||
connect_res = tokio::time::timeout(Duration::from_secs(7), TcpStream::connect(&target)) => {
|
||||
match connect_res {
|
||||
Ok(Ok(stream)) => {
|
||||
info!(stream_id, "✅ [Remote] Connected in {:?}", start.elapsed());
|
||||
let (r, w) = stream.into_split();
|
||||
|
||||
// 🔥 Защищаем и сам мост токеном отмены
|
||||
tokio::select! {
|
||||
_ = token.cancelled() => { debug!(stream_id, "🔪 TCP bridge closed by Eviction"); }
|
||||
_ = run_tcp_bridge(stream_id, r, w, muxer.clone(), v_rx) => {}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
error!(stream_id, "❌ [Remote] Target connection failed: {}", target);
|
||||
let _ = muxer.send_control(stream_id, FrameType::Close, Bytes::new()).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
muxer.remove_stream(stream_id);
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn open_udp(&self, stream_id: u32, target: String, v_rx: mpsc::Receiver<Bytes>) {
|
||||
pub async fn open_udp(
|
||||
&self,
|
||||
stream_id: u32,
|
||||
target: String,
|
||||
v_rx: mpsc::Receiver<Bytes>,
|
||||
token: CancellationToken,
|
||||
) {
|
||||
let muxer = self.muxer.clone();
|
||||
tokio::spawn(async move {
|
||||
info!(stream_id, "🚀 [Remote] Binding UDP for {}", target);
|
||||
let socket = UdpSocket::bind("0.0.0.0:0").await.ok();
|
||||
if let Some(s) = socket {
|
||||
if s.connect(&target).await.is_ok() {
|
||||
run_udp_bridge(stream_id, s, muxer.clone(), v_rx).await;
|
||||
}
|
||||
tokio::select! {
|
||||
_ = token.cancelled() => { return; }
|
||||
_ = async {
|
||||
let socket = UdpSocket::bind("0.0.0.0:0").await.ok();
|
||||
if let Some(s) = socket {
|
||||
if s.connect(&target).await.is_ok() {
|
||||
run_udp_bridge(stream_id, s, muxer.clone(), v_rx).await;
|
||||
}
|
||||
}
|
||||
} => {}
|
||||
}
|
||||
muxer.remove_stream(stream_id);
|
||||
});
|
||||
@@ -81,23 +104,27 @@ impl StreamHandler {
|
||||
let payload = frame.payload.as_ref();
|
||||
if payload == b"PING" {
|
||||
trace!(stream_id, "🤝 [Tunnel] PING received, replying PONG");
|
||||
let _ = self
|
||||
.muxer
|
||||
.send_control(stream_id, FrameType::Heartbeat, Bytes::from("PONG"))
|
||||
.await;
|
||||
let muxer = self.muxer.clone();
|
||||
tokio::spawn(async move {
|
||||
let _ = muxer
|
||||
.send_control(stream_id, FrameType::Heartbeat, Bytes::from("PONG"))
|
||||
.await;
|
||||
});
|
||||
} else if payload == b"PONG" {
|
||||
trace!(stream_id, "🤝 [Tunnel] PONG received");
|
||||
self.muxer.dispatch_to_local(stream_id, frame.payload).await;
|
||||
self.muxer.dispatch_to_local(stream_id, frame.payload);
|
||||
} else {
|
||||
if self.opener.is_some() {
|
||||
trace!(
|
||||
stream_id,
|
||||
"💓 [Server] Standard Heartbeat received, sending reply"
|
||||
);
|
||||
let _ = self
|
||||
.muxer
|
||||
.send_control(stream_id, FrameType::Heartbeat, Bytes::new())
|
||||
.await;
|
||||
let muxer = self.muxer.clone();
|
||||
tokio::spawn(async move {
|
||||
let _ = muxer
|
||||
.send_control(stream_id, FrameType::Heartbeat, Bytes::new())
|
||||
.await;
|
||||
});
|
||||
} else {
|
||||
trace!(stream_id, "💓 [Client] Standard Heartbeat reply received");
|
||||
}
|
||||
@@ -114,7 +141,8 @@ impl StreamHandler {
|
||||
}
|
||||
|
||||
FrameType::Data | FrameType::UdpData => {
|
||||
self.muxer.dispatch_to_local(stream_id, frame.payload).await;
|
||||
// Полностью синхронный вызов
|
||||
self.muxer.dispatch_to_local(stream_id, frame.payload);
|
||||
}
|
||||
|
||||
FrameType::Close => {
|
||||
@@ -130,22 +158,26 @@ impl StreamHandler {
|
||||
if let Some(opener) = &self.opener {
|
||||
let cap = NetworkConfig::global().channel_capacity;
|
||||
let (v_tx, v_rx) = mpsc::channel::<Bytes>(cap);
|
||||
self.muxer.register_stream(stream_id, v_tx);
|
||||
|
||||
// 🔥 Собираем токен для мгновенного обрыва связи при Eviction
|
||||
let cancel_token = self.muxer.register_stream(stream_id, v_tx);
|
||||
|
||||
if is_udp {
|
||||
opener.open_udp(stream_id, target, v_rx).await;
|
||||
opener.open_udp(stream_id, target, v_rx, cancel_token).await;
|
||||
} else {
|
||||
opener.open_tcp(stream_id, target, v_rx).await;
|
||||
opener.open_tcp(stream_id, target, v_rx, cancel_token).await;
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
stream_id,
|
||||
"⚠️ [Tunnel] Rejected incoming connection to {} (Client mode)", target
|
||||
);
|
||||
let _ = self
|
||||
.muxer
|
||||
.send_control(stream_id, FrameType::Close, Bytes::new())
|
||||
.await;
|
||||
let muxer = self.muxer.clone();
|
||||
tokio::spawn(async move {
|
||||
let _ = muxer
|
||||
.send_control(stream_id, FrameType::Close, Bytes::new())
|
||||
.await;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+150
-149
@@ -1,10 +1,11 @@
|
||||
use bytes::Bytes;
|
||||
use dashmap::DashMap;
|
||||
use netrunner_logger::{AppError, ERR_INFRA_TIMEOUT, info, instrument, trace, warn};
|
||||
use netrunner_logger::{info, instrument, trace, warn, AppError, ERR_INFRA_TIMEOUT};
|
||||
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::time::Instant;
|
||||
use tokio::sync::mpsc::Sender;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::net::{HEALTH_CHECK_TIMEOUT, MAX_TUNNEL_LEGS, MUXER_POOL_SIZE};
|
||||
use crate::nrxp::FrameType;
|
||||
@@ -24,6 +25,7 @@ pub struct StreamStats {
|
||||
|
||||
#[derive(Clone)]
|
||||
struct MuxLeg {
|
||||
id: u32,
|
||||
control_tx: Sender<MuxMessage>,
|
||||
data_tx: Sender<MuxMessage>,
|
||||
stats: Arc<LegStats>,
|
||||
@@ -31,12 +33,10 @@ struct MuxLeg {
|
||||
|
||||
impl MuxLeg {
|
||||
fn congestion_factor(&self) -> f64 {
|
||||
// СТАЛО: Вычисляем реальную нагрузку на канал
|
||||
let max = self.data_tx.max_capacity();
|
||||
let current_capacity = self.data_tx.capacity();
|
||||
let filled = max.saturating_sub(current_capacity);
|
||||
|
||||
(filled as f64) / (max as f64) // Возвращает от 0.0 (пусто) до 1.0 (забито наглухо)
|
||||
(filled as f64) / (max as f64)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,12 +46,10 @@ struct IdGenerator {
|
||||
|
||||
impl IdGenerator {
|
||||
pub fn new(is_client: bool) -> Self {
|
||||
let start = if is_client { 1 } else { 2 };
|
||||
Self {
|
||||
counter: AtomicU32::new(start),
|
||||
counter: AtomicU32::new(if is_client { 1 } else { 2 }),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&self) -> u32 {
|
||||
self.counter.fetch_add(2, Ordering::Relaxed)
|
||||
}
|
||||
@@ -69,7 +67,11 @@ pub static GLOBAL_MIN_RTT: AtomicU32 = AtomicU32::new(250);
|
||||
#[derive(Clone)]
|
||||
pub struct Muxer {
|
||||
legs: Arc<DashMap<u32, MuxLeg>>,
|
||||
streams: Arc<DashMap<u32, (Sender<Bytes>, Arc<StreamStats>)>>,
|
||||
// 🔥 ОПТИМИЗАЦИЯ: Lock-Free кэш для горячего пути
|
||||
active_legs_cache: Arc<RwLock<Arc<Vec<MuxLeg>>>>,
|
||||
|
||||
// Добавили CancellationToken для предотвращения утечек памяти (Зомби-задач)
|
||||
streams: Arc<DashMap<u32, (Sender<Bytes>, Arc<StreamStats>, CancellationToken)>>,
|
||||
stream_bindings: Arc<DashMap<u32, u32>>,
|
||||
pending_pings: Arc<DashMap<u32, Instant>>,
|
||||
id_gen: Arc<IdGenerator>,
|
||||
@@ -80,6 +82,7 @@ impl Muxer {
|
||||
pub fn new(is_client: bool, session_id: String) -> Self {
|
||||
Self {
|
||||
legs: Arc::new(DashMap::new()),
|
||||
active_legs_cache: Arc::new(RwLock::new(Arc::new(Vec::new()))),
|
||||
streams: Arc::new(DashMap::new()),
|
||||
stream_bindings: Arc::new(DashMap::new()),
|
||||
id_gen: Arc::new(IdGenerator::new(is_client)),
|
||||
@@ -88,6 +91,11 @@ impl Muxer {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_legs_cache(&self) {
|
||||
let new_cache: Vec<MuxLeg> = self.legs.iter().map(|kv| kv.value().clone()).collect();
|
||||
*self.active_legs_cache.write().unwrap() = Arc::new(new_cache);
|
||||
}
|
||||
|
||||
pub fn add_leg(
|
||||
&self,
|
||||
leg_id: u32,
|
||||
@@ -95,18 +103,20 @@ impl Muxer {
|
||||
data_tx: Sender<MuxMessage>,
|
||||
) {
|
||||
if self.legs.len() >= MAX_TUNNEL_LEGS as usize && !self.legs.contains_key(&leg_id) {
|
||||
warn!(leg_id, "MUXER: Max legs reached: {}", MAX_TUNNEL_LEGS);
|
||||
warn!(leg_id, "MUXER: Max legs reached");
|
||||
return;
|
||||
}
|
||||
|
||||
self.legs.insert(
|
||||
leg_id,
|
||||
MuxLeg {
|
||||
id: leg_id,
|
||||
control_tx,
|
||||
data_tx,
|
||||
stats: Arc::new(LegStats::default()),
|
||||
},
|
||||
);
|
||||
self.update_legs_cache(); // Обновляем Lock-Free кэш
|
||||
info!(
|
||||
leg_id,
|
||||
"MUXER: Physical TCP+TLS leg registered (Total: {})",
|
||||
@@ -115,71 +125,59 @@ impl Muxer {
|
||||
}
|
||||
|
||||
pub fn remove_leg(&self, leg_id: u32, tx: &Sender<MuxMessage>) {
|
||||
let should_remove = if let Some(leg) = self.legs.get(&leg_id) {
|
||||
leg.control_tx.same_channel(tx)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let should_remove = self
|
||||
.legs
|
||||
.get(&leg_id)
|
||||
.map_or(false, |leg| leg.control_tx.same_channel(tx));
|
||||
if should_remove {
|
||||
self.legs.remove(&leg_id);
|
||||
self.update_legs_cache(); // Обновляем Lock-Free кэш
|
||||
info!(
|
||||
leg_id,
|
||||
"MUXER: TCP leg removed safely, streams will re-balance"
|
||||
);
|
||||
} else {
|
||||
trace!(
|
||||
leg_id,
|
||||
"MUXER: Leg removal skipped (already removed or replaced by a new connection)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_all_legs(&self) {
|
||||
warn!("🚨 MUXER: Emergency reset! Removing all physical legs due to network change.");
|
||||
self.legs.clear();
|
||||
self.stream_bindings.clear();
|
||||
self.update_legs_cache();
|
||||
}
|
||||
|
||||
pub fn active_legs_count(&self) -> usize {
|
||||
self.legs.len()
|
||||
}
|
||||
|
||||
fn select_leg(&self, _frame_type: &FrameType, stream_id: u32) -> Option<(u32, MuxLeg)> {
|
||||
if self.legs.is_empty() {
|
||||
fn select_leg(&self, stream_id: u32) -> Option<MuxLeg> {
|
||||
let legs = self.active_legs_cache.read().unwrap().clone();
|
||||
if legs.is_empty() {
|
||||
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) {
|
||||
return Some((leg_id, leg.clone()));
|
||||
if let Some(leg) = legs.iter().find(|l| l.id == leg_id) {
|
||||
return Some(leg.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let mut candidates: Vec<(u32, MuxLeg)> = self
|
||||
.legs
|
||||
.iter()
|
||||
.map(|kv| (*kv.key(), kv.value().clone()))
|
||||
.collect();
|
||||
|
||||
candidates.sort_by(|(_, leg_a), (_, leg_b)| {
|
||||
let rtt_a = leg_a.stats.rtt_ms.load(Ordering::Relaxed) as f64;
|
||||
let rtt_b = leg_b.stats.rtt_ms.load(Ordering::Relaxed) as f64;
|
||||
let score_a = rtt_a + (leg_a.congestion_factor() * 2000.0);
|
||||
let score_b = rtt_b + (leg_b.congestion_factor() * 2000.0);
|
||||
|
||||
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)
|
||||
});
|
||||
|
||||
let pool_size = std::cmp::min(candidates.len(), MUXER_POOL_SIZE);
|
||||
let (selected_id, selected_leg) = candidates[stream_id as usize % pool_size].clone();
|
||||
let selected_leg = candidates[stream_id as usize % pool_size].clone();
|
||||
|
||||
self.stream_bindings.insert(stream_id, selected_id);
|
||||
|
||||
Some((selected_id, selected_leg))
|
||||
self.stream_bindings.insert(stream_id, selected_leg.id);
|
||||
Some(selected_leg)
|
||||
}
|
||||
|
||||
pub fn record_ping_sent(&self, leg_id: u32) {
|
||||
@@ -191,8 +189,6 @@ impl Muxer {
|
||||
let rtt = start_time.elapsed().as_millis() as u32;
|
||||
if let Some(leg) = self.legs.get(&leg_id) {
|
||||
leg.stats.rtt_ms.store(rtt, Ordering::Relaxed);
|
||||
trace!(leg_id, rtt, "💓 [Muxer] RTT updated for physical leg");
|
||||
|
||||
let min_rtt = self
|
||||
.legs
|
||||
.iter()
|
||||
@@ -200,68 +196,65 @@ impl Muxer {
|
||||
.filter(|&r| r > 0)
|
||||
.min()
|
||||
.unwrap_or(250);
|
||||
|
||||
GLOBAL_MIN_RTT.store(min_rtt, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(self, message), fields(
|
||||
session_id = %self.session_id,
|
||||
stream_id = message.stream_id,
|
||||
frame = ?message.frame_type
|
||||
))]
|
||||
pub async fn send_to_network(&self, message: MuxMessage) -> Result<(), AppError>{
|
||||
let size = message.data.len() as u64;
|
||||
let mut attempts = 0;
|
||||
|
||||
loop {
|
||||
if attempts >= 3 {
|
||||
return Err(AppError::new(ERR_INFRA_TIMEOUT, "Сбой передачи данных", "MUXER: All attempts to send failed"));
|
||||
#[instrument(skip(self, message), fields(session_id = %self.session_id, stream_id = message.stream_id, frame = ?message.frame_type))]
|
||||
pub async fn send_to_network(&self, message: MuxMessage) -> Result<(), AppError> {
|
||||
let leg = match self.select_leg(message.stream_id) {
|
||||
Some(l) => l,
|
||||
None => {
|
||||
return Err(AppError::new(
|
||||
ERR_INFRA_TIMEOUT,
|
||||
"Нет связи",
|
||||
"No active legs",
|
||||
))
|
||||
}
|
||||
attempts += 1;
|
||||
};
|
||||
|
||||
let (leg_id, leg) = match self.select_leg(&message.frame_type, message.stream_id) {
|
||||
Some(l) => l,
|
||||
None => return Err(AppError::new(ERR_INFRA_TIMEOUT, "Нет связи с сервером", "MUXER: No active physical legs available")),
|
||||
};
|
||||
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 target_tx = match message.frame_type {
|
||||
FrameType::Connect
|
||||
| FrameType::Close
|
||||
| FrameType::UdpConnect
|
||||
| FrameType::Heartbeat => &leg.control_tx,
|
||||
_ => &leg.data_tx,
|
||||
};
|
||||
let stream_id = message.stream_id;
|
||||
let size = message.data.len() as u64;
|
||||
|
||||
match tokio::time::timeout(
|
||||
std::time::Duration::from_secs(10),
|
||||
target_tx.send(message.clone()),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Ok(_)) => {
|
||||
if is_data {
|
||||
match target_tx.try_send(message) {
|
||||
Ok(_) => {
|
||||
leg.stats.tx_bytes.fetch_add(size, Ordering::Relaxed);
|
||||
if let Some(stream_ref) = self.streams.get(&message.stream_id) {
|
||||
if let Some(stream_ref) = self.streams.get(&stream_id) {
|
||||
stream_ref
|
||||
.value()
|
||||
.1
|
||||
.tx_bytes
|
||||
.fetch_add(size, Ordering::Relaxed);
|
||||
}
|
||||
return Ok(());
|
||||
Ok(())
|
||||
}
|
||||
Ok(Err(_)) => {
|
||||
self.remove_leg(leg_id, &leg.control_tx);
|
||||
continue;
|
||||
Err(tokio::sync::mpsc::error::TrySendError::Full(_)) => Ok(()), // HoL Blocking eliminated
|
||||
Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => {
|
||||
self.remove_leg(leg.id, &leg.control_tx);
|
||||
Err(AppError::new(ERR_INFRA_TIMEOUT, "Обрыв", "Leg closed"))
|
||||
}
|
||||
Err(_) => {
|
||||
warn!(
|
||||
message.stream_id,
|
||||
"Physical TCP TX full for 10s! Leg {} is dead. Evicting.", leg_id
|
||||
);
|
||||
self.remove_leg(leg_id, &leg.control_tx);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
match tokio::time::timeout(std::time::Duration::from_secs(2), target_tx.send(message))
|
||||
.await
|
||||
{
|
||||
Ok(Ok(_)) => Ok(()),
|
||||
_ => {
|
||||
self.remove_leg(leg.id, &leg.control_tx);
|
||||
Err(AppError::new(
|
||||
ERR_INFRA_TIMEOUT,
|
||||
"Таймаут",
|
||||
"Control send timeout",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -273,14 +266,13 @@ impl Muxer {
|
||||
data: Bytes,
|
||||
is_udp: bool,
|
||||
) -> Result<(), AppError> {
|
||||
let frame_type = if is_udp {
|
||||
FrameType::UdpData
|
||||
} else {
|
||||
FrameType::Data
|
||||
};
|
||||
self.send_to_network(MuxMessage {
|
||||
stream_id,
|
||||
frame_type,
|
||||
frame_type: if is_udp {
|
||||
FrameType::UdpData
|
||||
} else {
|
||||
FrameType::Data
|
||||
},
|
||||
data,
|
||||
})
|
||||
.await
|
||||
@@ -291,7 +283,7 @@ impl Muxer {
|
||||
stream_id: u32,
|
||||
f_type: FrameType,
|
||||
data: Bytes,
|
||||
) -> Result<(), AppError>{
|
||||
) -> Result<(), AppError> {
|
||||
self.send_to_network(MuxMessage {
|
||||
stream_id,
|
||||
frame_type: f_type,
|
||||
@@ -300,30 +292,35 @@ impl Muxer {
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn register_stream(&self, stream_id: u32, tx: Sender<Bytes>) {
|
||||
self.streams
|
||||
.insert(stream_id, (tx, Arc::new(StreamStats::default())));
|
||||
pub fn register_stream(&self, stream_id: u32, tx: Sender<Bytes>) -> CancellationToken {
|
||||
let token = CancellationToken::new();
|
||||
self.streams.insert(
|
||||
stream_id,
|
||||
(tx, Arc::new(StreamStats::default()), token.clone()),
|
||||
);
|
||||
token
|
||||
}
|
||||
|
||||
pub fn remove_stream(&self, stream_id: u32) {
|
||||
self.streams.remove(&stream_id);
|
||||
// 🔥 Мгновенно убиваем "зомби-задачи", привязанные к стриму!
|
||||
if let Some((_, (_, _, token))) = self.streams.remove(&stream_id) {
|
||||
token.cancel();
|
||||
}
|
||||
self.stream_bindings.remove(&stream_id);
|
||||
}
|
||||
|
||||
#[instrument(skip(self, data), fields(session_id = %self.session_id, stream_id = stream_id))]
|
||||
pub async fn dispatch_to_local(&self, stream_id: u32, data: Bytes) {
|
||||
let stream_opt = self.streams.get(&stream_id).map(|s| (s.value().0.clone(), s.value().1.clone()));
|
||||
|
||||
if let Some((tx, stats)) = stream_opt {
|
||||
#[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;
|
||||
|
||||
match tx.try_send(data) {
|
||||
Ok(_) => { stats.rx_bytes.fetch_add(size, Ordering::Relaxed); }
|
||||
Err(tokio::sync::mpsc::error::TrySendError::Full(_)) => {
|
||||
// 🔥 Защита от зависания всего VPN! Если стрим застрял - убиваем его.
|
||||
netrunner_logger::error!(stream_id, "Stream buffer full! Killing stream to save tunnel.");
|
||||
self.remove_stream(stream_id);
|
||||
Ok(_) => {
|
||||
stats.rx_bytes.fetch_add(size, Ordering::Relaxed);
|
||||
}
|
||||
Err(_) => { self.remove_stream(stream_id); }
|
||||
Err(_) => {} // Silent drop to avoid slowing down Reader Task
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -342,18 +339,23 @@ impl Muxer {
|
||||
let leg_ids: Vec<u32> = self.legs.iter().map(|kv| *kv.key()).collect();
|
||||
|
||||
for leg_id in leg_ids {
|
||||
// 🔥 ФИКС DEADLOCK'А: Изолируем блокировку DashMap
|
||||
let tx = {
|
||||
let Some(leg) = self.legs.get(&leg_id) else { continue; };
|
||||
let Some(leg) = self.legs.get(&leg_id) else {
|
||||
continue;
|
||||
};
|
||||
leg.control_tx.clone()
|
||||
}; // <-- Лок отпущен! Теперь можно делать .await
|
||||
};
|
||||
|
||||
let probe_stream_id = self.id_gen.next();
|
||||
let (probe_tx, mut probe_rx) = tokio::sync::mpsc::channel(10);
|
||||
self.register_stream(probe_stream_id, probe_tx);
|
||||
let _token = self.register_stream(probe_stream_id, probe_tx);
|
||||
self.record_ping_sent(leg_id);
|
||||
|
||||
let msg = MuxMessage { stream_id: probe_stream_id, frame_type: FrameType::Heartbeat, data: Bytes::from("PING") };
|
||||
let msg = MuxMessage {
|
||||
stream_id: probe_stream_id,
|
||||
frame_type: FrameType::Heartbeat,
|
||||
data: Bytes::from("PING"),
|
||||
};
|
||||
if tx.try_send(msg).is_err() {
|
||||
self.remove_leg(leg_id, &tx);
|
||||
self.remove_stream(probe_stream_id);
|
||||
@@ -372,6 +374,7 @@ impl Muxer {
|
||||
}
|
||||
|
||||
fn format_size(bytes: u64) -> String {
|
||||
/* ... (оставлено без изменений) ... */
|
||||
const KB: u64 = 1024;
|
||||
const MB: u64 = KB * 1024;
|
||||
const GB: u64 = MB * 1024;
|
||||
@@ -397,13 +400,11 @@ impl Muxer {
|
||||
let mut total_rx = 0;
|
||||
let mut legs_info = Vec::new();
|
||||
|
||||
for kv in self.legs.iter() {
|
||||
let id = kv.key();
|
||||
let stats = &kv.value().stats;
|
||||
let tx = stats.tx_bytes.load(Ordering::Relaxed);
|
||||
let rx = stats.rx_bytes.load(Ordering::Relaxed);
|
||||
let rtt = stats.rtt_ms.load(Ordering::Relaxed);
|
||||
|
||||
let cached_legs = self.active_legs_cache.read().unwrap().clone();
|
||||
for leg in cached_legs.iter() {
|
||||
let tx = leg.stats.tx_bytes.load(Ordering::Relaxed);
|
||||
let rx = leg.stats.rx_bytes.load(Ordering::Relaxed);
|
||||
let rtt = leg.stats.rtt_ms.load(Ordering::Relaxed);
|
||||
total_tx += tx;
|
||||
total_rx += rx;
|
||||
|
||||
@@ -412,10 +413,9 @@ impl Muxer {
|
||||
} else {
|
||||
format!("{}ms", rtt)
|
||||
};
|
||||
|
||||
legs_info.push(format!(
|
||||
" ├─ Leg {} (TCP+TLS) ─ ⇡ {:<9} | ⇣ {:<9} [RTT: {}]",
|
||||
id,
|
||||
leg.id,
|
||||
Self::format_size(tx),
|
||||
Self::format_size(rx),
|
||||
rtt_str
|
||||
@@ -423,44 +423,45 @@ impl Muxer {
|
||||
}
|
||||
|
||||
out.push_str(&format!(
|
||||
"├─ 📊 Global Traffic: ⇡ {} | ⇣ {}\n",
|
||||
"├─ 📊 Global Traffic: ⇡ {} | ⇣ {}\n├─ 🦵 Physical Connections (Active: {})\n",
|
||||
Self::format_size(total_tx),
|
||||
Self::format_size(total_rx)
|
||||
));
|
||||
out.push_str(&format!(
|
||||
"├─ 🦵 Physical Connections (Active: {})\n",
|
||||
Self::format_size(total_rx),
|
||||
legs_info.len()
|
||||
));
|
||||
|
||||
for (i, info) in legs_info.iter().enumerate() {
|
||||
if i == legs_info.len() - 1 {
|
||||
out.push_str(&format!("{}\n", info.replace("├─", "└─")));
|
||||
} else {
|
||||
out.push_str(&format!("{}\n", info));
|
||||
}
|
||||
out.push_str(&format!(
|
||||
"{}\n",
|
||||
if i == legs_info.len() - 1 {
|
||||
info.replace("├─", "└─")
|
||||
} else {
|
||||
info.clone()
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
let streams_count = self.streams.len();
|
||||
// Клонируем данные стримов, чтобы не блокировать DashMap при форматировании текста
|
||||
let streams_snapshot: Vec<(u32, u64, u64)> = self
|
||||
.streams
|
||||
.iter()
|
||||
.map(|kv| {
|
||||
(
|
||||
*kv.key(),
|
||||
kv.value().1.tx_bytes.load(Ordering::Relaxed),
|
||||
kv.value().1.rx_bytes.load(Ordering::Relaxed),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
out.push_str(&format!(
|
||||
"└─ 🔀 Virtual Streams (TCP/UDP multiplexed: {})\n",
|
||||
streams_count
|
||||
streams_snapshot.len()
|
||||
));
|
||||
|
||||
let mut count = 0;
|
||||
for kv in self.streams.iter() {
|
||||
count += 1;
|
||||
let id = kv.key();
|
||||
let stats = &kv.value().1;
|
||||
|
||||
let tx = stats.tx_bytes.load(Ordering::Relaxed);
|
||||
let rx = stats.rx_bytes.load(Ordering::Relaxed);
|
||||
|
||||
let prefix = if count == streams_count {
|
||||
for (count, (id, tx, rx)) in streams_snapshot.into_iter().enumerate() {
|
||||
let prefix = if count == self.streams.len() - 1 {
|
||||
" └─"
|
||||
} else {
|
||||
" ├─"
|
||||
};
|
||||
|
||||
out.push_str(&format!(
|
||||
"{} Stream {:<4} ─ ⇡ {:<9} | ⇣ {}\n",
|
||||
prefix,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
pub const MAX_SOCKETS: usize = 2048;
|
||||
pub const MAX_TUNNEL_LEGS: u32 = 6;
|
||||
pub const MAX_SOCKETS: usize = 64;
|
||||
pub const MAX_TUNNEL_LEGS: u32 = 4;
|
||||
pub const MUXER_POOL_SIZE: usize = 3;
|
||||
|
||||
pub const TCP_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(20);
|
||||
|
||||
Reference in New Issue
Block a user