diagnostics, recconections fix and bufferbloat fixes

This commit is contained in:
2026-06-25 17:14:26 +07:00
parent b6056b2a66
commit 47208853c9
25 changed files with 1231 additions and 232 deletions
+3
View File
@@ -1,3 +1,6 @@
// Workaround for rustc 1.94 ICE in check_mod_deathness (dead-code MIR pass).
#![allow(dead_code)]
use netrunner_core::net::NetworkConfig;
use uniffi;
uniffi::setup_scaffolding!();
+3
View File
@@ -1,3 +1,6 @@
// Workaround for rustc 1.94 ICE in check_mod_deathness (dead-code MIR pass).
#![allow(dead_code)]
use netrunner_logger::{error, info};
mod net;
mod tun;
+21 -10
View File
@@ -11,9 +11,20 @@ use std::time::{Duration, SystemTime};
use tokio::fs::{self, File};
use tokio::io::{AsyncBufReadExt, BufReader};
// --- Fake IP Storage ---
// --- Constants ---
const START_IP: u32 = 0x64400001; // 100.64.0.1
/// Start of the RFC 6598 (CGNAT) range used for fake DNS responses.
const FAKE_IP_START: u32 = 0x6440_0001; // 100.64.0.1
/// LRU capacity for the domain→IP and IP→domain mapping tables.
const FAKE_IP_CACHE_SIZE: usize = 2000;
/// Re-download the blocklist if the cached file is older than this.
const BLOCKLIST_UPDATE_INTERVAL: Duration = Duration::from_secs(7 * 24 * 3600);
/// Delay before starting a background blocklist download on first run.
const BLOCKLIST_DOWNLOAD_DELAY: Duration = Duration::from_secs(10);
/// HTTP timeout for the blocklist download request.
const BLOCKLIST_HTTP_TIMEOUT: Duration = Duration::from_secs(30);
/// TTL advertised in fake DNS A records (seconds).
const FAKE_DNS_TTL: u32 = 60;
pub struct FakeIpStore {
cache: LruCache<String, Ipv4Addr>,
@@ -23,11 +34,11 @@ pub struct FakeIpStore {
impl FakeIpStore {
pub fn new() -> Self {
info!("Initializing FakeIpStore starting at 100.64.0.1");
info!("Initializing FakeIpStore starting at {}", Ipv4Addr::from(FAKE_IP_START));
Self {
cache: LruCache::new(NonZeroUsize::new(2000).unwrap()),
rev_cache: LruCache::new(NonZeroUsize::new(2000).unwrap()),
next_ip: START_IP,
cache: LruCache::new(NonZeroUsize::new(FAKE_IP_CACHE_SIZE).unwrap()),
rev_cache: LruCache::new(NonZeroUsize::new(FAKE_IP_CACHE_SIZE).unwrap()),
next_ip: FAKE_IP_START,
}
}
@@ -90,7 +101,7 @@ impl DnsHandler {
SystemTime::now()
.duration_since(meta.modified()?)
.unwrap_or_default()
> Duration::from_secs(604800) // 1 неделя
> BLOCKLIST_UPDATE_INTERVAL
} else {
true
};
@@ -98,7 +109,7 @@ impl DnsHandler {
if needs_update {
let p_clone = self.cache_path.clone();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(10)).await;
tokio::time::sleep(BLOCKLIST_DOWNLOAD_DELAY).await;
if let Err(e) = Self::download_blocklist_async(p_clone).await {
error!("DNS: Background update failed: {}", e);
}
@@ -111,7 +122,7 @@ impl DnsHandler {
async fn download_blocklist_async(cache_path: String) -> Result<()> {
info!("DNS: Starting background download to {}", cache_path);
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(30))
.timeout(BLOCKLIST_HTTP_TIMEOUT)
.build()?;
let resp = client
@@ -183,7 +194,7 @@ impl DnsHandler {
let fake_ip = store.get_or_assign(&name);
res.add_answer(Record::from_rdata(
query.name().clone(),
60,
FAKE_DNS_TTL,
RData::A(fake_ip.into()),
));
res.set_response_code(ResponseCode::NoError);
+317 -68
View File
@@ -1,6 +1,11 @@
use bytes::Bytes;
use netrunner_core::net::ClientHandler;
use netrunner_core::net::NetworkConfig;
use netrunner_core::net::Muxer;
use netrunner_core::net::diagnostics::{
self, DiagnosisRx, DiagnosticsEvent, DiagnosticsSnapshot, DiagnosticsStore,
EngineMetrics, SocketMetrics, current_timestamp_ms,
};
use netrunner_core::rawcast::{RawCastEvent, RawCastFrame};
use smoltcp::iface::PollResult;
use smoltcp::phy::ChannelDevice;
@@ -12,13 +17,13 @@ use smoltcp::{
};
use std::net::Ipv4Addr;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use std::{sync::LazyLock, time::Instant as StdInstant};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::sync::mpsc;
use tokio::time::{Duration, sleep};
use tun::{DeviceReader, DeviceWriter};
use netrunner_core::net::STATS_LOG_INTERVAL;
use netrunner_logger::{debug, error, info, warn};
use crate::net::connection_manager::ConnectionManager;
@@ -30,13 +35,23 @@ 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).
/// Inbound packet slots in the smoltcp device RX queue before backpressure.
/// At MTU 1500 B × 128 slots ≈ 192 KB peak queue depth.
const DEVICE_RX_CAP: usize = 128;
/// Outbound packet slots staged in the smoltcp device TX queue.
/// Must be ≥ the engine→TUN channel cap so we never pop without a slot.
const DEVICE_TX_CAP: usize = 128;
/// Bounded capacity of the TUN-reader → engine mpsc channel (packets).
const TUN_CHAN_CAP: usize = 128;
/// Maximum packets drained from the TUN channel per loop iteration before
/// yielding, to prevent upload traffic from starving download processing.
const MAX_PACKETS_PER_TICK: usize = 250;
/// Read buffer allocated by the TUN reader task (covers the maximum IP packet).
const TUN_READ_BUF_SIZE: usize = 65536;
/// Upper bound on the smoltcp poll-delay sleep to keep latency low.
/// 2 ms is a good balance: low enough for interactive traffic (<5ms added RTT),
/// high enough to avoid spinning the CPU under light load.
const MAX_POLL_SLEEP: Duration = Duration::from_millis(2);
pub struct Engine {
interface: Interface,
@@ -50,6 +65,17 @@ pub struct Engine {
rx_from_tunnel: Option<mpsc::Receiver<RawCastFrame>>,
factory: Arc<dyn SocketProvider>,
stats: TrafficCounter,
/// Arc to the tunnel multiplexer — used to collect tunnel metrics in
/// diagnostics snapshots.
muxer: Option<Arc<Muxer>>,
/// Receiver end of the diagnostics event channel.
diag_rx: Option<DiagnosisRx>,
/// Shared store that holds the last N snapshots (readable via public API).
pub diag_store: Arc<DiagnosticsStore>,
/// A single download frame that couldn't be forwarded to its socket channel
/// (channel was full). Retried at the start of the next engine tick BEFORE
/// reading more frames from rx_tunnel. This preserves in-order delivery.
pending_download: Option<(u64, Bytes)>,
}
impl Engine {
@@ -83,6 +109,10 @@ impl Engine {
rx_from_tunnel: Some(rx_from_tunnel),
factory,
stats: TrafficCounter::new(),
muxer: None,
diag_rx: None,
diag_store: Arc::new(DiagnosticsStore::new(20)),
pending_download: None,
}
}
@@ -119,44 +149,76 @@ impl Engine {
let mut work_done = 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
};
//
// ORDERING CONTRACT: we must NEVER reorder bytes for a given socket.
// Spawning a background task when the channel is full is forbidden —
// it creates a race where later frames overtake the stalled one.
//
// Instead we use a single `pending_download` slot:
// • Retry any stalled frame FIRST each tick.
// • Only drain rx_tunnel once pending is empty.
// • If delivery fails again, set pending and skip rx_tunnel until
// the next tick (when smoltcp has had a chance to drain the channel).
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;
});
// — Retry stalled frame from previous tick —
if let Some((socket_id, payload)) = self.pending_download.take() {
let tx_opt = if let Some(cached) = local_cache.get(&socket_id) {
Some(cached.clone())
} else if let Some(ref_tx) = inbound_map.get(&socket_id) {
let val = ref_tx.value().clone();
local_cache.insert(socket_id, val.clone());
Some(val)
} else {
None
};
match tx_opt {
Some((tx, _)) => match tx.try_send(payload) {
Ok(_) => { work_done = true; }
Err(mpsc::error::TrySendError::Full(p)) => {
// Still full — will retry next tick.
self.pending_download = Some((socket_id, p));
}
Err(_) => {} // socket gone — drop
},
None => {} // socket gone — drop
}
}
// — Drain rx_tunnel only when no frame is waiting for its slot —
if self.pending_download.is_none() {
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 socket_id = frame.socket_id;
let tx_opt = if let Some(cached) = local_cache.get(&socket_id) {
Some(cached.clone())
} else if let Some(ref_tx) = inbound_map.get(&socket_id) {
let val = ref_tx.value().clone();
local_cache.insert(socket_id, val.clone());
Some(val)
} else {
None
};
if let Some((tx, _)) = tx_opt {
match tx.try_send(frame.payload) {
Ok(_) => {}
Err(mpsc::error::TrySendError::Full(data)) => {
// Channel full — stash and stop draining.
self.pending_download = Some((socket_id, data));
break;
}
Err(_) => {} // socket gone — drop
}
Err(_) => {}
}
}
}
Err(_) => break,
}
Err(_) => break,
}
}
@@ -166,13 +228,13 @@ impl Engine {
while !self.device.rx_full() {
match tun_rx.try_recv() {
Ok(pkt) => {
self.stats.record_rx(pkt.len());
self.stats.record_tx(pkt.len()); // upload: app → internet
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 {
if packets_read >= MAX_PACKETS_PER_TICK {
break; // Yield occasionally to prevent starvation.
}
}
@@ -193,20 +255,28 @@ impl Engine {
}
// ── 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;
// Check channel capacity BEFORE popping so we never consume a
// packet from the device TX queue without a guaranteed slot to
// send it. Packets left in the queue are regenerated by smoltcp
// on the next poll() call (TCP state machine handles retransmits,
// ACKs, etc.).
while self.tun_tx.capacity() > 0 {
match self.device.pop_tx() {
Some(pkt) => {
self.stats.record_rx(pkt.len()); // download: internet → app
// capacity() > 0 guarantees this won't fail
let _ = self.tun_tx.try_send(pkt);
work_done = true;
}
None => break,
}
}
// ── 5. Stats logging ─────────────────────────────────────────
if last_stats_log.elapsed() >= Duration::from_secs(5) {
if last_stats_log.elapsed() >= STATS_LOG_INTERVAL {
let stats = self.stats.get_stats();
info!(
"TunDevice Traffic: RX: {:.2} MB ({} pkts) | TX: {:.2} MB ({} pkts) | Speed: ↓{:.2} MB/s, ↑{:.2} MB/s",
"TunDevice Traffic: {:.2} MB ({} pkts) | {:.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,
@@ -220,7 +290,23 @@ impl Engine {
last_stats_log = StdInstant::now();
}
// ── 6. Adaptive timing ───────────────────────────────────────
// ── 6. Diagnostics event processing ─────────────────────────
// Take the receiver out (ends the borrow on self), drain events,
// build snapshots (needs &mut self for stats), then put it back.
if let Some(mut diag_rx) = self.diag_rx.take() {
loop {
match diag_rx.try_recv() {
Ok(event) => {
let snap = self.build_snapshot(event);
self.diag_store.push(snap);
}
Err(_) => break,
}
}
self.diag_rx = Some(diag_rx);
}
// ── 7. Adaptive timing ───────────────────────────────────────
if work_done {
tokio::task::yield_now().await;
} else {
@@ -229,24 +315,75 @@ impl Engine {
.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),
)
})
.unwrap_or(Duration::from_millis(5));
.map(|d| Duration::from_micros(d.micros()).min(MAX_POLL_SLEEP))
.unwrap_or(MAX_POLL_SLEEP);
tokio::select! {
_ = sleep(sleep_time) => {}
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);
if self.pending_download.is_some() {
// A frame is stalled — don't read more from rx_tunnel yet
// (would risk overwriting the pending slot or draining out-
// of-order). Just wait for smoltcp to drain inbound_tx.
tokio::select! {
_ = sleep(sleep_time) => {}
msg = tun_rx.recv() => {
match msg {
Some(pkt) => {
self.stats.record_tx(pkt.len());
self.manager.try_create_socket_from_packet(&pkt, &mut self.socket_set);
self.device.push_rx(pkt);
}
None => break,
}
}
}
} else {
tokio::select! {
_ = sleep(sleep_time) => {}
// Upload: wake immediately when the app sends data.
msg = tun_rx.recv() => {
match msg {
Some(pkt) => {
self.stats.record_tx(pkt.len());
self.manager.try_create_socket_from_packet(&pkt, &mut self.socket_set);
self.device.push_rx(pkt);
}
None => break,
}
}
// Download: wake immediately when tunnel data arrives.
// Dispatch inline; step 1 drains the rest next iteration.
frame = rx_tunnel.recv() => {
match frame {
None => break,
Some(f) => {
if f.event == RawCastEvent::Close {
local_cache.remove(&f.socket_id);
inbound_map.remove(&f.socket_id);
} else if f.event == RawCastEvent::Data {
let socket_id = f.socket_id;
let tx_opt = if let Some(cached) = local_cache.get(&socket_id) {
Some(cached.clone())
} else if let Some(ref_tx) = inbound_map.get(&socket_id) {
let val = ref_tx.value().clone();
local_cache.insert(socket_id, val.clone());
Some(val)
} else {
None
};
if let Some((tx, _)) = tx_opt {
match tx.try_send(f.payload) {
Ok(_) => {}
Err(mpsc::error::TrySendError::Full(data)) => {
// Stash — next tick retries.
self.pending_download = Some((socket_id, data));
}
Err(_) => {}
}
}
}
}
}
None => break,
}
}
}
@@ -263,7 +400,7 @@ impl Engine {
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];
let mut buf = vec![0u8; TUN_READ_BUF_SIZE];
loop {
match reader.read(&mut buf).await {
Ok(n) if n > 0 => {
@@ -301,6 +438,111 @@ impl Engine {
Instant::from_micros(duration.as_micros() as i64)
}
// ── Diagnostics ──────────────────────────────────────────────────────────
/// Returns all recent diagnostics snapshots as a pretty-printed JSON string.
/// Snapshots are captured automatically whenever a network problem occurs
/// (upload fail, download backpressure, leg disconnect, tunnel write stall).
pub fn get_diagnostics_json(&self) -> String {
self.diag_store.get_all_json()
}
/// Returns only the most recent snapshot as JSON, or `"null"` if none yet.
pub fn get_latest_diagnostics_json(&self) -> String {
self.diag_store.get_latest_json()
}
/// Builds a full diagnostics snapshot at this exact moment.
/// Collects: traffic counters, device queue depths, channel free space,
/// all active smoltcp socket states, and tunnel leg/stream metrics.
fn build_snapshot(&mut self, trigger: DiagnosticsEvent) -> DiagnosticsSnapshot {
let stats = self.stats.get_stats();
let engine_metrics = EngineMetrics {
rx_total_mb: stats.rx_bytes as f64 / 1_048_576.0,
tx_total_mb: stats.tx_bytes as f64 / 1_048_576.0,
rx_speed_mb_s: stats.rx_speed_mb_s,
tx_speed_mb_s: stats.tx_speed_mb_s,
rx_packets: stats.rx_packets,
tx_packets: stats.tx_packets,
device_rx_queue_depth: self.device.rx_len(),
// ChannelDevice doesn't expose tx_len; use 0 (best-effort data only)
device_tx_queue_depth: if self.device.has_tx() { 1 } else { 0 },
tun_tx_channel_free: self.tun_tx.capacity(),
// tun_rx is taken by `run()`; not available here
tun_rx_channel_free: 0,
};
// Collect socket handles and their smoltcp state first (immutable borrow).
struct SocketSnap {
handle_str: String,
state: String,
send_queue: usize,
send_cap: usize,
recv_queue: usize,
recv_cap: usize,
}
let snaps: Vec<SocketSnap> = self
.socket_set
.iter()
.filter_map(|(handle, socket)| {
if let smoltcp::socket::Socket::Tcp(tcp) = socket {
if tcp.is_active() {
return Some(SocketSnap {
handle_str: format!("{}", handle),
state: format!("{:?}", tcp.state()),
send_queue: tcp.send_queue(),
send_cap: tcp.send_capacity(),
recv_queue: tcp.recv_queue(),
recv_cap: tcp.recv_capacity(),
});
}
}
None
})
.collect();
// Collect pending_chunk sizes via manager (separate borrow).
let sockets: Vec<SocketMetrics> = snaps
.into_iter()
.map(|s| {
let stream_id: u32 = s.handle_str.parse().unwrap_or(0);
SocketMetrics {
stream_id,
state: s.state,
send_queue_bytes: s.send_queue,
send_capacity_bytes: s.send_cap,
recv_queue_bytes: s.recv_queue,
recv_capacity_bytes: s.recv_cap,
// pending_chunk is connection-private; omit for now
pending_chunk_bytes: 0,
tx_congested: false,
total_up_bytes: 0,
total_down_bytes: 0,
}
})
.collect();
let tunnel = self
.muxer
.as_ref()
.map(|m| m.snapshot_tunnel_metrics())
.unwrap_or_else(|| diagnostics::TunnelMetrics {
global_min_rtt_ms: 0,
active_legs: vec![],
total_streams: 0,
});
DiagnosticsSnapshot {
timestamp_ms: current_timestamp_ms(),
trigger,
engine: Some(engine_metrics),
tunnel,
sockets,
error_totals: diagnostics::DIAG_COUNTERS.snapshot(),
}
}
pub fn set_any_ip(&mut self, state: bool) {
self.interface.set_any_ip(state)
}
@@ -447,12 +689,16 @@ impl EngineBuilder {
caps.max_transmission_unit = self.config.mtu;
caps.medium = smoltcp::phy::Medium::Ip;
// Initialise the diagnostics event channel before connecting so that
// any events fired during leg establishment are captured.
let diag_rx = diagnostics::init_diagnostics();
let cap = NetworkConfig::global().channel_capacity;
let (tx_to_tunnel, rx_for_client_handler) = mpsc::channel::<RawCastFrame>(cap);
let (tx_for_client_handler, rx_from_tunnel) = mpsc::channel::<RawCastFrame>(cap);
info!("Establishing secure tunnel to proxy server...");
ClientHandler::connect(
let muxer = ClientHandler::connect(
&self.config.remote_address,
rx_for_client_handler,
tx_for_client_handler,
@@ -512,6 +758,9 @@ impl EngineBuilder {
factory,
);
engine.muxer = Some(muxer);
engine.diag_rx = Some(diag_rx);
engine.set_any_ip(self.config.any_ip);
if self.config.transparent_mode {
engine.set_transparent_mode();
+22 -10
View File
@@ -1,4 +1,8 @@
use netrunner_core::net::{MAX_SOCKETS, NetworkConfig};
use netrunner_core::net::{
BUFFERBLOAT_WARN_THRESHOLD, HTTPS_PORT, HTTP_ALT_PORT, HTTP_PORT, ICMP_BUFFER_SIZE,
ICMP_META_SLOTS, MAX_SOCKETS, NTP_PORT, RDP_PORT, RTMP_PORT, SSH_PORT, VNC_PORT, NetworkConfig,
DNS_PORT,
};
use netrunner_logger::{info, warn};
use smoltcp::{
iface::SocketSet,
@@ -26,9 +30,11 @@ pub const TCP_SOCKET_ACTIVE_TIMEOUT: Duration = Duration::from_secs(60);
impl TrafficProfile {
pub fn guess_from_port(port: u16, is_tcp: bool) -> Self {
match (port, is_tcp) {
(22, true) | (3389, true) | (5900, true) => Self::Interactive,
(443, true) | (80, true) | (8080, true) | (1935, true) => Self::Bulk,
(53, false) | (123, false) => Self::Dns,
(SSH_PORT, true) | (RDP_PORT, true) | (VNC_PORT, true) => Self::Interactive,
(HTTPS_PORT, true) | (HTTP_PORT, true) | (HTTP_ALT_PORT, true) | (RTMP_PORT, true) => {
Self::Bulk
}
(DNS_PORT, false) | (NTP_PORT, false) => Self::Dns,
_ => Self::Default,
}
}
@@ -143,11 +149,11 @@ impl SocketProvider for SmolSocketFactory {
app_pending_len / 1024 // Ждет входа в стек (из туннеля)
);
// Если очередь приложения раздута — это красный флаг
if app_pending_len > 1024 * 1024 {
if app_pending_len > BUFFERBLOAT_WARN_THRESHOLD {
warn!(
"⚠️ [TCP {}] Bufferbloat detected! Application queue is > 1MB",
handle
"⚠️ [TCP {}] Bufferbloat detected! Application queue is > {} KB",
handle,
BUFFERBLOAT_WARN_THRESHOLD / 1024
);
}
}
@@ -179,8 +185,14 @@ impl SocketProvider for SmolSocketFactory {
fn create_icmp(&self, _profile: TrafficProfile) -> icmp::Socket<'static> {
icmp::Socket::new(
icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY; 4], vec![0; 512]),
icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY; 4], vec![0; 512]),
icmp::PacketBuffer::new(
vec![icmp::PacketMetadata::EMPTY; ICMP_META_SLOTS],
vec![0; ICMP_BUFFER_SIZE],
),
icmp::PacketBuffer::new(
vec![icmp::PacketMetadata::EMPTY; ICMP_META_SLOTS],
vec![0; ICMP_BUFFER_SIZE],
),
)
}