Skip to main content

aya_friday/maps/of_maps/
array.rs

1//! An array of eBPF maps.
2
3use std::{
4    borrow::{Borrow, BorrowMut},
5    fmt,
6    marker::PhantomData,
7    os::fd::{AsFd as _, AsRawFd as _},
8    path::Path,
9};
10
11use crate::{
12    maps::{FromMapData, InnerMap, MapData, MapError, PinError, check_bounds, check_kv_size},
13    sys::{SyscallError, bpf_map_lookup_elem, bpf_map_update_elem},
14};
15
16/// An array of eBPF maps.
17///
18/// An `ArrayOfMaps` stores references to other eBPF maps.
19///
20/// # Minimum kernel version
21///
22/// The minimum kernel version required to use this feature is 4.12.
23#[doc(alias = "BPF_MAP_TYPE_ARRAY_OF_MAPS")]
24pub struct ArrayOfMaps<T, V = MapData> {
25    pub(crate) inner: T,
26    _v: PhantomData<V>,
27}
28
29impl<T: fmt::Debug, V> fmt::Debug for ArrayOfMaps<T, V> {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        f.debug_struct("ArrayOfMaps")
32            .field("inner", &self.inner)
33            .finish()
34    }
35}
36
37impl<T: Borrow<MapData>, V> ArrayOfMaps<T, V> {
38    pub(crate) fn new(map: T) -> Result<Self, MapError> {
39        let data = map.borrow();
40        check_kv_size::<u32, u32>(data)?;
41        Ok(Self {
42            inner: map,
43            _v: PhantomData,
44        })
45    }
46
47    /// Returns the number of elements in the array.
48    ///
49    /// This corresponds to the value of `bpf_map_def::max_entries` on the eBPF side.
50    pub fn len(&self) -> u32 {
51        self.inner.borrow().obj.max_entries()
52    }
53
54    /// Returns true if the array is empty.
55    pub fn is_empty(&self) -> bool {
56        self.len() == 0
57    }
58}
59
60impl<T: Borrow<MapData>, V: FromMapData> ArrayOfMaps<T, V> {
61    /// Returns the inner map stored at the given index.
62    ///
63    /// The inner map type `V` is determined by the type parameter on the
64    /// `ArrayOfMaps` itself.
65    ///
66    /// # File descriptor cost
67    ///
68    /// Each call opens a **new file descriptor** to the inner map. The caller
69    /// owns the returned map and its FD is closed on drop. Avoid calling this
70    /// in a tight loop without dropping previous results.
71    ///
72    /// # Errors
73    ///
74    /// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`]
75    /// if `bpf_map_lookup_elem` fails.
76    pub fn get(&self, index: &u32, flags: u64) -> Result<V, MapError> {
77        let data = self.inner.borrow();
78        check_bounds(data, *index)?;
79        let fd = data.fd().as_fd();
80
81        let value: Option<u32> =
82            bpf_map_lookup_elem(fd, index, flags).map_err(|io_error| SyscallError {
83                call: "bpf_map_lookup_elem",
84                io_error,
85            })?;
86        match value {
87            Some(id) => super::map_from_id(id),
88            None => Err(MapError::KeyNotFound),
89        }
90    }
91}
92
93impl<T: BorrowMut<MapData>, V: InnerMap> ArrayOfMaps<T, V> {
94    /// Sets the value of the element at the given index.
95    ///
96    /// # Errors
97    ///
98    /// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`]
99    /// if `bpf_map_update_elem` fails.
100    pub fn set(&mut self, index: u32, value: &V, flags: u64) -> Result<(), MapError> {
101        let data = self.inner.borrow_mut();
102        check_bounds(data, index)?;
103        let fd = data.fd().as_fd();
104        bpf_map_update_elem(fd, Some(&index), &value.fd().as_fd().as_raw_fd(), flags).map_err(
105            |io_error| SyscallError {
106                call: "bpf_map_update_elem",
107                io_error,
108            },
109        )?;
110        Ok(())
111    }
112}
113
114impl<V> ArrayOfMaps<MapData, V> {
115    /// Pins the map to a BPF filesystem.
116    ///
117    /// When a map is pinned it will remain loaded until the corresponding file
118    /// is deleted. All parent directories in the given `path` must already exist.
119    pub fn pin<P: AsRef<Path>>(self, path: P) -> Result<(), PinError> {
120        self.inner.pin(path)
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use std::io;
127
128    use assert_matches::assert_matches;
129    use aya_obj::generated::{bpf_cmd, bpf_map_type::BPF_MAP_TYPE_ARRAY_OF_MAPS};
130    use libc::{EFAULT, ENOENT};
131
132    use super::*;
133    use crate::{
134        maps::{Map, test_utils},
135        sys::{SysResult, Syscall, override_syscall},
136    };
137
138    fn new_obj_map() -> aya_obj::Map {
139        test_utils::new_obj_map::<u32>(BPF_MAP_TYPE_ARRAY_OF_MAPS)
140    }
141
142    fn new_map(obj: aya_obj::Map) -> MapData {
143        test_utils::new_map(obj)
144    }
145
146    fn sys_error(value: i32) -> SysResult {
147        Err((-1, io::Error::from_raw_os_error(value)))
148    }
149
150    #[test]
151    fn test_wrong_key_size() {
152        let map = new_map(test_utils::new_obj_map::<u8>(BPF_MAP_TYPE_ARRAY_OF_MAPS));
153        assert_matches!(
154            ArrayOfMaps::<_>::new(&map),
155            Err(MapError::InvalidKeySize {
156                size: 4,
157                expected: 1
158            })
159        );
160    }
161
162    #[test]
163    fn test_try_from_wrong_map() {
164        let map = new_map(test_utils::new_obj_map::<u32>(
165            aya_obj::generated::bpf_map_type::BPF_MAP_TYPE_HASH,
166        ));
167        let map = Map::HashMap(map);
168        assert_matches!(
169            ArrayOfMaps::<_>::try_from(&map),
170            Err(MapError::InvalidMapType { .. })
171        );
172    }
173
174    #[test]
175    fn test_new_ok() {
176        let map = new_map(new_obj_map());
177        let _: ArrayOfMaps<_> = ArrayOfMaps::new(&map).unwrap();
178    }
179
180    #[test]
181    fn test_set_syscall_error() {
182        let mut map = new_map(new_obj_map());
183        let inner_map = new_map(test_utils::new_obj_map::<u32>(
184            aya_obj::generated::bpf_map_type::BPF_MAP_TYPE_ARRAY,
185        ));
186        let mut arr = ArrayOfMaps::new(&mut map).unwrap();
187
188        override_syscall(|_| sys_error(EFAULT));
189
190        assert_matches!(
191            arr.set(0, &inner_map, 0),
192            Err(MapError::SyscallError(SyscallError {
193                call: "bpf_map_update_elem",
194                ..
195            }))
196        );
197    }
198
199    #[test]
200    fn test_set_ok() {
201        let mut map = new_map(new_obj_map());
202        let inner_map = new_map(test_utils::new_obj_map::<u32>(
203            aya_obj::generated::bpf_map_type::BPF_MAP_TYPE_ARRAY,
204        ));
205        let mut arr = ArrayOfMaps::new(&mut map).unwrap();
206
207        override_syscall(|call| match call {
208            Syscall::Ebpf {
209                cmd: bpf_cmd::BPF_MAP_UPDATE_ELEM,
210                ..
211            } => Ok(0),
212            _ => sys_error(EFAULT),
213        });
214
215        arr.set(0, &inner_map, 0).unwrap();
216    }
217
218    #[test]
219    fn test_set_out_of_bounds() {
220        let mut map = new_map(new_obj_map());
221        let inner_map = new_map(test_utils::new_obj_map::<u32>(
222            aya_obj::generated::bpf_map_type::BPF_MAP_TYPE_ARRAY,
223        ));
224        let mut arr = ArrayOfMaps::new(&mut map).unwrap();
225
226        assert_matches!(
227            arr.set(1024, &inner_map, 0),
228            Err(MapError::OutOfBounds { .. })
229        );
230    }
231
232    #[test]
233    fn test_get_syscall_error() {
234        let map = new_map(new_obj_map());
235        let arr: ArrayOfMaps<_> = ArrayOfMaps::new(&map).unwrap();
236
237        override_syscall(|_| sys_error(EFAULT));
238
239        assert_matches!(
240            arr.get(&0, 0),
241            Err(MapError::SyscallError(SyscallError {
242                call: "bpf_map_lookup_elem",
243                ..
244            }))
245        );
246    }
247
248    #[test]
249    fn test_get_not_found() {
250        let map = new_map(new_obj_map());
251        let arr: ArrayOfMaps<_> = ArrayOfMaps::new(&map).unwrap();
252
253        override_syscall(|call| match call {
254            Syscall::Ebpf {
255                cmd: bpf_cmd::BPF_MAP_LOOKUP_ELEM,
256                ..
257            } => sys_error(ENOENT),
258            _ => sys_error(EFAULT),
259        });
260
261        assert_matches!(arr.get(&0, 0), Err(MapError::KeyNotFound));
262    }
263
264    #[test]
265    fn test_get_out_of_bounds() {
266        let map = new_map(new_obj_map());
267        let arr: ArrayOfMaps<_> = ArrayOfMaps::new(&map).unwrap();
268
269        assert_matches!(arr.get(&1024, 0), Err(MapError::OutOfBounds { .. }));
270    }
271}