Skip to content

Commit

Permalink
Add crud support for BPF_MAP_TYPE_BLOOM_FILTER
Browse files Browse the repository at this point in the history
Update map_key documentation to include bloom filter

Add bloom filter test

Add specific lookup for bloom filter

Finalize PR

Improve error message
  • Loading branch information
barthr committed May 8, 2024
1 parent c10fffa commit 291b0de
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 3 deletions.
48 changes: 45 additions & 3 deletions libbpf-rs/src/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -556,10 +556,11 @@ impl MapHandle {
Ok(ncpu * aligned_val_size)
}

/// Apply a key check and return a null pointer in case of dealing with queue/stack map,
/// before passing the key to the bpf functions that support the map of type queue/stack.
/// Apply a key check and return a null pointer in case of dealing with queue/stack/bloom-filter map,
/// before passing the key to the bpf functions that support the map of type queue/stack/bloom-filter.
fn map_key(&self, key: &[u8]) -> *const c_void {
if self.key_size() == 0 && matches!(self.map_type(), MapType::Queue | MapType::Stack) {
// For all they keyless maps we null out the key per documentation of libbpf
if self.key_size() == 0 && self.map_type().is_keyless() {
return ptr::null();
}

Expand Down Expand Up @@ -631,7 +632,13 @@ impl MapHandle {
///
/// If the map is one of the per-cpu data structures, the function [`MapHandle::lookup_percpu()`]
/// must be used.
/// If the map is of type bloom_filter the function [`MapHandle::lookup_bloom_filter()`] must be used
pub fn lookup(&self, key: &[u8], flags: MapFlags) -> Result<Option<Vec<u8>>> {
if self.map_type().is_bloom_filter() {
return Err(Error::with_invalid_data(
"lookup_bloom_filter() must be used for bloom filter maps",
));
}
if self.map_type().is_percpu() {
return Err(Error::with_invalid_data(format!(
"lookup_percpu() must be used for per-cpu maps (type of the map is {})",
Expand All @@ -643,6 +650,30 @@ impl MapHandle {
self.lookup_raw(key, flags, out_size)
}

/// Returns if the given value is likely present in bloom_filter as `bool`.
///
/// `value` must have exactly [`MapHandle::value_size()`] elements.
pub fn lookup_bloom_filter(&self, value: &[u8]) -> Result<bool> {
let ret = unsafe {
libbpf_sys::bpf_map_lookup_elem(
self.fd.as_raw_fd(),
ptr::null(),
value.to_vec().as_mut_ptr() as *mut c_void,
)
};

if ret == 0 {
Ok(true)
} else {
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::NotFound {
Ok(false)
} else {
Err(Error::from(err))
}
}
}

/// Returns one value per cpu as `Vec` of `Vec` of `u8` for per per-cpu maps.
///
/// For normal maps, [`MapHandle::lookup()`] must be used.
Expand Down Expand Up @@ -1009,6 +1040,17 @@ impl MapType {
)
}

/// Returns if the map is keyless map type as per documentation of libbpf
/// Keyless map types are: Queues, Stacks and Bloom Filters
fn is_keyless(&self) -> bool {
matches!(self, MapType::Queue | MapType::Stack | MapType::BloomFilter)
}

/// Returns if the map is of bloom filter type
pub fn is_bloom_filter(&self) -> bool {
MapType::BloomFilter.eq(self)
}

/// Detects if host kernel supports this BPF map type.
///
/// Make sure the process has required set of CAP_* permissions (or runs as
Expand Down
6 changes: 6 additions & 0 deletions libbpf-rs/tests/bin/src/tracepoint.bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,10 @@ struct {
__type(value, __u32);
} stack SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_BLOOM_FILTER);
__uint(max_entries, 5);
__type(value, __u32);
} bloom_filter SEC(".maps");

char LICENSE[] SEC("license") = "GPL";
Binary file modified libbpf-rs/tests/bin/tracepoint.bpf.o
Binary file not shown.
53 changes: 53 additions & 0 deletions libbpf-rs/tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,58 @@ fn test_sudo_object_map_queue_crud() {
.is_none());
}

/// Test CRUD operations on map of type bloomfilter.
#[test]
fn test_sudo_object_map_bloom_filter_crud() {
bump_rlimit_mlock();

let obj = get_test_object("tracepoint.bpf.o");
let bloom_filter = obj
.map("bloom_filter")
.expect("failed to find bloom_filter map");

let key: [u8; 0] = [];
let value1 = 1337u32.to_ne_bytes();
let value2 = 2674u32.to_ne_bytes();

bloom_filter
.update(&key, &value1, MapFlags::ANY)
.expect("failed to add entry value1 to bloom filter");

bloom_filter
.update(&key, &value2, MapFlags::ANY)
.expect("failed to add entry value2 in bloom filter");

// Non empty keys should result in an error
bloom_filter
.update(&value1, &value1, MapFlags::ANY)
.expect_err("Non empty key should return an error");

for inserted_value in [value1, value2] {
let val = bloom_filter
.lookup_bloom_filter(&inserted_value)
.expect("failed retrieve item from bloom filter");

assert!(val);
}
// Test non existing element
let enoent_found = bloom_filter
.lookup_bloom_filter(&[1, 2, 3, 4])
.expect("failed retrieve item from bloom filter");

assert!(!enoent_found);

// Calling lookup should result in an error
bloom_filter
.lookup(&[1, 2, 3, 4], MapFlags::ANY)
.expect_err("lookup should fail since we should use lookup_bloom_filter");

// Deleting should not be possible
bloom_filter
.lookup_and_delete(&key)
.expect_err("Expect delete to fail");
}

/// Test CRUD operations on map of type stack.
#[test]
fn test_sudo_object_map_stack_crud() {
Expand All @@ -550,6 +602,7 @@ fn test_sudo_object_map_stack_crud() {
.lookup(&key, MapFlags::ANY)
.expect("failed to pop from stack")
.expect("failed to retrieve value");

assert_eq!(val.len(), 4);
assert_eq!(&val, &value2);

Expand Down

0 comments on commit 291b0de

Please sign in to comment.