1use std::collections::HashMap;
12use std::io;
13use std::net::SocketAddr;
14use std::os::fd::{IntoRawFd, RawFd};
15use std::path::Path;
16use std::time::Duration;
17
18use io_uring::types::{Fd, SubmitArgs, Timespec};
19
20use super::{Cqe, Engine, Poll};
21use crate::buffer::BufferPool;
22use crate::splice;
23use crate::token::Token;
24
25struct Listener {
27 fd: RawFd,
28 #[allow(dead_code)] token: Token,
30 sa: Box<[u8; 128]>,
32 sa_len: Box<libc::socklen_t>,
33}
34
35pub struct UringEngine {
37 ring: io_uring::IoUring,
38 slot_bases: Vec<*mut u8>,
40 buf_size: usize,
41 listeners: HashMap<u64, Listener>,
42 splice_dirs: HashMap<u64, (RawFd, RawFd)>,
44 accepted: HashMap<RawFd, SocketAddr>,
46 connect_addrs: HashMap<u64, Box<ConnectAddr>>,
51}
52
53struct ConnectAddr {
55 storage: libc::sockaddr_storage,
56 len: libc::socklen_t,
57}
58
59unsafe fn connect_addr_boxed(
66 addr: SocketAddr,
67) -> (Box<ConnectAddr>, *const libc::sockaddr, libc::socklen_t) {
68 let mut storage: libc::sockaddr_storage = unsafe { std::mem::zeroed() };
70 let len = match addr {
71 SocketAddr::V4(v4) => {
72 let sa: &mut libc::sockaddr_in =
74 unsafe { &mut *std::ptr::addr_of_mut!(storage).cast::<libc::sockaddr_in>() };
75 sa.sin_family = libc::AF_INET as _;
76 sa.sin_port = v4.port().to_be();
77 sa.sin_addr.s_addr = u32::from_ne_bytes(v4.ip().octets());
78 std::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t
79 }
80 SocketAddr::V6(v6) => {
81 let sa: &mut libc::sockaddr_in6 =
83 unsafe { &mut *std::ptr::addr_of_mut!(storage).cast::<libc::sockaddr_in6>() };
84 sa.sin6_family = libc::AF_INET6 as _;
85 sa.sin6_port = v6.port().to_be();
86 sa.sin6_addr.s6_addr = v6.ip().octets();
87 std::mem::size_of::<libc::sockaddr_in6>() as libc::socklen_t
88 }
89 };
90 let boxed = Box::new(ConnectAddr { storage, len });
91 let ptr = std::ptr::addr_of!(boxed.storage).cast::<libc::sockaddr>();
95 let out_len = boxed.len;
96 (boxed, ptr, out_len)
97}
98
99unsafe impl Send for UringEngine {}
101
102impl UringEngine {
103 pub fn new(entries: u32, pool: Option<&BufferPool>, sqpoll: bool) -> io::Result<Self> {
109 let ring = if sqpoll {
110 io_uring::IoUring::builder()
111 .setup_sqpoll(2_000)
112 .build(entries)?
113 } else {
114 io_uring::IoUring::new(entries)?
115 };
116 let (slot_bases, buf_size) = match pool {
117 Some(pool) => {
118 let bases = (0..pool.capacity() as u32)
120 .map(|i| pool.slot(i).as_ptr() as *mut u8)
121 .collect::<Vec<_>>();
122 let iovecs: Vec<libc::iovec> = bases
123 .iter()
124 .map(|p| libc::iovec {
125 iov_base: (*p).cast(),
127 iov_len: pool.buf_size(),
128 })
129 .collect();
130 unsafe {
132 ring.submitter().register_buffers(&iovecs)?;
133 }
134 (bases, pool.buf_size())
135 }
136 None => (Vec::new(), 0),
137 };
138 Ok(Self {
139 ring,
140 slot_bases,
141 buf_size,
142 listeners: HashMap::new(),
143 splice_dirs: HashMap::new(),
144 accepted: HashMap::new(),
145 connect_addrs: HashMap::new(),
146 })
147 }
148
149 fn push(&mut self, entry: io_uring::squeue::Entry, token: Token) {
155 let entry = entry.user_data(token.bits());
156 loop {
157 unsafe {
159 if self.ring.submission().push(&entry).is_ok() {
160 return;
161 }
162 }
163 let _ = self.ring.submit();
165 std::hint::spin_loop();
166 }
167 }
168
169 fn slot_ptr(&self, slot: u32) -> *mut u8 {
170 self.slot_bases[slot as usize]
171 }
172
173 fn arm_accept(&mut self, bits: u64) {
174 let Some(l) = self.listeners.get_mut(&bits) else {
175 return;
176 };
177 let fd = l.fd;
178 let sa_ptr = l.sa.as_mut_ptr();
179 let len_ptr: *mut libc::socklen_t = &mut *l.sa_len;
180 let entry = io_uring::opcode::Accept::new(Fd(fd), sa_ptr.cast(), len_ptr)
183 .flags(libc::SOCK_NONBLOCK | libc::SOCK_CLOEXEC)
184 .build();
185 self.push(entry, Token::from_bits(bits));
186 }
187
188 fn arm_readiness(&mut self, fd: RawFd, token: Token) {
189 let entry = io_uring::opcode::PollAdd::new(Fd(fd), libc::POLLIN as u32)
192 .multi(true)
193 .build();
194 self.push(entry, token);
195 }
196}
197
198impl Engine for UringEngine {
199 fn kind(&self) -> &'static str {
200 "io_uring"
201 }
202
203 fn add_listener(&mut self, fd: RawFd, token: Token) -> io::Result<()> {
204 let bits = token.bits();
205 self.listeners.insert(
206 bits,
207 Listener {
208 fd,
209 token,
210 sa: Box::new([0u8; 128]),
211 sa_len: Box::new(128),
212 },
213 );
214 self.arm_accept(bits);
215 Ok(())
216 }
217
218 fn add_stream(&mut self, _fd: RawFd, _token: Token) -> io::Result<()> {
219 Ok(())
222 }
223
224 fn read(&mut self, token: Token, fd: RawFd, slot: u32) -> io::Result<Poll> {
225 let ptr = self.slot_ptr(slot);
226 let entry =
230 io_uring::opcode::ReadFixed::new(Fd(fd), ptr, self.buf_size as u32, slot as u16)
231 .offset(0)
232 .build();
233 self.push(entry, token);
234 Ok(Poll::Pending)
235 }
236
237 fn write(
238 &mut self,
239 token: Token,
240 fd: RawFd,
241 slot: u32,
242 len: usize,
243 offset: usize,
244 ) -> io::Result<Poll> {
245 let ptr = self.slot_ptr(slot);
246 let entry = unsafe {
249 io_uring::opcode::WriteFixed::new(
250 Fd(fd),
251 ptr.add(offset),
252 (len - offset) as u32,
253 slot as u16,
254 )
255 .offset(0)
256 .build()
257 };
258 self.push(entry, token);
259 Ok(Poll::Pending)
260 }
261
262 fn connect(&mut self, token: Token, addr: SocketAddr) -> io::Result<(RawFd, Poll)> {
263 let domain = if addr.is_ipv4() {
264 socket2::Domain::IPV4
265 } else {
266 socket2::Domain::IPV6
267 };
268 let sock =
269 socket2::Socket::new(domain, socket2::Type::STREAM, Some(socket2::Protocol::TCP))?;
270 sock.set_nonblocking(true)?;
271 sock.set_tcp_nodelay(true)?;
272 let fd = sock.into_raw_fd();
273 let (owned, ptr, len) = unsafe { connect_addr_boxed(addr) };
277 let entry = io_uring::opcode::Connect::new(Fd(fd), ptr, len).build();
278 self.connect_addrs.insert(token.bits(), owned);
279 self.push(entry, token);
280 Ok((fd, Poll::Pending))
281 }
282
283 fn connect_unix(&mut self, token: Token, path: &Path) -> io::Result<(RawFd, Poll)> {
284 let sock = socket2::Socket::new(socket2::Domain::UNIX, socket2::Type::STREAM, None)?;
285 sock.set_nonblocking(true)?;
286 let fd = sock.into_raw_fd();
287 let sa = socket2::SockAddr::unix(path)?;
288 let mut storage: libc::sockaddr_storage = unsafe { std::mem::zeroed() };
291 let bytes = sa.as_ptr().cast::<u8>();
292 let copy_len = (sa.len() as usize).min(std::mem::size_of::<libc::sockaddr_storage>());
293 unsafe {
295 std::ptr::copy_nonoverlapping(bytes, std::ptr::addr_of_mut!(storage).cast(), copy_len)
296 };
297 let len = sa.len();
298 let owned = Box::new(ConnectAddr { storage, len });
299 let ptr = std::ptr::addr_of!(owned.storage).cast::<libc::sockaddr>();
301 let entry = io_uring::opcode::Connect::new(Fd(fd), ptr, len).build();
302 self.connect_addrs.insert(token.bits(), owned);
303 self.push(entry, token);
304 Ok((fd, Poll::Pending))
305 }
306
307 fn accept(&mut self, _lfd: RawFd, ltoken: Token) -> io::Result<Option<(RawFd, SocketAddr)>> {
308 let keys: Vec<RawFd> = self.accepted.keys().copied().collect();
310 if let Some(fd) = keys.into_iter().next() {
311 let addr = self.accepted.remove(&fd).expect("just listed");
312 return Ok(Some((fd, addr)));
313 }
314 let _ = ltoken;
319 Ok(None)
320 }
321
322 fn splice_pump(&mut self, a: Token, afd: i32, b: Token, bfd: i32) -> io::Result<()> {
323 debug_assert_ne!(a.bits(), b.bits(), "splice directions need distinct tokens");
326 self.splice_dirs.insert(a.bits(), (afd, bfd));
327 self.splice_dirs.insert(b.bits(), (bfd, afd));
328 self.arm_readiness(afd, a);
329 self.arm_readiness(bfd, b);
330 Ok(())
331 }
332
333 fn remove(&mut self, fd: RawFd) {
334 self.accepted.remove(&fd);
335 }
336
337 fn poll(&mut self, timeout: Option<Duration>, out: &mut Vec<Cqe>) -> io::Result<()> {
338 self.ring.submit()?;
340
341 match timeout {
342 None => match self.ring.submit_and_wait(1) {
343 Ok(_) => {}
344 Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
345 Err(e) => return Err(e),
346 },
347 Some(d) => {
348 let ms = d.as_millis().min(60_000) as u64;
350 let ts = Timespec::new()
351 .sec(ms / 1_000)
352 .nsec((ms % 1_000) as u32 * 1_000_000);
353 let args = SubmitArgs::new().timespec(&ts);
354 match self.ring.submitter().submit_with_args(1, &args) {
355 Ok(_) => {}
356 Err(e) if e.raw_os_error() == Some(libc::ETIME) => return Ok(()),
357 Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
358 Err(_) => {
359 self.ring.submit_and_wait(0)?;
361 }
362 }
363 }
364 }
365
366 let mut accept_hits: Vec<(Token, RawFd)> = Vec::new();
368 for cqe in self.ring.completion() {
369 let token = Token::from_bits(cqe.user_data());
370 let raw = cqe.result();
371 let result = if raw >= 0 {
372 Ok(raw as u32)
373 } else {
374 Err(io::Error::from_raw_os_error(-raw))
375 };
376 if token.op() == crate::token::Op::Connect {
379 self.connect_addrs.remove(&token.bits());
380 }
381 match token.op() {
382 crate::token::Op::Accept => {
383 if raw >= 0 {
384 accept_hits.push((token, raw as RawFd));
385 } else if -raw != libc::ECANCELED {
386 out.push(Cqe { token, result });
387 }
388 }
389 crate::token::Op::Splice => {
390 if raw < 0 {
391 if -raw != libc::ECANCELED {
392 out.push(Cqe { token, result });
393 }
394 } else if let Some(&(from, to)) = self.splice_dirs.get(&token.bits()) {
395 match splice::pump(from, to, 1 << 20) {
396 splice::PumpResult::Moved(n) => {
397 out.push(Cqe {
398 token,
399 result: Ok(n as u32),
400 });
401 }
402 splice::PumpResult::Eof => {
403 out.push(Cqe {
404 token,
405 result: Ok(0),
406 });
407 }
408 splice::PumpResult::WouldBlock => {}
409 splice::PumpResult::Err(code) => out.push(Cqe {
410 token,
411 result: Err(io::Error::from_raw_os_error(code)),
412 }),
413 }
414 }
415 }
416 _ => out.push(Cqe { token, result }),
417 }
418 }
419
420 for (token, fd) in accept_hits {
422 let bits = token.bits();
423 let addr = self.listeners.get(&bits).map_or_else(
424 || SocketAddr::from(([0, 0, 0, 0], 0)),
425 |l| parse_sockaddr(&l.sa),
426 );
427 self.accepted.insert(fd, addr);
428 self.arm_accept(bits);
429 }
430 Ok(())
431 }
432
433 fn take_accepted(&mut self, fd: RawFd) -> Option<SocketAddr> {
434 self.accepted.remove(&fd)
435 }
436}
437
438fn parse_sockaddr(buf: &[u8; 128]) -> SocketAddr {
439 let sa: &libc::sockaddr_storage = unsafe { &*buf.as_ptr().cast() };
441 match sa.ss_family as i32 {
442 libc::AF_INET => {
443 let a: &libc::sockaddr_in =
445 unsafe { &*(sa as *const libc::sockaddr_storage).cast::<libc::sockaddr_in>() };
446 SocketAddr::from((
447 std::net::Ipv4Addr::from(u32::from_be(a.sin_addr.s_addr)),
448 u16::from_be(a.sin_port),
449 ))
450 }
451 _ => {
452 let a: &libc::sockaddr_in6 =
454 unsafe { &*(sa as *const libc::sockaddr_storage).cast::<libc::sockaddr_in6>() };
455 SocketAddr::from((
456 std::net::Ipv6Addr::from(a.sin6_addr.s6_addr),
457 u16::from_be(a.sin6_port),
458 ))
459 }
460 }
461}
462
463#[cfg(test)]
464mod fault_tests {
465 use super::*;
466 use crate::buffer::DEFAULT_BUF_SIZE;
467 use crate::token::Op;
468
469 fn test_engine() -> (BufferPool, UringEngine) {
470 let pool = BufferPool::new(8, DEFAULT_BUF_SIZE).expect("pool");
471 let engine = UringEngine::new(64, Some(&pool), false).expect("uring available");
472 (pool, engine)
473 }
474
475 fn tok(op: Op) -> Token {
476 Token::new(op, 0, 0, 0)
477 }
478
479 fn sockpair() -> (RawFd, RawFd) {
480 let mut fds = [0 as RawFd; 2];
481 let rc = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
483 assert_eq!(rc, 0);
484 for fd in fds {
485 let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
487 unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) };
489 }
490 (fds[0], fds[1])
491 }
492
493 fn close(fd: RawFd) {
494 unsafe { libc::close(fd) };
496 }
497
498 fn drain(engine: &mut UringEngine, secs: u64) -> Vec<Cqe> {
499 let mut out = Vec::new();
500 engine
501 .poll(Some(std::time::Duration::from_secs(secs)), &mut out)
502 .expect("poll");
503 out
504 }
505
506 fn drain_until(engine: &mut UringEngine, mut pred: impl FnMut(&Cqe) -> bool) -> Vec<Cqe> {
509 let mut all = Vec::new();
510 for _ in 0..60 {
511 let mut out = Vec::new();
512 engine
513 .poll(Some(std::time::Duration::from_millis(100)), &mut out)
514 .expect("poll");
515 if out.iter().any(&mut pred) {
516 all.extend(out);
517 return all;
518 }
519 all.extend(out);
520 }
521 all
522 }
523
524 #[test]
525 fn write_then_read_roundtrip() {
526 let (_pool, mut engine) = test_engine();
527 let (a, b) = sockpair();
528 let wt = tok(Op::DownstreamWrite);
529 assert!(matches!(
530 engine.write(wt, a, 0, 32, 0).expect("write"),
531 Poll::Pending
532 ));
533 let cqes = drain(&mut engine, 2);
534 assert!(
535 cqes.iter().any(|c| c.token == wt && c.result.is_ok()),
536 "write CQE missing: {cqes:?}"
537 );
538 let rt = tok(Op::DownstreamRead);
540 assert!(matches!(
541 engine.read(rt, b, 1).expect("read"),
542 Poll::Pending
543 ));
544 let cqes = drain(&mut engine, 2);
545 let got = cqes.iter().find(|c| c.token == rt).expect("read CQE");
546 assert!(matches!(got.result, Ok(32)));
547 close(a);
548 close(b);
549 }
550
551 #[test]
552 fn connect_refused_completes_with_error() {
553 let (_pool, mut engine) = test_engine();
554 let addr: SocketAddr = "127.0.0.1:1".parse().expect("addr");
555 let t = tok(Op::Connect);
556 let (fd, poll) = engine.connect(t, addr).expect("connect issued");
557 match poll {
558 Poll::Done(_) => {}
559 Poll::Pending => {
560 let cqes = drain_until(&mut engine, |c| c.token == t);
561 assert!(
562 cqes.iter().any(|c| c.token == t && c.result.is_err()),
563 "refused connect must error: {cqes:?}"
564 );
565 }
566 }
567 close(fd);
568 }
569
570 #[test]
571 fn connect_unix_missing_path_errors() {
572 let (_pool, mut engine) = test_engine();
573 let t = tok(Op::Connect);
574 let dir = tempfile::tempdir().expect("dir");
575 let missing = dir.path().join("no.sock");
576 let res = engine.connect_unix(t, &missing);
577 assert!(res.is_err() || matches!(res, Ok((_, Poll::Pending))));
578 }
579
580 #[test]
581 fn accept_flow_materializes_connection() {
582 let (_pool, mut engine) = test_engine();
583 let listener =
584 crate::tcp_listener("127.0.0.1:0".parse().expect("addr"), true, 64).expect("bind");
585 let lfd = std::os::fd::AsRawFd::as_raw_fd(&listener);
586 engine.add_listener(lfd, Token::accept(0)).expect("add");
587 assert!(
589 engine
590 .accept(lfd, Token::accept(0))
591 .expect("accept")
592 .is_none()
593 );
594 let addr = listener.local_addr().expect("addr");
595 let _client = std::net::TcpStream::connect(addr).expect("connect");
596 let mut got = None;
599 for _ in 0..60 {
600 let mut out = Vec::new();
601 engine
602 .poll(Some(std::time::Duration::from_millis(100)), &mut out)
603 .expect("poll");
604 got = engine.accept(lfd, Token::accept(0)).expect("accept2");
605 if got.is_some() {
606 break;
607 }
608 }
609 assert!(got.is_some(), "materialized connection expected");
611 let (fd, peer) = got.expect("some");
612 assert!(peer.port() != 0);
613 close(fd);
614 }
615
616 #[test]
617 fn splice_moved_and_eof_paths() {
618 let (_pool, mut engine) = test_engine();
619 let mut fds = [0 as RawFd; 2];
620 assert_eq!(
622 unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_NONBLOCK | libc::O_CLOEXEC) },
624 0
625 );
626 let (pr, pw) = (fds[0], fds[1]);
627 let payload = b"uring-splice";
628 unsafe { libc::write(pw, payload.as_ptr().cast(), payload.len()) };
630 let (sa, sb) = sockpair();
631 let t = tok(Op::Splice);
632 let t_rev = Token::new(Op::Splice, 0, 0, 1);
633 engine.splice_pump(t, pr, t_rev, sb).expect("splice_pump");
634 let cqes = drain_until(&mut engine, |c| c.token == t);
635 assert!(
636 cqes.iter()
637 .any(|c| c.token == t && matches!(c.result, Ok(n) if n as usize == payload.len())),
638 "splice moved CQE: {cqes:?}"
639 );
640 let mut buf = [0u8; 64];
642 let n = unsafe { libc::read(sa, buf.as_mut_ptr().cast(), 64) };
644 assert_eq!(&buf[..n as usize], payload);
645 close(pw); let t2 = Token::new(Op::Splice, 1, 0, 0);
647 let t2_rev = Token::new(Op::Splice, 1, 0, 1);
648 engine
649 .splice_pump(t2, pr, t2_rev, sb)
650 .expect("splice_pump2");
651 let cqes = drain_until(&mut engine, |c| c.token == t2);
652 assert!(
653 cqes.iter()
654 .any(|c| c.token == t2 && matches!(c.result, Ok(0))),
655 "splice EOF CQE: {cqes:?}"
656 );
657 close(pr);
658 close(sa);
659 close(sb);
660 }
661
662 #[test]
663 fn remove_clears_tracked_fd() {
664 let (_pool, mut engine) = test_engine();
665 let (a, b) = sockpair();
666 engine
667 .add_stream(a, tok(Op::DownstreamRead))
668 .expect("add_stream");
669 engine.remove(a);
670 close(a);
672 close(b);
673 }
674}