device to smoltcp [AI]
This commit is contained in:
+103
-135
@@ -1,4 +1,4 @@
|
||||
use bytes::{Buf, Bytes};
|
||||
use bytes::Bytes;
|
||||
use netrunner_core::{
|
||||
net::{GLOBAL_MIN_RTT, NetworkConfig, UDP_IDLE_TIMEOUT},
|
||||
rawcast::{LocalProtocol, RawCastFrame},
|
||||
@@ -11,10 +11,11 @@ use smoltcp::{
|
||||
Ipv6Address,
|
||||
},
|
||||
};
|
||||
use std::{collections::VecDeque, sync::{Arc, atomic::{AtomicBool, Ordering}}};
|
||||
use std::{sync::{Arc, atomic::{AtomicBool, Ordering}}};
|
||||
use tokio::sync::{OwnedSemaphorePermit, mpsc, oneshot};
|
||||
|
||||
use netrunner_logger::{debug, info, instrument};
|
||||
|
||||
pub struct ConnectionCore<T> {
|
||||
pub handle: SocketHandle,
|
||||
pub tx: mpsc::Sender<T>,
|
||||
@@ -23,7 +24,9 @@ pub struct ConnectionCore<T> {
|
||||
}
|
||||
|
||||
impl<T> ConnectionCore<T> {
|
||||
pub fn new(handle: SocketHandle) -> (Self, mpsc::Receiver<T>, mpsc::Sender<Bytes>, Arc<AtomicBool>) {
|
||||
pub fn new(
|
||||
handle: SocketHandle,
|
||||
) -> (Self, mpsc::Receiver<T>, mpsc::Sender<Bytes>, Arc<AtomicBool>) {
|
||||
let cap = NetworkConfig::global().channel_capacity;
|
||||
let (tx_to_net, rx_from_smol) = mpsc::channel::<T>(cap);
|
||||
let (tx_to_smol, rx_from_net) = mpsc::channel::<Bytes>(cap);
|
||||
@@ -48,13 +51,12 @@ pub enum ConnectionState {
|
||||
Closed,
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub struct TcpConnection {
|
||||
core: ConnectionCore<Bytes>,
|
||||
state: ConnectionState,
|
||||
pending_data: VecDeque<Bytes>,
|
||||
pending_bytes: usize,
|
||||
/// A single in-flight chunk that partially fit into smoltcp's tx_buf.
|
||||
/// When Some, we retry flushing it before reading the next chunk from core.rx.
|
||||
pending_chunk: Option<Bytes>,
|
||||
handshake_rx: Option<oneshot::Receiver<()>>,
|
||||
chunk_buf: Vec<u8>,
|
||||
server_eof: bool,
|
||||
@@ -62,8 +64,7 @@ pub struct TcpConnection {
|
||||
|
||||
total_up_bytes: u64,
|
||||
total_down_bytes: u64,
|
||||
rx_congested: bool,
|
||||
tx_congested: bool,
|
||||
tx_congested: bool,
|
||||
last_rtt_push_ms: i64,
|
||||
last_pushed_rtt_ms: u32,
|
||||
}
|
||||
@@ -72,21 +73,15 @@ impl TcpConnection {
|
||||
const RTT_PUSH_INTERVAL_MS: i64 = 50;
|
||||
const RTT_CHANGE_RATIO: f64 = 0.10;
|
||||
|
||||
const BUF_FLOOR: usize = 64 * 1024;
|
||||
/// Верхняя граница (2 МБ) — тоже как в smoltcp.
|
||||
const BUF_CEIL: usize = 2 * 1024 * 1024;
|
||||
/// Fallback, когда BBR ещё не дал оценку BDP.
|
||||
const BUF_FALLBACK: usize = 256 * 1024;
|
||||
|
||||
pub fn new(
|
||||
handle: SocketHandle,
|
||||
permit: tokio::sync::OwnedSemaphorePermit,
|
||||
permit: OwnedSemaphorePermit,
|
||||
) -> (
|
||||
Self,
|
||||
mpsc::Receiver<Bytes>,
|
||||
mpsc::Sender<Bytes>,
|
||||
oneshot::Sender<()>,
|
||||
Arc<AtomicBool>
|
||||
Arc<AtomicBool>,
|
||||
) {
|
||||
let (core, rx_from_smol, tx_to_smol, is_saturated) = ConnectionCore::new(handle);
|
||||
let (handshake_tx, handshake_rx) = oneshot::channel();
|
||||
@@ -95,14 +90,12 @@ impl TcpConnection {
|
||||
core,
|
||||
state: ConnectionState::Handshaking,
|
||||
permit: Some(permit),
|
||||
pending_data: VecDeque::new(),
|
||||
pending_bytes: 0,
|
||||
pending_chunk: None,
|
||||
handshake_rx: Some(handshake_rx),
|
||||
chunk_buf: vec![0u8; NetworkConfig::global().tcp_chunk_size],
|
||||
server_eof: false,
|
||||
total_up_bytes: 0,
|
||||
total_down_bytes: 0,
|
||||
rx_congested: false,
|
||||
tx_congested: false,
|
||||
last_rtt_push_ms: i64::MIN,
|
||||
last_pushed_rtt_ms: 0,
|
||||
@@ -139,9 +132,9 @@ impl TcpConnection {
|
||||
|
||||
if matches!(socket.state(), tcp::State::Closed | tcp::State::TimeWait) {
|
||||
info!(
|
||||
%self.core.handle,
|
||||
UP = %self.total_up_bytes,
|
||||
DOWN = %self.total_down_bytes,
|
||||
%self.core.handle,
|
||||
UP = %self.total_up_bytes,
|
||||
DOWN = %self.total_down_bytes,
|
||||
"🏁 TCP Socket finished and closed"
|
||||
);
|
||||
self.state = ConnectionState::Closed;
|
||||
@@ -149,9 +142,7 @@ impl TcpConnection {
|
||||
}
|
||||
}
|
||||
|
||||
ConnectionState::Closed => {
|
||||
return false;
|
||||
}
|
||||
ConnectionState::Closed => return false,
|
||||
|
||||
ConnectionState::Established => {
|
||||
info!(
|
||||
@@ -167,13 +158,12 @@ impl TcpConnection {
|
||||
}
|
||||
|
||||
fn maybe_update_tunnel_rtt(
|
||||
&mut self,
|
||||
socket: &mut tcp::Socket,
|
||||
timestamp: smoltcp::time::Instant,
|
||||
&mut self,
|
||||
socket: &mut tcp::Socket,
|
||||
timestamp: smoltcp::time::Instant,
|
||||
) {
|
||||
let now_ms = timestamp.total_millis();
|
||||
|
||||
// 1. Троттлинг по времени (кроме самого первого вызова).
|
||||
if self.last_rtt_push_ms != i64::MIN
|
||||
&& now_ms - self.last_rtt_push_ms < Self::RTT_PUSH_INTERVAL_MS
|
||||
{
|
||||
@@ -181,13 +171,10 @@ impl TcpConnection {
|
||||
}
|
||||
|
||||
let current_rtt = GLOBAL_MIN_RTT.load(Ordering::Relaxed);
|
||||
|
||||
// 2. Игнорируем заведомо некорректное нулевое значение.
|
||||
if current_rtt == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Обновляем только при значимом изменении (или при первом запуске).
|
||||
let first_push = self.last_rtt_push_ms == i64::MIN;
|
||||
let changed_enough = if self.last_pushed_rtt_ms == 0 {
|
||||
true
|
||||
@@ -209,133 +196,122 @@ impl TcpConnection {
|
||||
}
|
||||
}
|
||||
|
||||
fn optimal_buffer_size(&self, socket: &tcp::Socket) -> usize {
|
||||
let bdp = socket.estimated_bdp();
|
||||
if bdp == 0 {
|
||||
// BBR ещё не оценил полосу (старт соединения) — безопасный дефолт.
|
||||
Self::BUF_FALLBACK
|
||||
} else {
|
||||
(bdp.saturating_mul(2)).clamp(Self::BUF_FLOOR, Self::BUF_CEIL)
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_and_process(&mut self, socket: &mut tcp::Socket, timestamp: smoltcp::time::Instant) {
|
||||
self.maybe_update_tunnel_rtt(socket, timestamp);
|
||||
|
||||
// 1. Читаем из браузера -> в Туннель (Upload)
|
||||
// ── Upload: browser → smoltcp rx_buf → channel → muxer ──────────
|
||||
while socket.can_recv() && self.core.tx.capacity() > 0 {
|
||||
if let Ok(n) = socket.peek_slice(&mut self.chunk_buf, timestamp) {
|
||||
if n == 0 { break; }
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
let chunk = Bytes::copy_from_slice(&self.chunk_buf[..n]);
|
||||
|
||||
match self.core.tx.try_send(chunk) {
|
||||
Ok(_) => {
|
||||
socket.recv_slice(&mut self.chunk_buf[..n]).unwrap();
|
||||
self.total_up_bytes += n as u64; // Учет Upload
|
||||
|
||||
// Если была перегрузка, а теперь прошло — пишем радостный лог
|
||||
self.total_up_bytes += n as u64;
|
||||
if self.tx_congested {
|
||||
netrunner_logger::debug!(%self.core.handle, "🟢 Upload channel cleared. Resuming read from browser.");
|
||||
netrunner_logger::debug!(
|
||||
%self.core.handle,
|
||||
"🟢 Upload channel cleared. Resuming read from browser."
|
||||
);
|
||||
self.tx_congested = false;
|
||||
}
|
||||
}
|
||||
Err(mpsc::error::TrySendError::Full(_)) => {
|
||||
if !self.tx_congested {
|
||||
netrunner_logger::debug!(%self.core.handle, "🟡 Upload Congestion: Channel to Muxer is full. Pausing read from browser.");
|
||||
netrunner_logger::debug!(
|
||||
%self.core.handle,
|
||||
"🟡 Upload Congestion: Channel to Muxer is full. Pausing."
|
||||
);
|
||||
self.tx_congested = true;
|
||||
}
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
Err(mpsc::error::TrySendError::Closed(_)) => {
|
||||
self.server_eof = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else { break; }
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Читаем из Туннеля -> в Браузер (Download)
|
||||
// ── Download: muxer → channel → smoltcp tx_buf → browser ─────────
|
||||
//
|
||||
// We keep at most ONE in-flight chunk (pending_chunk). When smoltcp's
|
||||
// tx_buf is full we stop reading from core.rx, which naturally
|
||||
// backpressures the bounded channel and eventually the muxer.
|
||||
if !self.server_eof {
|
||||
// Считаем пороги ОДИН раз до цикла: BDP за время цикла не изменится,
|
||||
// а дёргать BBR на каждой итерации незачем.
|
||||
let optimal = self.optimal_buffer_size(socket);
|
||||
let congestion_threshold = optimal + optimal / 2; // optimal * 1.5
|
||||
let relief_threshold = optimal / 2; // optimal * 0.5 (гистерезис 3:1)
|
||||
|
||||
loop {
|
||||
if self.pending_bytes > congestion_threshold {
|
||||
if !self.rx_congested {
|
||||
netrunner_logger::warn!(
|
||||
%self.core.handle,
|
||||
"🟡 Download Congestion: pending={} > {} (optimal={}). Pausing tunnel read.",
|
||||
self.pending_bytes, congestion_threshold, optimal
|
||||
);
|
||||
self.rx_congested = true;
|
||||
self.core.is_saturated.store(true, Ordering::Release);
|
||||
}
|
||||
// First flush any partially-sent chunk from a previous tick.
|
||||
let chunk = match self.pending_chunk.take() {
|
||||
Some(c) => c,
|
||||
None => match self.core.rx.try_recv() {
|
||||
Ok(data) => {
|
||||
self.total_down_bytes += data.len() as u64;
|
||||
data
|
||||
}
|
||||
Err(mpsc::error::TryRecvError::Empty) => break,
|
||||
Err(mpsc::error::TryRecvError::Disconnected) => {
|
||||
self.server_eof = true;
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if !socket.can_send() {
|
||||
// tx_buf is full → hold the chunk, signal saturation.
|
||||
self.pending_chunk = Some(chunk);
|
||||
self.core.is_saturated.store(true, Ordering::Release);
|
||||
break;
|
||||
}
|
||||
else if self.rx_congested && self.pending_bytes < relief_threshold {
|
||||
netrunner_logger::debug!(
|
||||
%self.core.handle,
|
||||
"🟢 Download buffer relieved: pending={} < {}. Resuming tunnel read.",
|
||||
self.pending_bytes, relief_threshold
|
||||
);
|
||||
self.rx_congested = false;
|
||||
self.core.is_saturated.store(false, Ordering::Release);
|
||||
}
|
||||
|
||||
match self.core.rx.try_recv() {
|
||||
Ok(data) => {
|
||||
self.pending_bytes += data.len();
|
||||
self.total_down_bytes += data.len() as u64;
|
||||
self.pending_data.push_back(data);
|
||||
match socket.send_slice(&chunk) {
|
||||
Ok(n) if n == chunk.len() => {
|
||||
// Entire chunk accepted.
|
||||
}
|
||||
Err(mpsc::error::TryRecvError::Empty) => break,
|
||||
Err(mpsc::error::TryRecvError::Disconnected) => {
|
||||
self.server_eof = true;
|
||||
Ok(n) => {
|
||||
// Partial write — keep the remainder for the next tick.
|
||||
self.pending_chunk = Some(chunk.slice(n..));
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
netrunner_logger::debug!(
|
||||
%self.core.handle,
|
||||
"smoltcp send error: {:?}", e
|
||||
);
|
||||
self.pending_chunk = Some(chunk);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear saturation when tx_buf has room and no chunk is pending.
|
||||
if self.pending_chunk.is_none() && socket.can_send() {
|
||||
self.core.is_saturated.store(false, Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Отправляем в smoltcp то, что накопилось (оставляем как было)
|
||||
while socket.can_send() {
|
||||
if let Some(mut chunk) = self.pending_data.pop_front() {
|
||||
match socket.send_slice(&chunk) {
|
||||
Ok(n) => {
|
||||
self.pending_bytes -= n;
|
||||
if n < chunk.len() {
|
||||
chunk.advance(n);
|
||||
self.pending_data.push_front(chunk);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
netrunner_logger::debug!(%self.core.handle, "Smoltcp send error: {:?}", e);
|
||||
self.pending_data.push_front(chunk);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else { break; }
|
||||
}
|
||||
|
||||
if self.server_eof && self.pending_data.is_empty() {
|
||||
if self.server_eof && self.pending_chunk.is_none() {
|
||||
let state = socket.state();
|
||||
if state == tcp::State::Established || state == tcp::State::CloseWait {
|
||||
netrunner_logger::debug!(%self.core.handle, "All data flushed, sending FIN to browser");
|
||||
netrunner_logger::debug!(
|
||||
%self.core.handle,
|
||||
"All data flushed, sending FIN to browser"
|
||||
);
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn app_pending_out_size(&self) -> usize {
|
||||
self.pending_bytes
|
||||
self.pending_chunk.as_ref().map(|c| c.len()).unwrap_or(0)
|
||||
}
|
||||
|
||||
#[instrument(skip(rx_smol, handshake_tx, tx_tunnel), fields(
|
||||
socket_id = socket_id,
|
||||
socket_id = socket_id,
|
||||
dst = %target
|
||||
))]
|
||||
pub fn spawn(
|
||||
@@ -359,27 +335,23 @@ impl TcpConnection {
|
||||
let _ = handshake_tx.send(());
|
||||
|
||||
while let Some(data) = rx_smol.recv().await {
|
||||
let data_frame = RawCastFrame::data(
|
||||
LocalProtocol::Tcp,
|
||||
socket_id,
|
||||
dst_ip,
|
||||
dst_port,
|
||||
data
|
||||
);
|
||||
|
||||
let data_frame =
|
||||
RawCastFrame::data(LocalProtocol::Tcp, socket_id, dst_ip, dst_port, data);
|
||||
if tx_tunnel.send(data_frame).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let close_frame = RawCastFrame::close(LocalProtocol::Tcp, socket_id, dst_ip, dst_port);
|
||||
let close_frame =
|
||||
RawCastFrame::close(LocalProtocol::Tcp, socket_id, dst_ip, dst_port);
|
||||
let _ = tx_tunnel.send(close_frame).await;
|
||||
|
||||
debug!("🏁 [TCP {}] Spawned task finished", socket_id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ─── UDP ────────────────────────────────────────────────────────────────────
|
||||
|
||||
pub type UdpPacketTarget = (Bytes, std::net::Ipv4Addr, u16);
|
||||
|
||||
pub struct UdpConnection {
|
||||
@@ -391,7 +363,7 @@ pub struct UdpConnection {
|
||||
impl UdpConnection {
|
||||
pub fn new(
|
||||
handle: SocketHandle,
|
||||
client_addr: smoltcp::wire::IpAddress,
|
||||
client_addr: IpAddress,
|
||||
client_port: u16,
|
||||
) -> (Self, mpsc::Receiver<UdpPacketTarget>, mpsc::Sender<Bytes>, Arc<AtomicBool>) {
|
||||
let (core, rx_from_smol, tx_to_smol, is_saturated) = ConnectionCore::new(handle);
|
||||
@@ -416,7 +388,7 @@ impl UdpConnection {
|
||||
|
||||
if socket.can_recv() {
|
||||
while let Ok((data, metadata)) = socket.recv(timestamp) {
|
||||
if let smoltcp::wire::IpAddress::Ipv4(ip) = metadata.endpoint.addr {
|
||||
if let IpAddress::Ipv4(ip) = metadata.endpoint.addr {
|
||||
self.last_client_endpoint = Some(metadata.endpoint);
|
||||
let target_ip = std::net::Ipv4Addr::from(ip);
|
||||
let target_port = metadata.endpoint.port;
|
||||
@@ -447,7 +419,7 @@ impl UdpConnection {
|
||||
}
|
||||
|
||||
#[instrument(skip(rx_smol, tx_tunnel), fields(
|
||||
socket_id = socket_id,
|
||||
socket_id = socket_id,
|
||||
dst = %target
|
||||
))]
|
||||
pub fn spawn(
|
||||
@@ -469,20 +441,22 @@ impl UdpConnection {
|
||||
}
|
||||
|
||||
while let Some((data, ip, port)) = rx_smol.recv().await {
|
||||
let data_frame =
|
||||
RawCastFrame::data(LocalProtocol::Udp, socket_id, ip, port, data);
|
||||
let data_frame = RawCastFrame::data(LocalProtocol::Udp, socket_id, ip, port, data);
|
||||
if tx_tunnel.send(data_frame).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let close_frame = RawCastFrame::close(LocalProtocol::Udp, socket_id, dst_ip, dst_port);
|
||||
let close_frame =
|
||||
RawCastFrame::close(LocalProtocol::Udp, socket_id, dst_ip, dst_port);
|
||||
let _ = tx_tunnel.send(close_frame).await;
|
||||
info!("🛑 [UDP {}] Task stopped", socket_id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ─── ICMP ───────────────────────────────────────────────────────────────────
|
||||
|
||||
use smoltcp::socket::icmp;
|
||||
|
||||
pub struct IcmpResponder;
|
||||
@@ -493,11 +467,8 @@ impl IcmpResponder {
|
||||
return;
|
||||
}
|
||||
|
||||
let result = socket.recv(timestamp);
|
||||
|
||||
if let Ok((data, src_addr)) = result {
|
||||
if let Ok((data, src_addr)) = socket.recv(timestamp) {
|
||||
let payload = data.to_vec();
|
||||
|
||||
match src_addr {
|
||||
IpAddress::Ipv4(_) => Self::reply_v4(socket, payload, src_addr),
|
||||
IpAddress::Ipv6(v6) => Self::reply_v6(socket, payload, v6),
|
||||
@@ -511,7 +482,6 @@ impl IcmpResponder {
|
||||
let mut reply_pkt = Icmpv4Packet::new_unchecked(&mut payload);
|
||||
reply_pkt.set_msg_type(Icmpv4Message::EchoReply);
|
||||
reply_pkt.fill_checksum();
|
||||
|
||||
let _ = socket.send_slice(&payload, src);
|
||||
info!("🏓 [ICMPv4] Echo Reply -> {}", src);
|
||||
}
|
||||
@@ -523,10 +493,8 @@ impl IcmpResponder {
|
||||
if pkt.msg_type() == Icmpv6Message::EchoRequest {
|
||||
let mut reply_pkt = Icmpv6Packet::new_unchecked(&mut payload);
|
||||
reply_pkt.set_msg_type(Icmpv6Message::EchoReply);
|
||||
|
||||
let gateway = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
|
||||
reply_pkt.fill_checksum(&gateway, &src);
|
||||
|
||||
let _ = socket.send_slice(&payload, src.into());
|
||||
info!("🏓 [ICMPv6] Echo Reply -> {}", src);
|
||||
}
|
||||
|
||||
+169
-134
@@ -3,6 +3,7 @@ use netrunner_core::net::ClientHandler;
|
||||
use netrunner_core::net::NetworkConfig;
|
||||
use netrunner_core::rawcast::{RawCastEvent, RawCastFrame};
|
||||
use smoltcp::iface::PollResult;
|
||||
use smoltcp::phy::ChannelDevice;
|
||||
use smoltcp::time::Instant;
|
||||
use smoltcp::wire::{IpAddress, IpCidr};
|
||||
use smoltcp::{
|
||||
@@ -10,13 +11,11 @@ use smoltcp::{
|
||||
phy::DeviceCapabilities,
|
||||
};
|
||||
use std::net::Ipv4Addr;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::{
|
||||
sync::{Arc, LazyLock, atomic::AtomicBool},
|
||||
time::Instant as StdInstant,
|
||||
};
|
||||
use std::{sync::LazyLock, time::Instant as StdInstant};
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::sync::mpsc::{self, Receiver, Sender, UnboundedReceiver, UnboundedSender};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::time::{Duration, sleep};
|
||||
use tun::{DeviceReader, DeviceWriter};
|
||||
|
||||
@@ -25,22 +24,32 @@ use netrunner_logger::{debug, error, info, warn};
|
||||
use crate::net::connection_manager::ConnectionManager;
|
||||
use crate::net::dns::DnsHandler;
|
||||
use crate::net::socket_factory::{SmolSocketFactory, SocketProvider};
|
||||
use crate::tun::device::{TokenBuffer, VirtTunDevice};
|
||||
use crate::tun::device::TrafficCounter;
|
||||
use crate::tun::routing::setup_platform_routing;
|
||||
use crate::tun::tun::Tun;
|
||||
|
||||
pub static START_TIME: LazyLock<StdInstant> = LazyLock::new(StdInstant::now);
|
||||
|
||||
/// How many inbound packets the device can hold before backpressure kicks in.
|
||||
/// Each packet is ≤ MTU bytes, so at 1500 B × 64 = 96 KB max queue.
|
||||
const DEVICE_RX_CAP: usize = 64;
|
||||
/// How many outbound packets smoltcp can stage before we drain them.
|
||||
const DEVICE_TX_CAP: usize = 64;
|
||||
/// Bounded capacity for the TUN-reader → engine channel (packets).
|
||||
const TUN_CHAN_CAP: usize = 128;
|
||||
|
||||
pub struct Engine {
|
||||
interface: Interface,
|
||||
socket_set: SocketSet<'static>,
|
||||
manager: ConnectionManager,
|
||||
device: VirtTunDevice,
|
||||
to_smoltcp_tx: UnboundedSender<TokenBuffer>,
|
||||
from_smoltcp_rx: Option<UnboundedReceiver<TokenBuffer>>,
|
||||
avail: Arc<AtomicBool>,
|
||||
rx_from_tunnel: Option<Receiver<RawCastFrame>>,
|
||||
device: ChannelDevice,
|
||||
/// Bounded channel from TUN reader task to engine loop.
|
||||
tun_rx: Option<mpsc::Receiver<Vec<u8>>>,
|
||||
/// Bounded channel from engine loop to TUN writer task.
|
||||
tun_tx: mpsc::Sender<Vec<u8>>,
|
||||
rx_from_tunnel: Option<mpsc::Receiver<RawCastFrame>>,
|
||||
factory: Arc<dyn SocketProvider>,
|
||||
stats: TrafficCounter,
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
@@ -48,28 +57,32 @@ impl Engine {
|
||||
config: Config,
|
||||
caps: DeviceCapabilities,
|
||||
dns_handler: DnsHandler,
|
||||
tx_to_tunnel: Sender<RawCastFrame>,
|
||||
rx_from_tunnel: Receiver<RawCastFrame>,
|
||||
tx_to_tunnel: mpsc::Sender<RawCastFrame>,
|
||||
rx_from_tunnel: mpsc::Receiver<RawCastFrame>,
|
||||
factory: Arc<dyn SocketProvider>,
|
||||
) -> Self {
|
||||
let now = Engine::current_time();
|
||||
|
||||
let (mut device, to_smoltcp_tx, from_smoltcp_rx, avail) = VirtTunDevice::new(caps);
|
||||
let mut device = ChannelDevice::new(caps, DEVICE_RX_CAP, DEVICE_TX_CAP);
|
||||
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());
|
||||
|
||||
// Bounded TUN writer channel — smoltcp's TCP window limits how many
|
||||
// outgoing packets can pile up, so a modest cap is enough.
|
||||
let (tun_tx, _placeholder) = mpsc::channel(DEVICE_TX_CAP * 2);
|
||||
|
||||
Self {
|
||||
interface,
|
||||
socket_set,
|
||||
device,
|
||||
to_smoltcp_tx,
|
||||
from_smoltcp_rx: Some(from_smoltcp_rx),
|
||||
avail,
|
||||
tun_rx: None,
|
||||
tun_tx,
|
||||
manager,
|
||||
rx_from_tunnel: Some(rx_from_tunnel),
|
||||
factory,
|
||||
stats: TrafficCounter::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,129 +90,163 @@ impl Engine {
|
||||
info!("Current routes: {:?}", self.interface.routes());
|
||||
let (writer, reader) = tun.split().expect("Failed to split TUN");
|
||||
|
||||
let (tun_to_engine_tx, mut tun_to_engine_rx) = mpsc::unbounded_channel::<TokenBuffer>();
|
||||
// Bounded: TUN reader blocks when engine is overloaded → kernel TUN
|
||||
// buffer fills → natural backpressure to the OS.
|
||||
let (tun_to_engine_tx, tun_to_engine_rx) = mpsc::channel::<Vec<u8>>(TUN_CHAN_CAP);
|
||||
// Bounded: engine drops TX packets if TUN writer is slow (TCP retransmits).
|
||||
let (engine_to_tun_tx, engine_to_tun_rx) = mpsc::channel::<Vec<u8>>(DEVICE_TX_CAP * 2);
|
||||
|
||||
Self::spawn_tun_reader(reader, tun_to_engine_tx, self.avail.clone());
|
||||
self.tun_tx = engine_to_tun_tx;
|
||||
self.tun_rx = Some(tun_to_engine_rx);
|
||||
|
||||
let from_smoltcp_rx = self.from_smoltcp_rx.take().expect("Engine started twice");
|
||||
Self::spawn_tun_writer(writer, from_smoltcp_rx);
|
||||
Self::spawn_tun_reader(reader, tun_to_engine_tx);
|
||||
Self::spawn_tun_writer(writer, engine_to_tun_rx);
|
||||
|
||||
let mut last_stats_log = StdInstant::now();
|
||||
|
||||
let inbound_map = self.manager.tracker.inbound_tx.clone();
|
||||
let mut rx_tunnel = self.rx_from_tunnel.take().unwrap();
|
||||
let mut tun_rx = self.tun_rx.take().unwrap();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut local_cache: std::collections::HashMap<
|
||||
u64,
|
||||
(mpsc::Sender<Bytes>, Arc<AtomicBool>),
|
||||
> = std::collections::HashMap::new();
|
||||
|
||||
while let Some(frame) = rx_tunnel.recv().await {
|
||||
if frame.event == RawCastEvent::Close {
|
||||
local_cache.remove(&frame.socket_id);
|
||||
inbound_map.remove(&frame.socket_id);
|
||||
} else if frame.event == RawCastEvent::Data {
|
||||
let tx_opt: Option<(mpsc::Sender<Bytes>, Arc<AtomicBool>)> =
|
||||
if let Some(cached) = local_cache.get(&frame.socket_id) {
|
||||
Some(cached.clone())
|
||||
} else if let Some(ref_tx) = inbound_map.get(&frame.socket_id) {
|
||||
let val = ref_tx.value().clone();
|
||||
local_cache.insert(frame.socket_id, val.clone());
|
||||
Some(val)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some((tx, is_saturated)) = tx_opt {
|
||||
// Pressure-Aware drop (нулевая нагрузка на ОС)
|
||||
if is_saturated.load(Ordering::Relaxed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
match tx.try_send(frame.payload) {
|
||||
Ok(_) => {}
|
||||
Err(tokio::sync::mpsc::error::TrySendError::Full(_)) => {
|
||||
netrunner_logger::trace!(
|
||||
"⚠️ Local socket {} buffer suddenly full. Dropping frame.",
|
||||
frame.socket_id
|
||||
);
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// Local cache to avoid DashMap lookups on every frame.
|
||||
let mut local_cache: std::collections::HashMap<
|
||||
u64,
|
||||
(mpsc::Sender<Bytes>, Arc<std::sync::atomic::AtomicBool>),
|
||||
> = std::collections::HashMap::new();
|
||||
|
||||
loop {
|
||||
let mut work_done = false;
|
||||
let mut repeat_poll = true;
|
||||
let now = Self::current_time();
|
||||
let mut work_done = false;
|
||||
|
||||
// 1. Опрашиваем сокеты до тех пор, пока есть движение данных
|
||||
while repeat_poll {
|
||||
self.manager.process_sockets(&mut self.socket_set, now);
|
||||
let poll_res = self.poll();
|
||||
self.manager.cleanup(&mut self.socket_set);
|
||||
if matches!(poll_res, PollResult::SocketStateChanged) {
|
||||
work_done = true;
|
||||
repeat_poll = true;
|
||||
} else {
|
||||
repeat_poll = false;
|
||||
// ── 1. Dispatch tunnel → local sockets (download) ────────────
|
||||
// Drain all available frames without blocking.
|
||||
loop {
|
||||
match rx_tunnel.try_recv() {
|
||||
Ok(frame) => {
|
||||
work_done = true;
|
||||
if frame.event == RawCastEvent::Close {
|
||||
local_cache.remove(&frame.socket_id);
|
||||
inbound_map.remove(&frame.socket_id);
|
||||
} else if frame.event == RawCastEvent::Data {
|
||||
let tx_opt = if let Some(cached) = local_cache.get(&frame.socket_id) {
|
||||
Some(cached.clone())
|
||||
} else if let Some(ref_tx) = inbound_map.get(&frame.socket_id) {
|
||||
let val = ref_tx.value().clone();
|
||||
local_cache.insert(frame.socket_id, val.clone());
|
||||
Some(val)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some((tx, is_saturated)) = tx_opt {
|
||||
if is_saturated.load(Ordering::Relaxed) {
|
||||
continue;
|
||||
}
|
||||
match tx.try_send(frame.payload) {
|
||||
Ok(_) => {}
|
||||
Err(mpsc::error::TrySendError::Full(data)) => {
|
||||
// Spawn a task to wait for space rather than drop.
|
||||
let tx2 = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
let _ = tx2.send(data).await;
|
||||
});
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
|
||||
// ── 2. Accept TUN packets → device (upload) ──────────────────
|
||||
// Stop reading when the device's RX queue is full (backpressure).
|
||||
let mut packets_read = 0;
|
||||
while !self.device.rx_full() {
|
||||
match tun_rx.try_recv() {
|
||||
Ok(pkt) => {
|
||||
self.stats.record_rx(pkt.len());
|
||||
self.manager
|
||||
.try_create_socket_from_packet(&pkt, &mut self.socket_set);
|
||||
self.device.push_rx(pkt);
|
||||
work_done = true;
|
||||
packets_read += 1;
|
||||
if packets_read >= 250 {
|
||||
break; // Yield occasionally to prevent starvation.
|
||||
}
|
||||
}
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
|
||||
// ── 3. Run smoltcp ───────────────────────────────────────────
|
||||
let mut repeat = true;
|
||||
while repeat {
|
||||
self.manager.process_sockets(&mut self.socket_set, now);
|
||||
let res = self.poll();
|
||||
self.manager.cleanup(&mut self.socket_set);
|
||||
repeat = matches!(res, PollResult::SocketStateChanged);
|
||||
if repeat {
|
||||
work_done = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 4. Drain smoltcp TX → TUN writer ─────────────────────────
|
||||
while let Some(pkt) = self.device.pop_tx() {
|
||||
self.stats.record_tx(pkt.len());
|
||||
// Non-blocking: if TUN writer is overloaded, drop the packet.
|
||||
// TCP will retransmit; UDP is best-effort.
|
||||
if self.tun_tx.try_send(pkt).is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 5. Stats logging ─────────────────────────────────────────
|
||||
if last_stats_log.elapsed() >= Duration::from_secs(5) {
|
||||
let stats = self.stats.get_stats();
|
||||
info!(
|
||||
"TunDevice Traffic: RX: {:.2} MB ({} pkts) | TX: {:.2} MB ({} pkts) | Speed: ↓{:.2} MB/s, ↑{:.2} MB/s",
|
||||
stats.rx_bytes as f64 / 1_048_576.0,
|
||||
stats.rx_packets,
|
||||
stats.tx_bytes as f64 / 1_048_576.0,
|
||||
stats.tx_packets,
|
||||
stats.rx_speed_mb_s,
|
||||
stats.tx_speed_mb_s,
|
||||
);
|
||||
let manager_ref = &self.manager;
|
||||
self.factory
|
||||
.log_stats(&self.socket_set, &|handle| manager_ref.get_buf_info(handle));
|
||||
last_stats_log = StdInstant::now();
|
||||
}
|
||||
|
||||
// 2. БАТЧИНГ: Выгребаем все доступные пакеты из TUN без блокировки
|
||||
let mut packets_processed = 0;
|
||||
while let Ok(token) = tun_to_engine_rx.try_recv() {
|
||||
self.manager
|
||||
.try_create_socket_from_packet(&token, &mut self.socket_set);
|
||||
if self.to_smoltcp_tx.send(token).is_ok() {
|
||||
self.device.mark_rx_available();
|
||||
}
|
||||
work_done = true;
|
||||
packets_processed += 1;
|
||||
// Предотвращаем starvation: если пакетов слишком много, даем другим потокам подышать
|
||||
if packets_processed > 250 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. АДАПТИВНЫЙ ТАЙМИНГ
|
||||
// ── 6. Adaptive timing ───────────────────────────────────────
|
||||
if work_done {
|
||||
// Если мы обработали пакеты или сокеты сдвинулись - НЕ СПИМ.
|
||||
// Уступаем квант времени другим задачам и моментально возвращаемся в цикл.
|
||||
tokio::task::yield_now().await;
|
||||
} else {
|
||||
// Мы отдыхаем только если сеть полностью простаивает
|
||||
let delay = self
|
||||
.interface
|
||||
.poll_delay(Self::current_time(), &self.socket_set);
|
||||
|
||||
let sleep_time = delay
|
||||
.map(|d| {
|
||||
std::cmp::min(Duration::from_micros(d.micros()), Duration::from_millis(5))
|
||||
std::cmp::min(
|
||||
Duration::from_micros(d.micros()),
|
||||
Duration::from_millis(5),
|
||||
)
|
||||
})
|
||||
.unwrap_or(Duration::from_millis(5));
|
||||
|
||||
tokio::select! {
|
||||
_ = sleep(sleep_time) => {}
|
||||
msg = tun_to_engine_rx.recv() => {
|
||||
if let Some(token) = msg {
|
||||
self.manager.try_create_socket_from_packet(&token, &mut self.socket_set);
|
||||
if self.to_smoltcp_tx.send(token).is_ok() {
|
||||
self.device.mark_rx_available();
|
||||
msg = tun_rx.recv() => {
|
||||
match msg {
|
||||
Some(pkt) => {
|
||||
self.stats.record_rx(pkt.len());
|
||||
self.manager.try_create_socket_from_packet(&pkt, &mut self.socket_set);
|
||||
self.device.push_rx(pkt);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,36 +260,23 @@ impl Engine {
|
||||
.poll(now, &mut self.device, &mut self.socket_set)
|
||||
}
|
||||
|
||||
fn spawn_tun_reader(
|
||||
mut reader: DeviceReader,
|
||||
to_engine: mpsc::UnboundedSender<TokenBuffer>,
|
||||
is_avail: Arc<AtomicBool>,
|
||||
) {
|
||||
fn spawn_tun_reader(mut reader: DeviceReader, to_engine: mpsc::Sender<Vec<u8>>) {
|
||||
tokio::spawn(async move {
|
||||
debug!("TUN Reader task started");
|
||||
|
||||
let mut buf = vec![0u8; 65536];
|
||||
loop {
|
||||
// 1. Берем чистый буфер
|
||||
let mut token = TokenBuffer::with_capacity(65536);
|
||||
|
||||
// 2. Используем временный срез для чтения
|
||||
// Это гарантирует, что мы пишем в начало буфера
|
||||
let mut temp_buf = [0u8; 65536];
|
||||
|
||||
match reader.read(&mut temp_buf).await {
|
||||
match reader.read(&mut buf).await {
|
||||
Ok(n) if n > 0 => {
|
||||
// 3. Копируем в наш TokenBuffer (это быстро, 64кБ - это L1 кэш)
|
||||
token.extend_from_slice(&temp_buf[..n]);
|
||||
|
||||
if to_engine.send(token).is_ok() {
|
||||
is_avail.store(true, Ordering::Release);
|
||||
} else {
|
||||
let pkt = buf[..n].to_vec();
|
||||
// .send().await blocks when engine channel is full →
|
||||
// backpressure propagates to OS TUN device.
|
||||
if to_engine.send(pkt).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(_) => break, // EOF
|
||||
Ok(_) => break,
|
||||
Err(e) => {
|
||||
error!("FATAL: TUN Reader task died with error: {}", e);
|
||||
error!("FATAL: TUN Reader task died: {}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -250,14 +284,11 @@ impl Engine {
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_tun_writer(
|
||||
mut writer: DeviceWriter,
|
||||
mut from_smoltcp: UnboundedReceiver<TokenBuffer>,
|
||||
) {
|
||||
fn spawn_tun_writer(mut writer: DeviceWriter, mut from_engine: mpsc::Receiver<Vec<u8>>) {
|
||||
tokio::spawn(async move {
|
||||
debug!("TUN Writer task started");
|
||||
while let Some(token) = from_smoltcp.recv().await {
|
||||
if writer.write_all(&token).await.is_err() {
|
||||
while let Some(pkt) = from_engine.recv().await {
|
||||
if writer.write_all(&pkt).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -265,7 +296,7 @@ impl Engine {
|
||||
});
|
||||
}
|
||||
|
||||
fn current_time() -> Instant {
|
||||
pub fn current_time() -> Instant {
|
||||
let duration = StdInstant::now().duration_since(*START_TIME);
|
||||
Instant::from_micros(duration.as_micros() as i64)
|
||||
}
|
||||
@@ -301,6 +332,8 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
// ─── EngineConfig & EngineBuilder (unchanged API surface) ──────────────────
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EngineConfig {
|
||||
pub remote_address: String,
|
||||
@@ -457,7 +490,10 @@ impl EngineBuilder {
|
||||
);
|
||||
#[cfg(target_os = "windows")]
|
||||
let _ = crate::tun::routing::run_cmd_ext(
|
||||
&format!("route add {} mask 255.255.255.255 {}", ipv4, phys_gw),
|
||||
&format!(
|
||||
"route add {} mask 255.255.255.255 {}",
|
||||
ipv4, phys_gw
|
||||
),
|
||||
true,
|
||||
);
|
||||
}
|
||||
@@ -480,7 +516,6 @@ impl EngineBuilder {
|
||||
if self.config.transparent_mode {
|
||||
engine.set_transparent_mode();
|
||||
}
|
||||
|
||||
engine.set_default_gateway(self.config.default_gateway);
|
||||
engine.activate();
|
||||
|
||||
|
||||
+24
-194
@@ -1,24 +1,5 @@
|
||||
use std::{
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::{
|
||||
Arc, LazyLock, Mutex,
|
||||
atomic::{AtomicBool, AtomicU64, Ordering},
|
||||
},
|
||||
time::Instant as StdInstant,
|
||||
};
|
||||
|
||||
use bytes::BytesMut;
|
||||
use netrunner_logger::info;
|
||||
use smoltcp::{
|
||||
phy::{self, Device, DeviceCapabilities},
|
||||
time::Instant,
|
||||
};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
const TOKEN_BUFFER_LIST_MAX_SIZE: usize = 64;
|
||||
static TOKEN_BUFFER_LIST: LazyLock<Mutex<Vec<BytesMut>>> = LazyLock::new(|| Mutex::new(Vec::new()));
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::time::Instant as StdInstant;
|
||||
|
||||
pub static GLOBAL_RX_BYTES: AtomicU64 = AtomicU64::new(0);
|
||||
pub static GLOBAL_TX_BYTES: AtomicU64 = AtomicU64::new(0);
|
||||
@@ -35,54 +16,8 @@ pub struct TrafficStats {
|
||||
pub tx_speed_mb_s: f64,
|
||||
}
|
||||
|
||||
pub struct TokenBuffer {
|
||||
buffer: BytesMut,
|
||||
}
|
||||
|
||||
impl Drop for TokenBuffer {
|
||||
fn drop(&mut self) {
|
||||
let mut list = TOKEN_BUFFER_LIST.lock().unwrap();
|
||||
if list.len() >= TOKEN_BUFFER_LIST_MAX_SIZE {
|
||||
return;
|
||||
}
|
||||
let empty_buffer = BytesMut::new();
|
||||
let mut buffer = mem::replace(&mut self.buffer, empty_buffer);
|
||||
buffer.clear();
|
||||
list.push(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
impl TokenBuffer {
|
||||
pub fn with_capacity(cap: usize) -> Self {
|
||||
let mut list = TOKEN_BUFFER_LIST.lock().unwrap();
|
||||
if let Some(mut buffer) = list.pop() {
|
||||
buffer.reserve(cap);
|
||||
return Self { buffer };
|
||||
}
|
||||
Self {
|
||||
buffer: BytesMut::with_capacity(cap),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for TokenBuffer {
|
||||
type Target = BytesMut;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.buffer
|
||||
}
|
||||
}
|
||||
impl DerefMut for TokenBuffer {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.buffer
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VirtTunDevice {
|
||||
capabilities: DeviceCapabilities,
|
||||
rx_queue: mpsc::UnboundedReceiver<TokenBuffer>,
|
||||
tx_queue: mpsc::UnboundedSender<TokenBuffer>,
|
||||
rx_avail: Arc<AtomicBool>,
|
||||
|
||||
/// Per-session traffic counter with rolling speed estimate.
|
||||
pub struct TrafficCounter {
|
||||
rx_bytes: u64,
|
||||
tx_bytes: u64,
|
||||
rx_packets: u64,
|
||||
@@ -93,62 +28,47 @@ pub struct VirtTunDevice {
|
||||
last_tx_bytes: u64,
|
||||
cached_rx_speed: f64,
|
||||
cached_tx_speed: f64,
|
||||
|
||||
last_log_time: StdInstant,
|
||||
}
|
||||
|
||||
impl VirtTunDevice {
|
||||
pub fn new(
|
||||
capabilities: DeviceCapabilities,
|
||||
) -> (
|
||||
Self,
|
||||
mpsc::UnboundedSender<TokenBuffer>,
|
||||
mpsc::UnboundedReceiver<TokenBuffer>,
|
||||
Arc<AtomicBool>,
|
||||
) {
|
||||
let (to_smoltcp_tx, to_smoltcp_rx) = mpsc::unbounded_channel();
|
||||
let (from_smoltcp_tx, from_smoltcp_rx) = mpsc::unbounded_channel();
|
||||
let rx_avail = Arc::new(AtomicBool::new(false));
|
||||
|
||||
impl TrafficCounter {
|
||||
pub fn new() -> Self {
|
||||
let now = StdInstant::now();
|
||||
let device = Self {
|
||||
capabilities,
|
||||
rx_queue: to_smoltcp_rx,
|
||||
tx_queue: from_smoltcp_tx,
|
||||
rx_avail: rx_avail.clone(),
|
||||
|
||||
Self {
|
||||
rx_bytes: 0,
|
||||
tx_bytes: 0,
|
||||
rx_packets: 0,
|
||||
tx_packets: 0,
|
||||
|
||||
last_speed_calc: now,
|
||||
last_rx_bytes: 0,
|
||||
last_tx_bytes: 0,
|
||||
cached_rx_speed: 0.0,
|
||||
cached_tx_speed: 0.0,
|
||||
|
||||
last_log_time: now,
|
||||
};
|
||||
(device, to_smoltcp_tx, from_smoltcp_rx, rx_avail)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn mark_rx_available(&self) {
|
||||
self.rx_avail.store(true, Ordering::Release);
|
||||
pub fn record_rx(&mut self, bytes: usize) {
|
||||
self.rx_bytes += bytes as u64;
|
||||
self.rx_packets += 1;
|
||||
GLOBAL_RX_BYTES.fetch_add(bytes as u64, Ordering::Relaxed);
|
||||
GLOBAL_RX_PACKETS.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn record_tx(&mut self, bytes: usize) {
|
||||
self.tx_bytes += bytes as u64;
|
||||
self.tx_packets += 1;
|
||||
GLOBAL_TX_BYTES.fetch_add(bytes as u64, Ordering::Relaxed);
|
||||
GLOBAL_TX_PACKETS.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn get_stats(&mut self) -> TrafficStats {
|
||||
let now = StdInstant::now();
|
||||
let elapsed_speed = now.duration_since(self.last_speed_calc).as_secs_f64();
|
||||
let elapsed = now.duration_since(self.last_speed_calc).as_secs_f64();
|
||||
|
||||
if elapsed_speed >= 1.0 {
|
||||
if elapsed >= 1.0 {
|
||||
let rx_diff = self.rx_bytes.saturating_sub(self.last_rx_bytes);
|
||||
let tx_diff = self.tx_bytes.saturating_sub(self.last_tx_bytes);
|
||||
|
||||
self.cached_rx_speed = (rx_diff as f64 / 1_048_576.0) / elapsed_speed;
|
||||
self.cached_tx_speed = (tx_diff as f64 / 1_048_576.0) / elapsed_speed;
|
||||
|
||||
self.cached_rx_speed = (rx_diff as f64 / 1_048_576.0) / elapsed;
|
||||
self.cached_tx_speed = (tx_diff as f64 / 1_048_576.0) / elapsed;
|
||||
self.last_rx_bytes = self.rx_bytes;
|
||||
self.last_tx_bytes = self.tx_bytes;
|
||||
self.last_speed_calc = now;
|
||||
@@ -163,94 +83,4 @@ impl VirtTunDevice {
|
||||
tx_speed_mb_s: self.cached_tx_speed,
|
||||
}
|
||||
}
|
||||
|
||||
fn check_and_log_stats(&mut self) {
|
||||
let now = StdInstant::now();
|
||||
if now.duration_since(self.last_log_time).as_secs() >= 5 {
|
||||
let stats = self.get_stats();
|
||||
info!(
|
||||
"TunDevice Traffic: RX: {:.2} MB ({} pkts) | TX: {:.2} MB ({} pkts) | Speed: ↓{:.2} MB/s, ↑{:.2} MB/s",
|
||||
stats.rx_bytes as f64 / 1_048_576.0,
|
||||
stats.rx_packets,
|
||||
stats.tx_bytes as f64 / 1_048_576.0,
|
||||
stats.tx_packets,
|
||||
stats.rx_speed_mb_s,
|
||||
stats.tx_speed_mb_s
|
||||
);
|
||||
self.last_log_time = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Device for VirtTunDevice {
|
||||
type RxToken<'a> = VirtRxToken<'a>;
|
||||
type TxToken<'a> = VirtTxToken<'a>;
|
||||
|
||||
fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
|
||||
self.check_and_log_stats();
|
||||
if let Ok(buffer) = self.rx_queue.try_recv() {
|
||||
let len = buffer.len() as u64;
|
||||
self.rx_bytes += buffer.len() as u64;
|
||||
self.rx_packets += 1;
|
||||
|
||||
GLOBAL_TX_BYTES.fetch_add(len as u64, Ordering::Relaxed);
|
||||
GLOBAL_TX_PACKETS.fetch_add(1, Ordering::Relaxed);
|
||||
let rx = Self::RxToken {
|
||||
buffer,
|
||||
phantom_device: PhantomData,
|
||||
};
|
||||
let tx = VirtTxToken(self);
|
||||
return Some((rx, tx));
|
||||
}
|
||||
self.rx_avail.store(false, Ordering::Release);
|
||||
None
|
||||
}
|
||||
|
||||
fn transmit(&mut self, _timestamp: Instant) -> Option<Self::TxToken<'_>> {
|
||||
Some(VirtTxToken(self))
|
||||
}
|
||||
|
||||
fn capabilities(&self) -> DeviceCapabilities {
|
||||
self.capabilities.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VirtRxToken<'a> {
|
||||
buffer: TokenBuffer,
|
||||
phantom_device: PhantomData<&'a VirtTunDevice>,
|
||||
}
|
||||
|
||||
impl phy::RxToken for VirtRxToken<'_> {
|
||||
fn consume<R, F>(self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&[u8]) -> R,
|
||||
{
|
||||
f(&self.buffer)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VirtTxToken<'a>(&'a mut VirtTunDevice);
|
||||
|
||||
impl phy::TxToken for VirtTxToken<'_> {
|
||||
fn consume<R, F>(self, len: usize, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> R,
|
||||
{
|
||||
let mut buffer = TokenBuffer::with_capacity(len);
|
||||
unsafe {
|
||||
buffer.set_len(len);
|
||||
}
|
||||
|
||||
let result = f(&mut buffer);
|
||||
|
||||
self.0.tx_bytes += len as u64;
|
||||
self.0.tx_packets += 1;
|
||||
|
||||
GLOBAL_RX_BYTES.fetch_add(len as u64, Ordering::Relaxed);
|
||||
GLOBAL_RX_PACKETS.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
// Просто шлем, `smoltcp` сам регулирует объемы своими внутренними окнами
|
||||
let _ = self.0.tx_queue.send(buffer);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user