aya/maps/xdp/
cpu_map.rs

1//! An array of available CPUs.
2
3use std::{
4    borrow::{Borrow, BorrowMut},
5    num::NonZeroU32,
6    os::fd::{AsFd, AsRawFd},
7};
8
9use aya_obj::generated::bpf_cpumap_val;
10
11use super::XdpMapError;
12use crate::{
13    maps::{check_bounds, check_kv_size, IterableMap, MapData, MapError},
14    programs::ProgramFd,
15    sys::{bpf_map_lookup_elem, bpf_map_update_elem, SyscallError},
16    Pod, FEATURES,
17};
18
19/// An array of available CPUs.
20///
21/// XDP programs can use this map to redirect packets to a target
22/// CPU for processing.
23///
24/// # Minimum kernel version
25///
26/// The minimum kernel version required to use this feature is 4.15.
27///
28/// # Examples
29/// ```no_run
30/// # let elf_bytes = &[];
31/// use aya::maps::xdp::CpuMap;
32/// use aya::util::nr_cpus;
33///
34/// let nr_cpus = nr_cpus().unwrap() as u32;
35/// let mut bpf = aya::EbpfLoader::new()
36///     .set_max_entries("CPUS", nr_cpus)
37///     .load(elf_bytes)
38///     .unwrap();
39/// let mut cpumap = CpuMap::try_from(bpf.map_mut("CPUS").unwrap())?;
40/// let flags = 0;
41/// let queue_size = 2048;
42/// for i in 0..nr_cpus {
43///     cpumap.set(i, queue_size, None, flags);
44/// }
45///
46/// # Ok::<(), aya::EbpfError>(())
47/// ```
48///
49/// # See also
50///
51/// Kernel documentation: <https://docs.kernel.org/next/bpf/map_cpumap.html>
52#[doc(alias = "BPF_MAP_TYPE_CPUMAP")]
53pub struct CpuMap<T> {
54    pub(crate) inner: T,
55}
56
57impl<T: Borrow<MapData>> CpuMap<T> {
58    pub(crate) fn new(map: T) -> Result<Self, MapError> {
59        let data = map.borrow();
60
61        if FEATURES.cpumap_prog_id() {
62            check_kv_size::<u32, bpf_cpumap_val>(data)?;
63        } else {
64            check_kv_size::<u32, u32>(data)?;
65        }
66
67        Ok(Self { inner: map })
68    }
69
70    /// Returns the number of elements in the array.
71    ///
72    /// This corresponds to the value of `bpf_map_def::max_entries` on the eBPF side.
73    pub fn len(&self) -> u32 {
74        self.inner.borrow().obj.max_entries()
75    }
76
77    /// Returns the queue size and optional program for a given CPU index.
78    ///
79    /// # Errors
80    ///
81    /// Returns [`MapError::OutOfBounds`] if `cpu_index` is out of bounds,
82    /// [`MapError::SyscallError`] if `bpf_map_lookup_elem` fails.
83    pub fn get(&self, cpu_index: u32, flags: u64) -> Result<CpuMapValue, MapError> {
84        let data = self.inner.borrow();
85        check_bounds(data, cpu_index)?;
86        let fd = data.fd().as_fd();
87
88        let value = if FEATURES.cpumap_prog_id() {
89            bpf_map_lookup_elem::<_, bpf_cpumap_val>(fd, &cpu_index, flags).map(|value| {
90                value.map(|value| CpuMapValue {
91                    queue_size: value.qsize,
92                    // SAFETY: map writes use fd, map reads use id.
93                    // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/include/uapi/linux/bpf.h#L6241
94                    prog_id: NonZeroU32::new(unsafe { value.bpf_prog.id }),
95                })
96            })
97        } else {
98            bpf_map_lookup_elem::<_, u32>(fd, &cpu_index, flags).map(|value| {
99                value.map(|qsize| CpuMapValue {
100                    queue_size: qsize,
101                    prog_id: None,
102                })
103            })
104        };
105        value
106            .map_err(|(_, io_error)| SyscallError {
107                call: "bpf_map_lookup_elem",
108                io_error,
109            })?
110            .ok_or(MapError::KeyNotFound)
111    }
112
113    /// An iterator over the elements of the map.
114    pub fn iter(&self) -> impl Iterator<Item = Result<CpuMapValue, MapError>> + '_ {
115        (0..self.len()).map(move |i| self.get(i, 0))
116    }
117}
118
119impl<T: BorrowMut<MapData>> CpuMap<T> {
120    /// Sets the queue size at the given CPU index, and optionally a chained program.
121    ///
122    /// When sending the packet to the CPU at the given index, the kernel will queue up to
123    /// `queue_size` packets before dropping them.
124    ///
125    /// Starting from Linux kernel 5.9, another XDP program can be passed in that will be run on the
126    /// target CPU, instead of the CPU that receives the packets. This allows to perform minimal
127    /// computations on CPUs that directly handle packets from a NIC's RX queues, and perform
128    /// possibly heavier ones in other, less busy CPUs.
129    ///
130    /// The chained program must be loaded with the `BPF_XDP_CPUMAP` attach type. When using
131    /// `aya-ebpf`, that means XDP programs that specify the `map = "cpumap"` argument. See the
132    /// kernel-space `aya_ebpf::xdp` for more information.
133    ///
134    /// # Errors
135    ///
136    /// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`]
137    /// if `bpf_map_update_elem` fails, [`XdpMapError::ChainedProgramNotSupported`] if the kernel
138    /// does not support chained programs and one is provided.
139    pub fn set(
140        &mut self,
141        cpu_index: u32,
142        queue_size: u32,
143        program: Option<&ProgramFd>,
144        flags: u64,
145    ) -> Result<(), XdpMapError> {
146        let data = self.inner.borrow_mut();
147        check_bounds(data, cpu_index)?;
148        let fd = data.fd().as_fd();
149
150        let res = if FEATURES.cpumap_prog_id() {
151            let mut value = unsafe { std::mem::zeroed::<bpf_cpumap_val>() };
152            value.qsize = queue_size;
153            // Default is valid as the kernel will only consider fd > 0:
154            // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/cpumap.c#L466
155            value.bpf_prog.fd = program
156                .map(|prog| prog.as_fd().as_raw_fd())
157                .unwrap_or_default();
158            bpf_map_update_elem(fd, Some(&cpu_index), &value, flags)
159        } else {
160            if program.is_some() {
161                return Err(XdpMapError::ChainedProgramNotSupported);
162            }
163            bpf_map_update_elem(fd, Some(&cpu_index), &queue_size, flags)
164        };
165
166        res.map_err(|(_, io_error)| {
167            MapError::from(SyscallError {
168                call: "bpf_map_update_elem",
169                io_error,
170            })
171        })?;
172        Ok(())
173    }
174}
175
176impl<T: Borrow<MapData>> IterableMap<u32, CpuMapValue> for CpuMap<T> {
177    fn map(&self) -> &MapData {
178        self.inner.borrow()
179    }
180
181    fn get(&self, key: &u32) -> Result<CpuMapValue, MapError> {
182        self.get(*key, 0)
183    }
184}
185
186unsafe impl Pod for bpf_cpumap_val {}
187
188#[derive(Clone, Copy, Debug)]
189/// The value of a CPU map.
190pub struct CpuMapValue {
191    /// Size of the for the CPU.
192    pub queue_size: u32,
193    /// Chained XDP program ID.
194    pub prog_id: Option<NonZeroU32>,
195}