fixed dedadlocks and buffer changes

This commit is contained in:
2026-03-31 10:34:15 +07:00
parent 71acb52386
commit dadf165da6
10 changed files with 145 additions and 107 deletions
+1
View File
@@ -2,3 +2,4 @@
things.txt things.txt
hosts_cache.txt hosts_cache.txt
/gen /gen
.env
+58 -27
View File
@@ -1,10 +1,42 @@
-include .env
# Настройки # Настройки
SERVER_IP = 147.45.43.70 SERVER_IP := $(strip $(SERVER_IP))
REMOTE_USER = root REMOTE_USER := $(strip $(REMOTE_USER))
REMOTE_PATH = /root/netr-core REMOTE_PATH := $(strip $(REMOTE_PATH))
SERVICE_NAME = netrunner-server SERVICE_NAME := $(strip $(SERVICE_NAME))
# Безопасное получение путей из ENV или дефолтов
ANDROID_ADB_HOST := $(strip $(ANDROID_ADB_HOST))
ANDROID_BUILD_SRC := $(strip $(ANDROID_BUILD_SRC))
ANDROID_PROJECT_LIBS := $(strip $(ANDROID_PROJECT_LIBS))
# Опции для стабильного SSH/Rsync в условиях плохого коннекта
# IPQoS=throughput помогает проталкивать пакеты через тайские магистрали
SSH_OPTS = -o IPQoS=throughput -o ServerAliveInterval=30
RSYNC_OPTS = -avz --inplace --progress -e "ssh $(SSH_OPTS)"
.PHONY: debug-client debug-server build-android build-server deploy-server logs ssh setup-server release-all
# --- Релизный цикл ---
# --- Релизный цикл ---
release-all: build-server deploy-server build-android
@echo "--- [Android] Проверка путей ---"
@if [ ! -d "$(ANDROID_BUILD_SRC)" ]; then echo "Ошибка: Директория сборки $(ANDROID_BUILD_SRC) не найдена!"; exit 1; fi
@echo "--- [Android] Синхронизация библиотек в проект ---"
@mkdir -p $(ANDROID_PROJECT_LIBS)
# Используем rsync локально: это безопаснее и быстрее, чем cp -r
# Удалит в jniLibs всё, чего нет в gen, чтобы билд был чистым
rsync -av "$(ANDROID_BUILD_SRC)/" "$(ANDROID_PROJECT_LIBS)/"
@echo "--- [ADB] Подключение к устройству ---"
@if [ -z "$(port)" ]; then \
echo "Ошибка: укажите порт, например: make release-all port=5555"; \
exit 1; \
fi
adb connect $(ANDROID_ADB_HOST):$(port)
@echo "--- Релиз полностью завершен ---"
.PHONY: debug-client debug-server build-android build-server deploy-server logs
debug-client: debug-client:
@echo "--- Сборка клиента (Debug) ---" @echo "--- Сборка клиента (Debug) ---"
@@ -20,7 +52,6 @@ debug-server:
@echo "--- Запуск сервера локально ---" @echo "--- Запуск сервера локально ---"
sudo ./target/debug/netrunner-server --port=4443 --host=0.0.0.0 sudo ./target/debug/netrunner-server --port=4443 --host=0.0.0.0
ABIS = arm64-v8a armeabi-v7a x86_64 x86 ABIS = arm64-v8a armeabi-v7a x86_64 x86
build-android: build-android:
@@ -28,46 +59,46 @@ build-android:
echo "Building for $$abi..."; \ echo "Building for $$abi..."; \
cargo ndk -t $$abi -o ./gen build --bin netrunner-client --release; \ cargo ndk -t $$abi -o ./gen build --bin netrunner-client --release; \
done done
cargo run --bin bindgen-tool generate \ cargo run --bin bindgen-tool generate \
--library gen/arm64-v8a/libnetrunner_client.so \ --library gen/arm64-v8a/libnetrunner_client.so \
--language kotlin \ --language kotlin \
--no-format \ --no-format \
--out-dir gen; \ --out-dir gen
build-server: build-server:
@echo "--- Сборка сервера (Release) ---"
cargo build --bin netrunner-server --release cargo build --bin netrunner-server --release
setup-server: setup-server:
@echo "--- Обновление системы и установка зависимостей ---" @echo "--- Подготовка сервера ---"
ssh $(REMOTE_USER)@$(SERVER_IP) "\ ssh $(SSH_OPTS) $(REMOTE_USER)@$(SERVER_IP) "\
apt-get update && \ apt-get update && \
apt-get upgrade -y && \ apt-get install -y build-essential libssl-dev pkg-config rsync && \
apt-get install -y build-essential libssl-dev pkg-config && \
mkdir -p $(REMOTE_PATH)" mkdir -p $(REMOTE_PATH)"
# Деплой # Деплой с использованием rsync и принудительной очисткой
deploy-server: deploy-server: build-server
@echo "--- Останавливаем старый сервер ---" @echo "--- [1/4] Остановка сервиса и очистка зависших процессов ---"
ssh $(REMOTE_USER)@$(SERVER_IP) "systemctl stop $(SERVICE_NAME)" || true ssh $(SSH_OPTS) $(REMOTE_USER)@$(SERVER_IP) "\
systemctl stop $(SERVICE_NAME) || true; \
pkill -9 $(SERVICE_NAME) || true; \
rm -f $(REMOTE_PATH)/$(SERVICE_NAME).tmp"
@echo "--- Копируем бинарник ---" @echo "--- [2/4] Копирование бинарника (rsync) ---"
ssh $(REMOTE_USER)@$(SERVER_IP) "mkdir -p $(REMOTE_PATH)" rsync $(RSYNC_OPTS) target/release/netrunner-server $(REMOTE_USER)@$(SERVER_IP):$(REMOTE_PATH)/
scp target/release/netrunner-server $(REMOTE_USER)@$(SERVER_IP):$(REMOTE_PATH)/
@echo "--- Копируем файл сервиса ---" @echo "--- [3/4] Обновление конфигурации systemd ---"
scp server/netrunner-server.service $(REMOTE_USER)@$(SERVER_IP):/etc/systemd/system/$(SERVICE_NAME).service rsync $(RSYNC_OPTS) server/netrunner-server.service $(REMOTE_USER)@$(SERVER_IP):/etc/systemd/system/
@echo "--- Запускаем сервис ---" @echo "--- [4/4] Перезапуск сервиса ---"
ssh $(REMOTE_USER)@$(SERVER_IP) "\ ssh $(SSH_OPTS) $(REMOTE_USER)@$(SERVER_IP) "\
systemctl daemon-reload && \ systemctl daemon-reload && \
systemctl enable $(SERVICE_NAME) && \ systemctl enable $(SERVICE_NAME) && \
systemctl start $(SERVICE_NAME)" systemctl start $(SERVICE_NAME)"
@echo "--- Деплой завершен успешно! ---"
# Логи
logs: logs:
ssh $(REMOTE_USER)@$(SERVER_IP) "journalctl -u $(SERVICE_NAME) -f" ssh $(SSH_OPTS) $(REMOTE_USER)@$(SERVER_IP) "journalctl -u $(SERVICE_NAME) -f"
# Вход на сервер
ssh: ssh:
ssh $(REMOTE_USER)@$(SERVER_IP) ssh $(SSH_OPTS) $(REMOTE_USER)@$(SERVER_IP)
+6 -10
View File
@@ -129,8 +129,6 @@ impl TcpConnection {
} }
fn poll_and_process(&mut self, socket: &mut tcp::Socket) { fn poll_and_process(&mut self, socket: &mut tcp::Socket) {
let max_pending = NetworkConfig::global().tcp_max_pending;
// 1. Вычитываем данные из smoltcp и шлем в Muxer // 1. Вычитываем данные из smoltcp и шлем в Muxer
while socket.can_recv() { while socket.can_recv() {
let mut full = false; let mut full = false;
@@ -166,19 +164,17 @@ impl TcpConnection {
// 2. Читаем данные из Muxer'а // 2. Читаем данные из Muxer'а
if !self.server_eof { if !self.server_eof {
loop { loop {
if self.pending_data.len() >= socket.send_capacity() {
break;
}
match self.core.rx.try_recv() { match self.core.rx.try_recv() {
Ok(data) => { Ok(data) => {
self.pending_data.extend_from_slice(&data); self.pending_data.extend_from_slice(&data);
if self.pending_data.len() >= max_pending {
break;
}
}
Err(mpsc::error::TryRecvError::Empty) => {
break;
} }
Err(mpsc::error::TryRecvError::Empty) => break,
Err(mpsc::error::TryRecvError::Disconnected) => { Err(mpsc::error::TryRecvError::Disconnected) => {
// ВАЖНО: Удаленный сервер прислал EOF! debug!(%self.core.handle, "Server sent EOF");
debug!(%self.core.handle, "Server sent EOF (channel disconnected).");
self.server_eof = true; self.server_eof = true;
break; break;
} }
+2 -4
View File
@@ -144,7 +144,7 @@ impl SocketFactory {
443 | 80 | 8080 => max_buf, 443 | 80 | 8080 => max_buf,
22 => 32 * 1024, 22 => 32 * 1024,
53 => 16 * 1024, 53 => 16 * 1024,
_ => max_buf / 4, _ => max_buf,
}; };
let mut socket = tcp::Socket::new( let mut socket = tcp::Socket::new(
@@ -240,9 +240,7 @@ impl ConnectionManager {
} }
} }
} else { } else {
// Если это происходит во время Connecting - значит сервер шлет ответы, trace!(
// а клиент уже забыл про этот ID или еще не создал его.
error!(
"👻 [Stream {}] ORPHAN packet from tunnel. ID mismatch?", "👻 [Stream {}] ORPHAN packet from tunnel. ID mismatch?",
frame.socket_id frame.socket_id
); );
+3 -5
View File
@@ -99,19 +99,17 @@ impl Engine {
} }
} }
// 3. Если затора нет, читаем новые кадры из VPN-туннеля
if stuck_frame.is_none() { if stuck_frame.is_none() {
while let Ok(frame) = self.rx_from_tunnel.try_recv() { while let Ok(frame) = self.rx_from_tunnel.try_recv() {
if let Err(returned_frame) = self.manager.try_inject_inbound(frame) { if let Err(returned_frame) = self.manager.try_inject_inbound(frame) {
stuck_frame = Some(returned_frame); stuck_frame = Some(returned_frame); // Возвращаем пакет в "заначку"
break; // СТОП! Локальный сокет переполнен, давим на тормоза! break; // ТОРМОЗИМ чтение туннеля, пока канал не освободится
} }
} }
} }
// 4. Двигаем стейт-машину smoltcp
let result = self.poll();
self.manager.process_sockets(&mut self.socket_set); self.manager.process_sockets(&mut self.socket_set);
let result = self.poll();
if last_log.elapsed() >= Duration::from_secs(5) { if last_log.elapsed() >= Duration::from_secs(5) {
self.manager.log_status(&self.socket_set); self.manager.log_status(&self.socket_set);
+7 -9
View File
@@ -34,7 +34,7 @@ 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).await.is_err() { break; } if muxer.send_to_network(msg).is_err() { break; }
} }
Err(e) => { Err(e) => {
error!(stream_id, error = %e, "Socket read error"); error!(stream_id, error = %e, "Socket read error");
@@ -61,13 +61,11 @@ pub(crate) async fn run_tcp_bridge<R, W>(
} }
} }
let _ = muxer let _ = muxer.send_to_network(MuxMessage {
.send_to_network(MuxMessage { stream_id,
stream_id, frame_type: FrameType::Close,
frame_type: FrameType::Close, data: Bytes::new(),
data: Bytes::new(), });
})
.await;
tokio::time::sleep(std::time::Duration::from_millis(500)).await; tokio::time::sleep(std::time::Duration::from_millis(500)).await;
@@ -97,7 +95,7 @@ pub(crate) async fn run_udp_bridge(
// КРИТИЧЕСКИЙ МОМЕНТ: передаем true (is_udp), // КРИТИЧЕСКИЙ МОМЕНТ: передаем true (is_udp),
// чтобы Muxer выбрал правильную ногу для реалтайм трафика // чтобы Muxer выбрал правильную ногу для реалтайм трафика
if let Err(e) = muxer.send_data_safe(stream_id, data, true).await { if let Err(e) = muxer.send_data_safe(stream_id, data, true) {
error!("❌ [UDP {}] Failed to send to tunnel: {}", stream_id, e); error!("❌ [UDP {}] Failed to send to tunnel: {}", stream_id, e);
break; break;
} }
+3 -5
View File
@@ -282,17 +282,15 @@ impl ClientHandler {
}); });
// Отправляем управляющий фрейм с адресом // Отправляем управляющий фрейм с адресом
let _ = muxer_inner.send_control(stream_id, f_type, payload).await; let _ = muxer_inner.send_control(stream_id, f_type, payload);
} }
FrameType::Data | FrameType::UdpData => { FrameType::Data | FrameType::UdpData => {
let _ = muxer_inner.send_data_safe(stream_id, payload, is_udp).await; let _ = muxer_inner.send_data_safe(stream_id, payload, is_udp);
} }
FrameType::Close => { FrameType::Close => {
let _ = muxer_inner let _ = muxer_inner.send_control(stream_id, FrameType::Close, Bytes::new());
.send_control(stream_id, FrameType::Close, Bytes::new())
.await;
muxer_inner.remove_stream(stream_id); muxer_inner.remove_stream(stream_id);
} }
+7 -8
View File
@@ -71,8 +71,9 @@ impl StreamHandler {
let elapsed = start.elapsed(); let elapsed = start.elapsed();
info!(stream_id, target = %target_str, "✅ [TCP] Established in {:?}. Starting bridge.", elapsed); info!(stream_id, target = %target_str, "✅ [TCP] Established in {:?}. Starting bridge.", elapsed);
let (r, w) = stream.into_split(); let (r, w) = stream.into_split();
run_tcp_bridge(stream_id, r, w, muxer, v_rx).await; run_tcp_bridge(stream_id, r, w, muxer.clone(), v_rx).await;
info!(stream_id, "🔚 [TCP Worker] Bridge task finished"); info!(stream_id, "🔚 [TCP Worker] Bridge task finished");
Self::close_stream(&muxer, stream_id).await;
} }
Ok(Err(e)) => { Ok(Err(e)) => {
error!(stream_id, target = %target_str, error = %e, "❌ [TCP] Connection failed"); error!(stream_id, target = %target_str, error = %e, "❌ [TCP] Connection failed");
@@ -86,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);
} }
} }
@@ -143,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) {
@@ -168,9 +169,7 @@ impl StreamHandler {
stream_id, stream_id,
"📡 [Control] Sending CLOSE signal to remote peer" "📡 [Control] Sending CLOSE signal to remote peer"
); );
let _ = muxer let _ = muxer.send_control(stream_id, FrameType::Close, Bytes::new());
.send_control(stream_id, FrameType::Close, Bytes::new())
.await;
muxer.remove_stream(stream_id); muxer.remove_stream(stream_id);
} }
} }
+46 -27
View File
@@ -7,7 +7,6 @@ use tokio::sync::mpsc::Sender;
use crate::net::network::NetworkConfig; use crate::net::network::NetworkConfig;
use crate::nrxp::FrameType; use crate::nrxp::FrameType;
// Вспомогательная структура для отдельного TLS-соединения
#[derive(Clone)] #[derive(Clone)]
struct MuxLeg { struct MuxLeg {
control_tx: Sender<MuxMessage>, control_tx: Sender<MuxMessage>,
@@ -52,11 +51,9 @@ impl Muxer {
streams: Arc::new(DashMap::new()), streams: Arc::new(DashMap::new()),
id_gen: Arc::new(IdGenerator::new(is_client)), id_gen: Arc::new(IdGenerator::new(is_client)),
leg_selector: Arc::new(AtomicU32::new(0)), leg_selector: Arc::new(AtomicU32::new(0)),
// leg_id_gen удален!
} }
} }
// ТЕПЕРЬ leg_id ПЕРЕДАЕТСЯ СНАРУЖИ
pub fn add_leg( pub fn add_leg(
&self, &self,
leg_id: u32, leg_id: u32,
@@ -82,7 +79,6 @@ impl Muxer {
self.legs.len() self.legs.len()
} }
// Возвращаем кортеж (leg_id, MuxLeg), чтобы знать, кого удалять в случае ошибки
fn select_leg(&self, frame_type: &FrameType) -> Option<(u32, MuxLeg)> { fn select_leg(&self, frame_type: &FrameType) -> Option<(u32, MuxLeg)> {
if self.legs.is_empty() { if self.legs.is_empty() {
return None; return None;
@@ -90,7 +86,6 @@ impl Muxer {
match frame_type { match frame_type {
FrameType::UdpData | FrameType::UdpConnect => { FrameType::UdpData | FrameType::UdpConnect => {
// Пытаемся взять UDP ногу (1), если её нет — берем TCP (0) как фоллбэк
if let Some(leg) = self.legs.get(&1) { if let Some(leg) = self.legs.get(&1) {
Some((1, leg.clone())) Some((1, leg.clone()))
} else if let Some(leg) = self.legs.get(&0) { } else if let Some(leg) = self.legs.get(&0) {
@@ -100,7 +95,6 @@ impl Muxer {
} }
} }
_ => { _ => {
// Пытаемся взять TCP ногу (0), фоллбэк на UDP (1)
if let Some(leg) = self.legs.get(&0) { if let Some(leg) = self.legs.get(&0) {
Some((0, leg.clone())) Some((0, leg.clone()))
} else if let Some(leg) = self.legs.get(&1) { } else if let Some(leg) = self.legs.get(&1) {
@@ -112,7 +106,8 @@ impl Muxer {
} }
} }
pub async fn send_to_network(&self, message: MuxMessage) -> Result<(), String> { // ТЕПЕРЬ СИНХРОННАЯ ФУНКЦИЯ БЕЗ БЛОКИРОВОК
pub fn send_to_network(&self, message: MuxMessage) -> Result<(), String> {
let (leg_id, leg) = self let (leg_id, leg) = self
.select_leg(&message.frame_type) .select_leg(&message.frame_type)
.ok_or_else(|| "MUXER: No active legs available".to_string())?; .ok_or_else(|| "MUXER: No active legs available".to_string())?;
@@ -125,16 +120,27 @@ impl Muxer {
_ => &leg.data_tx, _ => &leg.data_tx,
}; };
// Если канал мертв (TunnelEngine упал), удаляем ногу из роутинга! // Используем try_send вместо send().await
if let Err(e) = target_tx.send(message).await { match target_tx.try_send(message) {
self.remove_leg(leg_id); Ok(_) => Ok(()),
return Err(format!("MUXER: Leg {} died during send: {}", leg_id, e)); Err(tokio::sync::mpsc::error::TrySendError::Full(_)) => {
// Если очередь переполнена - просто дропаем пакет!
// Возвращаем Ok(()), чтобы не убивать соединение.
netrunner_logger::warn!(
leg_id,
"MUXER: Network queue full! Dropping outbound packet."
);
Ok(())
}
Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => {
self.remove_leg(leg_id);
Err(format!("MUXER: Leg {} died during send", leg_id))
}
} }
Ok(())
} }
pub async fn send_data_safe( // ТЕПЕРЬ СИНХРОННАЯ ФУНКЦИЯ БЕЗ БЛОКИРОВОК
pub fn send_data_safe(
&self, &self,
stream_id: u32, stream_id: u32,
mut data: Bytes, mut data: Bytes,
@@ -153,19 +159,16 @@ impl Muxer {
self.send_to_network(MuxMessage { self.send_to_network(MuxMessage {
stream_id, stream_id,
frame_type, frame_type: frame_type.clone(),
data: chunk, data: chunk,
}) })?;
.await?; // task::yield_now().await удален, так как отправка теперь мгновенная
if !data.is_empty() {
tokio::task::yield_now().await;
}
} }
Ok(()) Ok(())
} }
pub async fn send_control( // ТЕПЕРЬ СИНХРОННАЯ ФУНКЦИЯ БЕЗ БЛОКИРОВОК
pub fn send_control(
&self, &self,
stream_id: u32, stream_id: u32,
f_type: FrameType, f_type: FrameType,
@@ -176,7 +179,6 @@ impl Muxer {
frame_type: f_type, frame_type: f_type,
data, data,
}) })
.await
} }
pub fn register_stream(&self, stream_id: u32, tx: Sender<Bytes>) { pub fn register_stream(&self, stream_id: u32, tx: Sender<Bytes>) {
@@ -187,11 +189,28 @@ impl Muxer {
self.streams.remove(&stream_id); self.streams.remove(&stream_id);
} }
pub async fn dispatch_to_local(&self, stream_id: u32, data: Bytes) { // ТЕПЕРЬ СИНХРОННАЯ ФУНКЦИЯ БЕЗ БЛОКИРОВОК
if let Some(tx) = self.streams.get(&stream_id) { pub fn dispatch_to_local(&self, stream_id: u32, data: Bytes) {
if let Err(_) = tx.send(data).await { // Узнаем, мертв ли канал, не блокируя DashMap
self.remove_stream(stream_id); let is_closed = if let Some(tx) = self.streams.get(&stream_id) {
match tx.try_send(data) {
Ok(_) => false,
Err(tokio::sync::mpsc::error::TrySendError::Full(_)) => {
netrunner_logger::warn!(
stream_id,
"MUXER: Local worker queue full! Dropping inbound packet."
);
false
}
Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => true,
} }
} else {
false
};
// Удаляем стрим только после того, как отпустили ссылку на DashMap
if is_closed {
self.remove_stream(stream_id);
} }
} }
} }
+11 -11
View File
@@ -80,7 +80,6 @@ pub struct NetworkConfig {
pub udp_stream_capacity: usize, // Локальные каналы 1 сокета (Engine <-> Muxer) pub udp_stream_capacity: usize, // Локальные каналы 1 сокета (Engine <-> Muxer)
pub smoltcp_socket_buf: usize, pub smoltcp_socket_buf: usize,
pub tcp_max_pending: usize,
pub tcp_chunk_size: usize, pub tcp_chunk_size: usize,
} }
@@ -95,19 +94,20 @@ impl NetworkConfig {
max_wire_frame_size: max_wire_frame, max_wire_frame_size: max_wire_frame,
safe_payload_size: safe_payload, safe_payload_size: safe_payload,
// Увеличиваем пропускную способность для видео // Буферы ОС/Tokio (держим с запасом под быстрые всплески)
tcp_buffer_size: 512 * 1024, tcp_buffer_size: 1024 * 1024,
udp_buffer_size: 256 * 1024, udp_buffer_size: 512 * 1024,
// Уменьшаем очереди, чтобы не копить задержку // Глобальная очередь. 4096 * 16KB = ~65 MB в памяти (безопасно)
muxer_capacity: 64, // Это спасет от заторов, если 10 сокетов решат отправить данные одновременно
tcp_stream_capacity: 4, muxer_capacity: 2048,
udp_stream_capacity: 16, // Для видео (QUIC/UDP) лучше чуть больше
tcp_stream_capacity: 128,
udp_stream_capacity: 64,
// Настройки smoltcp // Настройки smoltcp
smoltcp_socket_buf: 256 * 1024, // Даем Ютубу раздышаться smoltcp_socket_buf: 512 * 1024,
tcp_max_pending: 32, // Но не даем ему "тупить" в очереди tcp_chunk_size: 16 * 1024,
tcp_chunk_size: 16 * 1024, // Чанки побольше — меньше системных вызовов
} }
} }