Skip to main content

wireshift_uring/
capabilities.rs

1//! Kernel capability probing for the native `io_uring` backend.
2
3use std::os::fd::AsRawFd;
4use std::sync::OnceLock;
5use io_uring::IoUring;
6
7/// Supported `io_uring` opcodes on the current kernel.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9#[allow(clippy::struct_excessive_bools)]
10pub struct UringCapabilities {
11    /// Native `IORING_OP_READ`.
12    pub read: bool,
13    /// Native `IORING_OP_WRITE`.
14    pub write: bool,
15    /// Native `IORING_OP_FSYNC`.
16    pub fsync: bool,
17    /// Native `IORING_OP_POLL_ADD`.
18    pub poll_add: bool,
19    /// Native `IORING_OP_ACCEPT`.
20    pub accept: bool,
21    /// Native `IORING_OP_OPENAT`.
22    pub openat: bool,
23    /// Native `IORING_OP_CLOSE`.
24    pub close: bool,
25    /// Native `IORING_OP_PROVIDE_BUFFERS`.
26    pub provide_buffers: bool,
27    /// Native `IORING_OP_SEND_ZC`.
28    pub send_zc: bool,
29}
30
31static CAPABILITIES: OnceLock<UringCapabilities> = OnceLock::new();
32
33const PROBE_OPS: usize = 64;
34const IO_URING_OP_SUPPORTED: u16 = 1;
35const IORING_REGISTER_PROBE: libc::c_uint = 8;
36const IORING_OP_POLL_ADD: u32 = 6;
37const IORING_OP_ACCEPT: u32 = 13;
38const IORING_OP_OPENAT: u32 = 18;
39const IORING_OP_CLOSE: u32 = 19;
40const IORING_OP_READ: u32 = 22;
41const IORING_OP_WRITE: u32 = 23;
42const IORING_OP_PROVIDE_BUFFERS: u32 = 31;
43const IORING_OP_SEND_ZC: u32 = 47;
44const IORING_OP_FSYNC: u32 = 3;
45
46/// Probes kernel `io_uring` support and returns the opcodes relevant to
47/// `wireshift`.
48#[must_use]
49pub fn probe_capabilities() -> UringCapabilities {
50    *CAPABILITIES.get_or_init(|| {
51        let Ok(ring) = IoUring::new(8) else {
52            return UringCapabilities::default();
53        };
54
55        let mut bytes = vec![0u8; 1024];
56
57        let rc = unsafe {
58            libc::syscall(
59                libc::SYS_io_uring_register,
60                ring.as_raw_fd(),
61                IORING_REGISTER_PROBE,
62                bytes.as_mut_ptr() as *mut libc::c_void,
63                64 as libc::c_uint,
64            )
65        };
66        if rc < 0 {
67            return UringCapabilities::default();
68        }
69
70        let last = usize::from(bytes[0]);
71        let ops_len = usize::from(bytes[1]).min(PROBE_OPS);
72        let supported = |opcode: u32| -> bool {
73            let opcode = opcode as usize;
74            if opcode >= ops_len || opcode > last {
75                return false;
76            }
77            let offset = 8 + opcode * 8 + 2;
78            let flags = u16::from_ne_bytes([bytes[offset], bytes[offset + 1]]);
79            (flags & IO_URING_OP_SUPPORTED) != 0
80        };
81
82        UringCapabilities {
83            read: supported(IORING_OP_READ),
84            write: supported(IORING_OP_WRITE),
85            fsync: supported(IORING_OP_FSYNC),
86            poll_add: supported(IORING_OP_POLL_ADD),
87            accept: supported(IORING_OP_ACCEPT),
88            openat: supported(IORING_OP_OPENAT),
89            close: supported(IORING_OP_CLOSE),
90            provide_buffers: supported(IORING_OP_PROVIDE_BUFFERS),
91            send_zc: supported(IORING_OP_SEND_ZC),
92        }
93    })
94}