xdp_socket/kick.rs
1//! # XDP Socket Kernel Wakeup
2//!
3//! ## Purpose
4//!
5//! This file implements the `kick` method for the `Socket`. The purpose of this
6//! method is to notify the kernel to process packets in XDP rings, especially
7//! when `XDP_USE_NEED_WAKEUP` is in use.
8//!
9//! ## How it works
10//!
11//! The `kick` method checks the `XDP_RING_NEED_WAKEUP` flag in the ring's flags
12//! field. If set, it performs a zero-length `sendto` syscall to signal the kernel.
13//! This prompts the kernel to check the rings for new descriptors to process.
14//!
15//! ## Main components
16//!
17//! - `kick`: Main method to trigger kernel wakeup for XDP socket rings.
18
19#![allow(private_interfaces)]
20#![allow(private_bounds)]
21
22use std::sync::atomic::Ordering;
23use std::{io, ptr};
24
25use crate::socket::{_Direction, Commit_, RingError, Socket};
26
27/// Implements the kernel wakeup logic for `Socket`.
28impl<const T: _Direction> Socket<T>
29where
30 Socket<T>: Commit_<T>,
31{
32 /// Wakes up the kernel to process descriptors in the rings.
33 ///
34 /// This method is used to notify the kernel that it needs to process packets,
35 /// which is particularly important when the `XDP_USE_NEED_WAKEUP` flag is set
36 /// on the socket. It checks if the `XDP_RING_NEED_WAKEUP` flag is set in the
37 /// ring's flags field and, if so, performs a syscall to wake up the kernel.
38 ///
39 /// # How it works
40 ///
41 /// It performs a `sendto` syscall with a zero-length buffer. This syscall does not transfer
42 /// any data but acts as a signal to the kernel.
43 ///
44 /// # Returns
45 ///
46 /// Returns `Ok(())` on success. On failure, it returns an `io::Error`, except
47 /// for certain non-critical errors like `EBUSY` or `EAGAIN`. A warning is
48 /// logged for `ENETDOWN`.
49 pub fn kick(&self) -> Result<(), io::Error> {
50 let need_wakeup = unsafe {
51 (*self.x_ring.mmap.flags).load(Ordering::Relaxed) & libc::XDP_RING_NEED_WAKEUP != 0
52 };
53
54 if need_wakeup {
55 let ret = unsafe {
56 libc::sendto(
57 self.raw_fd,
58 ptr::null(),
59 0,
60 libc::MSG_DONTWAIT | libc::MSG_NOSIGNAL,
61 ptr::null(),
62 0,
63 )
64 };
65
66 if ret < 0 {
67 match io::Error::last_os_error().raw_os_error() {
68 None | Some(libc::EBUSY | libc::ENOBUFS | libc::EAGAIN) => {}
69 Some(libc::ENETDOWN) => {
70 // TODO: better handling
71 log::warn!("network interface is down, cannot wake up");
72 }
73 Some(e) => {
74 return Err(io::Error::from_raw_os_error(e));
75 }
76 }
77 }
78 }
79 Ok(())
80 }
81
82 /// Commits a number of descriptors and notifies the kernel to process them.
83 ///
84 /// This method first calls `commit_` to commit `n` descriptors, and then
85 /// calls `kick` to notify the kernel to process the descriptors. The
86 /// `commit_` method is used to finalize operations on descriptors and make
87 /// them available to the kernel, and the `kick` method is used to signal to
88 /// the kernel that it needs to process the descriptors.
89 ///
90 /// # Returns
91 ///
92 /// This method returns `Ok(())` on success. If `commit_` fails, it returns
93 /// a `RingError`. If `kick` fails, it maps the error to a `RingError` using
94 /// `RingError::Io`.
95 pub fn commit_and_kick(&mut self, n: usize) -> Result<(), RingError> {
96 self.commit_(n)?;
97 self.kick().map_err(RingError::Io)
98 }
99}