use std::collections::VecDeque;
use std::os::unix::io::RawFd;
use smoltcp::phy::{self, Checksum, Device, DeviceCapabilities, Medium};
use smoltcp::time::Instant;
const MTU: usize = 1514; const MAX_PENDING_FRAMES: usize = 256;
pub struct VZDevice {
fd: RawFd,
recv_buf: Vec<u8>,
pending_rx: VecDeque<Vec<u8>>,
}
impl VZDevice {
pub fn new(fd: RawFd) -> Self {
VZDevice {
fd,
recv_buf: vec![0u8; MTU + 64], pending_rx: VecDeque::new(),
}
}
fn recv_one_frame(&mut self) -> Option<Vec<u8>> {
let n = unsafe {
libc::recv(
self.fd,
self.recv_buf.as_mut_ptr() as *mut libc::c_void,
self.recv_buf.len(),
libc::MSG_DONTWAIT,
)
};
if n <= 0 {
return None;
}
Some(self.recv_buf[..n as usize].to_vec())
}
pub fn drain_recv(&mut self) {
while self.pending_rx.len() < MAX_PENDING_FRAMES {
match self.recv_one_frame() {
Some(frame) if Self::is_icmp(&frame) => continue,
Some(frame) => self.pending_rx.push_back(frame),
None => break,
}
}
}
fn is_icmp(frame: &[u8]) -> bool {
const ETHERTYPE_IPV4: [u8; 2] = [0x08, 0x00];
const IP_PROTO_ICMP: u8 = 1;
frame.len() >= 24 && frame[12..14] == ETHERTYPE_IPV4 && frame[23] == IP_PROTO_ICMP
}
pub fn pending_frames(&self) -> impl Iterator<Item = &[u8]> {
self.pending_rx.iter().map(|v| v.as_slice())
}
}
pub struct VZRxToken {
buffer: Vec<u8>,
}
impl phy::RxToken for VZRxToken {
fn consume<R, F>(self, f: F) -> R
where
F: FnOnce(&[u8]) -> R,
{
f(&self.buffer)
}
}
pub struct VZTxToken {
fd: RawFd,
}
impl phy::TxToken for VZTxToken {
fn consume<R, F>(self, len: usize, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
let mut buffer = vec![0u8; len];
let result = f(&mut buffer);
let sent = unsafe {
libc::send(
self.fd,
buffer.as_ptr() as *const libc::c_void,
buffer.len(),
0,
)
};
if sent < 0 {
tracing::debug!("TX {len} bytes failed: sent={sent}");
}
result
}
}
impl Device for VZDevice {
type RxToken<'a> = VZRxToken;
type TxToken<'a> = VZTxToken;
fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
loop {
let buffer = self
.pending_rx
.pop_front()
.or_else(|| self.recv_one_frame())?;
if Self::is_icmp(&buffer) {
continue;
}
return Some((VZRxToken { buffer }, VZTxToken { fd: self.fd }));
}
}
fn transmit(&mut self, _timestamp: Instant) -> Option<Self::TxToken<'_>> {
Some(VZTxToken { fd: self.fd })
}
fn capabilities(&self) -> DeviceCapabilities {
let mut caps = DeviceCapabilities::default();
caps.medium = Medium::Ethernet;
caps.max_transmission_unit = MTU;
caps.checksum.ipv4 = Checksum::Tx;
caps.checksum.tcp = Checksum::Tx;
caps.checksum.udp = Checksum::Tx;
caps.checksum.icmpv4 = Checksum::Tx;
caps
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_frame(ip_proto: u8) -> Vec<u8> {
let mut frame = vec![0u8; 34]; frame[12] = 0x08;
frame[13] = 0x00;
frame[14] = 0x45;
frame[23] = ip_proto;
frame
}
#[test]
fn is_icmp_detects_icmp() {
assert!(VZDevice::is_icmp(&make_frame(1))); }
#[test]
fn is_icmp_ignores_tcp() {
assert!(!VZDevice::is_icmp(&make_frame(6))); }
#[test]
fn is_icmp_ignores_udp() {
assert!(!VZDevice::is_icmp(&make_frame(17))); }
#[test]
fn is_icmp_ignores_arp() {
let mut frame = vec![0u8; 42]; frame[12] = 0x08;
frame[13] = 0x06;
assert!(!VZDevice::is_icmp(&frame));
}
#[test]
fn is_icmp_ignores_short_frames() {
assert!(!VZDevice::is_icmp(&[]));
assert!(!VZDevice::is_icmp(&[0u8; 23])); }
}