bufferbloat fixes and reconnect

This commit is contained in:
2026-04-15 13:33:38 +07:00
parent fac16c5296
commit f5f1d20f54
8 changed files with 164 additions and 157 deletions
+16 -6
View File
@@ -132,11 +132,11 @@ impl TcpConnection {
fn poll_and_process(&mut self, socket: &mut tcp::Socket) {
let current_rtt = GLOBAL_MIN_RTT.load(Ordering::Relaxed);
let rtt = Duration::from_millis(current_rtt as u64);
let rtt = smoltcp::time::Duration::from_millis(current_rtt as u64);
socket.set_tunnel_rtt(rtt);
// Читаем из браузера в Туннель
if self.core.tx.capacity() > 0 {
if self.core.tx.capacity() > 200 {
while socket.can_recv() {
if let Ok(n) = socket.peek_slice(&mut self.chunk_buf) {
if n == 0 {
@@ -164,9 +164,20 @@ impl TcpConnection {
// Читаем из Туннеля в браузер
if !self.server_eof {
// 🔥 ФИКС БУФЕРБЛОАТА НА LTE: Ограничиваем локальный буфер ровно до 1 МБ!
// Это сбросит пинг на мобилках с 4500 мс до нормальных значений
while self.pending_bytes < 1024 * 1024 {
// 🔥 ДИНАМИЧЕСКИЙ BACKPRESSURE (Auto-Scaling Buffer)
// В зависимости от текущего пинга всей системы, мы разрешаем
// локальному сокету буферизовать разное количество данных.
let dynamic_limit = if current_rtt < 50 {
2 * 1024 * 1024 // 2 MB: Отличный Wi-Fi, качаем на максимум
} else if current_rtt < 150 {
1024 * 1024 // 1 MB: Хороший LTE
} else if current_rtt < 300 {
256 * 1024 // 256 KB: Загруженный 3G/LTE, начинаем зажимать
} else {
128 * 1024 // 128 KB: Сеть умирает (или сменилась вышка), спасаем пинг
};
while self.pending_bytes < dynamic_limit {
match self.core.rx.try_recv() {
Ok(data) => {
self.pending_bytes += data.len();
@@ -213,7 +224,6 @@ impl TcpConnection {
}
}
}
pub fn app_pending_out_size(&self) -> usize {
self.pending_bytes
}
+2 -29
View File
@@ -75,9 +75,9 @@ impl TargetResolver {
}
pub struct ConnectionManager {
tracker: SessionTracker,
pub tracker: SessionTracker, // 🔥 Теперь pub для Engine
resolver: TargetResolver,
tx_to_tunnel: mpsc::Sender<RawCastFrame>, // 🔥 Bounded
tx_to_tunnel: mpsc::Sender<RawCastFrame>,
factory: Arc<dyn SocketProvider>,
pending_connects: DashMap<FlowKey, Instant>,
}
@@ -97,33 +97,6 @@ impl ConnectionManager {
}
}
pub fn try_inject_inbound(&mut self, frame: RawCastFrame) -> Result<(), RawCastFrame> {
if frame.event == RawCastEvent::Close {
self.tracker.close_tunnel_session(frame.socket_id);
return Ok(());
}
if frame.event != RawCastEvent::Data {
return Ok(());
}
if let Some(tx) = self.tracker.get_inbound_tx(frame.socket_id) {
// 🔥 Защита от переполнения: Если очередь Bounded канала полна,
// мы тихо удаляем пакет. Очередь освободится, и TCP/BBR сделает свое дело (надежная доставка).
// Нельзя закрывать соединение!
if let Err(e) = tx.try_send(frame.payload.clone()) {
if let mpsc::error::TrySendError::Closed(_) = e {
self.tracker.close_tunnel_session(frame.socket_id);
} else {
// Backpressure in action: drop the packet, TCP will handle it
}
}
Ok(())
} else {
Ok(())
}
}
pub fn setup_sockets(factory: &dyn SocketProvider, n_icmp: usize) -> SocketSet<'static> {
factory.create_base_set(n_icmp)
}
+28 -18
View File
@@ -1,6 +1,6 @@
use netrunner_core::net::ClientHandler;
use netrunner_core::net::NetworkConfig;
use netrunner_core::rawcast::RawCastFrame;
use netrunner_core::rawcast::{RawCastEvent, RawCastFrame};
use smoltcp::iface::PollResult;
use smoltcp::time::Instant;
use smoltcp::wire::{IpAddress, IpCidr};
@@ -38,7 +38,7 @@ pub struct Engine {
to_smoltcp_tx: UnboundedSender<TokenBuffer>,
from_smoltcp_rx: Option<UnboundedReceiver<TokenBuffer>>,
avail: Arc<AtomicBool>,
rx_from_tunnel: Receiver<RawCastFrame>, // 🔥 Bounded
rx_from_tunnel: Option<Receiver<RawCastFrame>>, // 🔥 Теперь Option, чтобы отдать его в таску
factory: Arc<dyn SocketProvider>,
}
@@ -47,8 +47,8 @@ impl Engine {
config: Config,
caps: DeviceCapabilities,
dns_handler: DnsHandler,
tx_to_tunnel: Sender<RawCastFrame>, // 🔥 Bounded
rx_from_tunnel: Receiver<RawCastFrame>, // 🔥 Bounded
tx_to_tunnel: Sender<RawCastFrame>,
rx_from_tunnel: Receiver<RawCastFrame>,
factory: Arc<dyn SocketProvider>,
) -> Self {
let now = Engine::current_time();
@@ -57,7 +57,6 @@ impl Engine {
let interface = Interface::new(config, &mut device, now);
let socket_set = ConnectionManager::setup_sockets(factory.as_ref(), 2);
let manager = ConnectionManager::new(dns_handler, tx_to_tunnel, factory.clone());
Self {
@@ -68,7 +67,7 @@ impl Engine {
from_smoltcp_rx: Some(from_smoltcp_rx),
avail,
manager,
rx_from_tunnel,
rx_from_tunnel: Some(rx_from_tunnel),
factory,
}
}
@@ -86,6 +85,27 @@ impl Engine {
let mut last_stats_log = StdInstant::now();
// 🔥 ФИКС: Выносим чтение из туннеля в отдельную изолированную задачу!
// Это позволяет использовать `.await` при отправке данных в локальный сокет smoltcp.
// Если локальный сокет забит, эта задача мягко "засыпает", создавая идеальный Backpressure,
// при этом НЕ БЛОКИРУЯ основной цикл обработки интерфейсов Engine.
let inbound_map = self.manager.tracker.inbound_tx.clone();
let mut rx_tunnel = self.rx_from_tunnel.take().unwrap();
tokio::spawn(async move {
while let Some(frame) = rx_tunnel.recv().await {
if frame.event == RawCastEvent::Close {
inbound_map.remove(&frame.socket_id);
} else if frame.event == RawCastEvent::Data {
if let Some(tx) = inbound_map.get(&frame.socket_id) {
// ZERO DROP, PERFECT BACKPRESSURE.
// Ожидаем, пока локальный сокет не освободит место.
let _ = tx.send(frame.payload).await;
}
}
}
});
loop {
let mut repeat_poll = true;
while repeat_poll {
@@ -109,21 +129,10 @@ impl Engine {
.map(|d| std::cmp::min(Duration::from_micros(d.micros()), Duration::from_millis(5)))
.unwrap_or(Duration::from_millis(5));
// Основной цикл теперь занимается ТОЛЬКО прогонкой данных: TUN <-> smoltcp
tokio::select! {
_ = sleep(sleep_time) => {}
msg = self.rx_from_tunnel.recv() => {
if let Some(frame) = msg {
let _ = self.manager.try_inject_inbound(frame);
let mut count = 0;
while let Ok(frame) = self.rx_from_tunnel.try_recv() {
let _ = self.manager.try_inject_inbound(frame);
count += 1;
if count >= 64 { break; }
}
} else { break; }
}
msg = tun_to_engine_rx.recv() => {
if let Some(token) = msg {
self.manager.try_create_socket_from_packet(&token, &mut self.socket_set);
@@ -142,6 +151,7 @@ impl Engine {
.poll(now, &mut self.device, &mut self.socket_set)
}
// ... [Остальные методы spawn_tun_reader, spawn_tun_writer и set_route остаются без изменений] ...
fn spawn_tun_reader(
mut reader: DeviceReader,
to_engine: mpsc::UnboundedSender<TokenBuffer>,
+4 -6
View File
@@ -4,11 +4,13 @@ use std::{
};
use bytes::Bytes;
use dashmap::DashMap;
use smoltcp::{
iface::{SocketHandle, SocketSet},
socket::Socket,
wire::IpAddress,
};
use std::sync::Arc;
use tokio::sync::mpsc;
use crate::net::connection::{TcpConnection, UdpConnection};
@@ -17,7 +19,7 @@ pub struct SessionTracker {
last_activity: HashMap<SocketHandle, StdInstant>,
active_tcp: HashMap<SocketHandle, TcpConnection>,
active_udp: HashMap<SocketHandle, UdpConnection>,
inbound_tx: HashMap<u64, mpsc::Sender<Bytes>>, // 🔥 Bounded
pub inbound_tx: Arc<DashMap<u64, mpsc::Sender<Bytes>>>, // 🔥 Теперь Arc<DashMap>
handle_to_id: HashMap<SocketHandle, u64>,
id_to_handle: HashMap<u64, SocketHandle>,
@@ -32,7 +34,7 @@ impl SessionTracker {
last_activity: HashMap::new(),
active_tcp: HashMap::new(),
active_udp: HashMap::new(),
inbound_tx: HashMap::new(),
inbound_tx: Arc::new(DashMap::new()),
handle_to_id: HashMap::new(),
id_to_handle: HashMap::new(),
pending_tcp: HashMap::new(),
@@ -122,10 +124,6 @@ impl SessionTracker {
self.last_activity.insert(handle, StdInstant::now());
}
pub fn get_inbound_tx(&self, id: u64) -> Option<&mpsc::Sender<Bytes>> {
self.inbound_tx.get(&id)
}
pub fn close_tunnel_session(&mut self, id: u64) {
self.inbound_tx.remove(&id);
+1 -8
View File
@@ -95,14 +95,7 @@ impl SocketProvider for SmolSocketFactory {
fn reconfigure_tcp(&self, socket: &mut tcp::Socket, profile: TrafficProfile) {
socket.set_nagle_enabled(false);
match profile {
TrafficProfile::Interactive | TrafficProfile::Dns => {
socket.set_ack_delay(None);
}
TrafficProfile::Bulk | TrafficProfile::Default => {
socket.set_ack_delay(Some(Duration::from_millis(1)));
}
}
socket.set_ack_delay(Some(Duration::from_millis(50)));
socket.set_congestion_control(CongestionControl::Bbr);
}