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}