use std::ffi::CString;
use std::io;
use std::os::fd::RawFd;
use std::sync::Arc;
pub const UMEM_CHUNK_SIZE: usize = 2048;
pub const UMEM_RING_SIZE: u32 = 2048;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UmemPoolConfig {
pub num_chunks: usize,
pub chunk_size: usize,
pub pre_fault: bool,
pub lock_memory: bool,
}
impl Default for UmemPoolConfig {
fn default() -> Self {
Self {
num_chunks: 4096,
chunk_size: 2048,
pre_fault: true,
lock_memory: false,
}
}
}
pub struct UmemPool {
area: *mut libc::c_void,
size: usize,
chunk_size: usize,
free_chunks: Vec<u64>,
}
impl UmemPool {
pub fn new(num_chunks: usize, chunk_size: usize) -> io::Result<Self> {
Self::with_config(UmemPoolConfig {
num_chunks,
chunk_size,
pre_fault: true,
lock_memory: false,
})
}
pub fn new_lazy(num_chunks: usize, chunk_size: usize) -> io::Result<Self> {
Self::with_config(UmemPoolConfig {
num_chunks,
chunk_size,
pre_fault: false,
lock_memory: false,
})
}
pub fn with_config(config: UmemPoolConfig) -> io::Result<Self> {
if config.num_chunks == 0 || config.chunk_size == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"num_chunks and chunk_size must be positive",
));
}
let size = config
.num_chunks
.checked_mul(config.chunk_size)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "UMEM size overflow"))?;
let mut flags = libc::MAP_ANONYMOUS | libc::MAP_SHARED;
if config.pre_fault {
flags |= libc::MAP_POPULATE;
}
let area = unsafe {
libc::mmap(
std::ptr::null_mut(),
size,
libc::PROT_READ | libc::PROT_WRITE,
flags,
-1,
0,
)
};
if area == libc::MAP_FAILED {
return Err(io::Error::last_os_error());
}
if config.lock_memory {
let lock_ret = unsafe { libc::mlock(area, size) };
if lock_ret != 0 {
let err = io::Error::last_os_error();
unsafe {
libc::munmap(area, size);
}
return Err(err);
}
}
let free_chunks: Vec<u64> = (0..config.num_chunks)
.rev()
.map(|i| (i * config.chunk_size) as u64)
.collect();
Ok(Self {
area,
size,
chunk_size: config.chunk_size,
free_chunks,
})
}
pub fn alloc_chunk(&mut self) -> Option<u64> {
self.free_chunks.pop()
}
pub fn free_chunk(&mut self, addr: u64) {
debug_assert!(
(addr as usize) < self.size && addr.is_multiple_of(self.chunk_size as u64),
"corrupt chunk address returned to free pool: addr={addr}, size={}, chunk_size={}",
self.size,
self.chunk_size
);
self.free_chunks.push(addr);
}
pub fn chunk_slice(&self, addr: u64, len: usize) -> &[u8] {
assert!(
(addr as usize)
.checked_add(len)
.is_some_and(|end| end <= self.size),
"UMEM slice out of bounds: addr={addr}, len={len}, total size={}",
self.size
);
let offset = (addr as usize) % self.chunk_size;
assert!(
offset
.checked_add(len)
.is_some_and(|end| end <= self.chunk_size),
"UMEM slice len={len} with intra-chunk offset={offset} exceeds chunk_size={}",
self.chunk_size
);
unsafe {
let ptr = (self.area as *const u8).add(addr as usize);
std::slice::from_raw_parts(ptr, len)
}
}
pub fn chunk_slice_mut(&mut self, addr: u64, len: usize) -> &mut [u8] {
assert!(
(addr as usize)
.checked_add(len)
.is_some_and(|end| end <= self.size),
"UMEM slice out of bounds: addr={addr}, len={len}, total size={}",
self.size
);
let offset = (addr as usize) % self.chunk_size;
assert!(
offset
.checked_add(len)
.is_some_and(|end| end <= self.chunk_size),
"UMEM slice len={len} with intra-chunk offset={offset} exceeds chunk_size={}",
self.chunk_size
);
unsafe {
let ptr = (self.area as *mut u8).add(addr as usize);
std::slice::from_raw_parts_mut(ptr, len)
}
}
pub fn area_ptr(&self) -> *mut libc::c_void {
self.area
}
pub fn size(&self) -> usize {
self.size
}
pub fn chunk_size(&self) -> usize {
self.chunk_size
}
pub fn free_chunk_count(&self) -> usize {
self.free_chunks.len()
}
#[inline]
pub fn is_valid_chunk_addr(&self, addr: u64) -> bool {
(addr as usize) < self.size && addr.is_multiple_of(self.chunk_size as u64)
}
pub(crate) unsafe fn chunk_ptr_mut(&self, addr: u64, len: usize) -> *mut u8 {
assert!(
(addr as usize)
.checked_add(len)
.is_some_and(|end| end <= self.size),
"UMEM slice out of bounds: addr={addr}, len={len}, total size={}",
self.size
);
let offset = (addr as usize) % self.chunk_size;
assert!(
offset
.checked_add(len)
.is_some_and(|end| end <= self.chunk_size),
"UMEM slice len={len} with intra-chunk offset={offset} exceeds chunk_size={}",
self.chunk_size
);
unsafe { (self.area as *mut u8).add(addr as usize) }
}
pub fn create_slabs(self: &Arc<Self>, num_slabs: usize) -> io::Result<Vec<UmemSlab>> {
if num_slabs == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"num_slabs must be positive",
));
}
let total_chunks = self.size / self.chunk_size;
let chunks_per_slab = total_chunks / num_slabs;
if chunks_per_slab == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("not enough chunks ({total_chunks}) for requested slabs ({num_slabs})"),
));
}
let mut slabs = Vec::with_capacity(num_slabs);
for slab_id in 0..num_slabs {
let base_addr = (slab_id * chunks_per_slab * self.chunk_size) as u64;
let free_chunks: Vec<u64> = (0..chunks_per_slab)
.rev()
.map(|i| base_addr + (i * self.chunk_size) as u64)
.collect();
slabs.push(UmemSlab {
pool: Arc::clone(self),
slab_id,
base_addr,
num_chunks: chunks_per_slab,
chunk_size: self.chunk_size,
free_chunks,
});
}
Ok(slabs)
}
pub fn new_slabs(
num_chunks: usize,
chunk_size: usize,
num_slabs: usize,
) -> io::Result<(Arc<Self>, Vec<UmemSlab>)> {
Self::new_slabs_with_config(
UmemPoolConfig {
num_chunks,
chunk_size,
pre_fault: true,
lock_memory: false,
},
num_slabs,
)
}
pub fn new_slabs_with_config(
config: UmemPoolConfig,
num_slabs: usize,
) -> io::Result<(Arc<Self>, Vec<UmemSlab>)> {
let pool = Arc::new(Self::with_config(config)?);
let slabs = pool.create_slabs(num_slabs)?;
Ok((pool, slabs))
}
}
pub struct UmemSlab {
pool: Arc<UmemPool>,
slab_id: usize,
base_addr: u64,
num_chunks: usize,
chunk_size: usize,
free_chunks: Vec<u64>,
}
impl UmemSlab {
#[inline]
pub fn slab_id(&self) -> usize {
self.slab_id
}
#[inline]
pub fn base_addr(&self) -> u64 {
self.base_addr
}
#[inline]
pub fn end_addr(&self) -> u64 {
self.base_addr + (self.num_chunks * self.chunk_size) as u64
}
#[inline]
pub fn num_chunks(&self) -> usize {
self.num_chunks
}
#[inline]
pub fn total_chunks(&self) -> usize {
self.num_chunks
}
#[inline]
pub fn free_chunk_count(&self) -> usize {
self.free_chunks.len()
}
#[inline]
pub fn chunk_size(&self) -> usize {
self.chunk_size
}
#[inline]
pub fn pool(&self) -> &Arc<UmemPool> {
&self.pool
}
#[inline]
pub fn contains_addr(&self, addr: u64) -> bool {
addr >= self.base_addr
&& addr < self.end_addr()
&& (addr - self.base_addr).is_multiple_of(self.chunk_size as u64)
}
#[inline]
pub fn contains_addr_range(&self, addr: u64, len: usize) -> bool {
addr >= self.base_addr
&& (addr as usize)
.checked_add(len)
.is_some_and(|end| end as u64 <= self.end_addr())
}
#[inline]
pub fn alloc_chunk(&mut self) -> Option<u64> {
self.free_chunks.pop()
}
#[inline]
pub fn free_chunk(&mut self, addr: u64) {
debug_assert!(
self.contains_addr(addr),
"corrupt chunk address returned to slab {}: addr={addr}, base={}, end={}, chunk_size={}",
self.slab_id,
self.base_addr,
self.end_addr(),
self.chunk_size
);
self.free_chunks.push(addr);
}
pub fn alloc_batch(&mut self, out: &mut [u64]) -> usize {
let count = out.len().min(self.free_chunks.len());
if count == 0 {
return 0;
}
let start = self.free_chunks.len() - count;
for (i, slot) in out.iter_mut().take(count).enumerate() {
*slot = self.free_chunks[start + count - 1 - i];
}
self.free_chunks.truncate(start);
count
}
pub fn free_batch(&mut self, addrs: &[u64]) {
for &addr in addrs {
debug_assert!(
self.contains_addr(addr),
"corrupt chunk address in batch for slab {}: addr={addr}",
self.slab_id
);
}
self.free_chunks.extend_from_slice(addrs);
}
pub fn populate_fill_ring(&mut self, fill_ring: &mut FillRing, max_count: usize) -> usize {
let mut total = 0;
let mut buf = [0u64; 64];
while total < max_count && !self.free_chunks.is_empty() && !fill_ring.is_full() {
let to_alloc = (max_count - total).min(buf.len());
let allocated = self.alloc_batch(&mut buf[..to_alloc]);
if allocated == 0 {
break;
}
let produced = fill_ring.produce_batch(&buf[..allocated]);
total += produced;
if produced < allocated {
self.free_batch(&buf[produced..allocated]);
break;
}
}
total
}
pub fn recycle_from_rings(
&mut self,
comp_ring: &mut CompletionRing,
fill_ring: &mut FillRing,
max_batch: usize,
) -> usize {
let mut buf = [0u64; 64];
let batch_size = max_batch.min(buf.len());
let consumed = comp_ring.consume_batch(&mut buf[..batch_size]);
if consumed == 0 {
return 0;
}
let produced = fill_ring.produce_batch(&buf[..consumed]);
if produced < consumed {
self.free_batch(&buf[produced..consumed]);
}
consumed
}
pub fn chunk_slice(&self, addr: u64, len: usize) -> &[u8] {
assert!(
self.contains_addr_range(addr, len),
"UMEM slab {}: addr={addr}, len={len} out of slab bounds [{}..{})",
self.slab_id,
self.base_addr,
self.end_addr()
);
self.pool.chunk_slice(addr, len)
}
pub fn chunk_slice_mut(&mut self, addr: u64, len: usize) -> &mut [u8] {
assert!(
self.contains_addr_range(addr, len),
"UMEM slab {}: addr={addr}, len={len} out of slab bounds [{}..{})",
self.slab_id,
self.base_addr,
self.end_addr()
);
unsafe {
let ptr = self.pool.chunk_ptr_mut(addr, len);
std::slice::from_raw_parts_mut(ptr, len)
}
}
}
impl Drop for UmemPool {
fn drop(&mut self) {
if !self.area.is_null() && self.area != libc::MAP_FAILED {
unsafe {
libc::munmap(self.area, self.size);
}
}
}
}
unsafe impl Send for UmemPool {}
unsafe impl Sync for UmemPool {}
#[derive(Debug, Clone)]
pub struct FillRing {
entries: Vec<u64>,
capacity: u32,
head: u32,
tail: u32,
}
impl FillRing {
pub fn new(capacity: u32) -> Self {
assert!(
capacity > 0 && capacity.is_power_of_two(),
"FillRing capacity must be a power of two, got {capacity}"
);
Self {
entries: vec![0u64; capacity as usize],
capacity,
head: 0,
tail: 0,
}
}
pub fn capacity(&self) -> u32 {
self.capacity
}
pub fn len(&self) -> usize {
self.tail.wrapping_sub(self.head) as usize
}
pub fn is_empty(&self) -> bool {
self.head == self.tail
}
pub fn is_full(&self) -> bool {
self.len() >= self.capacity as usize
}
pub fn produce(&mut self, addr: u64) -> bool {
if self.is_full() {
return false;
}
let idx = (self.tail as usize) & ((self.capacity - 1) as usize);
self.entries[idx] = addr;
self.tail = self.tail.wrapping_add(1);
true
}
pub fn produce_batch(&mut self, addrs: &[u64]) -> usize {
let available = (self.capacity as usize).saturating_sub(self.len());
let to_produce = addrs.len().min(available);
if to_produce == 0 {
return 0;
}
let mask = (self.capacity - 1) as usize;
let start = (self.tail as usize) & mask;
let first_len = to_produce.min(self.entries.len() - start);
self.entries[start..start + first_len].copy_from_slice(&addrs[..first_len]);
if first_len < to_produce {
let second_len = to_produce - first_len;
self.entries[..second_len].copy_from_slice(&addrs[first_len..to_produce]);
}
self.tail = self.tail.wrapping_add(to_produce as u32);
to_produce
}
pub fn consume(&mut self) -> Option<u64> {
if self.is_empty() {
return None;
}
let idx = (self.head as usize) & ((self.capacity - 1) as usize);
let addr = self.entries[idx];
self.head = self.head.wrapping_add(1);
Some(addr)
}
pub fn consume_batch(&mut self, out: &mut [u64]) -> usize {
let available = self.len();
let to_consume = out.len().min(available);
if to_consume == 0 {
return 0;
}
let mask = (self.capacity - 1) as usize;
let start = (self.head as usize) & mask;
let first_len = to_consume.min(self.entries.len() - start);
out[..first_len].copy_from_slice(&self.entries[start..start + first_len]);
if first_len < to_consume {
let second_len = to_consume - first_len;
out[first_len..to_consume].copy_from_slice(&self.entries[..second_len]);
}
self.head = self.head.wrapping_add(to_consume as u32);
to_consume
}
}
#[derive(Debug, Clone)]
pub struct CompletionRing {
entries: Vec<u64>,
capacity: u32,
head: u32,
tail: u32,
}
impl CompletionRing {
pub fn new(capacity: u32) -> Self {
assert!(
capacity > 0 && capacity.is_power_of_two(),
"CompletionRing capacity must be a power of two, got {capacity}"
);
Self {
entries: vec![0u64; capacity as usize],
capacity,
head: 0,
tail: 0,
}
}
pub fn capacity(&self) -> u32 {
self.capacity
}
pub fn len(&self) -> usize {
self.tail.wrapping_sub(self.head) as usize
}
pub fn is_empty(&self) -> bool {
self.head == self.tail
}
pub fn is_full(&self) -> bool {
self.len() >= self.capacity as usize
}
pub fn produce(&mut self, addr: u64) -> bool {
if self.is_full() {
return false;
}
let idx = (self.tail as usize) & ((self.capacity - 1) as usize);
self.entries[idx] = addr;
self.tail = self.tail.wrapping_add(1);
true
}
pub fn produce_batch(&mut self, addrs: &[u64]) -> usize {
let available = (self.capacity as usize).saturating_sub(self.len());
let to_produce = addrs.len().min(available);
if to_produce == 0 {
return 0;
}
let mask = (self.capacity - 1) as usize;
let start = (self.tail as usize) & mask;
let first_len = to_produce.min(self.entries.len() - start);
self.entries[start..start + first_len].copy_from_slice(&addrs[..first_len]);
if first_len < to_produce {
let second_len = to_produce - first_len;
self.entries[..second_len].copy_from_slice(&addrs[first_len..to_produce]);
}
self.tail = self.tail.wrapping_add(to_produce as u32);
to_produce
}
pub fn consume(&mut self) -> Option<u64> {
if self.is_empty() {
return None;
}
let idx = (self.head as usize) & ((self.capacity - 1) as usize);
let addr = self.entries[idx];
self.head = self.head.wrapping_add(1);
Some(addr)
}
pub fn consume_batch(&mut self, out: &mut [u64]) -> usize {
let available = self.len();
let to_consume = out.len().min(available);
if to_consume == 0 {
return 0;
}
let mask = (self.capacity - 1) as usize;
let start = (self.head as usize) & mask;
let first_len = to_consume.min(self.entries.len() - start);
out[..first_len].copy_from_slice(&self.entries[start..start + first_len]);
if first_len < to_consume {
let second_len = to_consume - first_len;
out[first_len..to_consume].copy_from_slice(&self.entries[..second_len]);
}
self.head = self.head.wrapping_add(to_consume as u32);
to_consume
}
}
pub const XDP_COPY: u16 = 1 << 1;
pub const XDP_ZERO_COPY: u16 = 1 << 2;
pub const XDP_ZEROCOPY: u16 = XDP_ZERO_COPY;
pub const ENOTSUPP: i32 = 524;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum XskBindMode {
ZeroCopy,
Copy,
FallbackRecvmmsg,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct XskDesc {
pub addr: u64,
pub len: u32,
pub flags: u32,
}
impl XskDesc {
pub const fn new(addr: u64, len: u32, flags: u32) -> Self {
Self { addr, len, flags }
}
}
#[derive(Debug, Clone)]
pub struct RxRing {
entries: Vec<XskDesc>,
capacity: u32,
head: u32,
tail: u32,
}
impl RxRing {
pub fn new(capacity: u32) -> Self {
assert!(
capacity > 0 && capacity.is_power_of_two(),
"RxRing capacity must be a power of two, got {capacity}"
);
Self {
entries: vec![XskDesc::default(); capacity as usize],
capacity,
head: 0,
tail: 0,
}
}
pub fn capacity(&self) -> u32 {
self.capacity
}
pub fn len(&self) -> usize {
self.tail.wrapping_sub(self.head) as usize
}
pub fn is_empty(&self) -> bool {
self.head == self.tail
}
pub fn is_full(&self) -> bool {
self.len() >= self.capacity as usize
}
pub fn produce(&mut self, desc: XskDesc) -> bool {
if self.is_full() {
return false;
}
let idx = (self.tail as usize) & ((self.capacity - 1) as usize);
self.entries[idx] = desc;
self.tail = self.tail.wrapping_add(1);
true
}
pub fn produce_batch(&mut self, descs: &[XskDesc]) -> usize {
let available = (self.capacity as usize).saturating_sub(self.len());
let to_produce = descs.len().min(available);
if to_produce == 0 {
return 0;
}
let mask = (self.capacity - 1) as usize;
let start = (self.tail as usize) & mask;
let first_len = to_produce.min(self.entries.len() - start);
self.entries[start..start + first_len].copy_from_slice(&descs[..first_len]);
if first_len < to_produce {
let second_len = to_produce - first_len;
self.entries[..second_len].copy_from_slice(&descs[first_len..to_produce]);
}
self.tail = self.tail.wrapping_add(to_produce as u32);
to_produce
}
pub fn consume(&mut self) -> Option<XskDesc> {
if self.is_empty() {
return None;
}
let idx = (self.head as usize) & ((self.capacity - 1) as usize);
let desc = self.entries[idx];
self.head = self.head.wrapping_add(1);
Some(desc)
}
pub fn consume_batch(&mut self, out: &mut [XskDesc]) -> usize {
let available = self.len();
let to_consume = out.len().min(available);
if to_consume == 0 {
return 0;
}
let mask = (self.capacity - 1) as usize;
let start = (self.head as usize) & mask;
let first_len = to_consume.min(self.entries.len() - start);
out[..first_len].copy_from_slice(&self.entries[start..start + first_len]);
if first_len < to_consume {
let second_len = to_consume - first_len;
out[first_len..to_consume].copy_from_slice(&self.entries[..second_len]);
}
self.head = self.head.wrapping_add(to_consume as u32);
to_consume
}
}
#[derive(Debug, Clone)]
pub struct TxRing {
entries: Vec<XskDesc>,
capacity: u32,
head: u32,
tail: u32,
}
impl TxRing {
pub fn new(capacity: u32) -> Self {
assert!(
capacity > 0 && capacity.is_power_of_two(),
"TxRing capacity must be a power of two, got {capacity}"
);
Self {
entries: vec![XskDesc::default(); capacity as usize],
capacity,
head: 0,
tail: 0,
}
}
pub fn capacity(&self) -> u32 {
self.capacity
}
pub fn len(&self) -> usize {
self.tail.wrapping_sub(self.head) as usize
}
pub fn is_empty(&self) -> bool {
self.head == self.tail
}
pub fn is_full(&self) -> bool {
self.len() >= self.capacity as usize
}
pub fn produce(&mut self, desc: XskDesc) -> bool {
if self.is_full() {
return false;
}
let idx = (self.tail as usize) & ((self.capacity - 1) as usize);
self.entries[idx] = desc;
self.tail = self.tail.wrapping_add(1);
true
}
pub fn produce_batch(&mut self, descs: &[XskDesc]) -> usize {
let available = (self.capacity as usize).saturating_sub(self.len());
let to_produce = descs.len().min(available);
if to_produce == 0 {
return 0;
}
let mask = (self.capacity - 1) as usize;
let start = (self.tail as usize) & mask;
let first_len = to_produce.min(self.entries.len() - start);
self.entries[start..start + first_len].copy_from_slice(&descs[..first_len]);
if first_len < to_produce {
let second_len = to_produce - first_len;
self.entries[..second_len].copy_from_slice(&descs[first_len..to_produce]);
}
self.tail = self.tail.wrapping_add(to_produce as u32);
to_produce
}
pub fn consume(&mut self) -> Option<XskDesc> {
if self.is_empty() {
return None;
}
let idx = (self.head as usize) & ((self.capacity - 1) as usize);
let desc = self.entries[idx];
self.head = self.head.wrapping_add(1);
Some(desc)
}
pub fn consume_batch(&mut self, out: &mut [XskDesc]) -> usize {
let available = self.len();
let to_consume = out.len().min(available);
if to_consume == 0 {
return 0;
}
let mask = (self.capacity - 1) as usize;
let start = (self.head as usize) & mask;
let first_len = to_consume.min(self.entries.len() - start);
out[..first_len].copy_from_slice(&self.entries[start..start + first_len]);
if first_len < to_consume {
let second_len = to_consume - first_len;
out[first_len..to_consume].copy_from_slice(&self.entries[..second_len]);
}
self.head = self.head.wrapping_add(to_consume as u32);
to_consume
}
}
struct AutoCloseFd(RawFd);
impl AutoCloseFd {
fn into_raw(mut self) -> RawFd {
let fd = self.0;
self.0 = -1;
fd
}
}
impl Drop for AutoCloseFd {
fn drop(&mut self) {
if self.0 >= 0 {
unsafe {
libc::close(self.0);
}
}
}
}
pub struct XskSocket {
fd: RawFd,
mode: XskBindMode,
rx_ring: RxRing,
tx_ring: TxRing,
filter: Option<crate::bpf::XdpRedirectFilter>,
}
impl XskSocket {
pub fn bind_opportunistic(ifname: &str, queue_id: u32, umem: &UmemPool) -> io::Result<Self> {
let fd = unsafe { libc::socket(libc::AF_XDP, libc::SOCK_RAW, 0) };
if fd < 0 {
let err = io::Error::last_os_error();
match err.raw_os_error() {
Some(
libc::EPERM
| libc::EACCES
| libc::EAFNOSUPPORT
| libc::ENOPROTOOPT
| libc::EPROTONOSUPPORT,
) => return Ok(Self::fallback()),
_ => return Ok(Self::fallback()),
}
}
let sock_guard = AutoCloseFd(fd);
let mr = libc::xdp_umem_reg {
addr: umem.area_ptr() as u64,
len: umem.size() as u64,
chunk_size: umem.chunk_size() as u32,
headroom: 0,
flags: 0,
tx_metadata_len: 0,
};
let ret_umem = unsafe {
libc::setsockopt(
fd,
libc::SOL_XDP,
libc::XDP_UMEM_REG,
std::ptr::addr_of!(mr).cast::<libc::c_void>(),
std::mem::size_of::<libc::xdp_umem_reg>() as libc::socklen_t,
)
};
if ret_umem != 0 {
return Ok(Self::fallback());
}
let ring_size: u32 = UMEM_RING_SIZE;
let ret_fill = unsafe {
libc::setsockopt(
fd,
libc::SOL_XDP,
libc::XDP_UMEM_FILL_RING,
std::ptr::addr_of!(ring_size).cast::<libc::c_void>(),
std::mem::size_of::<u32>() as libc::socklen_t,
)
};
if ret_fill != 0 {
return Ok(Self::fallback());
}
let ret_comp = unsafe {
libc::setsockopt(
fd,
libc::SOL_XDP,
libc::XDP_UMEM_COMPLETION_RING,
std::ptr::addr_of!(ring_size).cast::<libc::c_void>(),
std::mem::size_of::<u32>() as libc::socklen_t,
)
};
if ret_comp != 0 {
return Ok(Self::fallback());
}
let ret_rx = unsafe {
libc::setsockopt(
fd,
libc::SOL_XDP,
libc::XDP_RX_RING,
std::ptr::addr_of!(ring_size).cast::<libc::c_void>(),
std::mem::size_of::<u32>() as libc::socklen_t,
)
};
if ret_rx != 0 {
return Ok(Self::fallback());
}
let ret_tx = unsafe {
libc::setsockopt(
fd,
libc::SOL_XDP,
libc::XDP_TX_RING,
std::ptr::addr_of!(ring_size).cast::<libc::c_void>(),
std::mem::size_of::<u32>() as libc::socklen_t,
)
};
if ret_tx != 0 {
return Ok(Self::fallback());
}
let c_ifname = match CString::new(ifname) {
Ok(c) => c,
Err(_) => return Ok(Self::fallback()),
};
let ifindex = unsafe { libc::if_nametoindex(c_ifname.as_ptr()) };
if ifindex == 0 {
return Ok(Self::fallback());
}
let mut sxdp: libc::sockaddr_xdp = unsafe {
std::mem::zeroed()
};
sxdp.sxdp_family = libc::AF_XDP as u16;
sxdp.sxdp_ifindex = ifindex;
sxdp.sxdp_queue_id = queue_id;
sxdp.sxdp_shared_umem_fd = 0;
sxdp.sxdp_flags = XDP_ZERO_COPY;
let ret_zc = unsafe {
libc::bind(
fd,
std::ptr::addr_of!(sxdp).cast::<libc::sockaddr>(),
std::mem::size_of::<libc::sockaddr_xdp>() as libc::socklen_t,
)
};
if ret_zc == 0 {
let fd = sock_guard.into_raw();
return Ok(Self {
fd,
mode: XskBindMode::ZeroCopy,
rx_ring: RxRing::new(UMEM_RING_SIZE),
tx_ring: TxRing::new(UMEM_RING_SIZE),
filter: None,
});
}
let zc_err = io::Error::last_os_error();
let should_try_copy = matches!(
zc_err.raw_os_error(),
Some(libc::EOPNOTSUPP | ENOTSUPP | libc::EINVAL)
);
if should_try_copy {
sxdp.sxdp_flags = XDP_COPY;
let ret_copy = unsafe {
libc::bind(
fd,
std::ptr::addr_of!(sxdp).cast::<libc::sockaddr>(),
std::mem::size_of::<libc::sockaddr_xdp>() as libc::socklen_t,
)
};
if ret_copy == 0 {
let fd = sock_guard.into_raw();
return Ok(Self {
fd,
mode: XskBindMode::Copy,
rx_ring: RxRing::new(UMEM_RING_SIZE),
tx_ring: TxRing::new(UMEM_RING_SIZE),
filter: None,
});
}
}
Ok(Self::fallback())
}
pub fn fallback() -> Self {
Self {
fd: -1,
mode: XskBindMode::FallbackRecvmmsg,
rx_ring: RxRing::new(UMEM_RING_SIZE),
tx_ring: TxRing::new(UMEM_RING_SIZE),
filter: None,
}
}
pub fn mode(&self) -> XskBindMode {
self.mode
}
pub fn fd(&self) -> RawFd {
self.fd
}
pub fn rx_ring_mut(&mut self) -> &mut RxRing {
&mut self.rx_ring
}
pub fn tx_ring_mut(&mut self) -> &mut TxRing {
&mut self.tx_ring
}
pub fn rx_ring(&self) -> &RxRing {
&self.rx_ring
}
pub fn tx_ring(&self) -> &TxRing {
&self.tx_ring
}
pub fn filter(&self) -> Option<&crate::bpf::XdpRedirectFilter> {
self.filter.as_ref()
}
pub fn filter_mut(&mut self) -> Option<&mut crate::bpf::XdpRedirectFilter> {
self.filter.as_mut()
}
pub fn attach_bpf_filter(
&mut self,
ifname: &str,
listen_port: u16,
queue_id: u32,
) -> crate::bpf::BpfFilterStatus {
if self.fd < 0 {
return crate::bpf::BpfFilterStatus::Unsupported;
}
match crate::bpf::XdpRedirectFilter::load_and_attach(ifname, listen_port, queue_id, self.fd)
{
Ok(f) => {
let prog_fd = f.prog_fd();
self.filter = Some(f);
crate::bpf::BpfFilterStatus::Attached(prog_fd)
}
Err(status) => status,
}
}
pub fn attach_bpf_filter_multi_queue(
&mut self,
ifname: &str,
listen_port: u16,
queue_fds: &[(u32, RawFd)],
) -> crate::bpf::BpfFilterStatus {
if self.fd < 0 {
return crate::bpf::BpfFilterStatus::Unsupported;
}
match crate::bpf::XdpRedirectFilter::load_multi_queue(ifname, listen_port, queue_fds) {
Ok(f) => {
let prog_fd = f.prog_fd();
self.filter = Some(f);
crate::bpf::BpfFilterStatus::Attached(prog_fd)
}
Err(status) => status,
}
}
pub fn set_bpf_filter_queue_socket(&mut self, queue_id: u32, xsk_fd: RawFd) -> io::Result<()> {
match self.filter.as_mut() {
Some(filter) => filter.set_socket_for_queue(queue_id, xsk_fd),
None => Err(io::Error::new(
io::ErrorKind::NotFound,
"no BPF filter attached to socket",
)),
}
}
pub fn bind_multi_queue(
ifname: &str,
queue_ids: &[u32],
umem: &UmemPool,
) -> io::Result<Vec<(u32, Self)>> {
let mut sockets = Vec::with_capacity(queue_ids.len());
for &qid in queue_ids {
let sock = Self::bind_opportunistic(ifname, qid, umem)?;
sockets.push((qid, sock));
}
Ok(sockets)
}
}
impl Drop for XskSocket {
fn drop(&mut self) {
if self.fd >= 0 {
unsafe {
libc::close(self.fd);
}
self.fd = -1;
}
}
}