pub mod csum;
pub mod net_types;
use crate::libc;
use std::fmt;
const SANE: usize = 4096;
#[inline(always)]
pub(crate) const fn unlikely<T>(x: T) -> T {
x
}
#[derive(Debug)]
pub enum PacketError {
InsufficientHeadroom {
diff: usize,
head: usize,
},
InvalidPacketLength {},
InvalidOffset {
offset: usize,
length: usize,
},
InsufficientData {
offset: usize,
size: usize,
length: usize,
},
ChecksumUnsupported,
TimestampUnsupported,
}
impl PacketError {
#[inline]
pub fn discriminant(&self) -> &'static str {
match self {
Self::InsufficientHeadroom { .. } => "insufficient headroom",
Self::InvalidPacketLength {} => "invalid packet length",
Self::InvalidOffset { .. } => "invalid offset",
Self::InsufficientData { .. } => "insufficient data",
Self::ChecksumUnsupported => "TX checksum unsupported",
Self::TimestampUnsupported => "TX timestamp unsupported",
}
}
}
impl std::error::Error for PacketError {}
impl fmt::Display for PacketError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
pub unsafe trait Pod: Sized {
#[inline]
fn size() -> usize {
std::mem::size_of::<Self>()
}
#[inline]
fn zeroed() -> Self {
unsafe { std::mem::zeroed() }
}
#[inline]
fn as_bytes(&self) -> &[u8] {
unsafe {
std::slice::from_raw_parts((self as *const Self).cast(), std::mem::size_of::<Self>())
}
}
}
pub enum CsumOffload {
Request {
start: u16,
offset: u16,
},
None,
}
pub struct Packet {
pub(crate) data: *mut u8,
pub(crate) capacity: usize,
pub(crate) head: usize,
pub(crate) tail: usize,
pub(crate) base: *const u8,
pub(crate) options: u32,
}
impl Packet {
#[doc(hidden)]
pub fn testing_new(buf: &mut [u8; 2 * 1024]) -> Self {
let data = &mut buf[libc::xdp::XDP_PACKET_HEADROOM as usize..];
Self {
data: data.as_mut_ptr(),
capacity: data.len(),
head: 0,
tail: 0,
base: std::ptr::null(),
options: 0,
}
}
#[inline]
pub fn len(&self) -> usize {
self.tail - self.head
}
#[inline]
pub fn is_empty(&self) -> bool {
self.head == self.tail
}
#[inline]
pub fn capacity(&self) -> usize {
self.capacity
}
#[inline]
pub fn clear(&mut self) {
self.tail = self.head;
}
#[inline]
pub fn is_continued(&self) -> bool {
(self.options & libc::xdp::XdpPktOptions::XDP_PKT_CONTD) != 0
}
#[inline]
pub fn can_offload_checksum(&self) -> bool {
(self.options & libc::InternalXdpFlags::SUPPORTS_CHECKSUM_OFFLOAD) != 0
}
#[inline]
pub fn adjust_head(&mut self, diff: i32) -> Result<(), PacketError> {
if diff < 0 {
let diff = diff.unsigned_abs() as usize;
if diff > self.head {
return Err(PacketError::InsufficientHeadroom {
diff,
head: self.head,
});
}
self.head -= diff;
} else {
let diff = diff as usize;
if self.head + diff > self.tail {
return Err(PacketError::InvalidPacketLength {});
}
self.head += diff;
}
Ok(())
}
#[inline]
pub fn adjust_tail(&mut self, diff: i32) -> Result<(), PacketError> {
if diff < 0 {
let diff = diff.unsigned_abs() as usize;
if diff > self.tail || self.tail - diff < self.head {
return Err(PacketError::InsufficientHeadroom {
diff,
head: self.head,
});
}
self.tail -= diff;
} else {
let diff = diff as usize;
if self.tail + diff > self.capacity {
return Err(PacketError::InvalidPacketLength {});
}
self.tail += diff;
}
Ok(())
}
#[inline]
pub fn read<T: Pod>(&self, offset: usize) -> Result<T, PacketError> {
if unlikely(offset >= SANE) {
return Err(PacketError::InvalidOffset {
offset,
length: self.len(),
});
}
let start = self.head + offset;
if start > self.tail {
return Err(PacketError::InvalidOffset {
offset,
length: self.len(),
});
}
let size = std::mem::size_of::<T>();
if unlikely(size >= SANE) || start + size > self.tail {
return Err(PacketError::InsufficientData {
offset,
size,
length: self.tail - offset,
});
}
Ok(unsafe { std::ptr::read_unaligned(self.data.byte_offset(start as _).cast()) })
}
#[inline]
pub fn write<T: Pod>(&mut self, offset: usize, item: T) -> Result<(), PacketError> {
if unlikely(offset >= SANE) {
return Err(PacketError::InvalidOffset {
offset,
length: self.len(),
});
}
let start = self.head + offset;
if start > self.tail {
return Err(PacketError::InvalidOffset {
offset,
length: self.len(),
});
}
let size = std::mem::size_of::<T>();
if unlikely(size >= SANE) || start + size > self.tail {
return Err(PacketError::InsufficientData {
offset,
size,
length: self.tail - offset,
});
}
unsafe {
std::ptr::write_unaligned(
self.data.byte_offset((self.head + offset) as _).cast(),
item,
);
}
Ok(())
}
#[inline]
pub fn array_at_offset<const N: usize>(
&self,
offset: usize,
array: &mut [u8; N],
) -> Result<(), PacketError> {
struct AssertReasonable<const N: usize>;
impl<const N: usize> AssertReasonable<N> {
const OK: () = assert!(N < 4096, "the array size far too large");
}
const fn assert_reasonable<const N: usize>() {
let () = AssertReasonable::<N>::OK;
}
assert_reasonable::<N>();
if unlikely(offset >= SANE) {
return Err(PacketError::InvalidOffset {
offset,
length: self.len(),
});
}
let start = self.head + offset;
if start + N > self.tail {
return Err(PacketError::InsufficientData {
offset,
size: N,
length: self.tail.saturating_sub(offset),
});
}
unsafe {
std::ptr::copy_nonoverlapping(self.data.byte_offset(start as _), array.as_mut_ptr(), N);
}
Ok(())
}
#[inline]
pub fn slice_at_offset(&self, offset: usize, len: usize) -> Result<&[u8], PacketError> {
if unlikely(offset >= SANE) {
return Err(PacketError::InvalidOffset {
offset,
length: self.len(),
});
}
let start = self.head + offset;
if unlikely(len >= SANE) || start + len > self.tail {
return Err(PacketError::InsufficientData {
offset,
size: len,
length: self.tail.saturating_sub(offset),
});
}
let slice = unsafe { std::slice::from_raw_parts(self.data.byte_offset(start as _), len) };
Ok(slice)
}
#[inline]
pub fn insert(&mut self, offset: usize, slice: &[u8]) -> Result<(), PacketError> {
if unlikely(slice.len() > SANE) || self.tail + slice.len() > self.capacity {
return Err(PacketError::InvalidPacketLength {});
} else if offset > self.tail || unlikely(offset >= SANE) {
return Err(PacketError::InvalidOffset {
offset,
length: self.len(),
});
}
let adjusted_offset = self.head + offset;
let shift = self.tail + self.head - adjusted_offset;
unsafe {
if shift > 0 {
std::ptr::copy(
self.data.byte_offset(adjusted_offset as isize),
self.data
.byte_offset((adjusted_offset + slice.len()) as isize),
shift,
);
}
std::ptr::copy_nonoverlapping(
slice.as_ptr(),
self.data.byte_offset(adjusted_offset as _),
slice.len(),
);
}
self.tail += slice.len();
Ok(())
}
#[inline]
pub fn append(&mut self, slice: &[u8]) -> Result<(), PacketError> {
if unlikely(slice.len() > SANE) || self.tail + slice.len() > self.capacity {
return Err(PacketError::InvalidPacketLength {});
}
unsafe {
std::ptr::copy_nonoverlapping(
slice.as_ptr(),
self.data.byte_offset(self.tail as _),
slice.len(),
);
}
self.tail += slice.len();
Ok(())
}
#[inline]
pub fn set_tx_metadata(
&mut self,
csum: CsumOffload,
request_timestamp: bool,
) -> Result<(), PacketError> {
use libc::xdp;
debug_assert!(request_timestamp || matches!(csum, CsumOffload::Request { .. }));
if matches!(csum, CsumOffload::Request { .. })
&& (self.options & libc::InternalXdpFlags::SUPPORTS_CHECKSUM_OFFLOAD) == 0
{
return Err(PacketError::ChecksumUnsupported);
} else if request_timestamp
&& (self.options & libc::InternalXdpFlags::SUPPORTS_TIMESTAMP) == 0
{
return Err(PacketError::TimestampUnsupported);
}
unsafe {
let mut tx_meta = std::mem::zeroed::<xdp::xsk_tx_metadata>();
if let CsumOffload::Request { start, offset } = csum {
tx_meta.flags |= xdp::XdpTxFlags::XDP_TXMD_FLAGS_CHECKSUM;
tx_meta.offload.request = xdp::xsk_tx_request {
csum_start: start,
csum_offset: offset,
};
}
if request_timestamp {
tx_meta.flags |= xdp::XdpTxFlags::XDP_TXMD_FLAGS_TIMESTAMP;
}
std::ptr::write_unaligned(
self.data
.byte_offset(
self.head as isize - std::mem::size_of::<xdp::xsk_tx_metadata>() as isize,
)
.cast(),
tx_meta,
);
}
self.options |= xdp::XdpPktOptions::XDP_TX_METADATA;
Ok(())
}
#[doc(hidden)]
#[inline]
pub fn __inner_copy(&mut self) -> Self {
Self {
data: self.data,
capacity: self.capacity,
head: self.head,
tail: self.tail,
base: self.base,
options: self.options,
}
}
}
impl std::ops::Deref for Packet {
type Target = [u8];
fn deref(&self) -> &Self::Target {
unsafe { &std::slice::from_raw_parts(self.data, self.capacity)[self.head..self.tail] }
}
}
impl std::ops::DerefMut for Packet {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe {
&mut std::slice::from_raw_parts_mut(self.data, self.capacity)[self.head..self.tail]
}
}
}
impl From<Packet> for libc::xdp::xdp_desc {
fn from(packet: Packet) -> Self {
libc::xdp::xdp_desc {
addr: unsafe {
packet
.data
.byte_offset(packet.head as _)
.offset_from(packet.base) as _
},
len: (packet.tail - packet.head) as _,
options: packet.options & !libc::InternalXdpFlags::MASK,
}
}
}
impl std::io::Write for Packet {
#[inline]
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match self.append(buf) {
Ok(()) => Ok(buf.len()),
Err(_) => Err(std::io::Error::new(
std::io::ErrorKind::StorageFull,
"not enough space available in packet",
)),
}
}
#[inline]
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}