Godones 7 месяцев назад
Родитель
Сommit
9632e57889

+ 2 - 0
aya-log/src/lib.rs

@@ -77,9 +77,11 @@ use thiserror::Error;
 
 #[derive(Copy, Clone)]
 #[repr(transparent)]
+#[allow(unused)]
 struct RecordFieldWrapper(RecordField);
 #[derive(Copy, Clone)]
 #[repr(transparent)]
+#[allow(unused)]
 struct ArgumentWrapper(Argument);
 #[derive(Copy, Clone)]
 #[repr(transparent)]

+ 538 - 0
aya/src/maps/hash_map/hash_map.rs

@@ -0,0 +1,538 @@
+use std::{
+    borrow::{Borrow, BorrowMut},
+    marker::PhantomData,
+    os::fd::AsFd as _,
+};
+
+use crate::{
+    maps::{check_kv_size, hash_map, IterableMap, MapData, MapError, MapIter, MapKeys},
+    sys::{bpf_map_lookup_elem, SyscallError},
+    Pod,
+};
+
+/// A hash map that can be shared between eBPF programs and user space.
+///
+/// # Minimum kernel version
+///
+/// The minimum kernel version required to use this feature is 3.19.
+///
+/// # Examples
+///
+/// ```no_run
+/// # let mut bpf = aya::Ebpf::load(&[])?;
+/// use aya::maps::HashMap;
+///
+/// let mut redirect_ports = HashMap::try_from(bpf.map_mut("REDIRECT_PORTS").unwrap())?;
+///
+/// // redirect port 80 to 8080
+/// redirect_ports.insert(80, 8080, 0);
+/// // redirect port 443 to 8443
+/// redirect_ports.insert(443, 8443, 0);
+/// # Ok::<(), aya::EbpfError>(())
+/// ```
+#[doc(alias = "BPF_MAP_TYPE_HASH")]
+#[doc(alias = "BPF_MAP_TYPE_LRU_HASH")]
+#[derive(Debug)]
+pub struct HashMap<T, K, V> {
+    pub(crate) inner: T,
+    _k: PhantomData<K>,
+    _v: PhantomData<V>,
+}
+
+impl<T: Borrow<MapData>, K: Pod, V: Pod> HashMap<T, K, V> {
+    pub(crate) fn new(map: T) -> Result<Self, MapError> {
+        let data = map.borrow();
+        check_kv_size::<K, V>(data)?;
+
+        Ok(Self {
+            inner: map,
+            _k: PhantomData,
+            _v: PhantomData,
+        })
+    }
+
+    /// Returns a copy of the value associated with the key.
+    pub fn get(&self, key: &K, flags: u64) -> Result<V, MapError> {
+        let fd = self.inner.borrow().fd().as_fd();
+        let value = bpf_map_lookup_elem(fd, key, flags).map_err(|(_, io_error)| SyscallError {
+            call: "bpf_map_lookup_elem",
+            io_error,
+        })?;
+        value.ok_or(MapError::KeyNotFound)
+    }
+
+    /// An iterator visiting all key-value pairs in arbitrary order. The
+    /// iterator item type is `Result<(K, V), MapError>`.
+    pub fn iter(&self) -> MapIter<'_, K, V, Self> {
+        MapIter::new(self)
+    }
+
+    /// An iterator visiting all keys in arbitrary order. The iterator element
+    /// type is `Result<K, MapError>`.
+    pub fn keys(&self) -> MapKeys<'_, K> {
+        MapKeys::new(self.inner.borrow())
+    }
+}
+
+impl<T: BorrowMut<MapData>, K: Pod, V: Pod> HashMap<T, K, V> {
+    /// Inserts a key-value pair into the map.
+    pub fn insert(
+        &mut self,
+        key: impl Borrow<K>,
+        value: impl Borrow<V>,
+        flags: u64,
+    ) -> Result<(), MapError> {
+        hash_map::insert(self.inner.borrow_mut(), key.borrow(), value.borrow(), flags)
+    }
+
+    /// Removes a key from the map.
+    pub fn remove(&mut self, key: &K) -> Result<(), MapError> {
+        hash_map::remove(self.inner.borrow_mut(), key)
+    }
+}
+
+impl<T: Borrow<MapData>, K: Pod, V: Pod> IterableMap<K, V> for HashMap<T, K, V> {
+    fn map(&self) -> &MapData {
+        self.inner.borrow()
+    }
+
+    fn get(&self, key: &K) -> Result<V, MapError> {
+        Self::get(self, key, 0)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::io;
+
+    use assert_matches::assert_matches;
+    use aya_obj::{
+        generated::{
+            bpf_attr, bpf_cmd,
+            bpf_map_type::{BPF_MAP_TYPE_HASH, BPF_MAP_TYPE_LRU_HASH},
+        },
+        obj,
+    };
+    use libc::{EFAULT, ENOENT};
+
+    use super::*;
+    use crate::{
+        maps::{
+            test_utils::{self, new_map},
+            Map,
+        },
+        sys::{override_syscall, SysResult, Syscall},
+    };
+
+    fn new_obj_map() -> obj::Map {
+        test_utils::new_obj_map::<u32>(BPF_MAP_TYPE_HASH)
+    }
+
+    fn sys_error(value: i32) -> SysResult<i64> {
+        Err((-1, io::Error::from_raw_os_error(value)))
+    }
+
+    #[test]
+    fn test_wrong_key_size() {
+        let map = new_map(new_obj_map());
+        assert_matches!(
+            HashMap::<_, u8, u32>::new(&map),
+            Err(MapError::InvalidKeySize {
+                size: 1,
+                expected: 4
+            })
+        );
+    }
+
+    #[test]
+    fn test_wrong_value_size() {
+        let map = new_map(new_obj_map());
+        assert_matches!(
+            HashMap::<_, u32, u16>::new(&map),
+            Err(MapError::InvalidValueSize {
+                size: 2,
+                expected: 4
+            })
+        );
+    }
+
+    #[test]
+    fn test_try_from_wrong_map() {
+        let map = new_map(new_obj_map());
+        let map = Map::Array(map);
+        assert_matches!(
+            HashMap::<_, u8, u32>::try_from(&map),
+            Err(MapError::InvalidMapType { .. })
+        );
+    }
+
+    #[test]
+    fn test_try_from_wrong_map_values() {
+        let map = new_map(new_obj_map());
+        let map = Map::HashMap(map);
+        assert_matches!(
+            HashMap::<_, u32, u16>::try_from(&map),
+            Err(MapError::InvalidValueSize {
+                size: 2,
+                expected: 4
+            })
+        );
+    }
+
+    #[test]
+    fn test_new_ok() {
+        let map = new_map(new_obj_map());
+        assert!(HashMap::<_, u32, u32>::new(&map).is_ok());
+    }
+
+    #[test]
+    fn test_try_from_ok() {
+        let map = new_map(new_obj_map());
+        let map = Map::HashMap(map);
+        assert!(HashMap::<_, u32, u32>::try_from(&map).is_ok())
+    }
+
+    #[test]
+    fn test_try_from_ok_lru() {
+        let map_data = || new_map(test_utils::new_obj_map::<u32>(BPF_MAP_TYPE_LRU_HASH));
+        let map = Map::HashMap(map_data());
+        assert!(HashMap::<_, u32, u32>::try_from(&map).is_ok());
+        let map = Map::LruHashMap(map_data());
+        assert!(HashMap::<_, u32, u32>::try_from(&map).is_ok())
+    }
+
+    #[test]
+    fn test_insert_syscall_error() {
+        let mut map = new_map(new_obj_map());
+        let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap();
+
+        override_syscall(|_| sys_error(EFAULT));
+
+        assert_matches!(
+            hm.insert(1, 42, 0),
+            Err(MapError::SyscallError(SyscallError { call: "bpf_map_update_elem", io_error })) if io_error.raw_os_error() == Some(EFAULT)
+        );
+    }
+
+    #[test]
+    fn test_insert_ok() {
+        let mut map = new_map(new_obj_map());
+        let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap();
+
+        override_syscall(|call| match call {
+            Syscall::Ebpf {
+                cmd: bpf_cmd::BPF_MAP_UPDATE_ELEM,
+                ..
+            } => Ok(1),
+            _ => sys_error(EFAULT),
+        });
+
+        assert!(hm.insert(1, 42, 0).is_ok());
+    }
+
+    #[test]
+    fn test_insert_boxed_ok() {
+        let mut map = new_map(new_obj_map());
+        let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap();
+
+        override_syscall(|call| match call {
+            Syscall::Ebpf {
+                cmd: bpf_cmd::BPF_MAP_UPDATE_ELEM,
+                ..
+            } => Ok(1),
+            _ => sys_error(EFAULT),
+        });
+
+        assert!(hm.insert(Box::new(1), Box::new(42), 0).is_ok());
+    }
+
+    #[test]
+    fn test_remove_syscall_error() {
+        let mut map = new_map(new_obj_map());
+        let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap();
+
+        override_syscall(|_| sys_error(EFAULT));
+
+        assert_matches!(
+            hm.remove(&1),
+            Err(MapError::SyscallError(SyscallError { call: "bpf_map_delete_elem", io_error })) if io_error.raw_os_error() == Some(EFAULT)
+        );
+    }
+
+    #[test]
+    fn test_remove_ok() {
+        let mut map = new_map(new_obj_map());
+        let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap();
+
+        override_syscall(|call| match call {
+            Syscall::Ebpf {
+                cmd: bpf_cmd::BPF_MAP_DELETE_ELEM,
+                ..
+            } => Ok(1),
+            _ => sys_error(EFAULT),
+        });
+
+        assert!(hm.remove(&1).is_ok());
+    }
+
+    #[test]
+    fn test_get_syscall_error() {
+        let map = new_map(new_obj_map());
+        override_syscall(|_| sys_error(EFAULT));
+        let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
+
+        assert_matches!(
+            hm.get(&1, 0),
+            Err(MapError::SyscallError(SyscallError { call: "bpf_map_lookup_elem", io_error })) if io_error.raw_os_error() == Some(EFAULT)
+        );
+    }
+
+    #[test]
+    fn test_get_not_found() {
+        let map = new_map(new_obj_map());
+        override_syscall(|call| match call {
+            Syscall::Ebpf {
+                cmd: bpf_cmd::BPF_MAP_LOOKUP_ELEM,
+                ..
+            } => sys_error(ENOENT),
+            _ => sys_error(EFAULT),
+        });
+        let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
+
+        assert_matches!(hm.get(&1, 0), Err(MapError::KeyNotFound));
+    }
+
+    fn bpf_key<T: Copy>(attr: &bpf_attr) -> Option<T> {
+        match unsafe { attr.__bindgen_anon_2.key } as *const T {
+            p if p.is_null() => None,
+            p => Some(unsafe { *p }),
+        }
+    }
+
+    fn set_next_key<T: Copy>(attr: &bpf_attr, next: T) {
+        let key = unsafe { attr.__bindgen_anon_2.__bindgen_anon_1.next_key } as *const T as *mut T;
+        unsafe { *key = next };
+    }
+
+    fn set_ret<T: Copy>(attr: &bpf_attr, ret: T) {
+        let value = unsafe { attr.__bindgen_anon_2.__bindgen_anon_1.value } as *const T as *mut T;
+        unsafe { *value = ret };
+    }
+
+    #[test]
+    fn test_keys_empty() {
+        let map = new_map(new_obj_map());
+        override_syscall(|call| match call {
+            Syscall::Ebpf {
+                cmd: bpf_cmd::BPF_MAP_GET_NEXT_KEY,
+                ..
+            } => sys_error(ENOENT),
+            _ => sys_error(EFAULT),
+        });
+        let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
+        let keys = hm.keys().collect::<Result<Vec<_>, _>>();
+        assert_matches!(keys, Ok(ks) if ks.is_empty())
+    }
+
+    fn get_next_key(attr: &bpf_attr) -> SysResult<i64> {
+        match bpf_key(attr) {
+            None => set_next_key(attr, 10),
+            Some(10) => set_next_key(attr, 20),
+            Some(20) => set_next_key(attr, 30),
+            Some(30) => return sys_error(ENOENT),
+            Some(_) => return sys_error(EFAULT),
+        };
+
+        Ok(1)
+    }
+
+    fn lookup_elem(attr: &bpf_attr) -> SysResult<i64> {
+        match bpf_key(attr) {
+            Some(10) => set_ret(attr, 100),
+            Some(20) => set_ret(attr, 200),
+            Some(30) => set_ret(attr, 300),
+            Some(_) => return sys_error(ENOENT),
+            None => return sys_error(EFAULT),
+        };
+
+        Ok(1)
+    }
+
+    #[test]
+    fn test_keys() {
+        let map = new_map(new_obj_map());
+
+        override_syscall(|call| match call {
+            Syscall::Ebpf {
+                cmd: bpf_cmd::BPF_MAP_GET_NEXT_KEY,
+                attr,
+            } => get_next_key(attr),
+            _ => sys_error(EFAULT),
+        });
+
+        let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
+
+        let keys = hm.keys().collect::<Result<Vec<_>, _>>().unwrap();
+        assert_eq!(&keys, &[10, 20, 30])
+    }
+
+    #[test]
+    fn test_keys_error() {
+        let map = new_map(new_obj_map());
+        override_syscall(|call| match call {
+            Syscall::Ebpf {
+                cmd: bpf_cmd::BPF_MAP_GET_NEXT_KEY,
+                attr,
+            } => {
+                match bpf_key(attr) {
+                    None => set_next_key(attr, 10),
+                    Some(10) => set_next_key(attr, 20),
+                    Some(_) => return sys_error(EFAULT),
+                };
+
+                Ok(1)
+            }
+            _ => sys_error(EFAULT),
+        });
+        let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
+
+        let mut keys = hm.keys();
+        assert_matches!(keys.next(), Some(Ok(10)));
+        assert_matches!(keys.next(), Some(Ok(20)));
+        assert_matches!(
+            keys.next(),
+            Some(Err(MapError::SyscallError(SyscallError {
+                call: "bpf_map_get_next_key",
+                io_error: _
+            })))
+        );
+        assert_matches!(keys.next(), None);
+    }
+
+    #[test]
+    fn test_iter() {
+        let map = new_map(new_obj_map());
+        override_syscall(|call| match call {
+            Syscall::Ebpf {
+                cmd: bpf_cmd::BPF_MAP_GET_NEXT_KEY,
+                attr,
+            } => get_next_key(attr),
+            Syscall::Ebpf {
+                cmd: bpf_cmd::BPF_MAP_LOOKUP_ELEM,
+                attr,
+            } => lookup_elem(attr),
+            _ => sys_error(EFAULT),
+        });
+        let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
+        let items = hm.iter().collect::<Result<Vec<_>, _>>().unwrap();
+        assert_eq!(&items, &[(10, 100), (20, 200), (30, 300)])
+    }
+
+    #[test]
+    fn test_iter_key_deleted() {
+        let map = new_map(new_obj_map());
+        override_syscall(|call| match call {
+            Syscall::Ebpf {
+                cmd: bpf_cmd::BPF_MAP_GET_NEXT_KEY,
+                attr,
+            } => get_next_key(attr),
+            Syscall::Ebpf {
+                cmd: bpf_cmd::BPF_MAP_LOOKUP_ELEM,
+                attr,
+            } => {
+                match bpf_key(attr) {
+                    Some(10) => set_ret(attr, 100),
+                    Some(20) => return sys_error(ENOENT),
+                    Some(30) => set_ret(attr, 300),
+                    Some(_) => return sys_error(ENOENT),
+                    None => return sys_error(EFAULT),
+                };
+
+                Ok(1)
+            }
+            _ => sys_error(EFAULT),
+        });
+        let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
+
+        let items = hm.iter().collect::<Result<Vec<_>, _>>().unwrap();
+        assert_eq!(&items, &[(10, 100), (30, 300)])
+    }
+
+    #[test]
+    fn test_iter_key_error() {
+        let map = new_map(new_obj_map());
+        override_syscall(|call| match call {
+            Syscall::Ebpf {
+                cmd: bpf_cmd::BPF_MAP_GET_NEXT_KEY,
+                attr,
+            } => {
+                match bpf_key(attr) {
+                    None => set_next_key(attr, 10),
+                    Some(10) => set_next_key(attr, 20),
+                    Some(20) => return sys_error(EFAULT),
+                    Some(30) => return sys_error(ENOENT),
+                    Some(i) => panic!("invalid key {}", i),
+                };
+
+                Ok(1)
+            }
+            Syscall::Ebpf {
+                cmd: bpf_cmd::BPF_MAP_LOOKUP_ELEM,
+                attr,
+            } => lookup_elem(attr),
+            _ => sys_error(EFAULT),
+        });
+        let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
+
+        let mut iter = hm.iter();
+        assert_matches!(iter.next(), Some(Ok((10, 100))));
+        assert_matches!(iter.next(), Some(Ok((20, 200))));
+        assert_matches!(
+            iter.next(),
+            Some(Err(MapError::SyscallError(SyscallError {
+                call: "bpf_map_get_next_key",
+                io_error: _
+            })))
+        );
+        assert_matches!(iter.next(), None);
+    }
+
+    #[test]
+    fn test_iter_value_error() {
+        let map = new_map(new_obj_map());
+        override_syscall(|call| match call {
+            Syscall::Ebpf {
+                cmd: bpf_cmd::BPF_MAP_GET_NEXT_KEY,
+                attr,
+            } => get_next_key(attr),
+            Syscall::Ebpf {
+                cmd: bpf_cmd::BPF_MAP_LOOKUP_ELEM,
+                attr,
+            } => {
+                match bpf_key(attr) {
+                    Some(10) => set_ret(attr, 100),
+                    Some(20) => return sys_error(EFAULT),
+                    Some(30) => set_ret(attr, 300),
+                    Some(_) => return sys_error(ENOENT),
+                    None => return sys_error(EFAULT),
+                };
+
+                Ok(1)
+            }
+            _ => sys_error(EFAULT),
+        });
+        let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
+
+        let mut iter = hm.iter();
+        assert_matches!(iter.next(), Some(Ok((10, 100))));
+        assert_matches!(
+            iter.next(),
+            Some(Err(MapError::SyscallError(SyscallError {
+                call: "bpf_map_lookup_elem",
+                io_error: _
+            })))
+        );
+        assert_matches!(iter.next(), Some(Ok((30, 300))));
+        assert_matches!(iter.next(), None);
+    }
+}

+ 45 - 0
aya/src/maps/hash_map/mod.rs

@@ -0,0 +1,45 @@
+//! Hash map types.
+use std::os::fd::AsFd as _;
+
+use crate::{
+    maps::MapError,
+    sys::{bpf_map_delete_elem, bpf_map_update_elem, SyscallError},
+    Pod,
+};
+
+#[allow(clippy::module_inception)]
+mod hash_map;
+mod per_cpu_hash_map;
+
+pub use hash_map::*;
+pub use per_cpu_hash_map::*;
+
+use super::MapData;
+
+pub(crate) fn insert<K: Pod, V: Pod>(
+    map: &MapData,
+    key: &K,
+    value: &V,
+    flags: u64,
+) -> Result<(), MapError> {
+    let fd = map.fd().as_fd();
+    bpf_map_update_elem(fd, Some(key), value, flags).map_err(|(_, io_error)| SyscallError {
+        call: "bpf_map_update_elem",
+        io_error,
+    })?;
+
+    Ok(())
+}
+
+pub(crate) fn remove<K: Pod>(map: &MapData, key: &K) -> Result<(), MapError> {
+    let fd = map.fd().as_fd();
+    bpf_map_delete_elem(fd, key)
+        .map(|_| ())
+        .map_err(|(_, io_error)| {
+            SyscallError {
+                call: "bpf_map_delete_elem",
+                io_error,
+            }
+            .into()
+        })
+}

+ 175 - 0
aya/src/maps/hash_map/per_cpu_hash_map.rs

@@ -0,0 +1,175 @@
+//! Per-CPU hash map.
+use std::{
+    borrow::{Borrow, BorrowMut},
+    marker::PhantomData,
+    os::fd::AsFd as _,
+};
+
+use crate::{
+    maps::{
+        check_kv_size, hash_map, IterableMap, MapData, MapError, MapIter, MapKeys, PerCpuValues,
+    },
+    sys::{bpf_map_lookup_elem_per_cpu, bpf_map_update_elem_per_cpu, SyscallError},
+    Pod,
+};
+
+/// Similar to [`HashMap`](crate::maps::HashMap) but each CPU holds a separate value for a given key. Typically used to
+/// minimize lock contention in eBPF programs.
+///
+/// This type can be used with eBPF maps of type `BPF_MAP_TYPE_PERCPU_HASH` and
+/// `BPF_MAP_TYPE_LRU_PERCPU_HASH`.
+///
+/// # Minimum kernel version
+///
+/// The minimum kernel version required to use this feature is 4.6.
+///
+/// # Examples
+///
+/// ```no_run
+/// # let mut bpf = aya::Ebpf::load(&[])?;
+/// use aya::maps::PerCpuHashMap;
+///
+/// const CPU_IDS: u8 = 1;
+/// const WAKEUPS: u8 = 2;
+///
+/// let mut hm = PerCpuHashMap::<_, u8, u32>::try_from(bpf.map_mut("PER_CPU_STORAGE").unwrap())?;
+/// let cpu_ids = unsafe { hm.get(&CPU_IDS, 0)? };
+/// let wakeups = unsafe { hm.get(&WAKEUPS, 0)? };
+/// for (cpu_id, wakeups) in cpu_ids.iter().zip(wakeups.iter()) {
+///     println!("cpu {} woke up {} times", cpu_id, wakeups);
+/// }
+/// # Ok::<(), aya::EbpfError>(())
+/// ```
+#[doc(alias = "BPF_MAP_TYPE_LRU_PERCPU_HASH")]
+#[doc(alias = "BPF_MAP_TYPE_PERCPU_HASH")]
+pub struct PerCpuHashMap<T, K: Pod, V: Pod> {
+    pub(crate) inner: T,
+    _k: PhantomData<K>,
+    _v: PhantomData<V>,
+}
+
+impl<T: Borrow<MapData>, K: Pod, V: Pod> PerCpuHashMap<T, K, V> {
+    pub(crate) fn new(map: T) -> Result<Self, MapError> {
+        let data = map.borrow();
+        check_kv_size::<K, V>(data)?;
+
+        Ok(Self {
+            inner: map,
+            _k: PhantomData,
+            _v: PhantomData,
+        })
+    }
+
+    /// Returns a slice of values - one for each CPU - associated with the key.
+    pub fn get(&self, key: &K, flags: u64) -> Result<PerCpuValues<V>, MapError> {
+        let fd = self.inner.borrow().fd().as_fd();
+        let values =
+            bpf_map_lookup_elem_per_cpu(fd, key, flags).map_err(|(_, io_error)| SyscallError {
+                call: "bpf_map_lookup_elem",
+                io_error,
+            })?;
+        values.ok_or(MapError::KeyNotFound)
+    }
+
+    /// An iterator visiting all key-value pairs in arbitrary order. The
+    /// iterator item type is `Result<(K, PerCpuValues<V>), MapError>`.
+    pub fn iter(&self) -> MapIter<'_, K, PerCpuValues<V>, Self> {
+        MapIter::new(self)
+    }
+
+    /// An iterator visiting all keys in arbitrary order. The iterator element
+    /// type is `Result<K, MapError>`.
+    pub fn keys(&self) -> MapKeys<'_, K> {
+        MapKeys::new(self.inner.borrow())
+    }
+}
+
+impl<T: BorrowMut<MapData>, K: Pod, V: Pod> PerCpuHashMap<T, K, V> {
+    /// Inserts a slice of values - one for each CPU - for the given key.
+    ///
+    /// # Examples
+    ///
+    /// ```no_run
+    /// # #[derive(thiserror::Error, Debug)]
+    /// # enum Error {
+    /// #     #[error(transparent)]
+    /// #     IO(#[from] std::io::Error),
+    /// #     #[error(transparent)]
+    /// #     Map(#[from] aya::maps::MapError),
+    /// #     #[error(transparent)]
+    /// #     Ebpf(#[from] aya::EbpfError)
+    /// # }
+    /// # let mut bpf = aya::Ebpf::load(&[])?;
+    /// use aya::maps::{PerCpuHashMap, PerCpuValues};
+    /// use aya::util::nr_cpus;
+    ///
+    /// const RETRIES: u8 = 1;
+    ///
+    /// let mut hm = PerCpuHashMap::<_, u8, u32>::try_from(bpf.map_mut("PER_CPU_STORAGE").unwrap())?;
+    /// hm.insert(
+    ///     RETRIES,
+    ///     PerCpuValues::try_from(vec![3u32; nr_cpus()?])?,
+    ///     0,
+    /// )?;
+    /// # Ok::<(), Error>(())
+    /// ```
+    pub fn insert(
+        &mut self,
+        key: impl Borrow<K>,
+        values: PerCpuValues<V>,
+        flags: u64,
+    ) -> Result<(), MapError> {
+        let fd = self.inner.borrow_mut().fd().as_fd();
+        bpf_map_update_elem_per_cpu(fd, key.borrow(), &values, flags).map_err(
+            |(_, io_error)| SyscallError {
+                call: "bpf_map_update_elem",
+                io_error,
+            },
+        )?;
+
+        Ok(())
+    }
+
+    /// Removes a key from the map.
+    pub fn remove(&mut self, key: &K) -> Result<(), MapError> {
+        hash_map::remove(self.inner.borrow_mut(), key)
+    }
+}
+
+impl<T: Borrow<MapData>, K: Pod, V: Pod> IterableMap<K, PerCpuValues<V>>
+    for PerCpuHashMap<T, K, V>
+{
+    fn map(&self) -> &MapData {
+        self.inner.borrow()
+    }
+
+    fn get(&self, key: &K) -> Result<PerCpuValues<V>, MapError> {
+        Self::get(self, key, 0)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use aya_obj::generated::bpf_map_type::{
+        BPF_MAP_TYPE_LRU_PERCPU_HASH, BPF_MAP_TYPE_PERCPU_HASH,
+    };
+
+    use super::*;
+    use crate::maps::{test_utils, Map};
+    #[test]
+    fn test_try_from_ok() {
+        let map = Map::PerCpuHashMap(test_utils::new_map(test_utils::new_obj_map::<u32>(
+            BPF_MAP_TYPE_PERCPU_HASH,
+        )));
+        assert!(PerCpuHashMap::<_, u32, u32>::try_from(&map).is_ok())
+    }
+    #[test]
+    fn test_try_from_ok_lru() {
+        let map_data =
+            || test_utils::new_map(test_utils::new_obj_map::<u32>(BPF_MAP_TYPE_LRU_PERCPU_HASH));
+        let map = Map::PerCpuHashMap(map_data());
+        assert!(PerCpuHashMap::<_, u32, u32>::try_from(&map).is_ok());
+        let map = Map::PerCpuLruHashMap(map_data());
+        assert!(PerCpuHashMap::<_, u32, u32>::try_from(&map).is_ok())
+    }
+}

+ 259 - 10
aya/src/maps/mod.rs

@@ -1,11 +1,14 @@
+pub mod hash_map;
 pub mod perf;
-
 use core::mem;
 use std::{
     ffi::CString,
     fmt, io,
+    marker::PhantomData,
+    ops::Deref,
     os::fd::{AsFd, BorrowedFd, OwnedFd},
     path::Path,
+    ptr,
 };
 
 use aya_obj::{
@@ -14,6 +17,7 @@ use aya_obj::{
     maps::{InvalidMapTypeError, PinningType},
     parse_map_info, EbpfSectionKind,
 };
+pub use hash_map::{HashMap, PerCpuHashMap};
 use libc::{getrlimit, rlim_t, rlimit, RLIMIT_MEMLOCK, RLIM_INFINITY};
 pub use perf::PerfEventArray;
 use thiserror::Error;
@@ -24,10 +28,11 @@ use crate::{
     pin::PinError,
     sys::{
         bpf_create_map, bpf_get_object, bpf_map_freeze, bpf_map_get_fd_by_id,
-        bpf_map_get_info_by_fd, bpf_map_update_elem_ptr, bpf_pin_object, iter_map_ids,
-        SyscallError,
+        bpf_map_get_info_by_fd, bpf_map_get_next_key, bpf_map_update_elem_ptr, bpf_pin_object,
+        iter_map_ids, SyscallError,
     },
-    util::{bytes_of_bpf_name, KernelVersion},
+    util::{bytes_of_bpf_name, nr_cpus, KernelVersion},
+    Pod,
 };
 
 #[derive(Error, Debug)]
@@ -307,17 +312,16 @@ impl MapData {
     pub(crate) fn finalize(&mut self) -> Result<(), MapError> {
         let Self { obj, fd } = self;
         if !obj.data().is_empty() && obj.section_kind() != EbpfSectionKind::Bss {
-            log::error!(
+            log::trace!(
                 "map data is not empty, but section kind is not BSS, {:?}",
                 obj.section_kind()
             );
             let data = obj.data();
-            let value = u64::from_le_bytes(data[0..8].try_into().unwrap());
-            log::error!(
-                "bpf_map_update_elem_ptr, key_ptr: {:?}, value_ptr: {:?}, value: {}",
+            log::trace!(
+                "bpf_map_update_elem_ptr, key_ptr: {:?}, value_ptr: {:?}, value: {:?}",
                 &0 as *const _,
-                obj.data_mut().as_mut_ptr(),
-                value
+                obj.data().as_ptr(),
+                data
             );
             bpf_map_update_elem_ptr(fd.as_fd(), &0 as *const _, obj.data_mut().as_mut_ptr(), 0)
                 .map_err(|(_, io_error)| SyscallError {
@@ -553,6 +557,246 @@ pub fn loaded_maps() -> impl Iterator<Item = Result<MapInfo, MapError>> {
     })
 }
 
+pub(crate) fn check_kv_size<K, V>(map: &MapData) -> Result<(), MapError> {
+    let size = mem::size_of::<K>();
+    let expected = map.obj.key_size() as usize;
+    if size != expected {
+        return Err(MapError::InvalidKeySize { size, expected });
+    }
+    let size = mem::size_of::<V>();
+    let expected = map.obj.value_size() as usize;
+    if size != expected {
+        return Err(MapError::InvalidValueSize { size, expected });
+    };
+    Ok(())
+}
+
+/// An iterable map
+pub trait IterableMap<K: Pod, V> {
+    /// Get a generic map handle
+    fn map(&self) -> &MapData;
+
+    /// Get the value for the provided `key`
+    fn get(&self, key: &K) -> Result<V, MapError>;
+}
+
+/// Iterator returned by `map.keys()`.
+pub struct MapKeys<'coll, K: Pod> {
+    map: &'coll MapData,
+    err: bool,
+    key: Option<K>,
+}
+
+impl<'coll, K: Pod> MapKeys<'coll, K> {
+    fn new(map: &'coll MapData) -> Self {
+        Self {
+            map,
+            err: false,
+            key: None,
+        }
+    }
+}
+
+impl<K: Pod> Iterator for MapKeys<'_, K> {
+    type Item = Result<K, MapError>;
+
+    fn next(&mut self) -> Option<Result<K, MapError>> {
+        if self.err {
+            return None;
+        }
+
+        let fd = self.map.fd().as_fd();
+        let key =
+            bpf_map_get_next_key(fd, self.key.as_ref()).map_err(|(_, io_error)| SyscallError {
+                call: "bpf_map_get_next_key",
+                io_error,
+            });
+        match key {
+            Err(err) => {
+                self.err = true;
+                Some(Err(err.into()))
+            }
+            Ok(key) => {
+                self.key = key;
+                key.map(Ok)
+            }
+        }
+    }
+}
+
+/// Iterator returned by `map.iter()`.
+pub struct MapIter<'coll, K: Pod, V, I: IterableMap<K, V>> {
+    keys: MapKeys<'coll, K>,
+    map: &'coll I,
+    _v: PhantomData<V>,
+}
+
+impl<'coll, K: Pod, V, I: IterableMap<K, V>> MapIter<'coll, K, V, I> {
+    fn new(map: &'coll I) -> Self {
+        Self {
+            keys: MapKeys::new(map.map()),
+            map,
+            _v: PhantomData,
+        }
+    }
+}
+
+impl<K: Pod, V, I: IterableMap<K, V>> Iterator for MapIter<'_, K, V, I> {
+    type Item = Result<(K, V), MapError>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        loop {
+            match self.keys.next() {
+                Some(Ok(key)) => match self.map.get(&key) {
+                    Ok(value) => return Some(Ok((key, value))),
+                    Err(MapError::KeyNotFound) => continue,
+                    Err(e) => return Some(Err(e)),
+                },
+                Some(Err(e)) => return Some(Err(e)),
+                None => return None,
+            }
+        }
+    }
+}
+pub(crate) struct PerCpuKernelMem {
+    bytes: Vec<u8>,
+}
+
+impl PerCpuKernelMem {
+    pub(crate) fn as_mut_ptr(&mut self) -> *mut u8 {
+        self.bytes.as_mut_ptr()
+    }
+}
+
+/// A slice of per-CPU values.
+///
+/// Used by maps that implement per-CPU storage like [`PerCpuHashMap`].
+///
+/// # Examples
+///
+/// ```no_run
+/// # #[derive(thiserror::Error, Debug)]
+/// # enum Error {
+/// #     #[error(transparent)]
+/// #     IO(#[from] std::io::Error),
+/// #     #[error(transparent)]
+/// #     Map(#[from] aya::maps::MapError),
+/// #     #[error(transparent)]
+/// #     Ebpf(#[from] aya::EbpfError)
+/// # }
+/// # let bpf = aya::Ebpf::load(&[])?;
+/// use aya::maps::PerCpuValues;
+/// use aya::util::nr_cpus;
+///
+/// let values = PerCpuValues::try_from(vec![42u32; nr_cpus()?])?;
+/// # Ok::<(), Error>(())
+/// ```
+#[derive(Debug)]
+pub struct PerCpuValues<T: Pod> {
+    values: Box<[T]>,
+}
+
+impl<T: Pod> TryFrom<Vec<T>> for PerCpuValues<T> {
+    type Error = io::Error;
+
+    fn try_from(values: Vec<T>) -> Result<Self, Self::Error> {
+        let nr_cpus = nr_cpus()?;
+        if values.len() != nr_cpus {
+            return Err(io::Error::new(
+                io::ErrorKind::InvalidInput,
+                format!("not enough values ({}), nr_cpus: {}", values.len(), nr_cpus),
+            ));
+        }
+        Ok(Self {
+            values: values.into_boxed_slice(),
+        })
+    }
+}
+
+impl<T: Pod> PerCpuValues<T> {
+    pub(crate) fn alloc_kernel_mem() -> Result<PerCpuKernelMem, io::Error> {
+        let value_size = (mem::size_of::<T>() + 7) & !7;
+        Ok(PerCpuKernelMem {
+            bytes: vec![0u8; nr_cpus()? * value_size],
+        })
+    }
+
+    pub(crate) unsafe fn from_kernel_mem(mem: PerCpuKernelMem) -> Self {
+        let mem_ptr = mem.bytes.as_ptr() as usize;
+        let value_size = (mem::size_of::<T>() + 7) & !7;
+        let mut values = Vec::new();
+        let mut offset = 0;
+        while offset < mem.bytes.len() {
+            values.push(ptr::read_unaligned((mem_ptr + offset) as *const _));
+            offset += value_size;
+        }
+
+        Self {
+            values: values.into_boxed_slice(),
+        }
+    }
+
+    pub(crate) fn build_kernel_mem(&self) -> Result<PerCpuKernelMem, io::Error> {
+        let mut mem = Self::alloc_kernel_mem()?;
+        let mem_ptr = mem.as_mut_ptr() as usize;
+        let value_size = (mem::size_of::<T>() + 7) & !7;
+        for i in 0..self.values.len() {
+            unsafe { ptr::write_unaligned((mem_ptr + i * value_size) as *mut _, self.values[i]) };
+        }
+
+        Ok(mem)
+    }
+}
+
+impl<T: Pod> Deref for PerCpuValues<T> {
+    type Target = Box<[T]>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.values
+    }
+}
+
+#[cfg(test)]
+mod test_utils {
+    use aya_obj::{
+        generated::{bpf_cmd, bpf_map_type},
+        maps::bpf_map_def,
+    };
+
+    use crate::{
+        maps::MapData,
+        obj::{self, maps::LegacyMap, EbpfSectionKind},
+        sys::{override_syscall, Syscall},
+    };
+
+    pub(super) fn new_map(obj: obj::Map) -> MapData {
+        override_syscall(|call| match call {
+            Syscall::Ebpf {
+                cmd: bpf_cmd::BPF_MAP_CREATE,
+                ..
+            } => Ok(crate::MockableFd::mock_signed_fd().into()),
+            call => panic!("unexpected syscall {:?}", call),
+        });
+        MapData::create(obj, "foo", None).unwrap()
+    }
+
+    pub(super) fn new_obj_map<K>(map_type: bpf_map_type) -> obj::Map {
+        obj::Map::Legacy(LegacyMap {
+            def: bpf_map_def {
+                map_type: map_type as u32,
+                key_size: std::mem::size_of::<K>() as u32,
+                value_size: 4,
+                max_entries: 1024,
+                ..Default::default()
+            },
+            section_index: 0,
+            section_kind: EbpfSectionKind::Maps,
+            data: Vec::new(),
+            symbol_index: None,
+        })
+    }
+}
+
 // Implements TryFrom<Map> for different map implementations. Different map implementations can be
 // constructed from different variants of the map enum. Also, the implementation may have type
 // parameters (which we assume all have the bound `Pod` and nothing else).
@@ -611,3 +855,8 @@ impl_try_from_map!(() {
 impl_try_from_map!(() {
     PerfEventArray,
 });
+
+impl_try_from_map!((K, V) {
+    HashMap from HashMap|LruHashMap,
+    PerCpuHashMap from PerCpuHashMap|PerCpuLruHashMap,
+});

+ 1 - 1
aya/src/maps/perf/perf_buffer.rs

@@ -243,7 +243,7 @@ impl PerfBuffer {
             let buf = &mut buffers[buf_n];
 
             let event_start = tail % self.size;
-            let mut event_buf = [0u8; size_of::<perf_event_header>()];
+            let mut event_buf = [0u8; core::mem::size_of::<perf_event_header>()];
             fill_buf(event_start, base, self.size, &mut event_buf);
             let event =
                 unsafe { ptr::read_unaligned(event_buf.as_ptr() as *const perf_event_header) };

+ 114 - 2
aya/src/sys/bpf.rs

@@ -3,7 +3,9 @@ use core::{
     ffi::{c_char, CStr},
     mem, slice,
 };
-use std::{ffi::CString, format, io, iter, os::fd::*, string::String, vec, vec::Vec};
+use std::{
+    ffi::CString, format, io, iter, mem::MaybeUninit, os::fd::*, string::String, vec, vec::Vec,
+};
 
 use assert_matches::assert_matches;
 use aya_obj::{
@@ -17,7 +19,7 @@ use libc::{ENOENT, ENOSPC};
 
 use crate::{
     bpf::BPF_OBJ_NAME_LEN,
-    maps::MapData,
+    maps::{MapData, PerCpuValues},
     sys::{syscall, SysResult, Syscall, SyscallError},
     util::KernelVersion,
     Pod, VerifierLogLevel,
@@ -779,6 +781,116 @@ pub(crate) fn is_bpf_cookie_supported() -> bool {
 
     bpf_prog_load(&mut attr).is_ok()
 }
+
+pub(crate) fn bpf_map_delete_elem<K: Pod>(fd: BorrowedFd<'_>, key: &K) -> SysResult<i64> {
+    let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
+
+    let u = unsafe { &mut attr.__bindgen_anon_2 };
+    u.map_fd = fd.as_raw_fd() as u32;
+    u.key = key as *const _ as u64;
+
+    sys_bpf(bpf_cmd::BPF_MAP_DELETE_ELEM, &mut attr)
+}
+
+pub(crate) fn bpf_map_get_next_key<K: Pod>(
+    fd: BorrowedFd<'_>,
+    key: Option<&K>,
+) -> SysResult<Option<K>> {
+    let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
+    let mut next_key = MaybeUninit::uninit();
+
+    let u = unsafe { &mut attr.__bindgen_anon_2 };
+    u.map_fd = fd.as_raw_fd() as u32;
+    if let Some(key) = key {
+        u.key = key as *const _ as u64;
+    }
+    u.__bindgen_anon_1.next_key = &mut next_key as *mut _ as u64;
+
+    match sys_bpf(bpf_cmd::BPF_MAP_GET_NEXT_KEY, &mut attr) {
+        Ok(_) => Ok(Some(unsafe { next_key.assume_init() })),
+        Err((_, io_error)) if io_error.raw_os_error() == Some(ENOENT) => Ok(None),
+        Err(e) => Err(e),
+    }
+}
+
+fn lookup<K: Pod, V: Pod>(
+    fd: BorrowedFd<'_>,
+    key: Option<&K>,
+    flags: u64,
+    cmd: bpf_cmd,
+) -> SysResult<Option<V>> {
+    let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
+    let mut value = MaybeUninit::zeroed();
+
+    let u = unsafe { &mut attr.__bindgen_anon_2 };
+    u.map_fd = fd.as_raw_fd() as u32;
+    if let Some(key) = key {
+        u.key = key as *const _ as u64;
+    }
+    u.__bindgen_anon_1.value = &mut value as *mut _ as u64;
+    u.flags = flags;
+
+    match sys_bpf(cmd, &mut attr) {
+        Ok(_) => Ok(Some(unsafe { value.assume_init() })),
+        Err((_, io_error)) if io_error.raw_os_error() == Some(ENOENT) => Ok(None),
+        Err(e) => Err(e),
+    }
+}
+
+pub(crate) fn bpf_map_lookup_elem<K: Pod, V: Pod>(
+    fd: BorrowedFd<'_>,
+    key: &K,
+    flags: u64,
+) -> SysResult<Option<V>> {
+    lookup(fd, Some(key), flags, bpf_cmd::BPF_MAP_LOOKUP_ELEM)
+}
+
+pub(crate) fn bpf_map_lookup_elem_per_cpu<K: Pod, V: Pod>(
+    fd: BorrowedFd<'_>,
+    key: &K,
+    flags: u64,
+) -> SysResult<Option<PerCpuValues<V>>> {
+    let mut mem = PerCpuValues::<V>::alloc_kernel_mem().map_err(|io_error| (-1, io_error))?;
+    match bpf_map_lookup_elem_ptr(fd, Some(key), mem.as_mut_ptr(), flags) {
+        Ok(_) => Ok(Some(unsafe { PerCpuValues::from_kernel_mem(mem) })),
+        Err((_, io_error)) if io_error.raw_os_error() == Some(ENOENT) => Ok(None),
+        Err(e) => Err(e),
+    }
+}
+
+pub(crate) fn bpf_map_update_elem_per_cpu<K: Pod, V: Pod>(
+    fd: BorrowedFd<'_>,
+    key: &K,
+    values: &PerCpuValues<V>,
+    flags: u64,
+) -> SysResult<i64> {
+    let mut mem = values.build_kernel_mem().map_err(|e| (-1, e))?;
+    bpf_map_update_elem_ptr(fd, key, mem.as_mut_ptr(), flags)
+}
+
+pub(crate) fn bpf_map_lookup_elem_ptr<K: Pod, V>(
+    fd: BorrowedFd<'_>,
+    key: Option<&K>,
+    value: *mut V,
+    flags: u64,
+) -> SysResult<Option<()>> {
+    let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
+
+    let u = unsafe { &mut attr.__bindgen_anon_2 };
+    u.map_fd = fd.as_raw_fd() as u32;
+    if let Some(key) = key {
+        u.key = key as *const _ as u64;
+    }
+    u.__bindgen_anon_1.value = value as u64;
+    u.flags = flags;
+
+    match sys_bpf(bpf_cmd::BPF_MAP_LOOKUP_ELEM, &mut attr) {
+        Ok(_) => Ok(Some(())),
+        Err((_, io_error)) if io_error.raw_os_error() == Some(ENOENT) => Ok(None),
+        Err(e) => Err(e),
+    }
+}
+
 fn bpf_prog_load(attr: &mut bpf_attr) -> SysResult<OwnedFd> {
     // SAFETY: BPF_PROG_LOAD returns a new file descriptor.
     unsafe { fd_sys_bpf(bpf_cmd::BPF_PROG_LOAD, attr) }

+ 4 - 4
aya/src/sys/mod.rs

@@ -1,4 +1,5 @@
 pub(crate) mod bpf;
+#[cfg(test)]
 pub(crate) mod fake;
 pub(crate) mod perf_event;
 
@@ -11,12 +12,11 @@ use std::{
 
 use aya_obj::generated::{bpf_attr, bpf_cmd, perf_event_attr};
 pub(crate) use bpf::*;
+#[cfg(test)]
+pub(crate) use fake::*;
 use libc::{pid_t, SYS_bpf, SYS_perf_event_open};
 use thiserror::Error;
 
-#[cfg(test)]
-use crate::sys::fake::{TEST_MMAP_RET, TEST_SYSCALL};
-
 pub(crate) type SysResult<T> = Result<T, (i64, io::Error)>;
 
 pub(crate) enum Syscall<'a> {
@@ -82,7 +82,7 @@ fn syscall(call: Syscall<'_>) -> SysResult<i64> {
     #[cfg(test)]
     return TEST_SYSCALL.with(|test_impl| unsafe { test_impl.borrow()(call) });
 
-    info!("syscall: {:?}", call);
+    log::debug!("syscall: {:?}", call);
     #[cfg_attr(test, allow(unreachable_code))]
     {
         let ret = unsafe {

+ 7 - 0
aya/src/util.rs

@@ -175,3 +175,10 @@ pub(crate) fn page_size() -> usize {
     // (unsafe { sysconf(_SC_PAGESIZE) }) as usize
     4096
 }
+
+/// Get the number of possible cpus.
+///
+/// See `/sys/devices/system/cpu/possible`.
+pub fn nr_cpus() -> Result<usize, io::Error> {
+    Ok(possible_cpus()?.len())
+}