xdp_socket/
socket.rs

1//! # XDP Socket Implementation
2//!
3//! ## Purpose
4//!
5//! This file implements the `Socket` struct, which provides a high-level interface for
6//! interacting with XDP sockets. It supports both sending (TX) and receiving (RX) of
7//! packets with high performance through zero-copy data transfers.
8//!
9//! ## How it works
10//!
11//! The `Socket` utilizes two main components for its operation: a UMEM (Userspace Memory)
12//! region and associated rings for communication with the kernel. The UMEM is a memory-mapped
13//! area shared between the userspace application and the kernel, which allows for zero-copy
14//! packet processing.
15//!
16//! - For sending packets (TX), the application writes packet data directly into frames within
17//!   the UMEM and then pushes descriptors to the TX ring, signaling the kernel to send them.
18//! - For receiving packets (RX), the application provides the kernel with descriptors pointing
19//!   to free frames in the UMEM via the Fill ring. The kernel writes incoming packet data
20//!   into these frames and notifies the application through the RX ring.
21//!
22//! ## Main components
23//!
24//! - `Socket<const t:_Direction>`: The primary struct representing an XDP socket. It is
25//!   generic over the direction (TX or RX) to provide a type-safe API for each use case.
26//! - `Ring<T>`: A generic ring buffer implementation that is used for the TX/RX rings and
27//!   the Fill/Completion rings for UMEM.
28//! - `Inner`: A struct that holds the owned file descriptor for the XDP socket and the
29//!   memory-mapped UMEM region.
30//! - `TxSocket` and `RxSocket`: Type aliases for `Socket<true>` and `Socket<false>`
31//!   respectively, providing a more intuitive API for users.
32
33#![allow(private_interfaces)]
34#![allow(private_bounds)]
35#![allow(non_upper_case_globals)]
36
37use crate::mmap::OwnedMmap;
38use crate::ring::{Ring, XdpDesc};
39use std::fmt::Display;
40use std::os::fd::{AsRawFd as _, OwnedFd};
41use std::sync::Arc;
42use std::{io, ptr};
43
44/// A high-level interface for an AF_XDP socket.
45///
46/// This struct is generic over the `_Direction` const parameter, which determines
47/// whether the socket is for sending (`_TX`) or receiving (`_RX`).
48pub struct Socket<const t: _Direction> {
49    /// The inner shared state, including the file descriptor and UMEM.
50    pub(crate) _inner: Option<Arc<Inner>>,
51    /// The primary ring for sending (TX) or receiving (RX) descriptors.
52    pub(crate) x_ring: Ring<XdpDesc>,
53    /// The UMEM-associated ring: Completion Ring for TX, Fill Ring for RX.
54    pub(crate) u_ring: Ring<u64>,
55    /// The number of available descriptors in the `x_ring`.
56    pub(crate) available: u32,
57    /// The cached producer index for the `x_ring`.
58    pub(crate) producer: u32,
59    /// The cached consumer index for the `x_ring`.
60    pub(crate) consumer: u32,
61    /// A raw pointer to the start of the UMEM frames area.
62    pub(crate) frames: *mut u8,
63    /// -
64    pub(crate) raw_fd: libc::c_int,
65}
66
67/// An error that can occur during ring operations.
68#[derive(Debug)]
69pub enum RingError {
70    /// The ring is full, and no more descriptors can be produced.
71    RingFull,
72    /// The ring is empty, and no descriptors can be consumed.
73    RingEmpty,
74    /// Not enough descriptors or frames are available for the requested operation.
75    NotAvailable,
76    /// An invalid index was used to access a ring descriptor.
77    InvalidIndex,
78    /// The provided data length exceeds the available space in a UMEM frame.
79    InvalidLength,
80    /// An underlying I/O error occurred.
81    Io(io::Error),
82}
83
84impl Display for RingError {
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        match self {
87            RingError::RingFull => write!(f, "Ring is full"),
88            RingError::RingEmpty => write!(f, "Ring is empty"),
89            RingError::NotAvailable => write!(f, "Not enough available frames"),
90            RingError::InvalidIndex => write!(f, "Invalid index for ring access"),
91            RingError::InvalidLength => write!(f, "Invalid length for ring access"),
92            RingError::Io(e) => write!(f, "I/O error: {e}"),
93        }
94    }
95}
96
97impl<const t: _Direction> Socket<t>
98where
99    Socket<t>: Seek_<t> + Commit_<t> + Send,
100{
101    /// Constructs a new `Socket`.
102    ///
103    /// This function initializes a socket for either sending or receiving based on the
104    /// generic const `t`. For TX sockets, it pre-fills the TX ring with descriptors
105    /// pointing to UMEM frames. For RX sockets, it pre-fills the Fill ring to provide
106    /// the kernel with available frames for incoming packets.
107    ///
108    /// # Arguments
109    ///
110    /// * `inner` - The shared inner socket state (file descriptor, UMEM).
111    /// * `x_ring` - The TX or RX ring.
112    /// * `u_ring` - The Completion or Fill ring.
113    /// * `skip_frames` - The number of frames to skip at the start of the UMEM.
114    pub(crate) fn new(inner: Option<Arc<Inner>>, x_ring: Ring<XdpDesc>, u_ring: Ring<u64>) -> Self {
115        if let Some(inner) = inner {
116            let frames = inner.umem.0 as *mut u8;
117            let raw_fd = inner.fd.as_raw_fd();
118            Self {
119                frames,
120                available: x_ring.len as u32,
121                producer: 0,
122                consumer: 0,
123                raw_fd,
124                _inner: Some(inner),
125                x_ring,
126                u_ring,
127            }
128        } else {
129            Self::default()
130        }
131    }
132
133    /// Ensures that at least one descriptor is available for the next operation and
134    /// returns the total number of available descriptors.
135    ///
136    /// For a `TxSocket`, this may involve reclaiming completed descriptors from the
137    /// Completion Ring. For an `RxSocket`, this checks for newly received packets.
138    ///
139    /// # Returns
140    ///
141    /// A `Result` containing the total number of available descriptors, or a
142    /// `RingError` if the operation fails.
143    #[inline]
144    pub fn seek(&mut self) -> Result<usize, RingError> {
145        self.seek_(1)
146    }
147
148    /// Ensures that at least `count` descriptors are available for the next operation
149    /// and returns the total number of available descriptors.
150    ///
151    /// For a `TxSocket`, this may involve reclaiming completed descriptors from the
152    /// Completion Ring. For an `RxSocket`, this checks for newly received packets.
153    ///
154    /// # Arguments
155    ///
156    /// * `count` - The desired number of available descriptors.
157    ///
158    /// # Returns
159    ///
160    /// A `Result` containing the total number of available descriptors, or a
161    /// `RingError` if the operation fails.
162    #[inline]
163    pub fn seek_n(&mut self, count: usize) -> Result<usize, RingError> {
164        self.seek_(count)
165    }
166
167    /// Commits one descriptor, making it available to the kernel.
168    ///
169    /// For a `TxSocket`, this signals to the kernel that a packet written to the
170    /// corresponding UMEM frame is ready to be sent.
171    ///
172    /// For an `RxSocket`, this returns a UMEM frame to the kernel's Fill Ring after
173    /// the application has finished processing the received packet, making the frame
174    /// available for new packets.
175    ///
176    /// # Returns
177    ///
178    /// `Ok(())` on success, or a `RingError` on failure.
179    #[inline]
180    pub fn commit(&mut self) -> Result<(), RingError> {
181        self.commit_(1)
182    }
183
184    /// Commits `n` descriptors, making them available to the kernel.
185    ///
186    /// For a `TxSocket`, this signals to the kernel that `n` packets are ready to be sent.
187    /// For an `RxSocket`, this returns `n` UMEM frames to the kernel's Fill Ring.
188    ///
189    /// # Arguments
190    ///
191    /// * `n` - The number of descriptors to commit.
192    ///
193    /// # Returns
194    ///
195    /// `Ok(())` on success, or a `RingError` on failure.
196    #[inline]
197    pub fn commit_n(&mut self, n: usize) -> Result<(), RingError> {
198        self.commit_(n)
199    }
200
201    /// Returns the size of a single frame in the UMEM.
202    ///
203    /// # Returns
204    ///
205    /// The size of a single frame in the UMEM in bytes.
206    #[inline]
207    pub fn frame_size(&self) -> usize {
208        self.x_ring.frame_size() as usize
209    }
210}
211
212// socket refers to shared mapped memory owned by _inner and rings
213//  so all pointers can be safely send over threads
214//  until mapped memory is alive
215unsafe impl<const t: _Direction> Send for Socket<t> {}
216
217/// A boolean flag indicating the direction of the socket (`true` for TX, `false` for RX).
218pub type _Direction = bool;
219
220/// Constant representing the Transmit (TX) direction.
221pub const _TX: _Direction = true;
222
223/// Constant representing the Receive (RX) direction.
224pub const _RX: _Direction = false;
225
226/// A type alias for a socket configured for sending packets.
227pub type TxSocket = Socket<_TX>;
228
229/// A type alias for a socket configured for receiving packets.
230pub type RxSocket = Socket<_RX>;
231
232impl<const t: _Direction> Default for Socket<t> {
233    fn default() -> Self {
234        Self {
235            _inner: None,
236            x_ring: Default::default(),
237            u_ring: Default::default(),
238            available: 0,
239            producer: 0,
240            consumer: 0,
241            frames: ptr::null_mut(),
242            raw_fd: 0,
243        }
244    }
245}
246
247/// A trait for direction-specific seeking logic (TX vs. RX).
248pub(crate) trait Seek_<const t: _Direction> {
249    fn seek_(&mut self, count: usize) -> Result<usize, RingError>;
250}
251
252pub(crate) trait Commit_<const t: _Direction> {
253    fn commit_(&mut self, count: usize) -> Result<(), RingError>;
254}
255
256/// Holds the owned components of an XDP socket that can be shared.
257pub(crate) struct Inner {
258    /// The memory-mapped UMEM region.
259    umem: OwnedMmap,
260    /// The owned file descriptor for the AF_XDP socket.
261    fd: OwnedFd,
262}
263
264impl Inner {
265    /// Constructs a new `Inner` with the given UMEM and file descriptor.
266    pub(crate) fn new(umem: OwnedMmap, fd: OwnedFd) -> Self {
267        Self { umem, fd }
268    }
269}