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