| #![allow(clippy::let_unit_value)] | |
| #![warn(clippy::absolute_paths)] | |
| mod common; | |
| use std::collections::HashSet; | |
| use std::env::current_exe; | |
| use std::ffi::c_int; | |
| use std::ffi::c_void; | |
| use std::ffi::OsStr; | |
| use std::fs; | |
| use std::hint; | |
| use std::io; | |
| use std::io::Read; | |
| use std::mem::size_of; | |
| use std::mem::size_of_val; | |
| use std::os::unix::io::AsFd; | |
| use std::path::Path; | |
| use std::path::PathBuf; | |
| use std::ptr; | |
| use std::ptr::addr_of; | |
| use std::slice; | |
| use std::sync::mpsc::channel; | |
| use std::time::Duration; | |
| use libbpf_rs::num_possible_cpus; | |
| use libbpf_rs::AsRawLibbpf; | |
| use libbpf_rs::Iter; | |
| use libbpf_rs::Linker; | |
| use libbpf_rs::Map; | |
| use libbpf_rs::MapCore; | |
| use libbpf_rs::MapFlags; | |
| use libbpf_rs::MapHandle; | |
| use libbpf_rs::MapInfo; | |
| use libbpf_rs::MapType; | |
| use libbpf_rs::Object; | |
| use libbpf_rs::ObjectBuilder; | |
| use libbpf_rs::Program; | |
| use libbpf_rs::ProgramInput; | |
| use libbpf_rs::ProgramType; | |
| use libbpf_rs::TracepointOpts; | |
| use libbpf_rs::UprobeOpts; | |
| use libbpf_rs::UsdtOpts; | |
| use libbpf_rs::UserRingBuffer; | |
| use plain::Plain; | |
| use probe::probe; | |
| use scopeguard::defer; | |
| use tempfile::NamedTempFile; | |
| use test_tag::tag; | |
| use crate::common::bump_rlimit_mlock; | |
| use crate::common::get_map; | |
| use crate::common::get_map_mut; | |
| use crate::common::get_prog_mut; | |
| use crate::common::get_test_object; | |
| use crate::common::get_test_object_path; | |
| use crate::common::open_test_object; | |
| /// A helper function for instantiating a `RingBuffer` with a callback meant to | |
| /// be invoked when `action` is executed and that is intended to trigger a write | |
| /// to said `RingBuffer` from kernel space, which then reads a single `i32` from | |
| /// this buffer from user space and returns it. | |
| fn with_ringbuffer<F>(map: &Map, action: F) -> i32 | |
| where | |
| F: FnOnce(), | |
| { | |
| let mut value = 0i32; | |
| { | |
| let callback = |data: &[u8]| { | |
| plain::copy_from_bytes(&mut value, data).expect("Wrong size"); | |
| 0 | |
| }; | |
| let mut builder = libbpf_rs::RingBufferBuilder::new(); | |
| builder.add(map, callback).expect("failed to add ringbuf"); | |
| let mgr = builder.build().expect("failed to build"); | |
| action(); | |
| mgr.consume().expect("failed to consume ringbuf"); | |
| } | |
| value | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_build_and_load() { | |
| bump_rlimit_mlock(); | |
| get_test_object("runqslower.bpf.o"); | |
| } | |
| #[test] | |
| fn test_object_build_from_memory() { | |
| let obj_path = get_test_object_path("runqslower.bpf.o"); | |
| let contents = fs::read(obj_path).expect("failed to read object file"); | |
| let mut builder = ObjectBuilder::default(); | |
| let obj = builder | |
| .name("memory name") | |
| .unwrap() | |
| .open_memory(&contents) | |
| .expect("failed to build object"); | |
| let name = obj.name().expect("failed to get object name"); | |
| assert!(name == "memory name"); | |
| let obj = unsafe { Object::from_ptr(obj.take_ptr()) }; | |
| let name = obj.name().expect("failed to get object name"); | |
| assert!(name == "memory name"); | |
| } | |
| #[test] | |
| fn test_object_build_from_memory_empty_name() { | |
| let obj_path = get_test_object_path("runqslower.bpf.o"); | |
| let contents = fs::read(obj_path).expect("failed to read object file"); | |
| let mut builder = ObjectBuilder::default(); | |
| let obj = builder | |
| .name("") | |
| .unwrap() | |
| .open_memory(&contents) | |
| .expect("failed to build object"); | |
| let name = obj.name().expect("failed to get object name"); | |
| assert!(name.is_empty()); | |
| let obj = unsafe { Object::from_ptr(obj.take_ptr()) }; | |
| let name = obj.name().expect("failed to get object name"); | |
| assert!(name.is_empty()); | |
| } | |
| /// Check that loading an object from an empty file fails as expected. | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_load_invalid() { | |
| let empty_file = NamedTempFile::new().unwrap(); | |
| let _err = ObjectBuilder::default() | |
| .debug(true) | |
| .open_file(empty_file.path()) | |
| .unwrap_err(); | |
| } | |
| #[test] | |
| fn test_object_name() { | |
| let obj_path = get_test_object_path("runqslower.bpf.o"); | |
| let mut builder = ObjectBuilder::default(); | |
| builder.name("test name").unwrap(); | |
| let obj = builder.open_file(obj_path).expect("failed to build object"); | |
| let obj_name = obj.name().expect("failed to get object name"); | |
| assert!(obj_name == "test name"); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_maps() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("runqslower.bpf.o"); | |
| let _map = get_map_mut(&mut obj, "start"); | |
| let _map = get_map_mut(&mut obj, "events"); | |
| assert!(!obj.maps().any(|map| map.name() == OsStr::new("asdf"))); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_maps_iter() { | |
| bump_rlimit_mlock(); | |
| let obj = get_test_object("runqslower.bpf.o"); | |
| for map in obj.maps() { | |
| eprintln!("{:?}", map.name()); | |
| } | |
| // This will include .rodata and .bss, so our expected count is 4, not 2 | |
| assert!(obj.maps().count() == 4); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_map_key_value_size() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("runqslower.bpf.o"); | |
| let start = get_map_mut(&mut obj, "start"); | |
| assert!(start.lookup(&[1, 2, 3, 4, 5], MapFlags::empty()).is_err()); | |
| assert!(start.delete(&[1]).is_err()); | |
| assert!(start.lookup_and_delete(&[1, 2, 3, 4, 5]).is_err()); | |
| assert!(start | |
| .update(&[1, 2, 3, 4, 5], &[1], MapFlags::empty()) | |
| .is_err()); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_map_update_batch() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("runqslower.bpf.o"); | |
| let start = get_map_mut(&mut obj, "start"); | |
| let key1 = 1u32.to_ne_bytes(); | |
| let key2 = 2u32.to_ne_bytes(); | |
| let key3 = 3u32.to_ne_bytes(); | |
| let key4 = 4u32.to_ne_bytes(); | |
| let value1 = 369u64.to_ne_bytes(); | |
| let value2 = 258u64.to_ne_bytes(); | |
| let value3 = 147u64.to_ne_bytes(); | |
| let value4 = 159u64.to_ne_bytes(); | |
| let batch_key1 = key1.into_iter().chain(key2).collect::<Vec<_>>(); | |
| let batch_value1 = value1.into_iter().chain(value2).collect::<Vec<_>>(); | |
| let batch_key2 = key2.into_iter().chain(key3).chain(key4).collect::<Vec<_>>(); | |
| let batch_value2 = value2 | |
| .into_iter() | |
| .chain(value3) | |
| .chain(value4) | |
| .collect::<Vec<_>>(); | |
| // Update batch with wrong key size | |
| assert!(start | |
| .update_batch( | |
| &[1, 2, 3], | |
| &batch_value1, | |
| 2, | |
| MapFlags::ANY, | |
| MapFlags::NO_EXIST | |
| ) | |
| .is_err()); | |
| // Update batch with wrong value size | |
| assert!(start | |
| .update_batch( | |
| &batch_key1, | |
| &[1, 2, 3], | |
| 2, | |
| MapFlags::ANY, | |
| MapFlags::NO_EXIST | |
| ) | |
| .is_err()); | |
| // Update batch with wrong count. | |
| assert!(start | |
| .update_batch( | |
| &batch_key1, | |
| &batch_value1, | |
| 1, | |
| MapFlags::ANY, | |
| MapFlags::NO_EXIST | |
| ) | |
| .is_err()); | |
| // Update batch with 1 key. | |
| assert!(start | |
| .update_batch(&key1, &value1, 1, MapFlags::ANY, MapFlags::NO_EXIST) | |
| .is_ok()); | |
| // Update batch with multiple keys. | |
| assert!(start | |
| .update_batch( | |
| &batch_key2, | |
| &batch_value2, | |
| 3, | |
| MapFlags::ANY, | |
| MapFlags::NO_EXIST | |
| ) | |
| .is_ok()); | |
| // Update batch with existing keys. | |
| assert!(start | |
| .update_batch( | |
| &batch_key2, | |
| &batch_value2, | |
| 3, | |
| MapFlags::NO_EXIST, | |
| MapFlags::NO_EXIST | |
| ) | |
| .is_err()); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_map_delete_batch() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("runqslower.bpf.o"); | |
| let start = get_map_mut(&mut obj, "start"); | |
| let key1 = 1u32.to_ne_bytes(); | |
| assert!(start | |
| .update(&key1, &9999u64.to_ne_bytes(), MapFlags::ANY) | |
| .is_ok()); | |
| let key2 = 2u32.to_ne_bytes(); | |
| assert!(start | |
| .update(&key2, &42u64.to_ne_bytes(), MapFlags::ANY) | |
| .is_ok()); | |
| let key3 = 3u32.to_ne_bytes(); | |
| assert!(start | |
| .update(&key3, &18u64.to_ne_bytes(), MapFlags::ANY) | |
| .is_ok()); | |
| let key4 = 4u32.to_ne_bytes(); | |
| assert!(start | |
| .update(&key4, &1337u64.to_ne_bytes(), MapFlags::ANY) | |
| .is_ok()); | |
| // Delete 1 incomplete key. | |
| assert!(start | |
| .delete_batch(&[0, 0, 1], 1, MapFlags::empty(), MapFlags::empty()) | |
| .is_err()); | |
| // Delete keys with wrong count. | |
| assert!(start | |
| .delete_batch(&key4, 2, MapFlags::empty(), MapFlags::empty()) | |
| .is_err()); | |
| // Delete 1 key successfully. | |
| assert!(start | |
| .delete_batch(&key4, 1, MapFlags::empty(), MapFlags::empty()) | |
| .is_ok()); | |
| // Delete remaining 3 keys. | |
| let keys = key1.into_iter().chain(key2).chain(key3).collect::<Vec<_>>(); | |
| assert!(start | |
| .delete_batch(&keys, 3, MapFlags::empty(), MapFlags::empty()) | |
| .is_ok()); | |
| // Map should be empty now. | |
| assert!(start.keys().collect::<Vec<_>>().is_empty()) | |
| } | |
| /// Test whether `MapInfo` works properly | |
| #[tag(root)] | |
| #[test] | |
| pub fn test_map_info() { | |
| #[allow(clippy::needless_update)] | |
| let opts = libbpf_sys::bpf_map_create_opts { | |
| sz: size_of::<libbpf_sys::bpf_map_create_opts>() as libbpf_sys::size_t, | |
| map_flags: libbpf_sys::BPF_ANY, | |
| btf_fd: 0, | |
| btf_key_type_id: 0, | |
| btf_value_type_id: 0, | |
| btf_vmlinux_value_type_id: 0, | |
| inner_map_fd: 0, | |
| map_extra: 0, | |
| numa_node: 0, | |
| map_ifindex: 0, | |
| // bpf_map_create_opts might have padding fields on some platform | |
| ..Default::default() | |
| }; | |
| let map = MapHandle::create(MapType::Hash, Some("simple_map"), 8, 64, 1024, &opts).unwrap(); | |
| let map_info = MapInfo::new(map.as_fd()).unwrap(); | |
| let name_received = map_info.name().unwrap(); | |
| assert_eq!(name_received, "simple_map"); | |
| assert_eq!(map_info.map_type(), MapType::Hash); | |
| assert_eq!(map_info.flags() & MapFlags::ANY, MapFlags::ANY); | |
| let map_info = &map_info.info; | |
| assert_eq!(map_info.key_size, 8); | |
| assert_eq!(map_info.value_size, 64); | |
| assert_eq!(map_info.max_entries, 1024); | |
| assert_eq!(map_info.btf_id, 0); | |
| assert_eq!(map_info.btf_key_type_id, 0); | |
| assert_eq!(map_info.btf_value_type_id, 0); | |
| assert_eq!(map_info.btf_vmlinux_value_type_id, 0); | |
| assert_eq!(map_info.map_extra, 0); | |
| assert_eq!(map_info.ifindex, 0); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_percpu_lookup() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("percpu_map.bpf.o"); | |
| let map = get_map_mut(&mut obj, "percpu_map"); | |
| let res = map | |
| .lookup_percpu(&(0_u32).to_ne_bytes(), MapFlags::ANY) | |
| .expect("failed to lookup") | |
| .expect("failed to find value for key"); | |
| assert_eq!( | |
| res.len(), | |
| num_possible_cpus().expect("must be one value per cpu") | |
| ); | |
| assert_eq!(res[0].len(), size_of::<u32>()); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_percpu_invalid_lookup_fn() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("percpu_map.bpf.o"); | |
| let map = get_map_mut(&mut obj, "percpu_map"); | |
| assert!(map.lookup(&(0_u32).to_ne_bytes(), MapFlags::ANY).is_err()); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_percpu_update() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("percpu_map.bpf.o"); | |
| let map = get_map_mut(&mut obj, "percpu_map"); | |
| let key = (0_u32).to_ne_bytes(); | |
| let mut vals: Vec<Vec<u8>> = Vec::new(); | |
| for i in 0..num_possible_cpus().unwrap() { | |
| vals.push((i as u32).to_ne_bytes().to_vec()); | |
| } | |
| map.update_percpu(&key, &vals, MapFlags::ANY) | |
| .expect("failed to update map"); | |
| let res = map | |
| .lookup_percpu(&key, MapFlags::ANY) | |
| .expect("failed to lookup") | |
| .expect("failed to find value for key"); | |
| assert_eq!(vals, res); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_percpu_invalid_update_fn() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("percpu_map.bpf.o"); | |
| let map = get_map_mut(&mut obj, "percpu_map"); | |
| let key = (0_u32).to_ne_bytes(); | |
| let val = (1_u32).to_ne_bytes().to_vec(); | |
| assert!(map.update(&key, &val, MapFlags::ANY).is_err()); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_percpu_lookup_update() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("percpu_map.bpf.o"); | |
| let map = get_map_mut(&mut obj, "percpu_map"); | |
| let key = (0_u32).to_ne_bytes(); | |
| let mut res = map | |
| .lookup_percpu(&key, MapFlags::ANY) | |
| .expect("failed to lookup") | |
| .expect("failed to find value for key"); | |
| for e in res.iter_mut() { | |
| e[0] &= 0xf0; | |
| } | |
| map.update_percpu(&key, &res, MapFlags::ANY) | |
| .expect("failed to update after first lookup"); | |
| let res2 = map | |
| .lookup_percpu(&key, MapFlags::ANY) | |
| .expect("failed to lookup") | |
| .expect("failed to find value for key"); | |
| assert_eq!(res, res2); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_map_empty_lookup() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("runqslower.bpf.o"); | |
| let start = get_map_mut(&mut obj, "start"); | |
| assert!(start | |
| .lookup(&[1, 2, 3, 4], MapFlags::empty()) | |
| .expect("err in map lookup") | |
| .is_none()); | |
| } | |
| /// Test CRUD operations on map of type queue. | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_map_queue_crud() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("tracepoint.bpf.o"); | |
| let queue = get_map_mut(&mut obj, "queue"); | |
| let key: [u8; 0] = []; | |
| let value1 = 42u32.to_ne_bytes(); | |
| let value2 = 43u32.to_ne_bytes(); | |
| // Test queue, FIFO expected | |
| queue | |
| .update(&key, &value1, MapFlags::ANY) | |
| .expect("failed to update in queue"); | |
| queue | |
| .update(&key, &value2, MapFlags::ANY) | |
| .expect("failed to update in queue"); | |
| let mut val = queue | |
| .lookup(&key, MapFlags::ANY) | |
| .expect("failed to peek the queue") | |
| .expect("failed to retrieve value"); | |
| assert_eq!(val.len(), 4); | |
| assert_eq!(&val, &value1); | |
| val = queue | |
| .lookup_and_delete(&key) | |
| .expect("failed to pop from queue") | |
| .expect("failed to retrieve value"); | |
| assert_eq!(val.len(), 4); | |
| assert_eq!(&val, &value1); | |
| val = queue | |
| .lookup_and_delete(&key) | |
| .expect("failed to pop from queue") | |
| .expect("failed to retrieve value"); | |
| assert_eq!(val.len(), 4); | |
| assert_eq!(&val, &value2); | |
| assert!(queue | |
| .lookup_and_delete(&key) | |
| .expect("failed to pop from queue") | |
| .is_none()); | |
| } | |
| /// Test CRUD operations on map of type bloomfilter. | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_map_bloom_filter_crud() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("tracepoint.bpf.o"); | |
| let bloom_filter = get_map_mut(&mut obj, "bloom_filter"); | |
| 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. | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_map_stack_crud() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("tracepoint.bpf.o"); | |
| let stack = get_map_mut(&mut obj, "stack"); | |
| let key: [u8; 0] = []; | |
| let value1 = 1337u32.to_ne_bytes(); | |
| let value2 = 2674u32.to_ne_bytes(); | |
| stack | |
| .update(&key, &value1, MapFlags::ANY) | |
| .expect("failed to update in stack"); | |
| stack | |
| .update(&key, &value2, MapFlags::ANY) | |
| .expect("failed to update in stack"); | |
| let mut val = stack | |
| .lookup(&key, MapFlags::ANY) | |
| .expect("failed to pop from stack") | |
| .expect("failed to retrieve value"); | |
| assert_eq!(val.len(), 4); | |
| assert_eq!(&val, &value2); | |
| val = stack | |
| .lookup_and_delete(&key) | |
| .expect("failed to pop from stack") | |
| .expect("failed to retrieve value"); | |
| assert_eq!(val.len(), 4); | |
| assert_eq!(&val, &value2); | |
| val = stack | |
| .lookup_and_delete(&key) | |
| .expect("failed to pop from stack") | |
| .expect("failed to retrieve value"); | |
| assert_eq!(val.len(), 4); | |
| assert_eq!(&val, &value1); | |
| assert!(stack | |
| .lookup_and_delete(&key) | |
| .expect("failed to pop from stack") | |
| .is_none()); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_map_mutation() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("runqslower.bpf.o"); | |
| let start = get_map_mut(&mut obj, "start"); | |
| start | |
| .update(&[1, 2, 3, 4], &[1, 2, 3, 4, 5, 6, 7, 8], MapFlags::empty()) | |
| .expect("failed to write"); | |
| let val = start | |
| .lookup(&[1, 2, 3, 4], MapFlags::empty()) | |
| .expect("failed to read map") | |
| .expect("failed to find key"); | |
| assert_eq!(val.len(), 8); | |
| assert_eq!(val, &[1, 2, 3, 4, 5, 6, 7, 8]); | |
| start.delete(&[1, 2, 3, 4]).expect("failed to delete key"); | |
| assert!(start | |
| .lookup(&[1, 2, 3, 4], MapFlags::empty()) | |
| .expect("failed to read map") | |
| .is_none()); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_map_lookup_flags() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("runqslower.bpf.o"); | |
| let start = get_map_mut(&mut obj, "start"); | |
| start | |
| .update(&[1, 2, 3, 4], &[1, 2, 3, 4, 5, 6, 7, 8], MapFlags::NO_EXIST) | |
| .expect("failed to write"); | |
| assert!(start | |
| .update(&[1, 2, 3, 4], &[1, 2, 3, 4, 5, 6, 7, 8], MapFlags::NO_EXIST) | |
| .is_err()); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_map_key_iter() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("runqslower.bpf.o"); | |
| let start = get_map_mut(&mut obj, "start"); | |
| let key1 = vec![1, 2, 3, 4]; | |
| let key2 = vec![1, 2, 3, 5]; | |
| let key3 = vec![1, 2, 3, 6]; | |
| start | |
| .update(&key1, &[1, 2, 3, 4, 5, 6, 7, 8], MapFlags::empty()) | |
| .expect("failed to write"); | |
| start | |
| .update(&key2, &[1, 2, 3, 4, 5, 6, 7, 8], MapFlags::empty()) | |
| .expect("failed to write"); | |
| start | |
| .update(&key3, &[1, 2, 3, 4, 5, 6, 7, 8], MapFlags::empty()) | |
| .expect("failed to write"); | |
| let mut keys = HashSet::new(); | |
| for key in start.keys() { | |
| keys.insert(key); | |
| } | |
| assert_eq!(keys.len(), 3); | |
| assert!(keys.contains(&key1)); | |
| assert!(keys.contains(&key2)); | |
| assert!(keys.contains(&key3)); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_map_key_iter_empty() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("runqslower.bpf.o"); | |
| let start = get_map_mut(&mut obj, "start"); | |
| let mut count = 0; | |
| for _ in start.keys() { | |
| count += 1; | |
| } | |
| assert_eq!(count, 0); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_map_pin() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("runqslower.bpf.o"); | |
| let mut map = get_map_mut(&mut obj, "start"); | |
| let path = "/sys/fs/bpf/mymap_test_object_map_pin"; | |
| // Unpinning a unpinned map should be an error | |
| assert!(map.unpin(path).is_err()); | |
| assert!(!Path::new(path).exists()); | |
| // Pin and unpin should be successful | |
| map.pin(path).expect("failed to pin map"); | |
| assert!(Path::new(path).exists()); | |
| map.unpin(path).expect("failed to unpin map"); | |
| assert!(!Path::new(path).exists()); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_loading_pinned_map_from_path() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("runqslower.bpf.o"); | |
| let mut map = get_map_mut(&mut obj, "start"); | |
| let path = "/sys/fs/bpf/mymap_test_pin_to_load_from_path"; | |
| map.pin(path).expect("pinning map failed"); | |
| let pinned_map = MapHandle::from_pinned_path(path).expect("loading a map from a path failed"); | |
| map.unpin(path).expect("unpinning map failed"); | |
| assert_eq!(map.name(), pinned_map.name()); | |
| assert_eq!( | |
| map.info().unwrap().info.id, | |
| pinned_map.info().unwrap().info.id | |
| ); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_loading_loaded_map_from_id() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("runqslower.bpf.o"); | |
| let map = get_map_mut(&mut obj, "start"); | |
| let id = map.info().expect("to get info from map 'start'").info.id; | |
| let map_by_id = MapHandle::from_map_id(id).expect("map to load from id"); | |
| assert_eq!(map.name(), map_by_id.name()); | |
| assert_eq!( | |
| map.info().unwrap().info.id, | |
| map_by_id.info().unwrap().info.id | |
| ); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_programs() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("runqslower.bpf.o"); | |
| let _prog = get_prog_mut(&mut obj, "handle__sched_wakeup"); | |
| let _prog = get_prog_mut(&mut obj, "handle__sched_wakeup_new"); | |
| let _prog = get_prog_mut(&mut obj, "handle__sched_switch"); | |
| assert!(!obj.progs().any(|prog| prog.name() == OsStr::new("asdf"))); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_programs_iter_mut() { | |
| bump_rlimit_mlock(); | |
| let obj = get_test_object("runqslower.bpf.o"); | |
| assert!(obj.progs().count() == 3); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_program_pin() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("runqslower.bpf.o"); | |
| let mut prog = get_prog_mut(&mut obj, "handle__sched_wakeup"); | |
| let path = "/sys/fs/bpf/myprog"; | |
| // Unpinning a unpinned prog should be an error | |
| assert!(prog.unpin(path).is_err()); | |
| assert!(!Path::new(path).exists()); | |
| // Pin should be successful | |
| prog.pin(path).expect("failed to pin prog"); | |
| assert!(Path::new(path).exists()); | |
| // Backup cleanup method in case test errors | |
| defer! { | |
| let _ = fs::remove_file(path); | |
| } | |
| // Unpin should be successful | |
| prog.unpin(path).expect("failed to unpin prog"); | |
| assert!(!Path::new(path).exists()); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_link_pin() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("runqslower.bpf.o"); | |
| let mut prog = get_prog_mut(&mut obj, "handle__sched_wakeup"); | |
| let mut link = prog.attach().expect("failed to attach prog"); | |
| let path = "/sys/fs/bpf/mylink"; | |
| // Unpinning a unpinned prog should be an error | |
| assert!(link.unpin().is_err()); | |
| assert!(!Path::new(path).exists()); | |
| // Pin should be successful | |
| link.pin(path).expect("failed to pin prog"); | |
| assert!(Path::new(path).exists()); | |
| // Backup cleanup method in case test errors | |
| defer! { | |
| let _ = fs::remove_file(path); | |
| } | |
| // Unpin should be successful | |
| link.unpin().expect("failed to unpin prog"); | |
| assert!(!Path::new(path).exists()); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_reuse_pined_map() { | |
| bump_rlimit_mlock(); | |
| let path = "/sys/fs/bpf/mymap_test_object_reuse_pined_map"; | |
| let key = vec![1, 2, 3, 4]; | |
| let val = vec![1, 2, 3, 4, 5, 6, 7, 8]; | |
| // Pin a map | |
| { | |
| let mut obj = get_test_object("runqslower.bpf.o"); | |
| let mut map = get_map_mut(&mut obj, "start"); | |
| map.update(&key, &val, MapFlags::empty()) | |
| .expect("failed to write"); | |
| // Pin map | |
| map.pin(path).expect("failed to pin map"); | |
| assert!(Path::new(path).exists()); | |
| } | |
| // Backup cleanup method in case test errors somewhere | |
| defer! { | |
| let _ = fs::remove_file(path); | |
| } | |
| // Reuse the pinned map | |
| let obj_path = get_test_object_path("runqslower.bpf.o"); | |
| let mut builder = ObjectBuilder::default(); | |
| builder.debug(true); | |
| let mut open_obj = builder.open_file(obj_path).expect("failed to open object"); | |
| let mut start = open_obj | |
| .maps_mut() | |
| .find(|map| map.name() == OsStr::new("start")) | |
| .expect("failed to find `start` map"); | |
| assert!(start.reuse_pinned_map("/asdf").is_err()); | |
| start.reuse_pinned_map(path).expect("failed to reuse map"); | |
| let mut obj = open_obj.load().expect("failed to load object"); | |
| let mut reused_map = get_map_mut(&mut obj, "start"); | |
| let found_val = reused_map | |
| .lookup(&key, MapFlags::empty()) | |
| .expect("failed to read map") | |
| .expect("failed to find key"); | |
| assert_eq!(&found_val, &val); | |
| // Cleanup | |
| reused_map.unpin(path).expect("failed to unpin map"); | |
| assert!(!Path::new(path).exists()); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_ringbuf_raw() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("ringbuf.bpf.o"); | |
| let mut prog = get_prog_mut(&mut obj, "handle__sys_enter_getpid"); | |
| let _link = prog.attach().expect("failed to attach prog"); | |
| static mut V1: i32 = 0; | |
| static mut V2: i32 = 0; | |
| fn callback1(data: &[u8]) -> i32 { | |
| let mut value: i32 = 0; | |
| plain::copy_from_bytes(&mut value, data).expect("Wrong size"); | |
| unsafe { | |
| V1 = value; | |
| } | |
| 0 | |
| } | |
| fn callback2(data: &[u8]) -> i32 { | |
| let mut value: i32 = 0; | |
| plain::copy_from_bytes(&mut value, data).expect("Wrong size"); | |
| unsafe { | |
| V2 = value; | |
| } | |
| 0 | |
| } | |
| // Test trying to build without adding any ringbufs | |
| // Can't use expect_err here since RingBuffer does not implement Debug | |
| let builder = libbpf_rs::RingBufferBuilder::new(); | |
| assert!( | |
| builder.build().is_err(), | |
| "Should not be able to build without adding at least one ringbuf" | |
| ); | |
| // Test building with multiple map objects | |
| let mut builder = libbpf_rs::RingBufferBuilder::new(); | |
| // Add a first map and callback | |
| let map1 = get_map(&obj, "ringbuf1"); | |
| builder | |
| .add(&map1, callback1) | |
| .expect("failed to add ringbuf"); | |
| // Add a second map and callback | |
| let map2 = get_map(&obj, "ringbuf2"); | |
| builder | |
| .add(&map2, callback2) | |
| .expect("failed to add ringbuf"); | |
| let mgr = builder.build().expect("failed to build"); | |
| // Call getpid to ensure the BPF program runs | |
| unsafe { libc::getpid() }; | |
| // Test raw primitives | |
| let ret = mgr.consume_raw(); | |
| // We can't check for exact return values, since other tasks in the system may call getpid(), | |
| // triggering the BPF program | |
| assert!(ret >= 2); | |
| unsafe { assert_eq!(V1, 1) }; | |
| unsafe { assert_eq!(V2, 2) }; | |
| // Consume from a (potentially) empty ring buffer | |
| let ret = mgr.consume_raw(); | |
| assert!(ret >= 0); | |
| // Consume from a (potentially) empty ring buffer using poll() | |
| let ret = mgr.poll_raw(Duration::from_millis(100)); | |
| assert!(ret >= 0); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_ringbuf_err_callback() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("ringbuf.bpf.o"); | |
| let mut prog = get_prog_mut(&mut obj, "handle__sys_enter_getpid"); | |
| let _link = prog.attach().expect("failed to attach prog"); | |
| // Immediately trigger an error that should be reported back to the consume_raw() or poll_raw() | |
| fn callback1(_data: &[u8]) -> i32 { | |
| -libc::ENOENT | |
| } | |
| // Immediately trigger an error that should be reported back to the consume_raw() or poll_raw() | |
| fn callback2(_data: &[u8]) -> i32 { | |
| -libc::EPERM | |
| } | |
| // Test trying to build without adding any ringbufs | |
| // Can't use expect_err here since RingBuffer does not implement Debug | |
| let builder = libbpf_rs::RingBufferBuilder::new(); | |
| assert!( | |
| builder.build().is_err(), | |
| "Should not be able to build without adding at least one ringbuf" | |
| ); | |
| // Test building with multiple map objects | |
| let mut builder = libbpf_rs::RingBufferBuilder::new(); | |
| // Add a first map and callback | |
| let map1 = get_map(&obj, "ringbuf1"); | |
| builder | |
| .add(&map1, callback1) | |
| .expect("failed to add ringbuf"); | |
| // Add a second map and callback | |
| let map2 = get_map(&obj, "ringbuf2"); | |
| builder | |
| .add(&map2, callback2) | |
| .expect("failed to add ringbuf"); | |
| let mgr = builder.build().expect("failed to build"); | |
| // Call getpid to ensure the BPF program runs | |
| unsafe { libc::getpid() }; | |
| // Test raw primitives | |
| let ret = mgr.consume_raw(); | |
| // The error originated from the first callback executed should be reported here, either | |
| // from callback1() or callback2() | |
| assert!(ret == -libc::ENOENT || ret == -libc::EPERM); | |
| unsafe { libc::getpid() }; | |
| // The same behavior should happen with poll_raw() | |
| let ret = mgr.poll_raw(Duration::from_millis(100)); | |
| assert!(ret == -libc::ENOENT || ret == -libc::EPERM); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_ringbuf() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("ringbuf.bpf.o"); | |
| let mut prog = get_prog_mut(&mut obj, "handle__sys_enter_getpid"); | |
| let _link = prog.attach().expect("failed to attach prog"); | |
| static mut V1: i32 = 0; | |
| static mut V2: i32 = 0; | |
| fn callback1(data: &[u8]) -> i32 { | |
| let mut value: i32 = 0; | |
| plain::copy_from_bytes(&mut value, data).expect("Wrong size"); | |
| unsafe { | |
| V1 = value; | |
| } | |
| 0 | |
| } | |
| fn callback2(data: &[u8]) -> i32 { | |
| let mut value: i32 = 0; | |
| plain::copy_from_bytes(&mut value, data).expect("Wrong size"); | |
| unsafe { | |
| V2 = value; | |
| } | |
| 0 | |
| } | |
| // Test trying to build without adding any ringbufs | |
| // Can't use expect_err here since RingBuffer does not implement Debug | |
| let builder = libbpf_rs::RingBufferBuilder::new(); | |
| assert!( | |
| builder.build().is_err(), | |
| "Should not be able to build without adding at least one ringbuf" | |
| ); | |
| // Test building with multiple map objects | |
| let mut builder = libbpf_rs::RingBufferBuilder::new(); | |
| // Add a first map and callback | |
| let map1 = get_map(&obj, "ringbuf1"); | |
| builder | |
| .add(&map1, callback1) | |
| .expect("failed to add ringbuf"); | |
| // Add a second map and callback | |
| let map2 = get_map(&obj, "ringbuf2"); | |
| builder | |
| .add(&map2, callback2) | |
| .expect("failed to add ringbuf"); | |
| let mgr = builder.build().expect("failed to build"); | |
| // Call getpid to ensure the BPF program runs | |
| unsafe { libc::getpid() }; | |
| // This should result in both callbacks being called | |
| mgr.consume().expect("failed to consume ringbuf"); | |
| // Our values should both reflect that the callbacks have been called | |
| unsafe { assert_eq!(V1, 1) }; | |
| unsafe { assert_eq!(V2, 2) }; | |
| // Reset both values | |
| unsafe { V1 = 0 }; | |
| unsafe { V2 = 0 }; | |
| // Call getpid to ensure the BPF program runs | |
| unsafe { libc::getpid() }; | |
| // This should result in both callbacks being called | |
| mgr.poll(Duration::from_millis(100)) | |
| .expect("failed to poll ringbuf"); | |
| // Our values should both reflect that the callbacks have been called | |
| unsafe { assert_eq!(V1, 1) }; | |
| unsafe { assert_eq!(V2, 2) }; | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_ringbuf_closure() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("ringbuf.bpf.o"); | |
| let mut prog = get_prog_mut(&mut obj, "handle__sys_enter_getpid"); | |
| let _link = prog.attach().expect("failed to attach prog"); | |
| let (sender1, receiver1) = channel(); | |
| let callback1 = move |data: &[u8]| -> i32 { | |
| let mut value: i32 = 0; | |
| plain::copy_from_bytes(&mut value, data).expect("Wrong size"); | |
| sender1.send(value).expect("failed to send value"); | |
| 0 | |
| }; | |
| let (sender2, receiver2) = channel(); | |
| let callback2 = move |data: &[u8]| -> i32 { | |
| let mut value: i32 = 0; | |
| plain::copy_from_bytes(&mut value, data).expect("Wrong size"); | |
| sender2.send(value).expect("failed to send value"); | |
| 0 | |
| }; | |
| // Test trying to build without adding any ringbufs | |
| // Can't use expect_err here since RingBuffer does not implement Debug | |
| let builder = libbpf_rs::RingBufferBuilder::new(); | |
| assert!( | |
| builder.build().is_err(), | |
| "Should not be able to build without adding at least one ringbuf" | |
| ); | |
| // Test building with multiple map objects | |
| let mut builder = libbpf_rs::RingBufferBuilder::new(); | |
| // Add a first map and callback | |
| let map1 = get_map(&obj, "ringbuf1"); | |
| builder | |
| .add(&map1, callback1) | |
| .expect("failed to add ringbuf"); | |
| // Add a second map and callback | |
| let map2 = get_map(&obj, "ringbuf2"); | |
| builder | |
| .add(&map2, callback2) | |
| .expect("failed to add ringbuf"); | |
| let mgr = builder.build().expect("failed to build"); | |
| // Call getpid to ensure the BPF program runs | |
| unsafe { libc::getpid() }; | |
| // This should result in both callbacks being called | |
| mgr.consume().expect("failed to consume ringbuf"); | |
| let v1 = receiver1.recv().expect("failed to receive value"); | |
| let v2 = receiver2.recv().expect("failed to receive value"); | |
| assert_eq!(v1, 1); | |
| assert_eq!(v2, 2); | |
| } | |
| /// Check that `RingBuffer` works correctly even if the map file descriptors | |
| /// provided during construction are closed. This test validates that `libbpf`'s | |
| /// refcount behavior is correctly reflected in our `RingBuffer` lifetimes. | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_ringbuf_with_closed_map() { | |
| bump_rlimit_mlock(); | |
| fn test(poll_fn: impl FnOnce(&libbpf_rs::RingBuffer)) { | |
| let mut value = 0i32; | |
| { | |
| let mut obj = get_test_object("tracepoint.bpf.o"); | |
| let mut prog = get_prog_mut(&mut obj, "handle__tracepoint"); | |
| let _link = prog | |
| .attach_tracepoint("syscalls", "sys_enter_getpid") | |
| .expect("failed to attach prog"); | |
| let map = get_map_mut(&mut obj, "ringbuf"); | |
| let callback = |data: &[u8]| { | |
| plain::copy_from_bytes(&mut value, data).expect("Wrong size"); | |
| 0 | |
| }; | |
| let mut builder = libbpf_rs::RingBufferBuilder::new(); | |
| builder.add(&map, callback).expect("failed to add ringbuf"); | |
| let ringbuf = builder.build().expect("failed to build"); | |
| drop(obj); | |
| // Trigger the tracepoint. At this point `map` along with the containing | |
| // `obj` have been destroyed. | |
| let _pid = unsafe { libc::getpid() }; | |
| let () = poll_fn(&ringbuf); | |
| } | |
| // If we see a 1 here the ring buffer was still working as expected. | |
| assert_eq!(value, 1); | |
| } | |
| test(|ringbuf| ringbuf.consume().expect("failed to consume ringbuf")); | |
| test(|ringbuf| { | |
| ringbuf | |
| .poll(Duration::from_secs(5)) | |
| .expect("failed to poll ringbuf") | |
| }); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_user_ringbuf() { | |
| #[repr(C)] | |
| struct MyStruct { | |
| key: u32, | |
| value: u32, | |
| } | |
| unsafe impl Plain for MyStruct {} | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("user_ringbuf.bpf.o"); | |
| let mut prog = get_prog_mut(&mut obj, "handle__sys_enter_getpid"); | |
| let _link = prog.attach().expect("failed to attach prog"); | |
| let urb_map = get_map_mut(&mut obj, "user_ringbuf"); | |
| let user_ringbuf = UserRingBuffer::new(&urb_map).expect("failed to create user ringbuf"); | |
| let mut urb_sample = user_ringbuf | |
| .reserve(size_of::<MyStruct>()) | |
| .expect("failed to reserve space"); | |
| let bytes = urb_sample.as_mut(); | |
| let my_struct = plain::from_mut_bytes::<MyStruct>(bytes).expect("failed to convert bytes"); | |
| my_struct.key = 42; | |
| my_struct.value = 1337; | |
| user_ringbuf | |
| .submit(urb_sample) | |
| .expect("failed to submit sample"); | |
| // Trigger BPF program. | |
| let _pid = unsafe { libc::getpid() }; | |
| // At this point, the BPF program should have run and consumed the sample in | |
| // the user ring buffer, and stored the key/value in the samples map. | |
| let samples_map = get_map_mut(&mut obj, "samples"); | |
| let key: u32 = 42; | |
| let value: u32 = 1337; | |
| let res = samples_map | |
| .lookup(&key.to_ne_bytes(), MapFlags::ANY) | |
| .expect("failed to lookup") | |
| .expect("failed to find value for key"); | |
| // The value in the samples map should be the same as the value we submitted | |
| assert_eq!(res.len(), size_of::<u32>()); | |
| let mut array = [0; size_of::<u32>()]; | |
| array.copy_from_slice(&res[..]); | |
| assert_eq!(u32::from_ne_bytes(array), value); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_user_ringbuf_reservation_too_big() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("user_ringbuf.bpf.o"); | |
| let mut prog = get_prog_mut(&mut obj, "handle__sys_enter_getpid"); | |
| let _link = prog.attach().expect("failed to attach prog"); | |
| let urb_map = get_map_mut(&mut obj, "user_ringbuf"); | |
| let user_ringbuf = UserRingBuffer::new(&urb_map).expect("failed to create user ringbuf"); | |
| let err = user_ringbuf.reserve(1024 * 1024).unwrap_err(); | |
| assert!( | |
| err.to_string().contains("requested size is too large"), | |
| "{err:#}" | |
| ); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_user_ringbuf_not_enough_space() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("user_ringbuf.bpf.o"); | |
| let mut prog = get_prog_mut(&mut obj, "handle__sys_enter_getpid"); | |
| let _link = prog.attach().expect("failed to attach prog"); | |
| let urb_map = get_map_mut(&mut obj, "user_ringbuf"); | |
| let user_ringbuf = UserRingBuffer::new(&urb_map).expect("failed to create user ringbuf"); | |
| let _ = user_ringbuf | |
| .reserve(1024 * 3) | |
| .expect("failed to reserve space"); | |
| let err = user_ringbuf.reserve(1024 * 3).unwrap_err(); | |
| assert!( | |
| err.to_string() | |
| .contains("not enough space in the ring buffer"), | |
| "{err:#}" | |
| ); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_task_iter() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("taskiter.bpf.o"); | |
| let mut prog = get_prog_mut(&mut obj, "dump_pid"); | |
| let link = prog.attach().expect("failed to attach prog"); | |
| let mut iter = Iter::new(&link).expect("failed to create iterator"); | |
| #[repr(C)] | |
| #[derive(Clone, Copy)] | |
| struct IndexPidPair { | |
| i: u32, | |
| pid: i32, | |
| } | |
| unsafe impl Plain for IndexPidPair {} | |
| let mut buf = Vec::new(); | |
| let bytes_read = iter | |
| .read_to_end(&mut buf) | |
| .expect("failed to read from iterator"); | |
| assert!(bytes_read > 0); | |
| assert_eq!(bytes_read % size_of::<IndexPidPair>(), 0); | |
| let items: &[IndexPidPair] = | |
| plain::slice_from_bytes(buf.as_slice()).expect("Input slice cannot satisfy length"); | |
| assert!(!items.is_empty()); | |
| assert_eq!(items[0].i, 0); | |
| assert!(items.windows(2).all(|w| w[0].i + 1 == w[1].i)); | |
| // Check for init | |
| assert!(items.iter().any(|&item| item.pid == 1)); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_map_iter() { | |
| bump_rlimit_mlock(); | |
| // Create a map for iteration test. | |
| let opts = libbpf_sys::bpf_map_create_opts { | |
| sz: size_of::<libbpf_sys::bpf_map_create_opts>() as libbpf_sys::size_t, | |
| map_flags: libbpf_sys::BPF_F_NO_PREALLOC, | |
| ..Default::default() | |
| }; | |
| let map = MapHandle::create( | |
| MapType::Hash, | |
| Some("mymap_test_object_map_iter"), | |
| 4, | |
| 8, | |
| 8, | |
| &opts, | |
| ) | |
| .expect("failed to create map"); | |
| // Insert 3 elements. | |
| for i in 0..3 { | |
| let key = i32::to_ne_bytes(i); | |
| // We can change i to larger for more robust test, that's why we use a and b. | |
| let val = [&key[..], &[0_u8; 4]].concat(); | |
| map.update(&key, val.as_slice(), MapFlags::empty()) | |
| .expect("failed to write"); | |
| } | |
| let mut obj = get_test_object("mapiter.bpf.o"); | |
| let mut prog = get_prog_mut(&mut obj, "map_iter"); | |
| let link = prog | |
| .attach_iter(map.as_fd()) | |
| .expect("failed to attach map iter prog"); | |
| let mut iter = Iter::new(&link).expect("failed to create map iterator"); | |
| let mut buf = Vec::new(); | |
| let bytes_read = iter | |
| .read_to_end(&mut buf) | |
| .expect("failed to read from iterator"); | |
| assert!(bytes_read > 0); | |
| assert_eq!(bytes_read % size_of::<u32>(), 0); | |
| // Convert buf to &[u32] | |
| let buf = | |
| plain::slice_from_bytes::<u32>(buf.as_slice()).expect("Input slice cannot satisfy length"); | |
| assert!(buf.contains(&0)); | |
| assert!(buf.contains(&1)); | |
| assert!(buf.contains(&2)); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_map_create_and_pin() { | |
| bump_rlimit_mlock(); | |
| let opts = libbpf_sys::bpf_map_create_opts { | |
| sz: size_of::<libbpf_sys::bpf_map_create_opts>() as libbpf_sys::size_t, | |
| map_flags: libbpf_sys::BPF_F_NO_PREALLOC, | |
| ..Default::default() | |
| }; | |
| let mut map = MapHandle::create( | |
| MapType::Hash, | |
| Some("mymap_test_object_map_create_and_pin"), | |
| 4, | |
| 8, | |
| 8, | |
| &opts, | |
| ) | |
| .expect("failed to create map"); | |
| assert_eq!(map.name(), "mymap_test_object_map_create_and_pin"); | |
| let key = vec![1, 2, 3, 4]; | |
| let val = vec![1, 2, 3, 4, 5, 6, 7, 8]; | |
| map.update(&key, &val, MapFlags::empty()) | |
| .expect("failed to write"); | |
| let res = map | |
| .lookup(&key, MapFlags::ANY) | |
| .expect("failed to lookup") | |
| .expect("failed to find value for key"); | |
| assert_eq!(val, res); | |
| let path = "/sys/fs/bpf/mymap_test_object_map_create_and_pin"; | |
| // Unpinning a unpinned map should be an error | |
| assert!(map.unpin(path).is_err()); | |
| assert!(!Path::new(path).exists()); | |
| // Pin and unpin should be successful | |
| map.pin(path).expect("failed to pin map"); | |
| assert!(Path::new(path).exists()); | |
| map.unpin(path).expect("failed to unpin map"); | |
| assert!(!Path::new(path).exists()); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_map_create_without_name() { | |
| bump_rlimit_mlock(); | |
| #[allow(clippy::needless_update)] | |
| let opts = libbpf_sys::bpf_map_create_opts { | |
| sz: size_of::<libbpf_sys::bpf_map_create_opts>() as libbpf_sys::size_t, | |
| map_flags: libbpf_sys::BPF_F_NO_PREALLOC, | |
| btf_fd: 0, | |
| btf_key_type_id: 0, | |
| btf_value_type_id: 0, | |
| btf_vmlinux_value_type_id: 0, | |
| inner_map_fd: 0, | |
| map_extra: 0, | |
| numa_node: 0, | |
| map_ifindex: 0, | |
| // bpf_map_create_opts might have padding fields on some platform | |
| ..Default::default() | |
| }; | |
| let map = MapHandle::create(MapType::Hash, Option::<&str>::None, 4, 8, 8, &opts) | |
| .expect("failed to create map"); | |
| assert!(map.name().is_empty()); | |
| let key = vec![1, 2, 3, 4]; | |
| let val = vec![1, 2, 3, 4, 5, 6, 7, 8]; | |
| map.update(&key, &val, MapFlags::empty()) | |
| .expect("failed to write"); | |
| let res = map | |
| .lookup(&key, MapFlags::ANY) | |
| .expect("failed to lookup") | |
| .expect("failed to find value for key"); | |
| assert_eq!(val, res); | |
| } | |
| /// Test whether we can obtain multiple `MapHandle`s from a `Map | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_map_handle_clone() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("runqslower.bpf.o"); | |
| let map = get_map_mut(&mut obj, "events"); | |
| let handle1 = MapHandle::try_from(&map).expect("failed to create handle from Map"); | |
| assert_eq!(map.name(), handle1.name()); | |
| assert_eq!(map.map_type(), handle1.map_type()); | |
| assert_eq!(map.key_size(), handle1.key_size()); | |
| assert_eq!(map.value_size(), handle1.value_size()); | |
| let handle2 = MapHandle::try_from(&handle1).expect("failed to duplicate existing handle"); | |
| assert_eq!(handle1.name(), handle2.name()); | |
| assert_eq!(handle1.map_type(), handle2.map_type()); | |
| assert_eq!(handle1.key_size(), handle2.key_size()); | |
| assert_eq!(handle1.value_size(), handle2.value_size()); | |
| let info1 = map.info().expect("failed to get map info from map"); | |
| let info2 = handle2.info().expect("failed to get map info from handle"); | |
| assert_eq!( | |
| info1.info.id, info2.info.id, | |
| "Map and MapHandle have different IDs" | |
| ); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_usdt() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("usdt.bpf.o"); | |
| let mut prog = get_prog_mut(&mut obj, "handle__usdt"); | |
| let path = current_exe().expect("failed to find executable name"); | |
| let _link = prog | |
| .attach_usdt( | |
| unsafe { libc::getpid() }, | |
| &path, | |
| "test_provider", | |
| "test_function", | |
| ) | |
| .expect("failed to attach prog"); | |
| let map = get_map_mut(&mut obj, "ringbuf"); | |
| let action = || { | |
| // Define a USDT probe point and exercise it as we are attaching to self. | |
| probe!(test_provider, test_function, 1); | |
| }; | |
| let result = with_ringbuffer(&map, action); | |
| assert_eq!(result, 1); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_usdt_cookie() { | |
| bump_rlimit_mlock(); | |
| let cookie_val = 1337u16; | |
| let mut obj = get_test_object("usdt.bpf.o"); | |
| let mut prog = get_prog_mut(&mut obj, "handle__usdt_with_cookie"); | |
| let path = current_exe().expect("failed to find executable name"); | |
| let _link = prog | |
| .attach_usdt_with_opts( | |
| unsafe { libc::getpid() }, | |
| &path, | |
| "test_provider", | |
| "test_function2", | |
| UsdtOpts { | |
| cookie: cookie_val.into(), | |
| ..UsdtOpts::default() | |
| }, | |
| ) | |
| .expect("failed to attach prog"); | |
| let map = get_map_mut(&mut obj, "ringbuf"); | |
| let action = || { | |
| // Define a USDT probe point and exercise it as we are attaching to self. | |
| probe!(test_provider, test_function2, 1); | |
| }; | |
| let result = with_ringbuffer(&map, action); | |
| assert_eq!(result, cookie_val.into()); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_map_probes() { | |
| bump_rlimit_mlock(); | |
| let supported = MapType::Array | |
| .is_supported() | |
| .expect("failed to query if Array map is supported"); | |
| assert!(supported); | |
| let supported_res = MapType::Unknown.is_supported(); | |
| assert!(supported_res.is_err()); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_program_probes() { | |
| bump_rlimit_mlock(); | |
| let supported = ProgramType::SocketFilter | |
| .is_supported() | |
| .expect("failed to query if SocketFilter program is supported"); | |
| assert!(supported); | |
| let supported_res = ProgramType::Unknown.is_supported(); | |
| assert!(supported_res.is_err()); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_program_helper_probes() { | |
| bump_rlimit_mlock(); | |
| let supported = ProgramType::SocketFilter | |
| .is_helper_supported(libbpf_sys::BPF_FUNC_map_lookup_elem) | |
| .expect("failed to query if helper supported"); | |
| assert!(supported); | |
| // redirect should not be supported from socket filter, as it is only used in TC/XDP. | |
| let supported = ProgramType::SocketFilter | |
| .is_helper_supported(libbpf_sys::BPF_FUNC_redirect) | |
| .expect("failed to query if helper supported"); | |
| assert!(!supported); | |
| let supported_res = MapType::Unknown.is_supported(); | |
| assert!(supported_res.is_err()); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_open_program_insns() { | |
| bump_rlimit_mlock(); | |
| let open_obj = open_test_object("usdt.bpf.o"); | |
| let prog = open_obj | |
| .progs() | |
| .find(|prog| prog.name() == OsStr::new("handle__usdt")) | |
| .expect("failed to find program"); | |
| let insns = prog.insns(); | |
| assert!(!insns.is_empty()); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_program_insns() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("usdt.bpf.o"); | |
| let prog = get_prog_mut(&mut obj, "handle__usdt"); | |
| let insns = prog.insns(); | |
| assert!(!insns.is_empty()); | |
| } | |
| /// Check that we can attach a BPF program to a kernel tracepoint. | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_tracepoint() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("tracepoint.bpf.o"); | |
| let mut prog = get_prog_mut(&mut obj, "handle__tracepoint"); | |
| let _link = prog | |
| .attach_tracepoint("syscalls", "sys_enter_getpid") | |
| .expect("failed to attach prog"); | |
| let map = get_map_mut(&mut obj, "ringbuf"); | |
| let action = || { | |
| let _pid = unsafe { libc::getpid() }; | |
| }; | |
| let result = with_ringbuffer(&map, action); | |
| assert_eq!(result, 1); | |
| } | |
| /// Check that we can attach a BPF program to a kernel tracepoint, providing | |
| /// additional options. | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_tracepoint_with_opts() { | |
| bump_rlimit_mlock(); | |
| let cookie_val = 42u16; | |
| let mut obj = get_test_object("tracepoint.bpf.o"); | |
| let mut prog = get_prog_mut(&mut obj, "handle__tracepoint_with_cookie"); | |
| let opts = TracepointOpts { | |
| cookie: cookie_val.into(), | |
| ..TracepointOpts::default() | |
| }; | |
| let _link = prog | |
| .attach_tracepoint_with_opts("syscalls", "sys_enter_getpid", opts) | |
| .expect("failed to attach prog"); | |
| let map = get_map_mut(&mut obj, "ringbuf"); | |
| let action = || { | |
| let _pid = unsafe { libc::getpid() }; | |
| }; | |
| let result = with_ringbuffer(&map, action); | |
| assert_eq!(result, cookie_val.into()); | |
| } | |
| #[inline(never)] | |
| #[no_mangle] | |
| extern "C" fn uprobe_target() -> usize { | |
| // Use `black_box` here as an additional barrier to inlining. | |
| hint::black_box(42) | |
| } | |
| /// Check that we can attach a BPF program to a uprobe. | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_uprobe_with_opts() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("uprobe.bpf.o"); | |
| let mut prog = get_prog_mut(&mut obj, "handle__uprobe"); | |
| let pid = unsafe { libc::getpid() }; | |
| let path = current_exe().expect("failed to find executable name"); | |
| let func_offset = 0; | |
| let opts = UprobeOpts { | |
| func_name: "uprobe_target".to_string(), | |
| ..Default::default() | |
| }; | |
| let _link = prog | |
| .attach_uprobe_with_opts(pid, path, func_offset, opts) | |
| .expect("failed to attach prog"); | |
| let map = get_map_mut(&mut obj, "ringbuf"); | |
| let action = || { | |
| let _ = uprobe_target(); | |
| }; | |
| let result = with_ringbuffer(&map, action); | |
| assert_eq!(result, 1); | |
| } | |
| /// Check that we can attach a BPF program to a uprobe and access the cookie | |
| /// provided during attach. | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_uprobe_with_cookie() { | |
| bump_rlimit_mlock(); | |
| let cookie_val = 5u16; | |
| let mut obj = get_test_object("uprobe.bpf.o"); | |
| let mut prog = get_prog_mut(&mut obj, "handle__uprobe_with_cookie"); | |
| let pid = unsafe { libc::getpid() }; | |
| let path = current_exe().expect("failed to find executable name"); | |
| let func_offset = 0; | |
| let opts = UprobeOpts { | |
| func_name: "uprobe_target".to_string(), | |
| cookie: cookie_val.into(), | |
| ..Default::default() | |
| }; | |
| let _link = prog | |
| .attach_uprobe_with_opts(pid, path, func_offset, opts) | |
| .expect("failed to attach prog"); | |
| let map = get_map_mut(&mut obj, "ringbuf"); | |
| let action = || { | |
| let _ = uprobe_target(); | |
| }; | |
| let result = with_ringbuffer(&map, action); | |
| assert_eq!(result, cookie_val.into()); | |
| } | |
| /// Check that we can link multiple object files. | |
| #[test] | |
| fn test_object_link_files() { | |
| fn test(files: Vec<PathBuf>) { | |
| let output_file = NamedTempFile::new().unwrap(); | |
| let mut linker = Linker::new(output_file.path()).unwrap(); | |
| let () = files | |
| .into_iter() | |
| .try_for_each(|file| linker.add_file(file)) | |
| .unwrap(); | |
| let () = linker.link().unwrap(); | |
| // Check that we can load the resulting object file. | |
| let _object = ObjectBuilder::default() | |
| .debug(true) | |
| .open_file(output_file.path()) | |
| .unwrap(); | |
| } | |
| let obj_path1 = get_test_object_path("usdt.bpf.o"); | |
| let obj_path2 = get_test_object_path("ringbuf.bpf.o"); | |
| test(vec![obj_path1.clone()]); | |
| test(vec![obj_path1, obj_path2]); | |
| } | |
| /// Get access to the underlying per-cpu ring buffer data. | |
| fn buffer<'a>(perf: &'a libbpf_rs::PerfBuffer, buf_idx: usize) -> &'a [u8] { | |
| let perf_buff_ptr = perf.as_libbpf_object(); | |
| let mut buffer_data_ptr: *mut c_void = ptr::null_mut(); | |
| let mut buffer_size: usize = 0; | |
| let ret = unsafe { | |
| libbpf_sys::perf_buffer__buffer( | |
| perf_buff_ptr.as_ptr(), | |
| buf_idx as i32, | |
| ptr::addr_of_mut!(buffer_data_ptr), | |
| ptr::addr_of_mut!(buffer_size) as *mut libbpf_sys::size_t, | |
| ) | |
| }; | |
| assert!(ret >= 0); | |
| unsafe { slice::from_raw_parts(buffer_data_ptr as *const u8, buffer_size) } | |
| } | |
| /// Check that we can see the raw ring buffer of the perf buffer and find a | |
| /// value we have sent. | |
| #[tag(root)] | |
| #[test] | |
| fn test_object_perf_buffer_raw() { | |
| use memmem::Searcher; | |
| use memmem::TwoWaySearcher; | |
| bump_rlimit_mlock(); | |
| let cookie_val = 42u16; | |
| let mut obj = get_test_object("tracepoint.bpf.o"); | |
| let mut prog = get_prog_mut(&mut obj, "handle__tracepoint_with_cookie_pb"); | |
| let opts = TracepointOpts { | |
| cookie: cookie_val.into(), | |
| ..TracepointOpts::default() | |
| }; | |
| let _link = prog | |
| .attach_tracepoint_with_opts("syscalls", "sys_enter_getpid", opts) | |
| .expect("failed to attach prog"); | |
| let map = get_map_mut(&mut obj, "pb"); | |
| let cookie_bytes = cookie_val.to_ne_bytes(); | |
| let searcher = TwoWaySearcher::new(&cookie_bytes[..]); | |
| let perf = libbpf_rs::PerfBufferBuilder::new(&map) | |
| .build() | |
| .expect("failed to build"); | |
| // Make an action that the tracepoint will see | |
| let _pid = unsafe { libc::getpid() }; | |
| let found_cookie = (0..perf.buffer_cnt()).any(|buf_idx| { | |
| let buf = buffer(&perf, buf_idx); | |
| searcher.search_in(buf).is_some() | |
| }); | |
| assert!(found_cookie); | |
| } | |
| /// Check that we can get map pin status and map pin path | |
| #[tag(root)] | |
| #[test] | |
| fn test_map_pinned_status() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("map_auto_pin.bpf.o"); | |
| let map = get_map_mut(&mut obj, "auto_pin_map"); | |
| let is_pinned = map.is_pinned(); | |
| assert!(is_pinned); | |
| let expected_path = "/sys/fs/bpf/auto_pin_map"; | |
| let get_path = map.get_pin_path().expect("get map pin path failed"); | |
| assert_eq!(expected_path, get_path.to_str().unwrap()); | |
| // cleanup | |
| let _ = fs::remove_file(expected_path); | |
| } | |
| /// Change the root_pin_path and see if it works. | |
| #[tag(root)] | |
| #[test] | |
| fn test_map_pinned_status_with_pin_root_path() { | |
| bump_rlimit_mlock(); | |
| let obj_path = get_test_object_path("map_auto_pin.bpf.o"); | |
| let mut obj = ObjectBuilder::default() | |
| .debug(true) | |
| .pin_root_path("/sys/fs/bpf/test_namespace") | |
| .expect("root_pin_path failed") | |
| .open_file(obj_path) | |
| .expect("failed to open object") | |
| .load() | |
| .expect("failed to load object"); | |
| let map = get_map_mut(&mut obj, "auto_pin_map"); | |
| let is_pinned = map.is_pinned(); | |
| assert!(is_pinned); | |
| let expected_path = "/sys/fs/bpf/test_namespace/auto_pin_map"; | |
| let get_path = map.get_pin_path().expect("get map pin path failed"); | |
| assert_eq!(expected_path, get_path.to_str().unwrap()); | |
| // cleanup | |
| let _ = fs::remove_file(expected_path); | |
| let _ = fs::remove_dir("/sys/fs/bpf/test_namespace"); | |
| } | |
| /// Check that we can get program fd by id and vice versa. | |
| #[tag(root)] | |
| #[test] | |
| fn test_program_get_fd_and_id() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("runqslower.bpf.o"); | |
| let prog = get_prog_mut(&mut obj, "handle__sched_wakeup"); | |
| let prog_fd = prog.as_fd(); | |
| let prog_id = Program::get_id_by_fd(prog_fd).expect("failed to get program id by fd"); | |
| let _owned_prog_fd = Program::get_fd_by_id(prog_id).expect("failed to get program fd by id"); | |
| } | |
| /// Check that autocreate disabled maps don't prevent object loading | |
| #[tag(root)] | |
| #[test] | |
| fn test_map_autocreate_disable() { | |
| bump_rlimit_mlock(); | |
| let mut open_obj = open_test_object("map_auto_pin.bpf.o"); | |
| let mut auto_pin_map = open_obj | |
| .maps_mut() | |
| .find(|map| map.name() == OsStr::new("auto_pin_map")) | |
| .expect("failed to find `auto_pin_map` map"); | |
| auto_pin_map | |
| .set_autocreate(false) | |
| .expect("set_autocreate() failed"); | |
| open_obj.load().expect("failed to load object"); | |
| } | |
| /// Check that we can resize a map. | |
| #[tag(root)] | |
| #[test] | |
| fn test_map_resize() { | |
| bump_rlimit_mlock(); | |
| let mut open_obj = open_test_object("map_auto_pin.bpf.o"); | |
| let mut resizable = open_obj | |
| .maps_mut() | |
| .find(|map| map.name() == OsStr::new(".data.resizable_data")) | |
| .expect("failed to find `.data.resizable_data` map"); | |
| let len = resizable.initial_value().unwrap().len(); | |
| assert_eq!(len, size_of::<u64>()); | |
| let () = resizable | |
| .set_value_size(len as u32 * 2) | |
| .expect("failed to set value size"); | |
| let new_len = resizable.initial_value().unwrap().len(); | |
| assert_eq!(new_len, len * 2); | |
| } | |
| /// Check that we are able to attach using ksyscall | |
| #[tag(root)] | |
| #[test] | |
| fn test_attach_ksyscall() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("ksyscall.bpf.o"); | |
| let mut prog = get_prog_mut(&mut obj, "handle__ksyscall"); | |
| let _link = prog | |
| .attach_ksyscall(false, "kill") | |
| .expect("failed to attach prog"); | |
| let map = get_map_mut(&mut obj, "ringbuf"); | |
| let action = || { | |
| // Send `SIGCHLD`, which is ignored by default, to our process. | |
| let ret = unsafe { libc::kill(libc::getpid(), libc::SIGCHLD) }; | |
| if ret < 0 { | |
| panic!("kill failed: {}", io::Error::last_os_error()); | |
| } | |
| }; | |
| let result = with_ringbuffer(&map, action); | |
| assert_eq!(result, 1); | |
| } | |
| /// Check that we can invoke a program directly. | |
| #[tag(root)] | |
| #[test] | |
| fn test_run_prog_success() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("run_prog.bpf.o"); | |
| let mut prog = get_prog_mut(&mut obj, "test_1"); | |
| #[repr(C)] | |
| struct bpf_dummy_ops_state { | |
| val: c_int, | |
| } | |
| let value = 42; | |
| let state = bpf_dummy_ops_state { val: value }; | |
| let mut args = [addr_of!(state) as u64]; | |
| let input = ProgramInput { | |
| context_in: Some(unsafe { | |
| slice::from_raw_parts_mut(&mut args as *mut _ as *mut u8, size_of_val(&args)) | |
| }), | |
| ..Default::default() | |
| }; | |
| let output = prog.test_run(input).unwrap(); | |
| assert_eq!(output.return_value, value as _); | |
| } | |
| /// Check that we fail program invocation when providing insufficient arguments. | |
| #[tag(root)] | |
| #[test] | |
| fn test_run_prog_fail() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("run_prog.bpf.o"); | |
| let mut prog = get_prog_mut(&mut obj, "test_2"); | |
| let input = ProgramInput::default(); | |
| let _err = prog.test_run(input).unwrap_err(); | |
| } |