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}