pub mod addr;
pub mod af_xdp;
pub mod batch;
pub mod bpf;
pub mod epoll;
pub(crate) mod gso;
pub mod nonce;
pub mod poll;
pub mod spsc;
pub mod tun_offload;
pub mod uring;
#[cfg(target_env = "musl")]
pub(crate) type MsgFlags = libc::c_uint;
#[cfg(not(target_env = "musl"))]
pub(crate) type MsgFlags = libc::c_int;
#[cfg(target_env = "musl")]
pub(crate) type CmsgLen = libc::socklen_t;
#[cfg(not(target_env = "musl"))]
pub(crate) type CmsgLen = usize;
pub use addr::{sockaddr_to_std, std_to_sockaddr};
pub use bpf::{BpfFilterStatus, XdpRedirectFilter};
#[inline]
pub(crate) fn empty_msghdr() -> libc::msghdr {
unsafe { std::mem::zeroed() }
}
#[inline]
pub(crate) fn empty_mmsghdr() -> libc::mmsghdr {
unsafe { std::mem::zeroed() }
}
pub const MAX_DATAGRAM_BATCH: usize = 64;
pub const MAX_WIRE_DATAGRAM: usize = 2048;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Backend {
IoUring,
AfXdpZeroCopy,
AfXdpCopy,
Mmsg,
}
use io_uring::{opcode, types, IoUring};
use std::io;
use std::net::UdpSocket;
use std::os::fd::AsRawFd;
pub trait DataPlaneIo {
fn backend(&self) -> Backend;
fn send(&mut self, datagram: &[u8]) -> std::io::Result<()>;
fn recv(&mut self, buf: &mut [u8]) -> std::io::Result<usize>;
fn send_batch(&mut self, datagrams: &[&[u8]]) -> io::Result<usize> {
let mut count = 0usize;
for dg in datagrams {
self.send(dg)?;
count += 1;
}
Ok(count)
}
fn recv_batch(
&mut self,
bufs: &mut [[u8; MAX_WIRE_DATAGRAM]],
lens: &mut [usize],
) -> io::Result<usize> {
if bufs.is_empty() || lens.is_empty() {
return Ok(0);
}
let n = self.recv(&mut bufs[0])?;
lens[0] = n;
Ok(1)
}
}
pub struct IoUringIo {
ring: IoUring,
socket: UdpSocket,
}
impl IoUringIo {
pub fn new(socket: UdpSocket) -> io::Result<IoUringIo> {
let ring = IoUring::new(8)?;
Ok(IoUringIo { ring, socket })
}
fn submit_and_reap(&mut self, entry: &io_uring::squeue::Entry) -> io::Result<usize> {
unsafe {
self.ring
.submission()
.push(entry)
.map_err(|_| io::Error::other("submission queue full"))?;
}
self.ring.submit_and_wait(1)?;
let cqe = self
.ring
.completion()
.next()
.ok_or_else(|| io::Error::other("missing completion"))?;
let res = cqe.result();
if res < 0 {
return Err(io::Error::from_raw_os_error(-res));
}
Ok(usize::try_from(res).expect("non-negative result fits usize"))
}
}
impl DataPlaneIo for IoUringIo {
fn backend(&self) -> Backend {
Backend::IoUring
}
fn send(&mut self, datagram: &[u8]) -> io::Result<()> {
let len =
u32::try_from(datagram.len()).map_err(|_| io::Error::other("datagram too large"))?;
let entry = opcode::Write::new(types::Fd(self.socket.as_raw_fd()), datagram.as_ptr(), len)
.build()
.user_data(0);
self.submit_and_reap(&entry)?;
Ok(())
}
fn recv(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let len = u32::try_from(buf.len()).map_err(|_| io::Error::other("buffer too large"))?;
let entry = opcode::Read::new(types::Fd(self.socket.as_raw_fd()), buf.as_mut_ptr(), len)
.build()
.user_data(1);
self.submit_and_reap(&entry)
}
}
pub struct PlainIo {
socket: UdpSocket,
}
impl PlainIo {
pub fn new(socket: UdpSocket) -> PlainIo {
PlainIo { socket }
}
}
impl DataPlaneIo for PlainIo {
fn backend(&self) -> Backend {
Backend::Mmsg
}
fn send(&mut self, datagram: &[u8]) -> io::Result<()> {
self.socket.send(datagram).map(|_| ())
}
fn recv(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.socket.recv(buf)
}
fn send_batch(&mut self, datagrams: &[&[u8]]) -> io::Result<usize> {
if datagrams.is_empty() {
return Ok(0);
}
let count = datagrams.len().min(MAX_DATAGRAM_BATCH);
let mut iovecs = [libc::iovec {
iov_base: std::ptr::null_mut(),
iov_len: 0,
}; MAX_DATAGRAM_BATCH];
let mut msgs = [empty_mmsghdr(); MAX_DATAGRAM_BATCH];
for (i, dg) in datagrams[..count].iter().enumerate() {
iovecs[i].iov_base = dg.as_ptr().cast_mut().cast::<libc::c_void>();
iovecs[i].iov_len = dg.len();
msgs[i].msg_hdr.msg_iov = &raw mut iovecs[i];
msgs[i].msg_hdr.msg_iovlen = 1;
}
let ret = unsafe {
libc::sendmmsg(
self.socket.as_raw_fd(),
msgs.as_mut_ptr(),
u32::try_from(count).expect("count ≤ MAX_DATAGRAM_BATCH ≤ 64 fits u32"),
libc::MSG_NOSIGNAL as MsgFlags,
)
};
if ret < 0 {
return Err(io::Error::last_os_error());
}
Ok(usize::try_from(ret).expect("non-negative sendmmsg return fits usize"))
}
fn recv_batch(
&mut self,
bufs: &mut [[u8; MAX_WIRE_DATAGRAM]],
lens: &mut [usize],
) -> io::Result<usize> {
if bufs.is_empty() {
return Ok(0);
}
let count = bufs.len().min(lens.len()).min(MAX_DATAGRAM_BATCH);
let mut iovecs = [libc::iovec {
iov_base: std::ptr::null_mut(),
iov_len: 0,
}; MAX_DATAGRAM_BATCH];
let mut msgs = [empty_mmsghdr(); MAX_DATAGRAM_BATCH];
for i in 0..count {
iovecs[i].iov_base = bufs[i].as_mut_ptr().cast::<libc::c_void>();
iovecs[i].iov_len = MAX_WIRE_DATAGRAM;
msgs[i].msg_hdr.msg_iov = &raw mut iovecs[i];
msgs[i].msg_hdr.msg_iovlen = 1;
}
let ret = unsafe {
libc::recvmmsg(
self.socket.as_raw_fd(),
msgs.as_mut_ptr(),
u32::try_from(count).expect("count ≤ MAX_DATAGRAM_BATCH ≤ 64 fits u32"),
libc::MSG_WAITFORONE as MsgFlags,
std::ptr::null_mut(),
)
};
if ret < 0 {
return Err(io::Error::last_os_error());
}
let received = usize::try_from(ret).expect("non-negative recvmmsg return fits usize");
for i in 0..received {
lens[i] = usize::try_from(msgs[i].msg_len).expect("msg_len fits usize");
}
Ok(received)
}
}
pub fn set_socket_buffers(sock: &UdpSocket, bytes: usize) -> io::Result<()> {
let size = i32::try_from(bytes)
.map_err(|_| io::Error::other("buffer size too large for setsockopt"))?;
let fd = sock.as_raw_fd();
let ret_snd = unsafe {
libc::setsockopt(
fd,
libc::SOL_SOCKET,
libc::SO_SNDBUF,
std::ptr::addr_of!(size).cast::<libc::c_void>(),
libc::socklen_t::try_from(std::mem::size_of::<i32>())
.expect("size_of::<i32>() fits socklen_t"),
)
};
if ret_snd != 0 {
return Err(io::Error::last_os_error());
}
let ret_rcv = unsafe {
libc::setsockopt(
fd,
libc::SOL_SOCKET,
libc::SO_RCVBUF,
std::ptr::addr_of!(size).cast::<libc::c_void>(),
libc::socklen_t::try_from(std::mem::size_of::<i32>())
.expect("size_of::<i32>() fits socklen_t"),
)
};
if ret_rcv != 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
pub fn pin_current_thread(core: usize) -> io::Result<()> {
let rc = unsafe {
let mut set: libc::cpu_set_t = std::mem::zeroed();
libc::CPU_ZERO(&mut set);
libc::CPU_SET(core, &mut set);
libc::sched_setaffinity(0, std::mem::size_of::<libc::cpu_set_t>(), &raw const set)
};
if rc != 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
pub fn select_backend(socket: UdpSocket) -> Box<dyn DataPlaneIo> {
let clone = socket.try_clone().expect("clone udp socket");
match IoUringIo::new(clone) {
Ok(io) => Box::new(io),
Err(_) => Box::new(PlainIo::new(socket)),
}
}
#[cfg(test)]
mod tests {
use super::*;
static URING_SERIAL: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[test]
fn backends_are_ordered_by_preference() {
assert_ne!(Backend::IoUring, Backend::Mmsg);
}
#[test]
fn iouring_sends_and_receives_over_udp() {
let _guard = URING_SERIAL.lock().unwrap();
use std::net::UdpSocket;
let rx = UdpSocket::bind("127.0.0.1:0").unwrap();
let tx = UdpSocket::bind("127.0.0.1:0").unwrap();
tx.connect(rx.local_addr().unwrap()).unwrap();
rx.connect(tx.local_addr().unwrap()).unwrap();
let mut tx_io = match IoUringIo::new(tx) {
Ok(io) => io,
Err(_) => {
eprintln!(
"SKIP iouring_sends_and_receives_over_udp: \
io_uring ring unavailable (RLIMIT_MEMLOCK)"
);
return;
}
};
let mut rx_io = match IoUringIo::new(rx) {
Ok(io) => io,
Err(_) => {
eprintln!(
"SKIP iouring_sends_and_receives_over_udp: \
io_uring ring unavailable (RLIMIT_MEMLOCK)"
);
return;
}
};
assert_eq!(tx_io.backend(), Backend::IoUring);
tx_io.send(b"datagram via uring").unwrap();
let mut buf = [0u8; 64];
let n = rx_io.recv(&mut buf).unwrap();
assert_eq!(&buf[..n], b"datagram via uring");
drop(tx_io);
drop(rx_io);
std::thread::sleep(std::time::Duration::from_millis(200));
}
#[test]
fn plain_io_sends_and_receives() {
use std::net::UdpSocket;
let rx = UdpSocket::bind("127.0.0.1:0").unwrap();
let tx = UdpSocket::bind("127.0.0.1:0").unwrap();
tx.connect(rx.local_addr().unwrap()).unwrap();
rx.connect(tx.local_addr().unwrap()).unwrap();
let mut t = PlainIo::new(tx);
let mut r = PlainIo::new(rx);
assert_eq!(t.backend(), Backend::Mmsg);
t.send(b"plain path").unwrap();
let mut buf = [0u8; 32];
let n = r.recv(&mut buf).unwrap();
assert_eq!(&buf[..n], b"plain path");
}
#[test]
fn select_backend_prefers_io_uring_or_falls_back() {
let _guard = URING_SERIAL.lock().unwrap();
use std::net::UdpSocket;
let probe_sock = UdpSocket::bind("127.0.0.1:0").unwrap();
let probe_peer = UdpSocket::bind("127.0.0.1:0").unwrap();
probe_sock
.connect(probe_peer.local_addr().unwrap())
.unwrap();
let uring_ok = IoUringIo::new(probe_sock).is_ok();
drop(probe_peer);
std::thread::sleep(std::time::Duration::from_millis(50));
let s = UdpSocket::bind("127.0.0.1:0").unwrap();
let io = select_backend(s);
if uring_ok {
assert_eq!(io.backend(), Backend::IoUring);
} else {
assert_eq!(io.backend(), Backend::Mmsg);
}
}
#[test]
fn set_socket_buffers_succeeds_on_loopback() {
use std::net::UdpSocket;
let sock = UdpSocket::bind("127.0.0.1:0").unwrap();
let result = set_socket_buffers(&sock, 4 * 1024 * 1024);
assert!(
result.is_ok(),
"set_socket_buffers returned error: {:?}",
result
);
}
#[test]
fn plainio_send_and_recv_batch_roundtrip() {
use std::net::UdpSocket;
let rx = UdpSocket::bind("127.0.0.1:0").unwrap();
let tx = UdpSocket::bind("127.0.0.1:0").unwrap();
tx.connect(rx.local_addr().unwrap()).unwrap();
rx.connect(tx.local_addr().unwrap()).unwrap();
let mut tx_io = PlainIo::new(tx);
let mut rx_io = PlainIo::new(rx);
let a = b"first-datagram".as_slice();
let b = b"second".as_slice();
let sent = tx_io.send_batch(&[a, b]).unwrap();
assert_eq!(sent, 2);
let mut bufs = vec![[0u8; MAX_WIRE_DATAGRAM]; 8];
let mut lens = [0usize; 8];
let mut got: Vec<Vec<u8>> = Vec::new();
while got.len() < 2 {
let n = rx_io.recv_batch(&mut bufs, &mut lens).unwrap();
for i in 0..n {
got.push(bufs[i][..lens[i]].to_vec());
}
}
assert!(got.contains(&a.to_vec()));
assert!(got.contains(&b.to_vec()));
}
#[test]
fn test_pin_current_thread_does_not_crash() {
let _ = pin_current_thread(0);
}
#[test]
fn test_plain_io_single_send_recv_and_defaults() {
use std::net::UdpSocket;
let rx = UdpSocket::bind("127.0.0.1:0").unwrap();
let tx = UdpSocket::bind("127.0.0.1:0").unwrap();
tx.connect(rx.local_addr().unwrap()).unwrap();
rx.connect(tx.local_addr().unwrap()).unwrap();
let mut tx_io = PlainIo::new(tx);
let mut rx_io = PlainIo::new(rx);
tx_io.send(b"single-packet").unwrap();
let mut buf = [0u8; 64];
let n = rx_io.recv(&mut buf).unwrap();
assert_eq!(&buf[..n], b"single-packet");
assert_eq!(tx_io.send_batch(&[]).unwrap(), 0);
let mut empty_lens = [0usize; 1];
assert_eq!(rx_io.recv_batch(&mut [], &mut empty_lens).unwrap(), 0);
assert_ne!(Backend::AfXdpZeroCopy, Backend::AfXdpCopy);
struct DefaultDataPlane {
payload: Vec<u8>,
}
impl DataPlaneIo for DefaultDataPlane {
fn backend(&self) -> Backend {
Backend::Mmsg
}
fn send(&mut self, datagram: &[u8]) -> std::io::Result<()> {
self.payload = datagram.to_vec();
Ok(())
}
fn recv(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let n = self.payload.len().min(buf.len());
buf[..n].copy_from_slice(&self.payload[..n]);
Ok(n)
}
}
let mut ddp = DefaultDataPlane {
payload: Vec::new(),
};
let sent = ddp.send_batch(&[b"test-1", b"test-2"]).unwrap();
assert_eq!(sent, 2);
let mut bufs = vec![[0u8; MAX_WIRE_DATAGRAM]; 2];
let mut lens = [0usize; 2];
let recvd = ddp.recv_batch(&mut bufs, &mut lens).unwrap();
assert_eq!(recvd, 1);
assert_eq!(&bufs[0][..lens[0]], b"test-2");
assert_eq!(ddp.recv_batch(&mut [], &mut lens).unwrap(), 0);
}
}