use std::io;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, UdpSocket};
use std::os::fd::{AsRawFd, RawFd};
use crate::MAX_WIRE_DATAGRAM;
pub const BATCH_SIZE: usize = 32;
pub const BATCH_SIZE_64: usize = 64;
pub const BATCH_SIZE_128: usize = 128;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ReceivedDatagram {
pub len: usize,
pub src: SocketAddr,
}
impl ReceivedDatagram {
#[must_use]
pub const fn empty() -> Self {
Self {
len: 0,
src: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)),
}
}
}
impl Default for ReceivedDatagram {
fn default() -> Self {
Self::empty()
}
}
#[derive(Debug)]
pub struct BatchUdpSocket {
fd: RawFd,
}
impl BatchUdpSocket {
#[must_use]
pub fn new(sock: &UdpSocket) -> Self {
Self {
fd: sock.as_raw_fd(),
}
}
#[must_use]
pub const fn from_raw_fd(fd: RawFd) -> Self {
Self { fd }
}
#[must_use]
pub const fn raw_fd(&self) -> RawFd {
self.fd
}
pub fn recvmmsg_batch<const N: usize>(
&mut self,
buffers: &mut [[u8; MAX_WIRE_DATAGRAM]; N],
out: &mut [ReceivedDatagram; N],
) -> io::Result<usize> {
if N == 0 {
return Ok(0);
}
let mut storages: [libc::sockaddr_storage; N] = unsafe {
std::mem::zeroed()
};
let addrlens = [libc::socklen_t::try_from(std::mem::size_of::<libc::sockaddr_storage>())
.expect("sockaddr_storage size fits socklen_t"); N];
let mut iovecs = [libc::iovec {
iov_base: std::ptr::null_mut(),
iov_len: 0,
}; N];
let mut msgs = [crate::empty_mmsghdr(); N];
for i in 0..N {
iovecs[i].iov_base = buffers[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;
msgs[i].msg_hdr.msg_name = std::ptr::from_mut(&mut storages[i]).cast::<libc::c_void>();
msgs[i].msg_hdr.msg_namelen = addrlens[i];
}
loop {
let ret = unsafe {
libc::recvmmsg(
self.fd,
msgs.as_mut_ptr(),
u32::try_from(N).expect("N fits u32"),
libc::MSG_DONTWAIT as crate::MsgFlags,
std::ptr::null_mut(),
)
};
if ret < 0 {
let err = io::Error::last_os_error();
let raw = err.raw_os_error().unwrap_or(0);
if raw == libc::EINTR {
continue;
}
if raw == libc::EWOULDBLOCK || raw == libc::EAGAIN {
return Ok(0);
}
return Err(err);
}
let received = usize::try_from(ret).expect("recvmmsg return fits usize");
assert!(received <= N, "recvmmsg returned count exceeding N");
for i in 0..received {
let len = usize::try_from(msgs[i].msg_len).expect("msg_len fits usize");
let src = crate::addr::sockaddr_to_std(&storages[i], msgs[i].msg_hdr.msg_namelen)
.unwrap_or_else(|_| {
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0))
});
out[i] = ReceivedDatagram { len, src };
}
return Ok(received);
}
}
pub fn sendmmsg_batch(&mut self, packets: &[(&[u8], SocketAddr)]) -> io::Result<usize> {
self.sendmmsg_burst::<BATCH_SIZE>(packets)
}
pub fn sendmmsg_burst<const N: usize>(
&mut self,
packets: &[(&[u8], SocketAddr)],
) -> io::Result<usize> {
let count = packets.len().min(N);
if count == 0 {
return Ok(0);
}
let mut storages: [libc::sockaddr_storage; N] = unsafe {
std::mem::zeroed()
};
let mut addrlens = [0 as libc::socklen_t; N];
let mut iovecs = [libc::iovec {
iov_base: std::ptr::null_mut(),
iov_len: 0,
}; N];
let mut msgs = [crate::empty_mmsghdr(); N];
for (i, &(payload, dst)) in packets[..count].iter().enumerate() {
let (storage, addr_len) = crate::addr::std_to_sockaddr(dst);
storages[i] = storage;
addrlens[i] = addr_len;
iovecs[i].iov_base = payload.as_ptr().cast_mut().cast::<libc::c_void>();
iovecs[i].iov_len = payload.len();
msgs[i].msg_hdr.msg_iov = &raw mut iovecs[i];
msgs[i].msg_hdr.msg_iovlen = 1;
msgs[i].msg_hdr.msg_name = std::ptr::from_mut(&mut storages[i]).cast::<libc::c_void>();
msgs[i].msg_hdr.msg_namelen = addrlens[i];
}
loop {
let ret = unsafe {
libc::sendmmsg(
self.fd,
msgs.as_mut_ptr(),
u32::try_from(count).expect("count fits u32"),
libc::MSG_NOSIGNAL as crate::MsgFlags,
)
};
if ret < 0 {
let err = io::Error::last_os_error();
let raw = err.raw_os_error().unwrap_or(0);
if raw == libc::EINTR {
continue;
}
if raw == libc::EWOULDBLOCK || raw == libc::EAGAIN || raw == libc::ENOBUFS {
return Ok(0);
}
return Err(err);
}
return Ok(usize::try_from(ret).expect("sendmmsg return fits usize"));
}
}
}
impl AsRawFd for BatchUdpSocket {
fn as_raw_fd(&self) -> RawFd {
self.fd
}
}
pub fn set_udp_gso_segment(sock: &UdpSocket, segment_size: u16) -> io::Result<bool> {
let val: libc::c_int = libc::c_int::from(segment_size);
let ret = unsafe {
libc::setsockopt(
sock.as_raw_fd(),
libc::SOL_UDP,
libc::UDP_SEGMENT,
std::ptr::from_ref(&val).cast::<libc::c_void>(),
std::mem::size_of::<libc::c_int>() as libc::socklen_t,
)
};
if ret == 0 {
Ok(true)
} else {
let err = io::Error::last_os_error();
let raw = err.raw_os_error().unwrap_or(0);
if raw == libc::EINVAL || raw == libc::ENOPROTOOPT || raw == libc::EOPNOTSUPP {
Ok(false)
} else {
Err(err)
}
}
}
pub fn send_gso_superpacket(
sock: &UdpSocket,
payload: &[u8],
segment_size: u16,
dst: SocketAddr,
) -> io::Result<usize> {
let (mut storage, addr_len) = crate::addr::std_to_sockaddr(dst);
let mut iov = libc::iovec {
iov_base: payload.as_ptr().cast_mut().cast::<libc::c_void>(),
iov_len: payload.len(),
};
let mut control = [0u8; 64];
let mut msg: libc::msghdr = unsafe { std::mem::zeroed() };
msg.msg_name = std::ptr::from_mut(&mut storage).cast::<libc::c_void>();
msg.msg_namelen = addr_len;
msg.msg_iov = &raw mut iov;
msg.msg_iovlen = 1;
msg.msg_control = control.as_mut_ptr().cast::<libc::c_void>();
let cmsg_space =
usize::try_from(unsafe { libc::CMSG_SPACE(std::mem::size_of::<u16>() as u32) })
.unwrap_or(control.len());
msg.msg_controllen = cmsg_space as crate::CmsgLen;
unsafe {
let cmsg = libc::CMSG_FIRSTHDR(&raw const msg);
if !cmsg.is_null() {
(*cmsg).cmsg_level = libc::SOL_UDP;
(*cmsg).cmsg_type = libc::UDP_SEGMENT;
(*cmsg).cmsg_len = usize::try_from(libc::CMSG_LEN(std::mem::size_of::<u16>() as u32))
.unwrap_or(0) as crate::CmsgLen;
let seg_ptr = libc::CMSG_DATA(cmsg).cast::<u16>();
*seg_ptr = segment_size;
}
}
loop {
let ret = unsafe { libc::sendmsg(sock.as_raw_fd(), &raw const msg, libc::MSG_NOSIGNAL) };
if ret < 0 {
let err = io::Error::last_os_error();
let raw = err.raw_os_error().unwrap_or(0);
if raw == libc::EINTR {
continue;
}
if raw == libc::EWOULDBLOCK || raw == libc::EAGAIN || raw == libc::ENOBUFS {
return Ok(0);
}
return Err(err);
}
return Ok(usize::try_from(ret).unwrap_or(0));
}
}