use std::ffi::c_void;
use std::io;
use std::mem;
use std::os::fd::{AsFd, AsRawFd, BorrowedFd};
use std::ptr::{self, NonNull};
use std::sync::atomic::{AtomicU32, Ordering};
use crate::block::{Block, Frame, RawRingMeta};
use crate::error::{Error, Result};
use crate::sys::Stats;
use crate::sys::linux::socket::PacketSocket;
use crate::sys::linux::tpacket::{RingLayout, TPACKET_V2, TPACKET_V3, tpacket_align};
#[derive(Debug)]
struct Mmap {
ptr: NonNull<u8>,
len: usize,
}
unsafe impl Send for Mmap {}
impl Mmap {
fn new(fd: BorrowedFd<'_>, len: usize) -> Result<Self> {
let raw = unsafe {
libc::mmap(
ptr::null_mut(),
len,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_SHARED,
fd.as_raw_fd(),
0,
)
};
if raw == libc::MAP_FAILED {
return Err(Error::Mmap(io::Error::last_os_error()));
}
let ptr = unsafe { NonNull::new_unchecked(raw.cast::<u8>()) };
Ok(Mmap { ptr, len })
}
fn base(&self) -> *mut u8 {
self.ptr.as_ptr()
}
fn bytes(&self, off: usize, len: usize) -> &[u8] {
assert!(off.checked_add(len).is_some_and(|end| end <= self.len));
unsafe { std::slice::from_raw_parts(self.ptr.as_ptr().add(off), len) }
}
}
impl Drop for Mmap {
fn drop(&mut self) {
unsafe {
libc::munmap(self.ptr.as_ptr().cast::<c_void>(), self.len);
}
}
}
#[derive(Debug)]
pub struct RxRing {
sock: PacketSocket,
mmap: Mmap,
layout: RingLayout,
current_block: usize,
}
impl RxRing {
pub fn open(sock: PacketSocket, layout: RingLayout) -> Result<Self> {
sock.set_packet_opt_int(libc::PACKET_VERSION, "PACKET_VERSION", TPACKET_V3)?;
let req = layout.to_req3();
unsafe { sock.set_packet_opt(libc::PACKET_RX_RING, "PACKET_RX_RING", &req)? };
let mmap = Mmap::new(sock.as_fd(), layout.map_len())?;
Ok(RxRing { sock, mmap, layout, current_block: 0 })
}
pub fn recv_block(&mut self) -> Option<Block<'_>> {
let Self { mmap, layout, current_block, .. } = self;
let block_size = layout.block_size as usize;
let block_off = *current_block * block_size;
let status_off = mem::offset_of!(libc::tpacket_block_desc, hdr);
let status: &AtomicU32 = unsafe {
AtomicU32::from_ptr(mmap.base().add(block_off + status_off).cast::<u32>())
};
let block_status = status.load(Ordering::Acquire);
if block_status & libc::TP_STATUS_USER == 0 {
return None;
}
let losing = block_status & libc::TP_STATUS_LOSING != 0;
let hdr = unsafe {
ptr::read_unaligned(
mmap.base().add(block_off + status_off).cast::<libc::tpacket_hdr_v1>(),
)
};
let block_bytes = mmap.bytes(block_off, block_size);
Some(Block::from_ring(RxBlock {
status,
block: block_bytes,
num_pkts: hdr.num_pkts,
first_off: hdr.offset_to_first_pkt as usize,
cursor: current_block,
block_count: layout.block_count as usize,
losing,
}))
}
pub fn block_ready(&self) -> bool {
let block_off = self.current_block * self.layout.block_size as usize;
let status_off = mem::offset_of!(libc::tpacket_block_desc, hdr);
let status: &AtomicU32 = unsafe {
AtomicU32::from_ptr(self.mmap.base().add(block_off + status_off).cast::<u32>())
};
status.load(Ordering::Acquire) & libc::TP_STATUS_USER != 0
}
pub fn as_fd(&self) -> BorrowedFd<'_> {
self.sock.as_fd()
}
pub fn set_promiscuous(&self, on: bool) -> Result<()> {
self.sock.set_promiscuous(on)
}
pub fn set_fanout(&self, group_id: u16, mode: u16) -> Result<()> {
self.sock.set_fanout(group_id, mode)
}
pub fn set_fanout_unique(&self, mode: u16, fallback_id: u16) -> Result<u16> {
self.sock.set_fanout_unique(mode, fallback_id)
}
pub fn stats(&self) -> io::Result<Stats> {
self.sock.statistics()
}
}
#[derive(Debug)]
pub struct RxBlock<'a> {
status: &'a AtomicU32,
block: &'a [u8],
num_pkts: u32,
first_off: usize,
cursor: &'a mut usize,
block_count: usize,
losing: bool,
}
impl<'a> RxBlock<'a> {
pub fn frame_count(&self) -> usize {
self.num_pkts as usize
}
pub fn is_losing(&self) -> bool {
self.losing
}
pub fn frames(&self) -> RxFrames<'_> {
RxFrames { block: self.block, off: self.first_off, remaining: self.num_pkts }
}
}
impl Drop for RxBlock<'_> {
fn drop(&mut self) {
self.status.store(libc::TP_STATUS_KERNEL, Ordering::Release);
*self.cursor = (*self.cursor + 1) % self.block_count;
}
}
#[derive(Debug)]
pub struct RxFrames<'a> {
block: &'a [u8],
off: usize,
remaining: u32,
}
pub(crate) fn frames_over(block: &[u8], first_off: usize, num_pkts: u32) -> RxFrames<'_> {
RxFrames { block, off: first_off, remaining: num_pkts }
}
impl<'a> Iterator for RxFrames<'a> {
type Item = Frame<'a>;
fn next(&mut self) -> Option<Frame<'a>> {
if self.remaining == 0 {
return None;
}
let hdr_end = self.off.checked_add(mem::size_of::<libc::tpacket3_hdr>());
if hdr_end.is_none_or(|e| e > self.block.len()) {
self.remaining = 0;
return None;
}
let hdr = unsafe {
ptr::read_unaligned(self.block.as_ptr().add(self.off).cast::<libc::tpacket3_hdr>())
};
let mac = hdr.tp_mac as usize;
let snap = hdr.tp_snaplen as usize;
let Some(start) = self.off.checked_add(mac) else {
self.remaining = 0;
return None;
};
let end = start.checked_add(snap);
if end.is_none_or(|e| e > self.block.len()) {
self.remaining = 0;
return None;
}
let data = &self.block[start..end.unwrap()];
let raw = RawRingMeta {
wire_len: hdr.tp_len,
ts_sec: hdr.tp_sec,
ts_nsec: hdr.tp_nsec,
status: hdr.tp_status,
vlan_tci: hdr.hv1.tp_vlan_tci as u16,
vlan_tpid: hdr.hv1.tp_vlan_tpid,
};
match hdr.tp_next_offset {
0 => self.remaining = 1, next => self.off = self.off.saturating_add(next as usize),
}
self.remaining -= 1;
Some(Frame::ring(data, raw))
}
fn size_hint(&self) -> (usize, Option<usize>) {
(0, Some(self.remaining as usize))
}
}
#[derive(Debug)]
pub struct TxRing {
sock: PacketSocket,
mmap: Mmap,
layout: RingLayout,
cursor: usize,
last_filled: Option<usize>,
data_off: usize,
}
impl TxRing {
pub fn open(sock: PacketSocket, layout: RingLayout) -> Result<Self> {
sock.set_packet_opt_int(libc::PACKET_VERSION, "PACKET_VERSION", TPACKET_V2)?;
let req = layout.to_req();
unsafe { sock.set_packet_opt(libc::PACKET_TX_RING, "PACKET_TX_RING", &req)? };
let mmap = Mmap::new(sock.as_fd(), layout.map_len())?;
let data_off = tpacket_align(mem::size_of::<libc::tpacket2_hdr>());
Ok(TxRing { sock, mmap, layout, cursor: 0, last_filled: None, data_off })
}
pub fn max_frame_len(&self) -> usize {
self.layout.frame_size as usize - self.data_off
}
fn slot_status(&self, idx: usize) -> &AtomicU32 {
let off = idx * self.layout.frame_size as usize;
unsafe { AtomicU32::from_ptr(self.mmap.base().add(off).cast::<u32>()) }
}
fn fill_slot(&mut self, frame: &[u8]) -> io::Result<()> {
if frame.len() > self.max_frame_len() {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "frame exceeds TX frame size"));
}
let idx = self.cursor;
let status = self.slot_status(idx);
let s = status.load(Ordering::Acquire);
if s != libc::TP_STATUS_AVAILABLE && s != libc::TP_STATUS_WRONG_FORMAT {
return Err(io::Error::from(io::ErrorKind::WouldBlock));
}
let slot_off = idx * self.layout.frame_size as usize;
unsafe {
let base = self.mmap.base().add(slot_off);
ptr::write_unaligned(base.add(4).cast::<u32>(), frame.len() as u32);
ptr::write_unaligned(base.add(8).cast::<u32>(), frame.len() as u32);
ptr::copy_nonoverlapping(frame.as_ptr(), base.add(self.data_off), frame.len());
}
status.store(libc::TP_STATUS_SEND_REQUEST, Ordering::Release);
self.last_filled = Some(idx);
self.cursor = (idx + 1) % self.layout.frame_count as usize;
Ok(())
}
fn flush(&self) -> io::Result<()> {
let rc = unsafe {
libc::send(self.sock.as_raw_fd(), ptr::null(), 0, libc::MSG_DONTWAIT)
};
if rc < 0 {
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::WouldBlock {
return Ok(());
}
return Err(err);
}
Ok(())
}
pub fn has_unsent(&self) -> bool {
match self.last_filled {
Some(idx) => {
self.slot_status(idx).load(Ordering::Acquire) == libc::TP_STATUS_SEND_REQUEST
}
None => false,
}
}
pub fn finish_kick(&self) -> io::Result<()> {
if !self.has_unsent() {
return Ok(());
}
blocking_kick(self.sock.as_fd())
}
pub fn as_fd(&self) -> BorrowedFd<'_> {
self.sock.as_fd()
}
}
pub(crate) fn blocking_kick(fd: BorrowedFd<'_>) -> io::Result<()> {
loop {
let rc = unsafe { libc::send(fd.as_raw_fd(), ptr::null(), 0, 0) };
if rc < 0 {
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::Interrupted {
continue;
}
return Err(err);
}
return Ok(());
}
}
impl crate::sys::RawChannel for TxRing {
fn send(&mut self, frame: &[u8]) -> io::Result<usize> {
self.fill_slot(frame)?;
self.flush()?;
Ok(frame.len())
}
fn send_batch(&mut self, frames: &[&[u8]]) -> io::Result<usize> {
let mut filled = 0;
for frame in frames {
match self.fill_slot(frame) {
Ok(()) => filled += 1,
Err(e) if filled > 0 && e.kind() == io::ErrorKind::WouldBlock => break,
Err(e) => return Err(e),
}
}
if filled > 0 {
self.flush()?;
}
Ok(filled)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn size_hint_lower_bound_is_zero_for_malformed_blocks() {
let block = [0u8; 8];
let it = frames_over(&block, 0, 5);
assert_eq!(it.size_hint(), (0, Some(5)));
assert_eq!(it.count(), 0);
}
#[test]
fn tx_data_offset_matches_kernel_header() {
let expected = tpacket_align(mem::size_of::<libc::tpacket2_hdr>());
assert_eq!(expected % libc::TPACKET_ALIGNMENT, 0);
assert!(expected >= mem::size_of::<libc::tpacket2_hdr>());
}
}