fixes and upgrades. async remove, buf changes

This commit is contained in:
2026-04-03 11:43:03 +07:00
parent 337f191cca
commit b90083539c
9 changed files with 225 additions and 146 deletions
+1 -1
View File
@@ -85,7 +85,7 @@ impl SessionManager {
cache_dir: String, cache_dir: String,
) -> Arc<Session> { ) -> Arc<Session> {
netrunner_logger::Logger::init(None); netrunner_logger::Logger::init(None);
netrunner_logger::Logger::global().set_level("info"); netrunner_logger::Logger::global().set_level("error");
let runtime = get_runtime(); let runtime = get_runtime();
let cancel_token = CancellationToken::new(); let cancel_token = CancellationToken::new();
let session_token = cancel_token.clone(); let session_token = cancel_token.clone();
+1 -1
View File
@@ -13,7 +13,7 @@ use tokio_util::sync::CancellationToken;
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
netrunner_logger::Logger::init(None); netrunner_logger::Logger::init(None);
netrunner_logger::Logger::global().set_level("debug"); netrunner_logger::Logger::global().set_level("error");
info!("Initializing NetRunner Stack..."); info!("Initializing NetRunner Stack...");
let remote_address = "147.45.43.70:443".to_string(); let remote_address = "147.45.43.70:443".to_string();
+2 -2
View File
@@ -223,8 +223,8 @@ impl UdpConnection {
client_addr: smoltcp::wire::IpAddress, client_addr: smoltcp::wire::IpAddress,
client_port: u16, client_port: u16,
) -> (Self, mpsc::Receiver<UdpPacketTarget>, mpsc::Sender<Bytes>) { ) -> (Self, mpsc::Receiver<UdpPacketTarget>, mpsc::Sender<Bytes>) {
// Для UDP используем фиксированный буфер 512 let capacity = NetworkConfig::global().udp_stream_capacity;
let (core, rx_from_smol, tx_to_smol) = ConnectionCore::new(handle, 512); let (core, rx_from_smol, tx_to_smol) = ConnectionCore::new(handle, capacity);
let conn = Self { let conn = Self {
core, core,
+92 -34
View File
@@ -1,12 +1,31 @@
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration;
use crate::net::connection::muxer::{MuxMessage, Muxer}; use crate::net::connection::muxer::{MuxMessage, Muxer};
use crate::net::network::NetworkConfig; use crate::net::network::NetworkConfig;
use crate::nrxp::FrameType; use crate::nrxp::FrameType;
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use netrunner_logger::{debug, error, info}; use netrunner_logger::{debug, error, info, warn};
use tokio::net::UdpSocket; use tokio::net::UdpSocket;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tokio::time::timeout;
/// Тайм-аут бездействия. Если данных нет 5 минут — закрываем мост.
const IDLE_TIMEOUT: Duration = Duration::from_secs(300);
/// RAII Guard: гарантирует удаление стрима из Muxer при любом выходе из функции,
/// даже если произошла паника (panic) или ранняя ошибка.
struct StreamGuard {
stream_id: u32,
muxer: Arc<Muxer>,
}
impl Drop for StreamGuard {
fn drop(&mut self) {
debug!(self.stream_id, "StreamGuard: Cleaning up resources");
self.muxer.remove_stream(self.stream_id);
}
}
pub(crate) async fn run_tcp_bridge<R, W>( pub(crate) async fn run_tcp_bridge<R, W>(
stream_id: u32, stream_id: u32,
@@ -18,15 +37,24 @@ pub(crate) async fn run_tcp_bridge<R, W>(
R: tokio::io::AsyncReadExt + Unpin, R: tokio::io::AsyncReadExt + Unpin,
W: tokio::io::AsyncWriteExt + Unpin, W: tokio::io::AsyncWriteExt + Unpin,
{ {
// Инициализируем защитника ресурсов
let _guard = StreamGuard {
stream_id,
muxer: muxer.clone(),
};
let mut buf = BytesMut::with_capacity(NetworkConfig::global().tcp_buffer_size); let mut buf = BytesMut::with_capacity(NetworkConfig::global().tcp_buffer_size);
loop { loop {
// Оборачиваем весь select в таймаут, чтобы убивать "мертвые" задачи
let select_res = timeout(IDLE_TIMEOUT, async {
tokio::select! { tokio::select! {
// Данные из реального TCP сокета -> в Muxer
res = reader.read_buf(&mut buf) => { res = reader.read_buf(&mut buf) => {
match res { match res {
Ok(0) => { Ok(0) => {
debug!(stream_id, "Socket closed (EOF)"); debug!(stream_id, "TCP Socket reached EOF");
break; return Ok(false); // Выход из цикла
} }
Ok(_) => { Ok(_) => {
let msg = MuxMessage { let msg = MuxMessage {
@@ -34,95 +62,125 @@ pub(crate) async fn run_tcp_bridge<R, W>(
frame_type: FrameType::Data, frame_type: FrameType::Data,
data: buf.split().freeze(), data: buf.split().freeze(),
}; };
if muxer.send_to_network(msg).is_err() { break; } // Если туннель закрыт — выходим
if muxer.send_to_network(msg).is_err() {
return Ok(false);
}
Ok(true)
} }
Err(e) => { Err(e) => {
error!(stream_id, error = %e, "Socket read error"); error!(stream_id, error = %e, "TCP Socket read error");
break; Err(e.to_string())
} }
} }
} }
// Данные из туннеля -> в реальный TCP сокет
maybe_data = v_rx.recv() => { maybe_data = v_rx.recv() => {
match maybe_data { match maybe_data {
Some(data) => { Some(data) => {
if data.is_empty() { continue; } if data.is_empty() { return Ok(true); }
if let Err(e) = writer.write_all(&data).await { if let Err(e) = writer.write_all(&data).await {
error!(stream_id, error = %e, "Socket write error"); error!(stream_id, error = %e, "TCP Socket write error");
break; return Err(e.to_string());
} }
Ok(true)
} }
None => { None => {
debug!(stream_id, "Virtual channel closed"); debug!(stream_id, "Virtual channel closed (Muxer removed stream)");
Ok(false)
}
}
}
}
})
.await;
match select_res {
Ok(Ok(true)) => continue,
Ok(Ok(false)) => break, // Штатное закрытие
Ok(Err(e)) => {
debug!(stream_id, "Bridge closing due to error: {}", e);
break; break;
} }
} Err(_) => {
warn!(stream_id, "TCP Bridge IDLE timeout reached. Evicting task.");
break;
} }
} }
} }
// Попытка вежливо сказать удаленной стороне, что мы закрылись
let _ = muxer.send_to_network(MuxMessage { let _ = muxer.send_to_network(MuxMessage {
stream_id, stream_id,
frame_type: FrameType::Close, frame_type: FrameType::Close,
data: Bytes::new(), data: Bytes::new(),
}); });
tokio::time::sleep(std::time::Duration::from_millis(500)).await; // Небольшая пауза, чтобы пакет Close успел уйти в сеть перед тем, как Guard удалит стрим
tokio::time::sleep(Duration::from_millis(50)).await;
muxer.remove_stream(stream_id);
} }
pub(crate) async fn run_udp_bridge( pub(crate) async fn run_udp_bridge(
stream_id: u32, stream_id: u32,
socket: UdpSocket, socket: UdpSocket,
muxer: Arc<Muxer>, muxer: Arc<Muxer>,
mut v_rx: mpsc::Receiver<Bytes>, mut v_rx: mpsc::Receiver<Bytes>,
) { ) {
let _guard = StreamGuard {
stream_id,
muxer: muxer.clone(),
};
let config = NetworkConfig::global(); let config = NetworkConfig::global();
let mut buf = vec![0u8; config.udp_buffer_size]; let mut buf = vec![0u8; config.udp_buffer_size];
info!( info!(stream_id, "🌉 UDP Bridge active");
"🌉 [UDP {}] Bridge active. Media traffic leg optimization enabled.",
stream_id
);
loop { loop {
let select_res = timeout(IDLE_TIMEOUT, async {
tokio::select! { tokio::select! {
// Данные ИЗ ИНТЕРНЕТА -> В ТУННЕЛЬ // Из интернета в туннель
res = socket.recv(&mut buf) => { res = socket.recv(&mut buf) => {
match res { match res {
Ok(n) if n > 0 => { Ok(n) if n > 0 => {
let data = Bytes::copy_from_slice(&buf[..n]); let data = Bytes::copy_from_slice(&buf[..n]);
// КРИТИЧЕСКИЙ МОМЕНТ: передаем true (is_udp),
// чтобы Muxer выбрал правильную ногу для реалтайм трафика
if let Err(e) = muxer.send_data_safe(stream_id, data, true) { if let Err(e) = muxer.send_data_safe(stream_id, data, true) {
error!("❌ [UDP {}] Failed to send to tunnel: {}", stream_id, e); error!(stream_id, "UDP Failed to send to tunnel: {}", e);
break; return Err(e);
} }
Ok(true)
} }
Ok(_) => break, Ok(_) => Ok(false),
Err(e) => { Err(e) => {
error!("❌ [UDP {}] Internet read error: {}", stream_id, e); error!(stream_id, "UDP Socket read error: {}", e);
break; Err(e.to_string())
} }
} }
} }
// Данные ИЗ ТУННЕЛЯ -> В ИНТЕРНЕТ // Из туннеля в интернет
maybe_data = v_rx.recv() => { maybe_data = v_rx.recv() => {
match maybe_data { match maybe_data {
Some(data) => { Some(data) => {
if let Err(e) = socket.send(&data).await { if let Err(e) = socket.send(&data).await {
error!("❌ [UDP {}] Internet write error: {}", stream_id, e); error!(stream_id, "UDP Internet write error: {}", e);
break; return Err(e.to_string());
}
Ok(true)
}
None => Ok(false),
} }
} }
None => break,
}
} }
})
.await;
match select_res {
Ok(Ok(true)) => continue,
_ => break, // Любая ошибка или таймаут закрывают UDP мост
} }
} }
info!("🔌 [UDP {}] Bridge closed", stream_id); debug!(stream_id, "🔌 UDP Bridge closed");
muxer.remove_stream(stream_id);
} }
+12 -15
View File
@@ -1,4 +1,4 @@
use std::{net::Ipv4Addr, sync::Arc}; use std::{net::Ipv4Addr, sync::Arc, time::Duration};
use crate::{ use crate::{
net::{ net::{
@@ -225,14 +225,14 @@ impl ClientHandler {
} }
// === ДЕМОН СТАТИСТИКИ (КЛИЕНТ) === // === ДЕМОН СТАТИСТИКИ (КЛИЕНТ) ===
let m_stats = muxer.clone(); let m_weak = Arc::downgrade(&muxer);
tokio::spawn(async move { tokio::spawn(async move {
loop { while let Some(m_stats) = m_weak.upgrade() {
tokio::time::sleep(std::time::Duration::from_secs(15)).await; tokio::time::sleep(Duration::from_secs(15)).await;
if m_stats.active_legs_count() > 0 { if m_stats.active_legs_count() == 0 {
m_stats.perform_health_check().await; break;
m_stats.print_topology_tree();
} }
m_stats.print_topology_tree();
} }
}); });
@@ -483,17 +483,14 @@ impl TunnelHandler for ServerHandler {
let handler = let handler =
std::sync::Arc::new(StreamHandler::new(muxer.clone(), ConnectionRole::Server)); std::sync::Arc::new(StreamHandler::new(muxer.clone(), ConnectionRole::Server));
// === ДЕМОН СТАТИСТИКИ (СЕРВЕР) === let m_weak = Arc::downgrade(&muxer);
let m_stats = muxer.clone();
tokio::spawn(async move { tokio::spawn(async move {
loop { while let Some(m_stats) = m_weak.upgrade() {
tokio::time::sleep(std::time::Duration::from_secs(15)).await; tokio::time::sleep(Duration::from_secs(15)).await;
if m_stats.active_legs_count() > 0 { if m_stats.active_legs_count() == 0 {
m_stats.perform_health_check().await;
m_stats.print_topology_tree();
} else {
break; break;
} }
m_stats.print_topology_tree();
} }
}); });
+4 -4
View File
@@ -87,7 +87,7 @@ impl StreamHandler {
}); });
} else { } else {
info!(stream_id, "📲 [TCP] Dispatching payload to local stack"); info!(stream_id, "📲 [TCP] Dispatching payload to local stack");
self.muxer.dispatch_to_local(stream_id, payload).await; self.muxer.dispatch_to_local(stream_id, payload);
} }
} }
@@ -144,19 +144,19 @@ impl StreamHandler {
stream_id, stream_id,
"📲 [UDP] Dispatching connection payload to local stack" "📲 [UDP] Dispatching connection payload to local stack"
); );
self.muxer.dispatch_to_local(stream_id, payload).await; self.muxer.dispatch_to_local(stream_id, payload);
} }
} }
async fn on_data(&self, stream_id: u32, payload: Bytes) { async fn on_data(&self, stream_id: u32, payload: Bytes) {
// Здесь info может быть избыточным при большой нагрузке, но для отладки полезно // Здесь info может быть избыточным при большой нагрузке, но для отладки полезно
debug!(stream_id, "📦 [TCP Data] Size: {} bytes", payload.len()); debug!(stream_id, "📦 [TCP Data] Size: {} bytes", payload.len());
self.muxer.dispatch_to_local(stream_id, payload).await; self.muxer.dispatch_to_local(stream_id, payload);
} }
async fn on_udp_data(&self, stream_id: u32, payload: Bytes) { async fn on_udp_data(&self, stream_id: u32, payload: Bytes) {
debug!(stream_id, "📦 [UDP Data] Size: {} bytes", payload.len()); debug!(stream_id, "📦 [UDP Data] Size: {} bytes", payload.len());
self.muxer.dispatch_to_local(stream_id, payload).await; self.muxer.dispatch_to_local(stream_id, payload);
} }
async fn on_close(&self, stream_id: u32) { async fn on_close(&self, stream_id: u32) {
+63 -37
View File
@@ -1,6 +1,7 @@
use bytes::Bytes; use bytes::Bytes;
use dashmap::DashMap; use dashmap::DashMap;
use netrunner_logger::{info, warn}; use netrunner_logger::{debug, info, warn};
use tokio::sync::mpsc::error::TrySendError;
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering}; use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::mpsc::Sender; use tokio::sync::mpsc::Sender;
@@ -13,7 +14,7 @@ use crate::nrxp::FrameType;
pub struct LegStats { pub struct LegStats {
pub tx_bytes: AtomicU64, pub tx_bytes: AtomicU64,
pub rx_bytes: AtomicU64, pub rx_bytes: AtomicU64,
pub rtt_ms: AtomicU32, // Задержка (пинг) конкретно этой ноги pub rtt_ms: AtomicU32,
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
@@ -107,35 +108,42 @@ impl Muxer {
self.legs.len() self.legs.len()
} }
// ДИНАМИЧЕСКИЙ ВЫБОР НОГИ ПО STREAM_ID (Session Affinity)
fn select_leg(&self, frame_type: &FrameType, stream_id: u32) -> Option<(u32, MuxLeg)> { fn select_leg(&self, frame_type: &FrameType, stream_id: u32) -> Option<(u32, MuxLeg)> {
if self.legs.is_empty() { if self.legs.is_empty() {
return None; return None;
} }
let mut active_legs: Vec<u32> = self.legs.iter().map(|kv| *kv.key()).collect();
active_legs.sort_unstable(); // Сортируем для предсказуемой привязки
let is_udp = matches!(frame_type, FrameType::UdpData | FrameType::UdpConnect); let is_udp = matches!(frame_type, FrameType::UdpData | FrameType::UdpConnect);
// Фильтруем: UDP идет по нечетным ногам, TCP по четным (если они есть) // Собираем доступные ноги
let preferred_legs: Vec<u32> = active_legs let mut candidates: Vec<(u32, MuxLeg)> = self.legs
.iter() .iter()
.copied() .map(|kv| (*kv.key(), kv.value().clone()))
.filter(|id| if is_udp { id % 2 != 0 } else { id % 2 == 0 }) .filter(|(id, _)| {
// Фильтр по протоколу, если ноги разделены
if is_udp { id % 2 != 0 } else { id % 2 == 0 }
})
.collect(); .collect();
// БАЛАНСИРОВКА: Привязываем stream_id к конкретной ноге. // Если по протоколу ничего не нашли, берем любые активные
// Все пакеты одного стрима пойдут строго по одному маршруту. if candidates.is_empty() {
let target_id = if !preferred_legs.is_empty() { candidates = self.legs.iter().map(|kv| (*kv.key(), kv.value().clone())).collect();
preferred_legs[(stream_id as usize) % preferred_legs.len()] }
} else {
active_legs[(stream_id as usize) % active_legs.len()]
};
self.legs // --- МАГИЯ ЗДЕСЬ ---
.get(&target_id) // Сортируем кандидатов по RTT (от меньшего к большему)
.map(|leg| (target_id, leg.clone())) // Ноги с RTT = 0 (еще не проверенные) или таймаутом считаем медленными
candidates.sort_by_key(|(_, leg)| {
let rtt = leg.stats.rtt_ms.load(Ordering::Relaxed);
if rtt == 0 { 9999 } else { rtt }
});
// Выбираем из ТОП-2 лучших ног по RTT, используя stream_id для аффинити
// Это защитит от ситуации, когда все стримы прыгнут на одну ногу с RTT 10мс
let pool_size = std::cmp::min(candidates.len(), 2);
let target = &candidates[stream_id as usize % pool_size];
Some(target.clone())
} }
// СИНХРОННАЯ ФУНКЦИЯ (с отбрасыванием пакетов при перегрузке) // СИНХРОННАЯ ФУНКЦИЯ (с отбрасыванием пакетов при перегрузке)
@@ -232,8 +240,8 @@ impl Muxer {
self.streams.remove(&stream_id); self.streams.remove(&stream_id);
} }
// ЛОКАЛЬНАЯ ДИСПЕТЧЕРИЗАЦИЯ (Остается async, так как тут мы используем wait для TUN) pub fn dispatch_to_local(&self, stream_id: u32, data: Bytes) {
pub async fn dispatch_to_local(&self, stream_id: u32, data: Bytes) { // Получаем клон Sender и Stats из DashMap
let stream_opt = self let stream_opt = self
.streams .streams
.get(&stream_id) .get(&stream_id)
@@ -242,13 +250,23 @@ impl Muxer {
if let Some((tx, stats)) = stream_opt { if let Some((tx, stats)) = stream_opt {
let size = data.len() as u64; let size = data.len() as u64;
if tx.send(data).await.is_err() { // Используем try_send вместо send().await
self.remove_stream(stream_id); match tx.try_send(data) {
} else { Ok(_) => {
stats.rx_bytes.fetch_add(size, Ordering::Relaxed); stats.rx_bytes.fetch_add(size, Ordering::Relaxed);
} }
Err(TrySendError::Full(_)) => {
netrunner_logger::warn!(
stream_id,
"Muxer -> Local: Buffer full, dropping packet. Check your bridge/TUN speed."
);
}
Err(TrySendError::Closed(_)) => {
self.remove_stream(stream_id);
} }
} }
}
}
pub fn record_leg_rx(&self, leg_id: u32, bytes: u64) { pub fn record_leg_rx(&self, leg_id: u32, bytes: u64) {
if let Some(leg) = self.legs.get(&leg_id) { if let Some(leg) = self.legs.get(&leg_id) {
@@ -256,41 +274,49 @@ impl Muxer {
} }
} }
// УМНЫЙ HEALTH CHECK: Пингуем каждую ногу отдельно для честного RTT
pub async fn perform_health_check(&self) { pub async fn perform_health_check(&self) {
let legs: Vec<(u32, Sender<MuxMessage>)> = self // Создаем снапшот ног, чтобы не держать lock DashMap слишком долго
.legs let legs: Vec<(u32, Sender<MuxMessage>)> = self.legs.iter()
.iter()
.map(|k| (*k.key(), k.value().control_tx.clone())) .map(|k| (*k.key(), k.value().control_tx.clone()))
.collect(); .collect();
for (leg_id, tx) in legs { for (leg_id, tx) in legs {
let probe_stream_id = self.id_gen.next(); let probe_stream_id = self.id_gen.next();
let (probe_tx, mut probe_rx) = tokio::sync::mpsc::channel(2); let (probe_tx, mut probe_rx) = tokio::sync::mpsc::channel(2);
// Регистрируем временный стрим для ответа на PING
self.register_stream(probe_stream_id, probe_tx); self.register_stream(probe_stream_id, probe_tx);
let start = std::time::Instant::now();
let msg = MuxMessage { let msg = MuxMessage {
stream_id: probe_stream_id, stream_id: probe_stream_id,
frame_type: FrameType::Handshake, frame_type: FrameType::Handshake,
data: Bytes::from("PING"), data: Bytes::from("PING"),
}; };
// Отправляем PING прямо в конкретную ногу в обход балансировщика let start = std::time::Instant::now();
// Отправляем напрямую в ногу
if tx.try_send(msg).is_ok() { if tx.try_send(msg).is_ok() {
if let Ok(Some(_)) = // Ждем ответа с жестким таймаутом
tokio::time::timeout(std::time::Duration::from_secs(2), probe_rx.recv()).await match tokio::time::timeout(std::time::Duration::from_secs(2), probe_rx.recv()).await {
{ Ok(Some(_)) => {
let rtt = start.elapsed().as_millis() as u32; let rtt = start.elapsed().as_millis() as u32;
// Сохраняем RTT в стату ноги
if let Some(leg) = self.legs.get(&leg_id) { if let Some(leg) = self.legs.get(&leg_id) {
leg.stats.rtt_ms.store(rtt, Ordering::Relaxed); leg.stats.rtt_ms.store(rtt, Ordering::Relaxed);
debug!(leg_id, rtt, "✅ Leg Health Check OK");
}
}
_ => {
// Если таймаут — задираем RTT до небес, чтобы select_leg ее не выбирал
if let Some(leg) = self.legs.get(&leg_id) {
leg.stats.rtt_ms.store(5000, Ordering::Relaxed);
warn!(leg_id, "❌ Leg Health Check Timeout (marked as slow)");
}
} }
} else {
warn!(leg_id, "❌ Health check timed out for Leg");
} }
} }
// Обязательная очистка, чтобы не было утечки памяти в DashMap streams
self.remove_stream(probe_stream_id); self.remove_stream(probe_stream_id);
} }
} }
+4 -6
View File
@@ -96,14 +96,12 @@ impl NetworkConfig {
// Буферы ОС/Tokio (держим с запасом под быстрые всплески) // Буферы ОС/Tokio (держим с запасом под быстрые всплески)
tcp_buffer_size: 1024 * 1024, tcp_buffer_size: 1024 * 1024,
udp_buffer_size: 512 * 1024, udp_buffer_size: 64 * 1024,
// Глобальная очередь. 4096 * 16KB = ~65 MB в памяти (безопасно) muxer_capacity: 512,
// Это спасет от заторов, если 10 сокетов решат отправить данные одновременно
muxer_capacity: 256,
tcp_stream_capacity: 64, tcp_stream_capacity: 16,
udp_stream_capacity: 64, udp_stream_capacity: 128,
// Настройки smoltcp // Настройки smoltcp
smoltcp_socket_buf: 256 * 1024, smoltcp_socket_buf: 256 * 1024,
+1 -1
View File
@@ -14,7 +14,7 @@ struct Args {
fn main() { fn main() {
Logger::init("./logs".into()); Logger::init("./logs".into());
Logger::global().set_level("info"); //set error in prod Logger::global().set_level("error"); //set error in prod
let args = Args::parse(); let args = Args::parse();
let net = Network::new(args.host, args.port, ConnectionRole::Server); let net = Network::new(args.host, args.port, ConnectionRole::Server);