use crate::mmap::OwnedMmap;
use crate::ring::{FRAME_COUNT, FRAME_SIZE, Ring, RingType, XdpDesc};
use crate::socket::{Inner, RxSocket, TxSocket};
use std::io;
use std::mem::size_of;
use std::os::fd::{FromRawFd as _, OwnedFd};
use std::sync::Arc;
pub fn create_socket(
if_index: u32,
if_queue: u32,
direction: Direction,
config: Option<XdpConfig>,
) -> Result<(Option<TxSocket>, Option<RxSocket>), io::Error> {
let (rx_ring_size, tx_ring_size) = match direction {
Direction::Tx => (0, FRAME_COUNT), Direction::Rx => (FRAME_COUNT, 0), Direction::Both => (FRAME_COUNT / 2, FRAME_COUNT / 2), };
let (fd, raw_fd) = unsafe {
let fd = libc::socket(libc::AF_XDP, libc::SOCK_RAW | libc::SOCK_CLOEXEC, 0);
if fd < 0 {
return Err(io::Error::last_os_error());
}
(OwnedFd::from_raw_fd(fd), fd)
};
let umem = setup_umem(raw_fd, config.as_ref())?;
RingType::Fill.set_size(raw_fd, tx_ring_size)?;
RingType::Completion.set_size(raw_fd, tx_ring_size)?;
if tx_ring_size > 0 {
RingType::Tx.set_size(raw_fd, tx_ring_size)?;
}
if rx_ring_size > 0 {
RingType::Rx.set_size(raw_fd, rx_ring_size)?;
}
let offsets = ring_offsets(raw_fd)?;
let (c_ring, tx_ring) = if direction == Direction::Rx {
(Ring::default(), Ring::default())
} else {
(
RingType::Completion.mmap(raw_fd, &offsets, tx_ring_size)?,
{
let mut tx_ring: Ring<XdpDesc> =
RingType::Tx.mmap(raw_fd, &offsets, tx_ring_size)?;
tx_ring.fill(0);
tx_ring
},
)
};
let (rx_ring, f_ring) = if direction == Direction::Tx {
(Ring::default(), Ring::default())
} else {
(RingType::Rx.mmap(raw_fd, &offsets, rx_ring_size)?, {
let mut f_ring: Ring<u64> = RingType::Fill.mmap(raw_fd, &offsets, rx_ring_size)?;
f_ring.fill(tx_ring_size as u32);
f_ring.update_producer(f_ring.len as u32);
f_ring
})
};
let zero_copy = match config.and_then(|cfg| cfg.zero_copy) {
Some(true) => libc::XDP_ZEROCOPY,
Some(false) => libc::XDP_COPY,
None => 0,
};
let need_wakeup = if config.and_then(|cfg| cfg.need_wakeup).unwrap_or(true) {
libc::XDP_USE_NEED_WAKEUP
} else {
0
};
let sxdp = libc::sockaddr_xdp {
sxdp_family: libc::AF_XDP as libc::sa_family_t,
sxdp_flags: need_wakeup | zero_copy,
sxdp_ifindex: if_index,
sxdp_queue_id: if_queue,
sxdp_shared_umem_fd: 0,
};
if unsafe {
libc::bind(
raw_fd,
&sxdp as *const _ as *const libc::sockaddr,
size_of::<libc::sockaddr_xdp>() as libc::socklen_t,
) < 0
} {
return Err(io::Error::other(format!(
"Failed to bind: {}",
io::Error::last_os_error()
)));
}
#[allow(clippy::arc_with_non_send_sync)]
let inner = Arc::new(Inner::new(umem, fd));
let tx_socket = if direction != Direction::Rx {
Some(TxSocket::new(Some(inner.clone()), tx_ring, c_ring))
} else {
None
};
let rx_socket = if direction != Direction::Tx {
Some(RxSocket::new(Some(inner.clone()), rx_ring, f_ring))
} else {
None
};
Ok((tx_socket, rx_socket))
}
pub fn create_tx_socket(
if_index: u32,
if_queue: u32,
config: Option<XdpConfig>,
) -> Result<TxSocket, io::Error> {
let (tx_socket, _) = create_socket(if_index, if_queue, Direction::Tx, config)?;
tx_socket.ok_or_else(|| io::Error::other("Failed to create Tx socket"))
}
pub fn create_rx_socket(
if_index: u32,
if_queue: u32,
config: Option<XdpConfig>,
) -> Result<RxSocket, io::Error> {
let (_, rx_socket) = create_socket(if_index, if_queue, Direction::Rx, config)?;
rx_socket.ok_or_else(|| io::Error::other("Failed to create Rx socket"))
}
pub fn create_bi_socket(
if_index: u32,
if_queue: u32,
config: Option<XdpConfig>,
) -> Result<(TxSocket, RxSocket), io::Error> {
let (tx_socket, rx_socket) = create_socket(if_index, if_queue, Direction::Both, config)?;
Ok((
tx_socket.ok_or_else(|| io::Error::other("Failed to create Tx socket"))?,
rx_socket.ok_or_else(|| io::Error::other("Failed to create Rx socket"))?,
))
}
pub fn ring_offsets(raw_fd: libc::c_int) -> io::Result<libc::xdp_mmap_offsets> {
let mut offsets: libc::xdp_mmap_offsets = unsafe { std::mem::zeroed() };
let mut optlen = size_of::<libc::xdp_mmap_offsets>() as libc::socklen_t;
unsafe {
if libc::getsockopt(
raw_fd,
libc::SOL_XDP,
libc::XDP_MMAP_OFFSETS,
&mut offsets as *mut _ as *mut libc::c_void,
&mut optlen,
) < 0
{
return Err(io::Error::last_os_error());
}
}
Ok(offsets)
}
pub fn setup_umem(raw_fd: libc::c_int, config: Option<&XdpConfig>) -> io::Result<OwnedMmap> {
let umem = OwnedMmap::mmap(
FRAME_COUNT * FRAME_SIZE,
config.and_then(|cfg| cfg.huge_page),
)
.map_err(|e| io::Error::other(format!("Failed to allocate UMEM: {}", e)))?;
let reg = unsafe {
libc::xdp_umem_reg {
addr: umem.as_void_ptr() as u64,
len: umem.len() as u64,
chunk_size: FRAME_SIZE as u32,
..std::mem::zeroed()
}
};
unsafe {
if libc::setsockopt(
raw_fd,
libc::SOL_XDP,
libc::XDP_UMEM_REG,
® as *const _ as *const libc::c_void,
size_of::<libc::xdp_umem_reg>() as libc::socklen_t,
) < 0
{
return Err(io::Error::other(format!(
"Failed to register UMEM: {}",
io::Error::last_os_error()
)));
}
}
Ok(umem)
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(i32)]
pub enum Direction {
Tx = 0,
Rx = 1,
Both = -1,
}
#[derive(Debug, Copy, Clone, Default)]
pub struct XdpConfig {
pub zero_copy: Option<bool>,
pub huge_page: Option<bool>,
pub need_wakeup: Option<bool>,
}