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}