#![allow(clippy::module_name_repetitions)]
use std::alloc::{Layout, alloc_zeroed, dealloc};
use std::mem::ManuallyDrop;
use std::ptr::NonNull;
use std::sync::atomic::{AtomicU16, Ordering};
use io_uring::types::BufRingEntry;
use thiserror::Error;
use super::uring::{Proactor, ProactorError};
const PAGE_SIZE: usize = 4096;
#[derive(Debug, Error)]
pub enum BufferRingError {
#[error("entries ({0}) must be a non-zero power of two ≤ 32768")]
InvalidEntryCount(u16),
#[error("buf_size ({0}) must be > 0")]
InvalidBufSize(u32),
#[error("ring memory layout failed")]
Layout,
#[error("ring memory allocation failed")]
AllocFailed,
#[error("proactor: {0}")]
Proactor(#[from] ProactorError),
}
pub struct BufferRing {
ring_mem: NonNull<BufRingEntry>,
ring_layout: Layout,
buf_storage: ManuallyDrop<Box<[u8]>>,
entries: u16,
buf_size: u32,
bgid: u16,
local_tail: u16,
unregistered: bool,
}
unsafe impl Send for BufferRing {}
impl std::fmt::Debug for BufferRing {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BufferRing")
.field("bgid", &self.bgid)
.field("entries", &self.entries)
.field("buf_size", &self.buf_size)
.field("local_tail", &self.local_tail)
.field("unregistered", &self.unregistered)
.finish()
}
}
impl BufferRing {
pub fn new(
proactor: &mut Proactor,
bgid: u16,
entries: u16,
buf_size: u32,
) -> Result<Self, BufferRingError> {
if entries == 0 || !entries.is_power_of_two() {
return Err(BufferRingError::InvalidEntryCount(entries));
}
if buf_size == 0 {
return Err(BufferRingError::InvalidBufSize(buf_size));
}
let ring_bytes = usize::from(entries) * std::mem::size_of::<BufRingEntry>();
let ring_layout =
Layout::from_size_align(ring_bytes, PAGE_SIZE).map_err(|_| BufferRingError::Layout)?;
let raw = unsafe { alloc_zeroed(ring_layout) };
#[allow(clippy::cast_ptr_alignment)]
let ring_mem =
NonNull::new(raw.cast::<BufRingEntry>()).ok_or(BufferRingError::AllocFailed)?;
let buf_storage: ManuallyDrop<Box<[u8]>> = ManuallyDrop::new(
vec![0_u8; usize::from(entries) * buf_size as usize].into_boxed_slice(),
);
let buf_base = buf_storage.as_ptr() as u64;
for i in 0..entries {
let entry = unsafe { &mut *ring_mem.as_ptr().add(usize::from(i)) };
entry.set_addr(buf_base + u64::from(i) * u64::from(buf_size));
entry.set_len(buf_size);
entry.set_bid(i);
}
let tail_ptr = unsafe { BufRingEntry::tail(ring_mem.as_ptr()) }.cast_mut();
let tail_atomic = unsafe { AtomicU16::from_ptr(tail_ptr) };
tail_atomic.store(entries, Ordering::Release);
unsafe {
proactor.register_buf_ring(ring_mem.as_ptr().cast::<u8>(), entries, bgid)?;
}
Ok(Self {
ring_mem,
ring_layout,
buf_storage,
entries,
buf_size,
bgid,
local_tail: entries,
unregistered: false,
})
}
#[inline]
#[must_use]
pub const fn bgid(&self) -> u16 {
self.bgid
}
#[inline]
#[must_use]
pub const fn entries(&self) -> u16 {
self.entries
}
#[inline]
#[must_use]
pub const fn buf_size(&self) -> u32 {
self.buf_size
}
#[must_use]
pub fn buffer(&self, bid: u16) -> &[u8] {
assert!(
bid < self.entries,
"bid {bid} out of range {}",
self.entries
);
let offset = usize::from(bid) * self.buf_size as usize;
unsafe {
self.buf_storage
.get_unchecked(offset..offset + self.buf_size as usize)
}
}
pub fn recycle(&mut self, bid: u16) {
assert!(
bid < self.entries,
"BufferRing::recycle: bid {bid} out of range (entries = {})",
self.entries
);
let mask = self.entries - 1;
let slot = self.local_tail & mask;
let buf_base = self.buf_storage.as_ptr() as u64;
let new_addr = buf_base + u64::from(bid) * u64::from(self.buf_size);
{
let entry = unsafe { &mut *self.ring_mem.as_ptr().add(usize::from(slot)) };
entry.set_addr(new_addr);
entry.set_len(self.buf_size);
entry.set_bid(bid);
}
self.local_tail = self.local_tail.wrapping_add(1);
let tail_ptr = unsafe { BufRingEntry::tail(self.ring_mem.as_ptr()) }.cast_mut();
unsafe { AtomicU16::from_ptr(tail_ptr) }.store(self.local_tail, Ordering::Release);
}
pub fn unregister(&mut self, proactor: &mut Proactor) -> Result<(), BufferRingError> {
if self.unregistered {
return Ok(());
}
proactor.unregister_buf_ring(self.bgid)?;
self.unregistered = true;
Ok(())
}
}
impl Drop for BufferRing {
fn drop(&mut self) {
debug_assert!(
self.unregistered,
"BufferRing dropped without unregister() —— kernel 仍持着已释放内存的地址"
);
if !self.unregistered {
tracing::warn!(
bgid = self.bgid,
"BufferRing dropped without unregister; leaking ring + buf_storage to avoid kernel UAF"
);
return;
}
unsafe {
ManuallyDrop::drop(&mut self.buf_storage);
}
unsafe {
dealloc(self.ring_mem.as_ptr().cast::<u8>(), self.ring_layout);
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)]
mod tests {
use super::*;
use crate::proactor::{
Domain, OpKind, ProactorConfig, SockAddr, SqeFlags, TcpSocket, UserData,
};
use std::io::Write;
use std::net::{Ipv4Addr, SocketAddrV4, TcpListener};
use std::thread;
#[test]
fn multishot_recv_roundtrip() {
let listener = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0)).unwrap();
let local_addr = listener.local_addr().unwrap();
let server = thread::spawn(move || {
let (mut s, _) = listener.accept().unwrap();
s.set_nodelay(true).unwrap();
s.write_all(b"hello").unwrap();
thread::sleep(std::time::Duration::from_millis(50));
s.write_all(b"world").unwrap();
});
let sock_addr = SockAddr::from_std(local_addr);
let socket = TcpSocket::new(Domain::V4).unwrap();
socket.set_nodelay(true).unwrap();
let fd = socket.as_raw_fd();
let mut proactor = Proactor::new(ProactorConfig::default()).unwrap();
unsafe {
proactor
.submit_connect(
fd,
&sock_addr,
UserData::new(OpKind::Connect, 0),
SqeFlags::NONE,
)
.unwrap();
}
proactor.submit_and_wait(1).unwrap();
let mut n = 0_usize;
proactor.drain_completions(|c| {
assert!(c.to_result().is_ok());
n += 1;
});
assert_eq!(n, 1);
let mut ring = BufferRing::new(
&mut proactor,
1,
4,
256,
)
.expect("BufferRing");
unsafe {
proactor
.submit_recv_multishot(fd, ring.bgid(), UserData::new(OpKind::Recv, 0))
.unwrap();
}
let mut got_hello = false;
let mut got_world = false;
let mut multishot_ended = false;
let mut bids_to_recycle: Vec<u16> = Vec::new();
for _ in 0..10 {
if multishot_ended {
break;
}
proactor.submit_and_wait(1).unwrap();
proactor.drain_completions(|c| {
assert_eq!(c.user_data.kind(), Some(OpKind::Recv));
if let Some(bid) = c.buffer_id() {
let n = usize::try_from(c.result).unwrap();
let bytes = &ring.buffer(bid)[..n];
if bytes == b"hello" {
got_hello = true;
} else if bytes == b"world" {
got_world = true;
}
bids_to_recycle.push(bid);
} else {
assert!(
!c.has_more(),
"control CQE 没 buffer_id 通常意味着 multishot 结束"
);
multishot_ended = true;
}
});
for bid in bids_to_recycle.drain(..) {
ring.recycle(bid);
}
}
assert!(got_hello, "should have received \"hello\"");
assert!(got_world, "should have received \"world\"");
ring.unregister(&mut proactor).unwrap();
server.join().unwrap();
}
#[test]
fn invalid_entry_count_rejected() {
let mut proactor = Proactor::new(ProactorConfig::default()).unwrap();
assert!(matches!(
BufferRing::new(&mut proactor, 0, 0, 4096),
Err(BufferRingError::InvalidEntryCount(0))
));
assert!(matches!(
BufferRing::new(&mut proactor, 0, 3, 4096),
Err(BufferRingError::InvalidEntryCount(3))
));
}
#[test]
fn invalid_buf_size_rejected() {
let mut proactor = Proactor::new(ProactorConfig::default()).unwrap();
assert!(matches!(
BufferRing::new(&mut proactor, 0, 4, 0),
Err(BufferRingError::InvalidBufSize(0))
));
}
}