From c4262f793dbce8558c5823a94db257ac227a5a0b Mon Sep 17 00:00:00 2001 From: Kenjiro Nakayama Date: Wed, 25 May 2022 21:48:58 +0900 Subject: [PATCH] Add support for BPF_MAP_TYPE_BLOOM_FILTER This patch adds support for `BPF_MAP_TYPE_BLOOM_FILTER`. --- aya/src/maps/bloom_filter.rs | 313 +++++++++++++++++++++++++++ aya/src/maps/mod.rs | 2 + aya/src/maps/stack_trace.rs | 2 +- aya/src/sys/bpf.rs | 8 +- bpf/aya-bpf/src/maps/bloom_filter.rs | 76 +++++++ bpf/aya-bpf/src/maps/mod.rs | 2 + 6 files changed, 399 insertions(+), 4 deletions(-) create mode 100644 aya/src/maps/bloom_filter.rs create mode 100644 bpf/aya-bpf/src/maps/bloom_filter.rs diff --git a/aya/src/maps/bloom_filter.rs b/aya/src/maps/bloom_filter.rs new file mode 100644 index 00000000..0b7091ef --- /dev/null +++ b/aya/src/maps/bloom_filter.rs @@ -0,0 +1,313 @@ +//! A Bloom Filter. +use std::{convert::TryFrom, marker::PhantomData, ops::Deref}; + +use core::mem; + +use crate::{ + generated::bpf_map_type::BPF_MAP_TYPE_BLOOM_FILTER, + maps::{Map, MapError, MapRef, MapRefMut}, + sys::{bpf_map_lookup_elem_ptr, bpf_map_push_elem}, + Pod, +}; + +/// A Bloom Filter. +/// +/// # Minimum kernel version +/// +/// The minimum kernel version required to use this feature is 5.16. +/// +/// # Examples +/// +/// ```no_run +/// # let bpf = aya::Bpf::load(&[])?; +/// use aya::maps::bloom_filter::BloomFilter; +/// use std::convert::TryFrom; +/// +/// let mut bloom_filter = BloomFilter::try_from(bpf.map_mut("BLOOM_FILTER")?)?; +/// +/// bloom_filter.insert(1, 0)?; +/// +/// assert!(bloom_filter.contains(1, 0).is_ok()); +/// assert!(bloom_filter.contains(2, 0).is_err()); +/// +/// # Ok::<(), aya::BpfError>(()) +/// ``` + +#[doc(alias = "BPF_MAP_TYPE_BLOOM_FILTER")] +pub struct BloomFilter, V: Pod> { + inner: T, + _v: PhantomData, +} + +impl, V: Pod> BloomFilter { + pub(crate) fn new(map: T) -> Result, MapError> { + let map_type = map.obj.def.map_type; + + // validate the map definition + if map_type != BPF_MAP_TYPE_BLOOM_FILTER as u32 { + return Err(MapError::InvalidMapType { + map_type: map_type as u32, + }); + } + + let size = mem::size_of::(); + let expected = map.obj.def.value_size as usize; + if size != expected { + return Err(MapError::InvalidValueSize { size, expected }); + }; + + let _ = map.fd_or_err()?; + + Ok(BloomFilter { + inner: map, + _v: PhantomData, + }) + } + + /// Query the existence of the elelment. + pub fn contains(&self, mut value: V, flags: u64) -> Result<(), MapError> { + let fd = self.inner.deref().fd_or_err()?; + + bpf_map_lookup_elem_ptr::(fd, None, &mut value, flags) + .map_err(|(code, io_error)| MapError::SyscallError { + call: "bpf_map_lookup_elem".to_owned(), + code, + io_error, + })? + .ok_or(MapError::ElementNotFound)?; + Ok(()) + } + + /// Inserts a value into the map. + pub fn insert(&self, value: V, flags: u64) -> Result<(), MapError> { + let fd = self.inner.deref().fd_or_err()?; + bpf_map_push_elem(fd, &value, flags).map_err(|(code, io_error)| { + MapError::SyscallError { + call: "bpf_map_push_elem".to_owned(), + code, + io_error, + } + })?; + Ok(()) + } +} + +impl TryFrom for BloomFilter { + type Error = MapError; + + fn try_from(a: MapRef) -> Result, MapError> { + BloomFilter::new(a) + } +} + +impl TryFrom for BloomFilter { + type Error = MapError; + + fn try_from(a: MapRefMut) -> Result, MapError> { + BloomFilter::new(a) + } +} + +impl<'a, V: Pod> TryFrom<&'a Map> for BloomFilter<&'a Map, V> { + type Error = MapError; + + fn try_from(a: &'a Map) -> Result, MapError> { + BloomFilter::new(a) + } +} + +impl<'a, V: Pod> TryFrom<&'a mut Map> for BloomFilter<&'a mut Map, V> { + type Error = MapError; + + fn try_from(a: &'a mut Map) -> Result, MapError> { + BloomFilter::new(a) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + bpf_map_def, + generated::{ + bpf_cmd, + bpf_map_type::{BPF_MAP_TYPE_BLOOM_FILTER, BPF_MAP_TYPE_PERF_EVENT_ARRAY}, + }, + obj, + sys::{override_syscall, SysResult, Syscall}, + }; + use libc::{EFAULT, ENOENT}; + use std::io; + + fn new_obj_map() -> obj::Map { + obj::Map { + def: bpf_map_def { + map_type: BPF_MAP_TYPE_BLOOM_FILTER as u32, + key_size: 4, + value_size: 4, + max_entries: 1024, + ..Default::default() + }, + section_index: 0, + symbol_index: 0, + data: Vec::new(), + kind: obj::MapKind::Other, + } + } + + fn sys_error(value: i32) -> SysResult { + Err((-1, io::Error::from_raw_os_error(value))) + } + + #[test] + fn test_wrong_value_size() { + let map = Map { + obj: new_obj_map(), + fd: None, + pinned: false, + }; + assert!(matches!( + BloomFilter::<_, u16>::new(&map), + Err(MapError::InvalidValueSize { + size: 2, + expected: 4 + }) + )); + } + + #[test] + fn test_try_from_wrong_map() { + let map = Map { + obj: obj::Map { + def: bpf_map_def { + map_type: BPF_MAP_TYPE_PERF_EVENT_ARRAY as u32, + key_size: 4, + value_size: 4, + max_entries: 1024, + ..Default::default() + }, + section_index: 0, + symbol_index: 0, + data: Vec::new(), + kind: obj::MapKind::Other, + }, + fd: None, + pinned: false, + }; + + assert!(matches!( + BloomFilter::<_, u32>::try_from(&map), + Err(MapError::InvalidMapType { .. }) + )); + } + + #[test] + fn test_new_not_created() { + let mut map = Map { + obj: new_obj_map(), + fd: None, + pinned: false, + }; + + assert!(matches!( + BloomFilter::<_, u32>::new(&mut map), + Err(MapError::NotCreated { .. }) + )); + } + + #[test] + fn test_new_ok() { + let mut map = Map { + obj: new_obj_map(), + fd: Some(42), + pinned: false, + }; + + assert!(BloomFilter::<_, u32>::new(&mut map).is_ok()); + } + + #[test] + fn test_try_from_ok() { + let map = Map { + obj: new_obj_map(), + fd: Some(42), + pinned: false, + }; + assert!(BloomFilter::<_, u32>::try_from(&map).is_ok()) + } + + #[test] + fn test_insert_syscall_error() { + override_syscall(|_| sys_error(EFAULT)); + + let mut map = Map { + obj: new_obj_map(), + fd: Some(42), + pinned: false, + }; + let bloom_filter = BloomFilter::<_, u32>::new(&mut map).unwrap(); + + assert!(matches!( + bloom_filter.insert(1, 0), + Err(MapError::SyscallError { call, code: -1, io_error }) if call == "bpf_map_push_elem" && io_error.raw_os_error() == Some(EFAULT) + )); + } + + #[test] + fn test_insert_ok() { + override_syscall(|call| match call { + Syscall::Bpf { + cmd: bpf_cmd::BPF_MAP_UPDATE_ELEM, + .. + } => Ok(1), + _ => sys_error(EFAULT), + }); + + let mut map = Map { + obj: new_obj_map(), + fd: Some(42), + pinned: false, + }; + + let bloom_filter = BloomFilter::<_, u32>::new(&mut map).unwrap(); + assert!(bloom_filter.insert(0, 42).is_ok()); + } + + #[test] + fn test_contains_syscall_error() { + override_syscall(|_| sys_error(EFAULT)); + let map = Map { + obj: new_obj_map(), + fd: Some(42), + pinned: false, + }; + let bloom_filter = BloomFilter::<_, u32>::new(&map).unwrap(); + + assert!(matches!( + bloom_filter.contains(1, 0), + Err(MapError::SyscallError { call, code: -1, io_error }) if call == "bpf_map_lookup_elem" && io_error.raw_os_error() == Some(EFAULT) + )); + } + + #[test] + fn test_contains_not_found() { + override_syscall(|call| match call { + Syscall::Bpf { + cmd: bpf_cmd::BPF_MAP_LOOKUP_ELEM, + .. + } => sys_error(ENOENT), + _ => sys_error(EFAULT), + }); + let map = Map { + obj: new_obj_map(), + fd: Some(42), + pinned: false, + }; + let bloom_filter = BloomFilter::<_, u32>::new(&map).unwrap(); + + assert!(matches!( + bloom_filter.contains(1, 0), + Err(MapError::ElementNotFound) + )); + } +} diff --git a/aya/src/maps/mod.rs b/aya/src/maps/mod.rs index 81a0ec35..dcbca570 100644 --- a/aya/src/maps/mod.rs +++ b/aya/src/maps/mod.rs @@ -49,6 +49,7 @@ use crate::{ mod map_lock; pub mod array; +pub mod bloom_filter; pub mod hash_map; pub mod lpm_trie; pub mod perf; @@ -421,6 +422,7 @@ impl TryFrom for bpf_map_type { x if x == BPF_MAP_TYPE_LRU_HASH as u32 => BPF_MAP_TYPE_LRU_HASH, x if x == BPF_MAP_TYPE_LRU_PERCPU_HASH as u32 => BPF_MAP_TYPE_LRU_PERCPU_HASH, x if x == BPF_MAP_TYPE_LPM_TRIE as u32 => BPF_MAP_TYPE_LPM_TRIE, + x if x == BPF_MAP_TYPE_BLOOM_FILTER as u32 => BPF_MAP_TYPE_BLOOM_FILTER, x if x == BPF_MAP_TYPE_ARRAY_OF_MAPS as u32 => BPF_MAP_TYPE_ARRAY_OF_MAPS, x if x == BPF_MAP_TYPE_HASH_OF_MAPS as u32 => BPF_MAP_TYPE_HASH_OF_MAPS, x if x == BPF_MAP_TYPE_DEVMAP as u32 => BPF_MAP_TYPE_DEVMAP, diff --git a/aya/src/maps/stack_trace.rs b/aya/src/maps/stack_trace.rs index d412413b..e4aeac8b 100644 --- a/aya/src/maps/stack_trace.rs +++ b/aya/src/maps/stack_trace.rs @@ -115,7 +115,7 @@ impl> StackTraceMap { let fd = self.inner.fd_or_err()?; let mut frames = vec![0; self.max_stack_depth]; - bpf_map_lookup_elem_ptr(fd, stack_id, frames.as_mut_ptr(), flags) + bpf_map_lookup_elem_ptr(fd, Some(stack_id), frames.as_mut_ptr(), flags) .map_err(|(code, io_error)| MapError::SyscallError { call: "bpf_map_lookup_elem".to_owned(), code, diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs index c175649f..d6223434 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -193,7 +193,7 @@ pub(crate) fn bpf_map_lookup_elem_per_cpu( flags: u64, ) -> Result>, (c_long, io::Error)> { let mut mem = PerCpuValues::::alloc_kernel_mem().map_err(|io_error| (-1, io_error))?; - match bpf_map_lookup_elem_ptr(fd, key, mem.as_mut_ptr(), flags) { + 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), @@ -202,7 +202,7 @@ pub(crate) fn bpf_map_lookup_elem_per_cpu( pub(crate) fn bpf_map_lookup_elem_ptr( fd: RawFd, - key: &K, + key: Option<&K>, value: *mut V, flags: u64, ) -> Result, (c_long, io::Error)> { @@ -210,7 +210,9 @@ pub(crate) fn bpf_map_lookup_elem_ptr( let u = unsafe { &mut attr.__bindgen_anon_2 }; u.map_fd = fd as u32; - u.key = key as *const _ as u64; + if let Some(key) = key { + u.key = key as *const _ as u64; + } u.__bindgen_anon_1.value = value as u64; u.flags = flags; diff --git a/bpf/aya-bpf/src/maps/bloom_filter.rs b/bpf/aya-bpf/src/maps/bloom_filter.rs new file mode 100644 index 00000000..25a1e749 --- /dev/null +++ b/bpf/aya-bpf/src/maps/bloom_filter.rs @@ -0,0 +1,76 @@ +use core::{marker::PhantomData, mem}; + +use aya_bpf_cty::c_void; + +use crate::{ + bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_BLOOM_FILTER}, + helpers::{bpf_map_peek_elem, bpf_map_push_elem}, + maps::PinningType, +}; + +#[repr(transparent)] +pub struct BloomFilter { + def: bpf_map_def, + _t: PhantomData, +} + +impl BloomFilter { + pub const fn with_max_entries(max_entries: u32, flags: u32) -> BloomFilter { + BloomFilter { + def: build_def::( + BPF_MAP_TYPE_BLOOM_FILTER, + max_entries, + flags, + PinningType::None, + ), + _t: PhantomData, + } + } + + pub const fn pinned(max_entries: u32, flags: u32) -> BloomFilter { + BloomFilter { + def: build_def::( + BPF_MAP_TYPE_BLOOM_FILTER, + max_entries, + flags, + PinningType::ByName, + ), + _t: PhantomData, + } + } + + #[inline] + pub fn contains(&mut self, value: &T) -> Result<(), i64> { + let ret = unsafe { + bpf_map_peek_elem( + &mut self.def as *mut _ as *mut _, + value as *const _ as *mut c_void, + ) + }; + (ret == 0).then(|| ()).ok_or(ret) + } + + #[inline] + pub fn insert(&mut self, value: &T, flags: u64) -> Result<(), i64> { + let ret = unsafe { + bpf_map_push_elem( + &mut self.def as *mut _ as *mut _, + value as *const _ as *const _, + flags, + ) + }; + (ret == 0).then(|| ()).ok_or(ret) + } +} + +const fn build_def(ty: u32, max_entries: u32, flags: u32, pin: PinningType) -> bpf_map_def { + bpf_map_def { + type_: ty, + key_size: 0, + value_size: mem::size_of::() as u32, + max_entries, + map_flags: flags, + id: 0, + pinning: pin as u32, + } +} diff --git a/bpf/aya-bpf/src/maps/mod.rs b/bpf/aya-bpf/src/maps/mod.rs index fc4f1732..b29cda39 100644 --- a/bpf/aya-bpf/src/maps/mod.rs +++ b/bpf/aya-bpf/src/maps/mod.rs @@ -6,6 +6,7 @@ pub(crate) enum PinningType { } pub mod array; +pub mod bloom_filter; pub mod hash_map; pub mod lpm_trie; pub mod per_cpu_array; @@ -17,6 +18,7 @@ pub mod sock_map; pub mod stack_trace; pub use array::Array; +pub use bloom_filter::BloomFilter; pub use hash_map::{HashMap, LruHashMap, LruPerCpuHashMap, PerCpuHashMap}; pub use lpm_trie::LpmTrie; pub use per_cpu_array::PerCpuArray;