leg select change and improve speed
This commit is contained in:
@@ -33,7 +33,6 @@ impl SessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Генерация переехала сюда
|
||||
pub fn generate_id() -> String {
|
||||
let mut rng = rand::rng();
|
||||
format!("{:016x}{:016x}", rng.next_u64(), rng.next_u64())
|
||||
@@ -42,11 +41,11 @@ impl SessionManager {
|
||||
pub fn get_or_create(&self, session_id: &str) -> Arc<Muxer> {
|
||||
self.sessions
|
||||
.entry(session_id.to_string())
|
||||
.or_insert_with(|| Arc::new(Muxer::new(false)))
|
||||
// ОБНОВЛЕНО: Передаем session_id в конструктор Муксера
|
||||
.or_insert_with(|| Arc::new(Muxer::new(false, session_id.to_string())))
|
||||
.clone()
|
||||
}
|
||||
|
||||
// Удаление мертвых сессий, чтобы не текла память
|
||||
pub fn remove(&self, session_id: &str) {
|
||||
if self.sessions.remove(session_id).is_some() {
|
||||
netrunner_logger::info!("🧹 Session {} completely closed and cleaned up", session_id);
|
||||
@@ -95,6 +94,7 @@ impl Connection {
|
||||
|
||||
type StreamContext = (Ipv4Addr, u16, LocalProtocol);
|
||||
pub struct ClientHandler;
|
||||
|
||||
impl ClientHandler {
|
||||
async fn establish_leg(
|
||||
remote_proxy_addr: &str,
|
||||
@@ -102,11 +102,15 @@ impl ClientHandler {
|
||||
muxer: Arc<Muxer>,
|
||||
session_id: &str,
|
||||
) -> Result<(), String> {
|
||||
let leg_name = if leg_id == 0 { "TCP-Leg" } else { "UDP-Leg" };
|
||||
let leg_name = if leg_id % 2 == 0 {
|
||||
"TCP-Leg"
|
||||
} else {
|
||||
"UDP-Leg"
|
||||
};
|
||||
|
||||
info!(
|
||||
"Establishing dedicated {} to {}...",
|
||||
leg_name, remote_proxy_addr
|
||||
"Establishing dedicated {} (ID: {}) to {}...",
|
||||
leg_name, leg_id, remote_proxy_addr
|
||||
);
|
||||
|
||||
let stream = TcpStream::connect(remote_proxy_addr)
|
||||
@@ -148,13 +152,12 @@ impl ClientHandler {
|
||||
info!("{} TLS Handshake complete.", leg_name);
|
||||
let handshake_payload = Bytes::from(format!("{}:{}", session_id, leg_id));
|
||||
|
||||
// 2. Шифруем через Codec! (Внутри сгенерится auth_tag)
|
||||
// Шифруем кастомный Handshake
|
||||
let encrypted_handshake = conn
|
||||
.codec
|
||||
.encrypt_data(0, FrameType::Handshake, handshake_payload)
|
||||
.map_err(|e| format!("Failed to encrypt Handshake: {:?}", e))?;
|
||||
|
||||
// 3. Отправляем в сокет
|
||||
conn.outbound
|
||||
.write_all(&encrypted_handshake)
|
||||
.await
|
||||
@@ -168,13 +171,13 @@ impl ClientHandler {
|
||||
let (control_tx, control_rx) = mpsc::channel(NetworkConfig::global().muxer_capacity);
|
||||
let (data_tx, data_rx) = mpsc::channel(NetworkConfig::global().muxer_capacity);
|
||||
|
||||
// ОБНОВЛЕННЫЙ ВЫЗОВ: передаем leg_id
|
||||
muxer.add_leg(leg_id, control_tx, data_tx);
|
||||
|
||||
let handler =
|
||||
std::sync::Arc::new(StreamHandler::new(muxer.clone(), ConnectionRole::Client));
|
||||
|
||||
let engine = TunnelEngine {
|
||||
leg_id, // ОБНОВЛЕНО: Передаем leg_id, чтобы движок писал стату
|
||||
inbound: conn.inbound,
|
||||
outbound: conn.outbound,
|
||||
codec: conn.codec,
|
||||
@@ -182,9 +185,10 @@ impl ClientHandler {
|
||||
control_rx,
|
||||
data_rx,
|
||||
handler,
|
||||
muxer: muxer.clone(), // Не забудь передать muxer (он нужен движку для record_leg_rx)
|
||||
};
|
||||
|
||||
engine.run().await;
|
||||
engine.run().await.map_err(|e| e.to_string())?;
|
||||
Err(format!("{} Engine stopped", leg_name))
|
||||
}
|
||||
|
||||
@@ -193,39 +197,54 @@ impl ClientHandler {
|
||||
mut rx_from_engine: mpsc::Receiver<RawCastFrame>,
|
||||
tx_to_engine: mpsc::Sender<RawCastFrame>,
|
||||
) -> Result<(), String> {
|
||||
let muxer = Arc::new(Muxer::new(true));
|
||||
let registry: Arc<DashMap<u32, StreamContext>> = Arc::new(DashMap::new());
|
||||
|
||||
let mut rng = rand::rng();
|
||||
let session_id = format!("{:016x}{:016x}", rng.next_u64(), rng.next_u64());
|
||||
let session_id = SessionManager::generate_id();
|
||||
info!("🔑 Generated Master Session ID: {}", session_id);
|
||||
|
||||
for id in 0..2 {
|
||||
// ОБНОВЛЕНО: Передаем session_id в Муксер
|
||||
let muxer = Arc::new(Muxer::new(true, session_id.clone()));
|
||||
let registry: Arc<DashMap<u32, StreamContext>> = Arc::new(DashMap::new());
|
||||
|
||||
info!("🚀 Netrunner Multi-Path Tunnel Initializing (10 Legs max).");
|
||||
|
||||
// === ДИНАМИЧЕСКИЙ ПУЛ НА 10 НОГ ===
|
||||
for id in 0..10 {
|
||||
let addr = remote_proxy_addr.to_string();
|
||||
let m = muxer.clone();
|
||||
let sid = session_id.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
// Плавный запуск (staggered start), чтобы не задудосить свой же сервер
|
||||
tokio::time::sleep(std::time::Duration::from_millis(id as u64 * 250)).await;
|
||||
loop {
|
||||
if let Err(e) = Self::establish_leg(&addr, id, m.clone(), &sid).await {
|
||||
error!("Leg {} failed: {}. Reconnecting...", id, e);
|
||||
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
|
||||
error!("Leg {} disconnected: {}. Reconnecting in 3s...", id, e);
|
||||
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
info!("🚀 Netrunner Dual-Leg Tunnel Active.");
|
||||
// === ДЕМОН СТАТИСТИКИ (КЛИЕНТ) ===
|
||||
let m_stats = muxer.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(15)).await;
|
||||
if m_stats.active_legs_count() > 0 {
|
||||
m_stats.perform_health_check().await;
|
||||
m_stats.print_topology_tree();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// === ЛОКАЛЬНАЯ МАРШРУТИЗАЦИЯ (RawCast -> Nrxp) ===
|
||||
let muxer_inner = muxer.clone();
|
||||
tokio::spawn(async move {
|
||||
while let Some(raw_frame) = rx_from_engine.recv().await {
|
||||
// 1. Сохраняем метаданные для локального реестра (до того, как raw_frame исчезнет)
|
||||
let dst_ip = raw_frame.dst_ip;
|
||||
let dst_port = raw_frame.dst_port;
|
||||
let protocol = raw_frame.protocol;
|
||||
let is_udp = protocol == LocalProtocol::Udp;
|
||||
|
||||
// 2. Вся магия здесь: адаптер сам решит, это Connect или Data, и склеит IP:Port
|
||||
let nrxp_frame = match RawCastAdapter::to_nrxp(raw_frame) {
|
||||
Ok(frame) => frame,
|
||||
Err(e) => {
|
||||
@@ -238,20 +257,19 @@ impl ClientHandler {
|
||||
let f_type = nrxp_frame.header.frame_type;
|
||||
let payload = nrxp_frame.payload;
|
||||
|
||||
// 3. Маршрутизация на основе ГОТОВОГО FrameType
|
||||
match f_type {
|
||||
FrameType::Connect | FrameType::UdpConnect => {
|
||||
info!(
|
||||
"🔗 [Muxer] Forwarding {} request to {} (stream {})",
|
||||
if is_udp { "UDP" } else { "TCP" },
|
||||
String::from_utf8_lossy(&payload), // Адаптер уже положил сюда "IP:Port"
|
||||
String::from_utf8_lossy(&payload),
|
||||
stream_id
|
||||
);
|
||||
|
||||
registry.insert(stream_id, (dst_ip, dst_port, protocol));
|
||||
|
||||
let (v_tx, mut v_rx) =
|
||||
mpsc::channel(NetworkConfig::global().tcp_buffer_size);
|
||||
mpsc::channel(NetworkConfig::global().tcp_stream_capacity);
|
||||
muxer_inner.register_stream(stream_id, v_tx);
|
||||
|
||||
let tx_to_tun = tx_to_engine.clone();
|
||||
@@ -268,7 +286,6 @@ impl ClientHandler {
|
||||
};
|
||||
let mock_nrxp = Frame::new(stream_id, out_f_type, back_payload);
|
||||
|
||||
// Здесь from_nrxp уже используется у тебя правильно!
|
||||
if let Ok(raw) = RawCastAdapter::from_nrxp(
|
||||
mock_nrxp,
|
||||
ip,
|
||||
@@ -281,7 +298,6 @@ impl ClientHandler {
|
||||
}
|
||||
});
|
||||
|
||||
// Отправляем управляющий фрейм с адресом
|
||||
let _ = muxer_inner.send_control(stream_id, f_type, payload);
|
||||
}
|
||||
|
||||
@@ -304,6 +320,7 @@ impl ClientHandler {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ServerHandler {
|
||||
pub(crate) conn: Connection,
|
||||
pub(crate) session_manager: Arc<SessionManager>,
|
||||
@@ -363,14 +380,12 @@ impl ServerHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
use crate::parser::Parser; // Не забудь импортировать трейт Parser!
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl TunnelHandler for ServerHandler {
|
||||
async fn run(mut self) -> Result<(), String> {
|
||||
info!("Acting as TLS Server with Stealth Fallback");
|
||||
|
||||
// 1. TLS Хендшейк (без изменений)
|
||||
let handshake_timeout = std::time::Duration::from_secs(1);
|
||||
let hello = loop {
|
||||
let buf_snapshot = self.conn.read_buf.clone().freeze();
|
||||
@@ -418,7 +433,6 @@ impl TunnelHandler for ServerHandler {
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// 1. ЧИТАЕМ ЗАШИФРОВАННЫЙ HANDSHAKE ЧЕРЕЗ CODEC
|
||||
let (session_id, leg_id) = loop {
|
||||
let n = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(3),
|
||||
@@ -432,7 +446,6 @@ impl TunnelHandler for ServerHandler {
|
||||
return Err("Client closed connection before Handshake".into());
|
||||
}
|
||||
|
||||
// Кодек сам расшифрует и проверит auth_tag
|
||||
match self.conn.codec.inbound(&mut self.conn.read_buf) {
|
||||
Ok(Some(frame)) => {
|
||||
if frame.header.frame_type == FrameType::Handshake {
|
||||
@@ -454,25 +467,39 @@ impl TunnelHandler for ServerHandler {
|
||||
return Err("Invalid first frame (Expected Handshake)".into());
|
||||
}
|
||||
}
|
||||
Ok(None) => continue, // Ждем еще байт
|
||||
Ok(None) => continue,
|
||||
Err(e) => return Err(format!("Decryption/Auth failed during Handshake: {:?}", e)),
|
||||
}
|
||||
};
|
||||
|
||||
// 2. РЕГИСТРИРУЕМ НОГУ
|
||||
// РЕГИСТРИРУЕМ НОГУ
|
||||
let muxer = self.session_manager.get_or_create(&session_id);
|
||||
|
||||
let (control_tx, control_rx) = mpsc::channel(NetworkConfig::global().muxer_capacity);
|
||||
let (data_tx, data_rx) = mpsc::channel(NetworkConfig::global().muxer_capacity);
|
||||
|
||||
// ОБНОВЛЕННЫЙ ВЫЗОВ: регистрируем конкретный ID ноги
|
||||
muxer.add_leg(leg_id, control_tx, data_tx);
|
||||
|
||||
let handler =
|
||||
std::sync::Arc::new(StreamHandler::new(muxer.clone(), ConnectionRole::Server));
|
||||
|
||||
// 3. ЗАПУСКАЕМ ДВИЖОК
|
||||
// === ДЕМОН СТАТИСТИКИ (СЕРВЕР) ===
|
||||
let m_stats = muxer.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(15)).await;
|
||||
if m_stats.active_legs_count() > 0 {
|
||||
m_stats.perform_health_check().await;
|
||||
m_stats.print_topology_tree();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ЗАПУСКАЕМ ДВИЖОК
|
||||
let engine = TunnelEngine {
|
||||
leg_id, // ОБНОВЛЕНО
|
||||
inbound: self.conn.inbound,
|
||||
outbound: self.conn.outbound,
|
||||
codec: self.conn.codec,
|
||||
@@ -480,11 +507,12 @@ impl TunnelHandler for ServerHandler {
|
||||
control_rx,
|
||||
data_rx,
|
||||
handler,
|
||||
muxer: muxer.clone(), // ОБНОВЛЕНО
|
||||
};
|
||||
|
||||
let _ = engine.run().await;
|
||||
|
||||
// 4. ОЧИСТКА ПОСЛЕ ОТКЛЮЧЕНИЯ
|
||||
// ОЧИСТКА ПОСЛЕ ОТКЛЮЧЕНИЯ НОГИ
|
||||
muxer.remove_leg(leg_id);
|
||||
if muxer.active_legs_count() == 0 {
|
||||
self.session_manager.remove(&session_id);
|
||||
|
||||
@@ -10,7 +10,10 @@ use tokio::{
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::{
|
||||
net::connection::{handler::StreamHandler, muxer::MuxMessage},
|
||||
net::{
|
||||
connection::{handler::StreamHandler, muxer::MuxMessage},
|
||||
network::NetworkConfig,
|
||||
},
|
||||
nrxp::{Codec, ErrorAction, FrameType},
|
||||
};
|
||||
|
||||
@@ -22,6 +25,8 @@ pub(crate) struct TunnelEngine {
|
||||
pub control_rx: Receiver<MuxMessage>,
|
||||
pub data_rx: Receiver<MuxMessage>,
|
||||
pub handler: Arc<StreamHandler>,
|
||||
pub leg_id: u32,
|
||||
pub muxer: Arc<crate::net::connection::muxer::Muxer>,
|
||||
}
|
||||
|
||||
impl TunnelEngine {
|
||||
@@ -35,6 +40,11 @@ impl TunnelEngine {
|
||||
let control_rx = self.control_rx;
|
||||
let data_rx = self.data_rx;
|
||||
let handler = self.handler;
|
||||
|
||||
// === ИЗВЛЕКАЕМ НОВЫЕ ПОЛЯ ===
|
||||
let leg_id = self.leg_id;
|
||||
let muxer = self.muxer.clone();
|
||||
|
||||
let token = CancellationToken::new();
|
||||
|
||||
let codec_reader = codec.clone();
|
||||
@@ -56,7 +66,6 @@ impl TunnelEngine {
|
||||
res = inbound.read_buf(&mut read_buf) => {
|
||||
let n = res.map_err(|e| e.to_string())?;
|
||||
|
||||
|
||||
if n == 0 {
|
||||
if read_buf.is_empty() {
|
||||
info!("Connection closed by peer (Clean EOF)");
|
||||
@@ -67,12 +76,12 @@ impl TunnelEngine {
|
||||
return Err::<(), String>("EOF".into());
|
||||
}
|
||||
|
||||
|
||||
// === РЕГИСТРИРУЕМ ВХОДЯЩИЙ ТРАФИК ДЛЯ СТАТИСТИКИ ===
|
||||
muxer.record_leg_rx(leg_id, n as u64);
|
||||
|
||||
let mut frames = Vec::new();
|
||||
|
||||
{
|
||||
|
||||
let mut c = codec_reader.lock().await;
|
||||
loop {
|
||||
match c.inbound(&mut read_buf) {
|
||||
@@ -93,7 +102,6 @@ impl TunnelEngine {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for frame in frames {
|
||||
handler.handle(frame).await;
|
||||
}
|
||||
@@ -118,7 +126,6 @@ impl TunnelEngine {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
msg_opt = control_rx.recv() => {
|
||||
if let Some(msg) = msg_opt {
|
||||
Self::handle_outbound(&mut outbound, &codec_writer, msg).await?;
|
||||
@@ -127,13 +134,11 @@ impl TunnelEngine {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_ = heartbeat.tick() => {
|
||||
let msg = MuxMessage { stream_id: 0, frame_type: FrameType::Heartbeat, data: Bytes::new() };
|
||||
Self::handle_outbound(&mut outbound, &codec_writer, msg).await?;
|
||||
}
|
||||
|
||||
|
||||
msg_opt = data_rx.recv() => {
|
||||
if let Some(msg) = msg_opt {
|
||||
Self::handle_outbound(&mut outbound, &codec_writer, msg).await?;
|
||||
@@ -163,8 +168,6 @@ impl TunnelEngine {
|
||||
codec: &Arc<Mutex<Codec>>,
|
||||
msg: MuxMessage,
|
||||
) -> Result<(), String> {
|
||||
const MAX_CHUNK_SIZE: usize = 1024 * 4;
|
||||
|
||||
let mut data = msg.data;
|
||||
let stream_id = msg.stream_id;
|
||||
let frame_type = msg.frame_type;
|
||||
@@ -184,7 +187,8 @@ impl TunnelEngine {
|
||||
}
|
||||
} else {
|
||||
while !data.is_empty() {
|
||||
let chunk_size = std::cmp::min(data.len(), MAX_CHUNK_SIZE);
|
||||
let chunk_size =
|
||||
std::cmp::min(data.len(), NetworkConfig::global().tcp_chunk_size);
|
||||
let chunk = data.split_to(chunk_size);
|
||||
|
||||
match c.encrypt_data(stream_id, frame_type.clone(), chunk) {
|
||||
|
||||
@@ -1,16 +1,32 @@
|
||||
use bytes::Bytes;
|
||||
use dashmap::DashMap;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use netrunner_logger::{info, warn};
|
||||
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc::Sender;
|
||||
|
||||
use crate::net::network::NetworkConfig;
|
||||
use crate::nrxp::FrameType;
|
||||
|
||||
// === СТРУКТУРЫ СТАТИСТИКИ ===
|
||||
#[derive(Default, Debug)]
|
||||
pub struct LegStats {
|
||||
pub tx_bytes: AtomicU64,
|
||||
pub rx_bytes: AtomicU64,
|
||||
pub rtt_ms: AtomicU32, // Задержка (пинг) конкретно этой ноги
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct StreamStats {
|
||||
pub tx_bytes: AtomicU64,
|
||||
pub rx_bytes: AtomicU64,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct MuxLeg {
|
||||
control_tx: Sender<MuxMessage>,
|
||||
data_tx: Sender<MuxMessage>,
|
||||
stats: Arc<LegStats>,
|
||||
}
|
||||
|
||||
struct IdGenerator {
|
||||
@@ -39,18 +55,20 @@ pub struct MuxMessage {
|
||||
#[derive(Clone)]
|
||||
pub struct Muxer {
|
||||
legs: Arc<DashMap<u32, MuxLeg>>,
|
||||
streams: Arc<DashMap<u32, Sender<Bytes>>>,
|
||||
// Храним канал + статистику стрима
|
||||
streams: Arc<DashMap<u32, (Sender<Bytes>, Arc<StreamStats>)>>,
|
||||
id_gen: Arc<IdGenerator>,
|
||||
leg_selector: Arc<AtomicU32>,
|
||||
session_id: Arc<String>,
|
||||
}
|
||||
|
||||
impl Muxer {
|
||||
pub fn new(is_client: bool) -> Self {
|
||||
// Конструктор теперь принимает session_id
|
||||
pub fn new(is_client: bool, session_id: String) -> Self {
|
||||
Self {
|
||||
legs: Arc::new(DashMap::new()),
|
||||
streams: Arc::new(DashMap::new()),
|
||||
id_gen: Arc::new(IdGenerator::new(is_client)),
|
||||
leg_selector: Arc::new(AtomicU32::new(0)),
|
||||
session_id: Arc::new(session_id),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,56 +78,74 @@ impl Muxer {
|
||||
control_tx: Sender<MuxMessage>,
|
||||
data_tx: Sender<MuxMessage>,
|
||||
) {
|
||||
// Ограничение: максимум 10 физических ног
|
||||
if self.legs.len() >= 10 {
|
||||
warn!(
|
||||
leg_id,
|
||||
"MUXER: Maximum of 10 legs reached. Ignoring new leg."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
self.legs.insert(
|
||||
leg_id,
|
||||
MuxLeg {
|
||||
control_tx,
|
||||
data_tx,
|
||||
stats: Arc::new(LegStats::default()),
|
||||
},
|
||||
);
|
||||
netrunner_logger::info!(leg_id, "MUXER: Leg registered");
|
||||
info!(leg_id, "MUXER: Leg registered (Total: {})", self.legs.len());
|
||||
}
|
||||
|
||||
pub fn remove_leg(&self, leg_id: u32) {
|
||||
self.legs.remove(&leg_id);
|
||||
netrunner_logger::info!(leg_id, "MUXER: Leg removed");
|
||||
info!(leg_id, "MUXER: Leg removed");
|
||||
}
|
||||
|
||||
pub fn active_legs_count(&self) -> usize {
|
||||
self.legs.len()
|
||||
}
|
||||
|
||||
fn select_leg(&self, frame_type: &FrameType) -> Option<(u32, MuxLeg)> {
|
||||
// ДИНАМИЧЕСКИЙ ВЫБОР НОГИ ПО STREAM_ID (Session Affinity)
|
||||
fn select_leg(&self, frame_type: &FrameType, stream_id: u32) -> Option<(u32, MuxLeg)> {
|
||||
if self.legs.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match frame_type {
|
||||
FrameType::UdpData | FrameType::UdpConnect => {
|
||||
if let Some(leg) = self.legs.get(&1) {
|
||||
Some((1, leg.clone()))
|
||||
} else if let Some(leg) = self.legs.get(&0) {
|
||||
Some((0, leg.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if let Some(leg) = self.legs.get(&0) {
|
||||
Some((0, leg.clone()))
|
||||
} else if let Some(leg) = self.legs.get(&1) {
|
||||
Some((1, leg.clone()))
|
||||
} else {
|
||||
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);
|
||||
|
||||
// Фильтруем: UDP идет по нечетным ногам, TCP по четным (если они есть)
|
||||
let preferred_legs: Vec<u32> = active_legs
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|id| if is_udp { id % 2 != 0 } else { id % 2 == 0 })
|
||||
.collect();
|
||||
|
||||
// БАЛАНСИРОВКА: Привязываем stream_id к конкретной ноге.
|
||||
// Все пакеты одного стрима пойдут строго по одному маршруту.
|
||||
let target_id = if !preferred_legs.is_empty() {
|
||||
preferred_legs[(stream_id as usize) % preferred_legs.len()]
|
||||
} else {
|
||||
active_legs[(stream_id as usize) % active_legs.len()]
|
||||
};
|
||||
|
||||
self.legs
|
||||
.get(&target_id)
|
||||
.map(|leg| (target_id, leg.clone()))
|
||||
}
|
||||
|
||||
// ТЕПЕРЬ СИНХРОННАЯ ФУНКЦИЯ БЕЗ БЛОКИРОВОК
|
||||
// СИНХРОННАЯ ФУНКЦИЯ (с отбрасыванием пакетов при перегрузке)
|
||||
pub fn send_to_network(&self, message: MuxMessage) -> Result<(), String> {
|
||||
let stream_id = message.stream_id;
|
||||
let size = message.data.len() as u64;
|
||||
|
||||
// Передаем stream_id в select_leg
|
||||
let (leg_id, leg) = self
|
||||
.select_leg(&message.frame_type)
|
||||
.select_leg(&message.frame_type, stream_id)
|
||||
.ok_or_else(|| "MUXER: No active legs available".to_string())?;
|
||||
|
||||
let target_tx = match message.frame_type {
|
||||
@@ -120,13 +156,22 @@ impl Muxer {
|
||||
_ => &leg.data_tx,
|
||||
};
|
||||
|
||||
// Используем try_send вместо send().await
|
||||
match target_tx.try_send(message) {
|
||||
Ok(_) => Ok(()),
|
||||
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(_)) => {
|
||||
// Если очередь переполнена - просто дропаем пакет!
|
||||
// Возвращаем Ok(()), чтобы не убивать соединение.
|
||||
netrunner_logger::warn!(
|
||||
// Если очередь переполнена - просто дропаем пакет (Backpressure)
|
||||
warn!(
|
||||
leg_id,
|
||||
"MUXER: Network queue full! Dropping outbound packet."
|
||||
);
|
||||
@@ -139,14 +184,13 @@ impl Muxer {
|
||||
}
|
||||
}
|
||||
|
||||
// ТЕПЕРЬ СИНХРОННАЯ ФУНКЦИЯ БЕЗ БЛОКИРОВОК
|
||||
pub fn send_data_safe(
|
||||
&self,
|
||||
stream_id: u32,
|
||||
mut data: Bytes,
|
||||
is_udp: bool,
|
||||
) -> Result<(), String> {
|
||||
const MAX_PAYLOAD_CHUNK: usize = 1300;
|
||||
let max_chunk = NetworkConfig::global().tcp_chunk_size;
|
||||
let frame_type = if is_udp {
|
||||
FrameType::UdpData
|
||||
} else {
|
||||
@@ -154,7 +198,7 @@ impl Muxer {
|
||||
};
|
||||
|
||||
while !data.is_empty() {
|
||||
let chunk_size = std::cmp::min(data.len(), MAX_PAYLOAD_CHUNK);
|
||||
let chunk_size = std::cmp::min(data.len(), max_chunk);
|
||||
let chunk = data.split_to(chunk_size);
|
||||
|
||||
self.send_to_network(MuxMessage {
|
||||
@@ -162,12 +206,10 @@ impl Muxer {
|
||||
frame_type: frame_type.clone(),
|
||||
data: chunk,
|
||||
})?;
|
||||
// task::yield_now().await удален, так как отправка теперь мгновенная
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ТЕПЕРЬ СИНХРОННАЯ ФУНКЦИЯ БЕЗ БЛОКИРОВОК
|
||||
pub fn send_control(
|
||||
&self,
|
||||
stream_id: u32,
|
||||
@@ -182,26 +224,172 @@ impl Muxer {
|
||||
}
|
||||
|
||||
pub fn register_stream(&self, stream_id: u32, tx: Sender<Bytes>) {
|
||||
self.streams.insert(stream_id, tx);
|
||||
self.streams
|
||||
.insert(stream_id, (tx, Arc::new(StreamStats::default())));
|
||||
}
|
||||
|
||||
pub fn remove_stream(&self, stream_id: u32) {
|
||||
self.streams.remove(&stream_id);
|
||||
}
|
||||
|
||||
// ТЕПЕРЬ СИНХРОННАЯ ФУНКЦИЯ БЕЗ БЛОКИРОВОК
|
||||
// ЛОКАЛЬНАЯ ДИСПЕТЧЕРИЗАЦИЯ (Остается async, так как тут мы используем wait для TUN)
|
||||
pub async fn dispatch_to_local(&self, stream_id: u32, data: Bytes) {
|
||||
// 1. Клонируем Sender и СРАЗУ отпускаем блокировку DashMap.
|
||||
// Это спасет Muxer от дедлока, пока мы будем висеть на .await
|
||||
let tx_opt = self.streams.get(&stream_id).map(|tx_ref| tx_ref.clone());
|
||||
let stream_opt = self
|
||||
.streams
|
||||
.get(&stream_id)
|
||||
.map(|s| (s.0.clone(), s.1.clone()));
|
||||
|
||||
if let Some((tx, stats)) = stream_opt {
|
||||
let size = data.len() as u64;
|
||||
|
||||
if let Some(tx) = tx_opt {
|
||||
// 2. Ждем, если локальная очередь забита (Backpressure).
|
||||
// Пакет НЕ дропается. Tokio перестает читать из физического сокета.
|
||||
if tx.send(data).await.is_err() {
|
||||
// Если канал закрыт (клиент отключился), удаляем стрим
|
||||
self.remove_stream(stream_id);
|
||||
} else {
|
||||
stats.rx_bytes.fetch_add(size, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record_leg_rx(&self, leg_id: u32, bytes: u64) {
|
||||
if let Some(leg) = self.legs.get(&leg_id) {
|
||||
leg.stats.rx_bytes.fetch_add(bytes, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
// УМНЫЙ HEALTH CHECK: Пингуем каждую ногу отдельно для честного RTT
|
||||
pub async fn perform_health_check(&self) {
|
||||
let legs: Vec<(u32, Sender<MuxMessage>)> = self
|
||||
.legs
|
||||
.iter()
|
||||
.map(|k| (*k.key(), k.value().control_tx.clone()))
|
||||
.collect();
|
||||
|
||||
for (leg_id, tx) in legs {
|
||||
let probe_stream_id = self.id_gen.next();
|
||||
let (probe_tx, mut probe_rx) = tokio::sync::mpsc::channel(2);
|
||||
self.register_stream(probe_stream_id, probe_tx);
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
let msg = MuxMessage {
|
||||
stream_id: probe_stream_id,
|
||||
frame_type: FrameType::Handshake,
|
||||
data: Bytes::from("PING"),
|
||||
};
|
||||
|
||||
// Отправляем PING прямо в конкретную ногу в обход балансировщика
|
||||
if tx.try_send(msg).is_ok() {
|
||||
if let Ok(Some(_)) =
|
||||
tokio::time::timeout(std::time::Duration::from_secs(2), probe_rx.recv()).await
|
||||
{
|
||||
let rtt = start.elapsed().as_millis() as u32;
|
||||
// Сохраняем RTT в стату ноги
|
||||
if let Some(leg) = self.legs.get(&leg_id) {
|
||||
leg.stats.rtt_ms.store(rtt, Ordering::Relaxed);
|
||||
}
|
||||
} else {
|
||||
warn!(leg_id, "❌ Health check timed out for Leg");
|
||||
}
|
||||
}
|
||||
|
||||
self.remove_stream(probe_stream_id);
|
||||
}
|
||||
}
|
||||
|
||||
// ФОРМАТИРОВАНИЕ БАЙТ
|
||||
fn format_size(bytes: u64) -> String {
|
||||
const KB: u64 = 1024;
|
||||
const MB: u64 = KB * 1024;
|
||||
const GB: u64 = MB * 1024;
|
||||
if bytes >= GB {
|
||||
format!("{:.2} GB", bytes as f64 / GB as f64)
|
||||
} else if bytes >= MB {
|
||||
format!("{:.2} MB", bytes as f64 / MB as f64)
|
||||
} else if bytes >= KB {
|
||||
format!("{:.2} KB", bytes as f64 / KB as f64)
|
||||
} else {
|
||||
format!("{} B", bytes)
|
||||
}
|
||||
}
|
||||
|
||||
// ВЫВОД КРАСИВОЙ СТАТИСТИКИ
|
||||
pub fn print_topology_tree(&self) {
|
||||
println!(
|
||||
"\n🌐 Netrunner Tunnel Topology [Session: {}]",
|
||||
self.session_id
|
||||
);
|
||||
|
||||
let mut total_tx = 0;
|
||||
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);
|
||||
|
||||
total_tx += tx;
|
||||
total_rx += rx;
|
||||
|
||||
let leg_type = if id % 2 == 0 { "TCP" } else { "UDP" };
|
||||
let rtt_str = if rtt == 0 {
|
||||
"N/A".to_string()
|
||||
} else {
|
||||
format!("{}ms", rtt)
|
||||
};
|
||||
|
||||
legs_info.push(format!(
|
||||
" ├─ Leg {} ({}) ─ ⇡ {:<9} | ⇣ {:<9} [RTT: {}]",
|
||||
id,
|
||||
leg_type,
|
||||
Self::format_size(tx),
|
||||
Self::format_size(rx),
|
||||
rtt_str
|
||||
));
|
||||
}
|
||||
|
||||
println!(
|
||||
"├─ 📊 Global Traffic: ⇡ {} | ⇣ {}",
|
||||
Self::format_size(total_tx),
|
||||
Self::format_size(total_rx)
|
||||
);
|
||||
|
||||
println!("├─ 🦵 Physical Legs (Active: {})", legs_info.len());
|
||||
for (i, info) in legs_info.iter().enumerate() {
|
||||
if i == legs_info.len() - 1 {
|
||||
println!("{}", info.replace("├─", "└─"));
|
||||
} else {
|
||||
println!("{}", info);
|
||||
}
|
||||
}
|
||||
|
||||
let streams_count = self.streams.len();
|
||||
println!("└─ 🔀 Virtual Streams (Active: {})", streams_count);
|
||||
|
||||
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 {
|
||||
" └─"
|
||||
} else {
|
||||
" ├─"
|
||||
};
|
||||
|
||||
println!(
|
||||
"{} Stream {:<4} ─ ⇡ {:<9} | ⇣ {}",
|
||||
prefix,
|
||||
id,
|
||||
Self::format_size(tx),
|
||||
Self::format_size(rx)
|
||||
);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user