xdp_socket/ring.rs
1//! # AF_XDP Ring Buffer Management
2//!
3//! ## Purpose
4//!
5//! This file defines the core data structures and logic for managing the various
6//! ring buffers used in AF_XDP sockets. These rings are the primary mechanism for
7//! communication between the userspace application and the kernel.
8//!
9//! ## How it works
10//!
11//! It provides a generic `Ring<T>` struct that encapsulates a memory-mapped ring buffer.
12//! This struct provides methods for atomically accessing and updating the producer and
13//! consumer indices, accessing descriptors, and checking ring flags. It also defines
14//! the `XdpDesc` struct for packet descriptors. The `RingType` enum helps manage the
15//! specifics of the four different rings (TX, RX, Fill, Completion), such as their
16//! memory map offsets and socket option names.
17//!
18//! ## Main components
19//!
20//! - `Ring<T>`: A generic struct representing a shared memory ring buffer.
21//! - `RingMmap<T>`: A struct holding the raw memory-mapped components of a ring.
22//! - `XdpDesc`: The descriptor structure for packets in the TX and RX rings, containing
23//! address, length, and options.
24//! - `RingType`: An enum to differentiate between ring types and handle their specific
25//! setup requirements.
26
27use crate::mmap::OwnedMmap;
28use std::sync::atomic::AtomicU32;
29use std::{io, mem::size_of, ptr, slice};
30
31/// The size of a single frame in the UMEM, typically 2KB or 4KB.
32pub const FRAME_SIZE: usize = 2048;
33/// The default number of frames to allocate for the UMEM.
34pub const FRAME_COUNT: usize = 4096;
35
36/// Holds the raw memory-mapped components of a ring buffer.
37///
38/// This struct contains raw pointers to the producer/consumer indices, the descriptor
39/// array, and flags within the memory-mapped region. It is managed by the `Ring` struct.
40pub struct RingMmap<T> {
41 /// The memory-mapped region owned by this struct.
42 pub mmap: OwnedMmap,
43 /// A pointer to the atomic producer index of the ring.
44 pub producer: *mut AtomicU32,
45 /// A pointer to the atomic consumer index of the ring.
46 pub consumer: *mut AtomicU32,
47 /// A pointer to the beginning of the descriptor array.
48 pub desc: *mut T,
49 /// A pointer to the atomic flags field of the ring.
50 pub flags: *mut AtomicU32,
51}
52
53
54impl<T> Default for RingMmap<T> {
55 fn default() -> Self {
56 RingMmap {
57 mmap: OwnedMmap(ptr::null_mut(), 0),
58 producer: ptr::null_mut(),
59 consumer: ptr::null_mut(),
60 desc: ptr::null_mut(),
61 flags: ptr::null_mut(),
62 }
63 }
64}
65
66/// An XDP descriptor, used in the TX and RX rings.
67///
68/// This struct corresponds to `struct xdp_desc` in the kernel and describes a
69/// single packet buffer in the UMEM.
70#[repr(C)]
71#[derive(Debug, Clone, Copy, Default)]
72pub struct XdpDesc {
73 /// The address of the packet data within the UMEM.
74 pub addr: u64,
75 /// The length of the packet data.
76 pub len: u32,
77 /// Options for the descriptor, currently unused.
78 pub options: u32,
79}
80
81impl XdpDesc {
82 /// Creates a new `XdpDesc`.
83 pub fn new(addr: u64, len: u32, options: u32) -> Self {
84 XdpDesc { addr, len, options }
85 }
86}
87
88/// A generic, safe wrapper for an AF_XDP ring buffer.
89///
90/// This struct provides safe methods to interact with a memory-mapped ring,
91/// handling atomic operations for producer/consumer indices and access to descriptors.
92#[derive(Default)]
93pub struct Ring<T> {
94 /// The memory-mapped components of the ring.
95 pub mmap: RingMmap<T>,
96 /// The number of descriptors the ring can hold.
97 pub len: usize,
98 /// A mask used for wrapping around the ring (len - 1).
99 pub mod_mask: u32,
100}
101
102impl<T> Ring<T>
103where
104 T: Copy,
105{
106 /// Returns the size of a single UMEM frame.
107 #[inline]
108 pub fn frame_size(&self) -> u64 {
109 FRAME_SIZE as u64
110 }
111
112 /// Memory-maps a ring from a file descriptor.
113 ///
114 /// # How it works
115 ///
116 /// This function calls the lower-level `mmap_ring` function to perform the `mmap`
117 /// syscall with the correct offsets for the given ring type. It then initializes
118 /// a `Ring` struct to manage the mapped memory.
119 pub fn mmap(
120 fd: i32,
121 len: usize,
122 ring_type: u64,
123 offsets: &libc::xdp_ring_offset,
124 ) -> Result<Self, io::Error> {
125 debug_assert!(len.is_power_of_two());
126 Ok(Ring {
127 mmap: mmap_ring(fd, len * size_of::<T>(), offsets, ring_type)?,
128 len,
129 mod_mask: len as u32 - 1,
130 })
131 }
132 /// Atomically reads the consumer index of the ring.
133 pub fn consumer(&self) -> u32 {
134 unsafe { (*self.mmap.consumer).load(std::sync::atomic::Ordering::Acquire) }
135 }
136 /// Atomically reads the producer index of the ring.
137 pub fn producer(&self) -> u32 {
138 unsafe { (*self.mmap.producer).load(std::sync::atomic::Ordering::Acquire) }
139 }
140 /// Atomically updates the producer index of the ring.
141 pub fn update_producer(&mut self, value: u32) {
142 unsafe {
143 (*self.mmap.producer).store(value, std::sync::atomic::Ordering::Release);
144 }
145 }
146 /// Atomically updates the consumer index of the ring.
147 pub fn update_consumer(&mut self, value: u32) {
148 unsafe {
149 (*self.mmap.consumer).store(value, std::sync::atomic::Ordering::Release);
150 }
151 }
152
153 /// Atomically reads the flags of the ring.
154 ///
155 /// Flags can indicate states like `XDP_RING_NEED_WAKEUP`.
156 pub fn flags(&self) -> u32 {
157 unsafe {
158 (*self.mmap.flags).load(std::sync::atomic::Ordering::Acquire)
159 }
160 }
161 /// Increments a value, wrapping it around the ring size.
162 pub fn increment(&self, value: &mut u32) -> u32 {
163 *value = (*value + 1) & (self.len - 1) as u32;
164 *value
165 }
166
167 /// Returns a mutable reference to the descriptor at a given index.
168 ///
169 /// # Panics
170 ///
171 /// This function will panic in debug builds if the index is out of bounds.
172 pub fn mut_desc_at(&mut self, index: u32) -> &mut T {
173 debug_assert!((index as usize) < self.len);
174 unsafe { &mut *self.mmap.desc.add(index as usize) }
175 }
176
177 /// Returns a copy of the descriptor at a given index.
178 ///
179 /// # Panics
180 ///
181 /// This function will panic in debug builds if the index is out of bounds.
182 pub fn desc_at(&self, index: u32) -> T {
183 debug_assert!((index as usize) < self.len);
184 unsafe { *self.mmap.desc.add(index as usize) }
185 }
186}
187
188impl Ring<u64> {
189 /// Fills the ring (typically the Fill Ring) with UMEM frame addresses.
190 ///
191 /// # Arguments
192 /// * `start_frame` - The starting frame number to begin filling from.
193 pub fn fill(&mut self, start_frame: u32) {
194 for i in 0..self.len as u32 {
195 let desc = self.mut_desc_at(i);
196 *desc = (i + start_frame) as u64 * FRAME_SIZE as u64;
197 }
198 }
199}
200
201impl Ring<XdpDesc> {
202 /// Fills the ring (typically the TX ring) with default `XdpDesc` values.
203 ///
204 /// This pre-populates the ring with descriptors pointing to corresponding
205 /// UMEM frames.
206 ///
207 /// # Arguments
208 /// * `start_frame` - The starting frame number to begin filling from.
209 pub fn fill(&mut self, start_frame: u32) {
210 for i in 0..self.len as u32 {
211 let desc = self.mut_desc_at(i);
212 *desc = XdpDesc {
213 addr: (i + start_frame) as u64 * FRAME_SIZE as u64,
214 len: 0,
215 options: 0,
216 }
217 }
218 }
219 /// Returns a mutable byte slice for a packet buffer in the UMEM.
220 ///
221 /// This function gets the descriptor at `index`, calculates the memory address
222 /// within the UMEM, and returns a mutable slice of `len` bytes. It also updates
223 /// the descriptor's length field.
224 ///
225 /// # Panics
226 ///
227 /// This function will panic in debug builds if the index or length are out of bounds.
228 pub(crate) fn mut_bytes_at(&mut self, ptr: *mut u8, index: u32, len: usize) -> &mut [u8] {
229 #[cfg(not(feature="no_safety_checks"))]
230 assert!(index < FRAME_COUNT as u32);
231 #[cfg(not(feature="no_safety_checks"))]
232 assert!((len as u32) < FRAME_SIZE as u32);
233
234 let desc = self.mut_desc_at(index);
235
236 #[cfg(not(feature="no_safety_checks"))]
237 assert!(FRAME_SIZE * FRAME_COUNT > desc.addr as usize + len);
238
239 unsafe {
240 let buf_ptr = ptr.offset(desc.addr as isize);
241 desc.len = len as u32;
242 slice::from_raw_parts_mut(buf_ptr, len)
243 }
244 }
245
246 /// Sets the descriptor at `index` to a specific length.
247 ///
248 /// The address is calculated based on the index and frame size.
249 pub fn set(&mut self, index: u32, len: u32) {
250 #[cfg(not(feature="no_safety_checks"))]
251 assert!(index < FRAME_COUNT as u32);
252 #[cfg(not(feature="no_safety_checks"))]
253 assert!(len < FRAME_SIZE as u32);
254
255 let desc = self.mut_desc_at(index);
256 *desc = XdpDesc {
257 addr: (index as u64 * FRAME_SIZE as u64),
258 len,
259 options: 0,
260 };
261 }
262}
263
264/// A low-level function to memory-map a single AF_XDP ring.
265///
266/// # How it works
267///
268/// It calculates the total size required for the mapping, including the area for
269/// producer/consumer indices and the descriptor array. It then calls `libc::mmap`
270/// with the appropriate file descriptor, size, and page offset for the given ring
271/// type. On success, it returns a `RingMmap` containing pointers to the relevant
272/// parts of the mapped region.
273pub fn mmap_ring<T>(
274 fd: i32,
275 size: usize,
276 offsets: &libc::xdp_ring_offset,
277 ring_type: u64,
278) -> Result<RingMmap<T>, io::Error> {
279 let map_size = (offsets.desc as usize).saturating_add(size);
280 let map_addr = unsafe {
281 libc::mmap(
282 ptr::null_mut(),
283 map_size,
284 libc::PROT_READ | libc::PROT_WRITE,
285 libc::MAP_SHARED | libc::MAP_POPULATE,
286 fd,
287 ring_type as i64,
288 )
289 };
290 if map_addr == libc::MAP_FAILED {
291 return Err(io::Error::last_os_error());
292 }
293 let producer = unsafe { map_addr.add(offsets.producer as usize) as *mut AtomicU32 };
294 let consumer = unsafe { map_addr.add(offsets.consumer as usize) as *mut AtomicU32 };
295 let desc = unsafe { map_addr.add(offsets.desc as usize) as *mut T };
296 let flags = unsafe { map_addr.add(offsets.flags as usize) as *mut AtomicU32 };
297 Ok(RingMmap {
298 mmap: OwnedMmap(map_addr, map_size),
299 producer,
300 consumer,
301 desc,
302 flags,
303 })
304}
305
306/// An enum representing the four types of AF_XDP rings.
307#[derive(Copy, Clone, Debug, PartialEq)]
308pub enum RingType {
309 /// The Transmit (TX) ring, for sending packets.
310 Tx,
311 /// The Receive (RX) ring, for receiving packets.
312 Rx,
313 /// The Fill ring, for providing the kernel with free UMEM frames.
314 Fill,
315 /// The Completion ring, for retrieving used UMEM frames from the kernel.
316 Completion,
317}
318
319impl RingType {
320 fn as_index(&self) -> libc::c_int {
321 match self {
322 RingType::Tx => libc::XDP_TX_RING,
323 RingType::Rx => libc::XDP_RX_RING,
324 RingType::Fill => libc::XDP_UMEM_FILL_RING,
325 RingType::Completion => libc::XDP_UMEM_COMPLETION_RING,
326 }
327 }
328
329 fn as_offset(&self) -> u64 {
330 match self {
331 RingType::Tx => libc::XDP_PGOFF_TX_RING as u64,
332 RingType::Rx => libc::XDP_PGOFF_RX_RING as u64,
333 RingType::Fill => libc::XDP_UMEM_PGOFF_FILL_RING,
334 RingType::Completion => libc::XDP_UMEM_PGOFF_COMPLETION_RING,
335 }
336 }
337
338 /// Sets the size of a specific ring via `setsockopt`.
339 ///
340 /// # Arguments
341 /// * `raw_fd` - The raw file descriptor of the XDP socket.
342 /// * `ring_size` - The number of descriptors for the ring.
343 pub fn set_size(self, raw_fd: libc::c_int, mut ring_size: usize) -> io::Result<()> {
344 if ring_size == 0 && (self == RingType::Fill || self == RingType::Completion) {
345 ring_size = 1 // Fill and Completion rings must have at least one entry
346 }
347 unsafe {
348 if libc::setsockopt(
349 raw_fd,
350 libc::SOL_XDP,
351 self.as_index() as libc::c_int,
352 &ring_size as *const _ as *const libc::c_void,
353 size_of::<u32>() as libc::socklen_t,
354 ) < 0
355 {
356 return Err(io::Error::last_os_error());
357 }
358 }
359 Ok(())
360 }
361 /// Memory-maps a ring of a specific type.
362 ///
363 /// This is a convenience method that selects the correct offsets from `xdp_mmap_offsets`
364 /// based on the `RingType` and then calls the generic `Ring::mmap` function.
365 ///
366 /// # Arguments
367 /// * `raw_fd` - The raw file descriptor of the XDP socket.
368 /// * `offsets` - The struct containing the memory map offsets for all rings.
369 /// * `ring_size` - The number of descriptors for the ring.
370 pub fn mmap<T: Copy>(
371 self,
372 raw_fd: libc::c_int,
373 offsets: &libc::xdp_mmap_offsets,
374 ring_size: usize,
375 ) -> io::Result<Ring<T>> {
376 let ring_offs = match self {
377 RingType::Tx => &offsets.tx,
378 RingType::Rx => &offsets.rx,
379 RingType::Fill => &offsets.fr,
380 _ => &offsets.cr,
381 };
382 Ring::<T>::mmap(raw_fd, ring_size, self.as_offset(), ring_offs)
383 }
384}