backpressure, bufferbloat fix and legs reconnects
This commit is contained in:
+12
-7
@@ -9,9 +9,11 @@ pub struct NetworkConfig {
|
||||
pub connection_buf_size: usize,
|
||||
pub tcp_buffer_size: usize,
|
||||
pub udp_buffer_size: usize,
|
||||
|
||||
pub tcp_chunk_size: usize,
|
||||
|
||||
// 🔥 Единый конфиг для всех каналов Tokio
|
||||
pub channel_capacity: usize,
|
||||
|
||||
// Буферы сокетов smoltcp
|
||||
pub tcp_rx_heavy: usize,
|
||||
pub tcp_tx_heavy: usize,
|
||||
@@ -28,14 +30,17 @@ impl NetworkConfig {
|
||||
pub fn new(system_mtu: usize) -> Self {
|
||||
Self {
|
||||
mtu: system_mtu,
|
||||
connection_buf_size: 1024 * 1024 * 2,
|
||||
tcp_buffer_size: 512 * 1024,
|
||||
connection_buf_size: 1024 * 1024 * 4,
|
||||
tcp_buffer_size: 1024 * 1024,
|
||||
udp_buffer_size: 64 * 1024,
|
||||
|
||||
tcp_chunk_size: system_mtu - 100,
|
||||
// 🔥 Расширяем TCP окна под BBR (Разблокируем Gigabit на дальние дистанции)
|
||||
tcp_rx_heavy: 256 * 1024, //512
|
||||
tcp_tx_heavy: 256 * 1024, // 1 MB
|
||||
|
||||
// 🔥 Канал расширен для сглаживания микро-обрывов связи
|
||||
channel_capacity: 2048,
|
||||
|
||||
// Расширяем TCP окна под BBR
|
||||
tcp_rx_heavy: 256 * 1024,
|
||||
tcp_tx_heavy: 256 * 1024,
|
||||
|
||||
tcp_rx_light: 32 * 1024,
|
||||
tcp_tx_light: 32 * 1024,
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::nrxp::FrameType;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use netrunner_logger::{debug, error, info, warn};
|
||||
use tokio::net::UdpSocket;
|
||||
use tokio::sync::mpsc; // Подтянет UnboundedReceiver
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::time::timeout;
|
||||
|
||||
struct StreamGuard {
|
||||
@@ -27,7 +27,7 @@ pub(crate) async fn run_tcp_bridge<R, W>(
|
||||
mut reader: R,
|
||||
mut writer: W,
|
||||
muxer: Arc<Muxer>,
|
||||
mut v_rx: mpsc::UnboundedReceiver<Bytes>, // 🔥 Перешли на Unbounded
|
||||
mut v_rx: mpsc::Receiver<Bytes>,
|
||||
) where
|
||||
R: tokio::io::AsyncReadExt + Unpin,
|
||||
W: tokio::io::AsyncWriteExt + Unpin,
|
||||
@@ -43,7 +43,6 @@ pub(crate) async fn run_tcp_bridge<R, W>(
|
||||
buf.reserve(NetworkConfig::global().tcp_buffer_size);
|
||||
let select_res = timeout(BRIDGE_IDLE_TIMEOUT, async {
|
||||
tokio::select! {
|
||||
// 1. Читаем из реального сокета интернета -> шлем в туннель
|
||||
res = reader.read_buf(&mut buf) => {
|
||||
match res {
|
||||
Ok(0) => {
|
||||
@@ -57,10 +56,11 @@ pub(crate) async fn run_tcp_bridge<R, W>(
|
||||
data: buf.split().freeze(),
|
||||
};
|
||||
|
||||
// send_to_network теперь внутри использует UnboundedSender,
|
||||
// поэтому этот вызов больше никогда не заблокирует цикл из-за Full
|
||||
if muxer.send_to_network(msg).await.is_err() {
|
||||
return Ok(false);
|
||||
// 🔥 ФИКС: Если нет живых ног туннеля — просто дропаем пакет!
|
||||
// Мы НЕ закрываем мост (return Ok(false)),
|
||||
// TCP-стек на клиенте сам сделает ретрансмиссию
|
||||
warn!(stream_id, "All tunnel legs dead. Dropping packet. TCP will retransmit.");
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
@@ -71,7 +71,6 @@ pub(crate) async fn run_tcp_bridge<R, W>(
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Получаем из туннеля -> пишем в реальный сокет
|
||||
maybe_data = v_rx.recv() => {
|
||||
match maybe_data {
|
||||
Some(data) => {
|
||||
@@ -106,7 +105,6 @@ pub(crate) async fn run_tcp_bridge<R, W>(
|
||||
}
|
||||
}
|
||||
|
||||
// Сообщаем другой стороне, что стрим закрыт
|
||||
let _ = muxer
|
||||
.send_to_network(MuxMessage {
|
||||
stream_id,
|
||||
@@ -122,7 +120,7 @@ pub(crate) async fn run_udp_bridge(
|
||||
stream_id: u32,
|
||||
socket: UdpSocket,
|
||||
muxer: Arc<Muxer>,
|
||||
mut v_rx: mpsc::UnboundedReceiver<Bytes>, // 🔥 Перешли на Unbounded
|
||||
mut v_rx: mpsc::Receiver<Bytes>,
|
||||
) {
|
||||
let _guard = StreamGuard {
|
||||
stream_id,
|
||||
@@ -137,15 +135,13 @@ pub(crate) async fn run_udp_bridge(
|
||||
loop {
|
||||
let select_res = timeout(BRIDGE_IDLE_TIMEOUT, async {
|
||||
tokio::select! {
|
||||
// Из интернета в туннель
|
||||
res = socket.recv(&mut buf) => {
|
||||
match res {
|
||||
Ok(n) if n > 0 => {
|
||||
let data = Bytes::copy_from_slice(&buf[..n]);
|
||||
// Внутри muxer теперь тоже Unbounded
|
||||
if let Err(e) = muxer.send_data_safe(stream_id, data, true).await {
|
||||
error!(stream_id, "UDP Failed to send to tunnel: {}", e);
|
||||
return Err(e);
|
||||
warn!(stream_id, "UDP Tunnel legs dead. Dropping packet: {}", e);
|
||||
// 🔥 ФИКС: Опять же, не обрываем стрим из-за мертвого туннеля!
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
@@ -157,7 +153,6 @@ pub(crate) async fn run_udp_bridge(
|
||||
}
|
||||
}
|
||||
|
||||
// Из туннеля в интернет
|
||||
maybe_data = v_rx.recv() => {
|
||||
match maybe_data {
|
||||
Some(data) => {
|
||||
|
||||
@@ -90,11 +90,7 @@ impl ClientHandler {
|
||||
muxer: Arc<Muxer>,
|
||||
session_id: &str,
|
||||
) -> Result<(), String> {
|
||||
let leg_name = if leg_id % 2 == 0 {
|
||||
"TCP-Leg"
|
||||
} else {
|
||||
"UDP-Leg"
|
||||
};
|
||||
let leg_name = format!("TCP-Leg-{}", leg_id);
|
||||
|
||||
let mut addrs = tokio::net::lookup_host(remote_proxy_addr)
|
||||
.await
|
||||
@@ -174,7 +170,6 @@ impl ClientHandler {
|
||||
let codec = Codec::new(cipher, session_keys.get_auth_key());
|
||||
let (rx_codec, mut tx_codec) = codec.split();
|
||||
|
||||
// 🔥 ФИКС: Используем Heartbeat для первой авторизации соединения
|
||||
let auth_payload = Bytes::from(format!("{}:{}", session_id, leg_id));
|
||||
let encrypted_auth = tx_codec
|
||||
.encode_frame(0, FrameType::Heartbeat, auth_payload)
|
||||
@@ -185,8 +180,9 @@ impl ClientHandler {
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let (control_tx, control_rx) = mpsc::unbounded_channel::<MuxMessage>();
|
||||
let (data_tx, data_rx) = mpsc::unbounded_channel::<MuxMessage>();
|
||||
let cap = NetworkConfig::global().channel_capacity;
|
||||
let (control_tx, control_rx) = mpsc::channel::<MuxMessage>(cap);
|
||||
let (data_tx, data_rx) = mpsc::channel::<MuxMessage>(cap);
|
||||
muxer.add_leg(leg_id, control_tx, data_tx);
|
||||
|
||||
let handler = Arc::new(StreamHandler::new(muxer.clone(), None));
|
||||
@@ -208,8 +204,8 @@ impl ClientHandler {
|
||||
|
||||
pub async fn connect(
|
||||
remote_proxy_addr: &str,
|
||||
mut rx_from_engine: mpsc::UnboundedReceiver<RawCastFrame>,
|
||||
tx_to_engine: mpsc::UnboundedSender<RawCastFrame>,
|
||||
mut rx_from_engine: mpsc::Receiver<RawCastFrame>,
|
||||
tx_to_engine: mpsc::Sender<RawCastFrame>,
|
||||
) -> Result<(), String> {
|
||||
let session_id = SessionManager::generate_id();
|
||||
let muxer = Arc::new(Muxer::new(true, session_id.clone()));
|
||||
@@ -266,7 +262,8 @@ impl ClientHandler {
|
||||
),
|
||||
);
|
||||
|
||||
let (v_tx, mut v_rx) = mpsc::unbounded_channel::<Bytes>();
|
||||
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();
|
||||
@@ -291,7 +288,7 @@ impl ClientHandler {
|
||||
port,
|
||||
proto == LocalProtocol::Udp,
|
||||
) {
|
||||
let _ = tx_to_tun.send(raw);
|
||||
let _ = tx_to_tun.send(raw).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -330,7 +327,6 @@ impl ClientHandler {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ServerHandler {
|
||||
pub(crate) conn: Connection,
|
||||
pub(crate) session_manager: Arc<SessionManager>,
|
||||
@@ -358,14 +354,23 @@ impl ServerHandler {
|
||||
|
||||
if let Ok(Ok(target_server)) = target_stream {
|
||||
let (mut server_read, mut server_write) = target_server.into_split();
|
||||
|
||||
// Отправляем всё, что уже успели прочитать от клиента
|
||||
if !initial_data.is_empty() {
|
||||
let _ = server_write.write_all(&initial_data).await;
|
||||
if server_write.write_all(&initial_data).await.is_err() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
let _ = tokio::io::copy_bidirectional(
|
||||
&mut tokio::io::join(&mut client_inbound, &mut client_outbound),
|
||||
&mut tokio::io::join(&mut server_read, &mut server_write),
|
||||
)
|
||||
.await;
|
||||
|
||||
// 🔥 ПРАВИЛЬНЫЙ способ связать 4 половинки сокетов в Tokio
|
||||
let client_to_server = tokio::io::copy(&mut client_inbound, &mut server_write);
|
||||
let server_to_client = tokio::io::copy(&mut server_read, &mut client_outbound);
|
||||
|
||||
// Ждем, пока любое из направлений не закроется
|
||||
let _ = tokio::join!(client_to_server, server_to_client);
|
||||
debug!("Stealth fallback connection closed.");
|
||||
} else {
|
||||
warn!("Failed to connect to fallback host.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -382,25 +387,26 @@ impl TunnelHandler for ServerHandler {
|
||||
} = self.conn;
|
||||
let mut session_keys = SessionKeys::new(false);
|
||||
|
||||
// --- ФАЗА 1: ПЕРВЫЙ ПАКЕТ И АВТОРИЗАЦИЯ ---
|
||||
let hello = loop {
|
||||
let buf_snapshot = read_buf.clone().freeze();
|
||||
|
||||
match TlsBridge::unpack_handshake(&mut read_buf) {
|
||||
Ok(Some(client_msg)) => {
|
||||
info!("✅ Valid Netrunner ClientHello detected");
|
||||
match TlsBridge::wrap_server_hello(
|
||||
&client_msg,
|
||||
&mut session_keys,
|
||||
&ServerProfile::MODERN,
|
||||
) {
|
||||
Ok(sh) => break sh,
|
||||
Ok(sh) => {
|
||||
info!("✅ Valid Netrunner ClientHello detected");
|
||||
break sh;
|
||||
}
|
||||
Err(e) => {
|
||||
if e.execute_strategy() == ErrorAction::Redirect {
|
||||
Self::handle_stealth_fallback(inbound, outbound, buf_snapshot)
|
||||
.await;
|
||||
return Ok(());
|
||||
}
|
||||
return Err("ServerHello Generation Failed".into());
|
||||
// 🔥 ФИКС: ЛЮБАЯ ошибка (включая неверный Auth Tag) ведет на донора!
|
||||
warn!("❌ Unauthorized/Invalid ClientHello. Triggering Stealth Fallback. Reason: {:?}", e.stage);
|
||||
Self::handle_stealth_fallback(inbound, outbound, buf_snapshot).await;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -418,10 +424,10 @@ impl TunnelHandler for ServerHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if e.execute_strategy() == ErrorAction::Redirect {
|
||||
Self::handle_stealth_fallback(inbound, outbound, buf_snapshot).await;
|
||||
}
|
||||
Err(_) => {
|
||||
// 🔥 ФИКС: Мусор или сканер протоколов (не TLS) -> ведет на донора
|
||||
warn!("❌ Handshake parse failed (Not a valid TLS probe). Triggering Stealth Fallback.");
|
||||
Self::handle_stealth_fallback(inbound, outbound, buf_snapshot).await;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
@@ -439,7 +445,6 @@ impl TunnelHandler for ServerHandler {
|
||||
let codec = Codec::new(cipher, session_keys.get_auth_key());
|
||||
let (mut rx_codec, tx_codec) = codec.split();
|
||||
|
||||
// 🔥 ФИКС: Ожидаем Heartbeat в качестве авторизационного фрейма!
|
||||
let (session_id, leg_id) = loop {
|
||||
match rx_codec.decode_inbound(&mut read_buf) {
|
||||
Ok(Some(frame)) => {
|
||||
@@ -475,8 +480,9 @@ impl TunnelHandler for ServerHandler {
|
||||
};
|
||||
|
||||
let muxer = self.session_manager.get_or_create(&session_id);
|
||||
let (control_tx, control_rx) = mpsc::unbounded_channel::<MuxMessage>();
|
||||
let (data_tx, data_rx) = mpsc::unbounded_channel::<MuxMessage>();
|
||||
let cap = NetworkConfig::global().channel_capacity;
|
||||
let (control_tx, control_rx) = mpsc::channel::<MuxMessage>(cap);
|
||||
let (data_tx, data_rx) = mpsc::channel::<MuxMessage>(cap);
|
||||
|
||||
muxer.add_leg(leg_id, control_tx, data_tx);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ use netrunner_logger::{debug, error, info};
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
net::tcp::{OwnedReadHalf, OwnedWriteHalf},
|
||||
sync::mpsc::UnboundedReceiver,
|
||||
sync::mpsc::Receiver,
|
||||
};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
@@ -23,8 +23,8 @@ pub(crate) struct TunnelEngine {
|
||||
pub rx_codec: RxCodec,
|
||||
pub tx_codec: TxCodec,
|
||||
pub read_buf: BytesMut,
|
||||
pub control_rx: UnboundedReceiver<MuxMessage>,
|
||||
pub data_rx: UnboundedReceiver<MuxMessage>,
|
||||
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>,
|
||||
@@ -94,7 +94,6 @@ impl TunnelEngine {
|
||||
}
|
||||
|
||||
for frame in frames {
|
||||
// Любой Heartbeat – это подтверждение жизни туннеля, обновляем пинг
|
||||
if frame.header.frame_type == FrameType::Heartbeat {
|
||||
let m = muxer.clone();
|
||||
tokio::spawn(async move {
|
||||
|
||||
@@ -19,12 +19,7 @@ pub struct RemoteOpener {
|
||||
}
|
||||
|
||||
impl RemoteOpener {
|
||||
pub async fn open_tcp(
|
||||
&self,
|
||||
stream_id: u32,
|
||||
target: String,
|
||||
v_rx: mpsc::UnboundedReceiver<Bytes>,
|
||||
) {
|
||||
pub async fn open_tcp(&self, stream_id: u32, target: String, v_rx: mpsc::Receiver<Bytes>) {
|
||||
let muxer = self.muxer.clone();
|
||||
tokio::spawn(async move {
|
||||
info!(stream_id, "🌐 [Remote] Connecting to {}", target);
|
||||
@@ -53,12 +48,7 @@ impl RemoteOpener {
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn open_udp(
|
||||
&self,
|
||||
stream_id: u32,
|
||||
target: String,
|
||||
v_rx: mpsc::UnboundedReceiver<Bytes>,
|
||||
) {
|
||||
pub async fn open_udp(&self, stream_id: u32, target: String, v_rx: mpsc::Receiver<Bytes>) {
|
||||
let muxer = self.muxer.clone();
|
||||
tokio::spawn(async move {
|
||||
info!(stream_id, "🚀 [Remote] Binding UDP for {}", target);
|
||||
@@ -138,7 +128,8 @@ impl StreamHandler {
|
||||
let target = String::from_utf8_lossy(&payload).to_string();
|
||||
|
||||
if let Some(opener) = &self.opener {
|
||||
let (v_tx, v_rx) = mpsc::unbounded_channel::<Bytes>();
|
||||
let cap = NetworkConfig::global().channel_capacity;
|
||||
let (v_tx, v_rx) = mpsc::channel::<Bytes>(cap);
|
||||
self.muxer.register_stream(stream_id, v_tx);
|
||||
|
||||
if is_udp {
|
||||
|
||||
@@ -4,7 +4,7 @@ use netrunner_logger::{debug, info, trace, warn};
|
||||
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use tokio::sync::mpsc::Sender;
|
||||
|
||||
use crate::net::{HEALTH_CHECK_TIMEOUT, MAX_TUNNEL_LEGS, MUXER_POOL_SIZE};
|
||||
use crate::nrxp::FrameType;
|
||||
@@ -24,8 +24,8 @@ pub struct StreamStats {
|
||||
|
||||
#[derive(Clone)]
|
||||
struct MuxLeg {
|
||||
control_tx: UnboundedSender<MuxMessage>,
|
||||
data_tx: UnboundedSender<MuxMessage>,
|
||||
control_tx: Sender<MuxMessage>,
|
||||
data_tx: Sender<MuxMessage>,
|
||||
stats: Arc<LegStats>,
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ impl IdGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
// 🔥 Клонируем сообщения для Retry-логики
|
||||
#[derive(Clone)]
|
||||
pub struct MuxMessage {
|
||||
pub stream_id: u32,
|
||||
pub frame_type: FrameType,
|
||||
@@ -63,7 +65,7 @@ pub static GLOBAL_MIN_RTT: AtomicU32 = AtomicU32::new(250);
|
||||
#[derive(Clone)]
|
||||
pub struct Muxer {
|
||||
legs: Arc<DashMap<u32, MuxLeg>>,
|
||||
streams: Arc<DashMap<u32, (UnboundedSender<Bytes>, Arc<StreamStats>)>>,
|
||||
streams: Arc<DashMap<u32, (Sender<Bytes>, Arc<StreamStats>)>>,
|
||||
stream_bindings: Arc<DashMap<u32, u32>>,
|
||||
pending_pings: Arc<DashMap<u32, Instant>>,
|
||||
id_gen: Arc<IdGenerator>,
|
||||
@@ -85,8 +87,8 @@ impl Muxer {
|
||||
pub fn add_leg(
|
||||
&self,
|
||||
leg_id: u32,
|
||||
control_tx: UnboundedSender<MuxMessage>,
|
||||
data_tx: UnboundedSender<MuxMessage>,
|
||||
control_tx: Sender<MuxMessage>,
|
||||
data_tx: Sender<MuxMessage>,
|
||||
) {
|
||||
if self.legs.len() >= MAX_TUNNEL_LEGS as usize {
|
||||
warn!(leg_id, "MUXER: Max legs reached: {}", MAX_TUNNEL_LEGS);
|
||||
@@ -106,19 +108,24 @@ impl Muxer {
|
||||
|
||||
pub fn remove_leg(&self, leg_id: u32) {
|
||||
self.legs.remove(&leg_id);
|
||||
self.stream_bindings.clear();
|
||||
info!(leg_id, "MUXER: Leg removed, all streams re-balanced");
|
||||
// Мы не чистим stream_bindings полностью,
|
||||
// балансировщик сам переназначит мертвые привязки
|
||||
info!(
|
||||
leg_id,
|
||||
"MUXER: Leg removed, streams will re-balance dynamically"
|
||||
);
|
||||
}
|
||||
|
||||
pub fn active_legs_count(&self) -> usize {
|
||||
self.legs.len()
|
||||
}
|
||||
|
||||
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() {
|
||||
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) {
|
||||
@@ -126,23 +133,14 @@ impl Muxer {
|
||||
}
|
||||
}
|
||||
|
||||
let is_udp = matches!(frame_type, FrameType::UdpData | FrameType::UdpConnect);
|
||||
|
||||
// 🔥 ФИКС: Убрано разделение на TCP/UDP ноги.
|
||||
// Все физические ноги у нас TCP, поэтому мы балансируем трафик по всем ногам!
|
||||
let mut candidates: Vec<(u32, MuxLeg)> = self
|
||||
.legs
|
||||
.iter()
|
||||
.map(|kv| (*kv.key(), kv.value().clone()))
|
||||
.filter(|(id, _)| if is_udp { id % 2 != 0 } else { id % 2 == 0 })
|
||||
.collect();
|
||||
|
||||
if candidates.is_empty() {
|
||||
candidates = 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;
|
||||
@@ -156,6 +154,8 @@ impl Muxer {
|
||||
|
||||
let pool_size = std::cmp::min(candidates.len(), MUXER_POOL_SIZE);
|
||||
let (selected_id, selected_leg) = candidates[stream_id as usize % pool_size].clone();
|
||||
|
||||
// Обновляем привязку
|
||||
self.stream_bindings.insert(stream_id, selected_id);
|
||||
|
||||
Some((selected_id, selected_leg))
|
||||
@@ -185,37 +185,46 @@ impl Muxer {
|
||||
}
|
||||
}
|
||||
|
||||
// 🔥 ФИКС: Добавлена логика повторной отправки (Retry) при обрыве связи!
|
||||
pub async fn send_to_network(&self, message: MuxMessage) -> Result<(), String> {
|
||||
let stream_id = message.stream_id;
|
||||
let size = message.data.len() as u64;
|
||||
let mut attempts = 0;
|
||||
|
||||
let (leg_id, leg) = self
|
||||
.select_leg(&message.frame_type, stream_id)
|
||||
.ok_or_else(|| "MUXER: No active legs available".to_string())?;
|
||||
loop {
|
||||
if attempts >= 3 {
|
||||
return Err("MUXER: All attempts to send failed".to_string());
|
||||
}
|
||||
attempts += 1;
|
||||
|
||||
let target_tx = match message.frame_type {
|
||||
FrameType::Connect
|
||||
| FrameType::Close
|
||||
| FrameType::UdpConnect
|
||||
| FrameType::Heartbeat => leg.control_tx.clone(),
|
||||
_ => leg.data_tx.clone(),
|
||||
};
|
||||
let (leg_id, leg) = match self.select_leg(&message.frame_type, message.stream_id) {
|
||||
Some(l) => l,
|
||||
None => return Err("MUXER: No active legs available".to_string()),
|
||||
};
|
||||
|
||||
if target_tx.send(message).is_err() {
|
||||
self.remove_leg(leg_id);
|
||||
return Err(format!("MUXER: Leg {} died during send", leg_id));
|
||||
let target_tx = match message.frame_type {
|
||||
FrameType::Connect
|
||||
| FrameType::Close
|
||||
| FrameType::UdpConnect
|
||||
| FrameType::Heartbeat => &leg.control_tx,
|
||||
_ => &leg.data_tx,
|
||||
};
|
||||
|
||||
if target_tx.send(message.clone()).await.is_err() {
|
||||
// Нога умерла! Выкидываем её и пробуем отправить пакет в ДРУГУЮ ногу (continue)
|
||||
self.remove_leg(leg_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
leg.stats.tx_bytes.fetch_add(size, Ordering::Relaxed);
|
||||
if let Some(stream_ref) = self.streams.get(&message.stream_id) {
|
||||
stream_ref
|
||||
.value()
|
||||
.1
|
||||
.tx_bytes
|
||||
.fetch_add(size, Ordering::Relaxed);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
pub async fn send_data_safe(
|
||||
@@ -251,7 +260,7 @@ impl Muxer {
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn register_stream(&self, stream_id: u32, tx: UnboundedSender<Bytes>) {
|
||||
pub fn register_stream(&self, stream_id: u32, tx: Sender<Bytes>) {
|
||||
self.streams
|
||||
.insert(stream_id, (tx, Arc::new(StreamStats::default())));
|
||||
}
|
||||
@@ -269,7 +278,7 @@ impl Muxer {
|
||||
.map(|s| (s.value().0.clone(), s.value().1.clone()));
|
||||
if let Some((tx, stats)) = stream_opt {
|
||||
let size = data.len() as u64;
|
||||
if tx.send(data).is_err() {
|
||||
if tx.send(data).await.is_err() {
|
||||
self.remove_stream(stream_id);
|
||||
} else {
|
||||
stats.rx_bytes.fetch_add(size, Ordering::Relaxed);
|
||||
@@ -297,20 +306,19 @@ impl Muxer {
|
||||
let tx = leg.control_tx.clone();
|
||||
|
||||
let probe_stream_id = self.id_gen.next();
|
||||
let (probe_tx, mut probe_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
let cap = crate::net::NetworkConfig::global().channel_capacity;
|
||||
let (probe_tx, mut probe_rx) = tokio::sync::mpsc::channel(cap);
|
||||
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, // 🔥 Теперь Heartbeat
|
||||
frame_type: FrameType::Heartbeat,
|
||||
data: Bytes::from("PING"),
|
||||
};
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
if tx.send(msg).is_err() {
|
||||
if tx.send(msg).await.is_err() {
|
||||
warn!(leg_id, "❌ MUXER: Leg channel dropped, killing leg");
|
||||
self.remove_leg(leg_id);
|
||||
self.remove_stream(probe_stream_id);
|
||||
@@ -330,15 +338,6 @@ impl Muxer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_min_rtt(&self) -> u32 {
|
||||
self.legs
|
||||
.iter()
|
||||
.map(|kv| kv.value().stats.rtt_ms.load(Ordering::Relaxed))
|
||||
.filter(|&rtt| rtt > 0)
|
||||
.min()
|
||||
.unwrap_or(250) // Fallback
|
||||
}
|
||||
|
||||
fn format_size(bytes: u64) -> String {
|
||||
const KB: u64 = 1024;
|
||||
const MB: u64 = KB * 1024;
|
||||
|
||||
Reference in New Issue
Block a user