diff --git a/aya/src/maps/mod.rs b/aya/src/maps/mod.rs index 0d3e0ebf..c8c19e4d 100644 --- a/aya/src/maps/mod.rs +++ b/aya/src/maps/mod.rs @@ -370,6 +370,32 @@ impl Map { Self::XskMap(map) => map.pin(path), } } + + /// Constructs a [`Map`] enum variant directly from a [`MapData`] instance. + /// + /// This allows creating a user-space handle to a pinned BPF map. It only supports: + /// - [`bpf_map_type::BPF_MAP_TYPE_HASH`] + /// - [`bpf_map_type::BPF_MAP_TYPE_LRU_HASH`] + /// - [`bpf_map_type::BPF_MAP_TYPE_PERCPU_HASH`] + /// - [`bpf_map_type::BPF_MAP_TYPE_LRU_PERCPU_HASH`] + /// + /// # Arguments + /// + /// * `map_data` - The map data obtained from MapData::from_pin(...) + /// + /// # Errors + /// + /// Returns an error if the map type is not supported. + pub fn from_map_data(map_data: MapData) -> Result { + let map_type = map_data.obj.map_type(); + match bpf_map_type::try_from(map_type)? { + bpf_map_type::BPF_MAP_TYPE_HASH => Ok(Self::HashMap(map_data)), + bpf_map_type::BPF_MAP_TYPE_LRU_HASH => Ok(Self::LruHashMap(map_data)), + bpf_map_type::BPF_MAP_TYPE_PERCPU_HASH => Ok(Self::PerCpuHashMap(map_data)), + bpf_map_type::BPF_MAP_TYPE_LRU_PERCPU_HASH => Ok(Self::PerCpuLruHashMap(map_data)), + _ => Err(MapError::InvalidMapType { map_type }), + } + } } // Implements map pinning for different map implementations @@ -1241,4 +1267,84 @@ mod tests { } ); } + + #[test] + #[cfg_attr(miri, ignore = "file operations not supported in Miri isolation")] + fn test_pin_and_reopen_hashmap() { + use std::{env, fs, mem::size_of}; + + use aya_obj::generated::{bpf_cmd, bpf_map_info, bpf_map_type}; + + use crate::{ + Pod, + maps::{HashMap, Map, MapData}, + sys::{Syscall, override_syscall}, + }; + + #[repr(transparent)] + #[derive(Copy, Clone)] + struct TestKey(u32); + unsafe impl Pod for TestKey {} + + #[repr(transparent)] + #[derive(Copy, Clone)] + struct TestVal(u32); + unsafe impl Pod for TestVal {} + + override_syscall(|call| match call { + Syscall::Ebpf { + cmd: bpf_cmd::BPF_MAP_CREATE, + .. + } => Ok(crate::MockableFd::mock_signed_fd().into()), + Syscall::Ebpf { + cmd: bpf_cmd::BPF_OBJ_PIN, + .. + } => Ok(0), + Syscall::Ebpf { + cmd: bpf_cmd::BPF_OBJ_GET, + .. + } => Ok(crate::MockableFd::mock_signed_fd().into()), + Syscall::Ebpf { + cmd: bpf_cmd::BPF_OBJ_GET_INFO_BY_FD, + attr, + } => { + let attr = unsafe { &mut *(attr as *mut aya_obj::generated::bpf_attr) }; + let info = unsafe { &mut *(attr.info.info as *mut bpf_map_info) }; + info.key_size = size_of::() as u32; + info.value_size = size_of::() as u32; + info.type_ = bpf_map_type::BPF_MAP_TYPE_HASH as u32; + Ok(0) + } + call => panic!("unexpected syscall {call:?}"), + }); + + let obj_map = aya_obj::maps::Map::Legacy(aya_obj::maps::LegacyMap { + def: aya_obj::maps::bpf_map_def { + map_type: bpf_map_type::BPF_MAP_TYPE_HASH as u32, + key_size: size_of::() as u32, + value_size: size_of::() as u32, + max_entries: 1024, + map_flags: 0, + id: 0, + pinning: PinningType::None, + }, + section_index: 0, + section_kind: EbpfSectionKind::Maps, + symbol_index: None, + data: Vec::new(), + }); + + let map_data = MapData::create(obj_map, "test_map", None).unwrap(); + let map = Map::HashMap(map_data); + + let pin_path = env::temp_dir().join("aya_test_hash_map"); + let _ = fs::remove_file(&pin_path); + + map.pin(&pin_path).unwrap(); + let reopened_map_data = MapData::from_pin(&pin_path).unwrap(); + let reopened_map = Map::from_map_data(reopened_map_data).unwrap(); + let _: HashMap<_, TestKey, TestVal> = HashMap::try_from(&reopened_map).unwrap(); + + let _ = fs::remove_file(&pin_path); + } } diff --git a/test/integration-test/src/tests.rs b/test/integration-test/src/tests.rs index deaa63a5..2b4901a1 100644 --- a/test/integration-test/src/tests.rs +++ b/test/integration-test/src/tests.rs @@ -7,6 +7,7 @@ mod iter; mod linear_data_structures; mod load; mod log; +mod map_pin; mod raw_tracepoint; mod rbpf; mod relocations; diff --git a/test/integration-test/src/tests/map_pin.rs b/test/integration-test/src/tests/map_pin.rs new file mode 100644 index 00000000..80729545 --- /dev/null +++ b/test/integration-test/src/tests/map_pin.rs @@ -0,0 +1,55 @@ +use std::path::PathBuf; + +use aya::{ + Ebpf, + maps::{HashMap, Map, MapData, MapType}, + programs::{ProgramType, SocketFilter}, + sys::{is_map_supported, is_program_supported}, +}; + +#[test_log::test] +fn pin_and_reopen_hashmap() { + if !is_program_supported(ProgramType::SocketFilter).unwrap() { + eprintln!("skipping test - socket_filter program not supported"); + return; + } else if !is_map_supported(MapType::Hash).unwrap() { + eprintln!("skipping test - hash map not supported"); + return; + } else if !is_map_supported(MapType::Array).unwrap() { + eprintln!("skipping test - array map not supported"); + return; + } + + // Load a program with maps + let mut bpf: Ebpf = Ebpf::load(crate::MAP_TEST).unwrap(); + let prog: &mut SocketFilter = bpf.program_mut("simple_prog").unwrap().try_into().unwrap(); + prog.load().unwrap(); + + // Grab the HashMap from the program and insert some data into it + let mut hash_to_pin: HashMap<_, u32, u8> = + HashMap::try_from(bpf.map_mut("BAR").unwrap()).unwrap(); + hash_to_pin.insert(0, 1, 0).unwrap(); + + // Pin the map + let pin_path = PathBuf::from("/sys/fs/bpf/pin_and_reopen_hashmap_test"); + let _ = std::fs::remove_file(&pin_path); + hash_to_pin.pin(&pin_path).unwrap(); + + // Get a fresh reference to the original map + let hash_from_bpf: HashMap<_, u32, u8> = HashMap::try_from(bpf.map("BAR").unwrap()).unwrap(); + + // Open the pinned map again + let reopened_map_data = MapData::from_pin(&pin_path).unwrap(); + let mut reopened_map = Map::from_map_data(reopened_map_data).unwrap(); + let mut hash_from_pin: HashMap<_, u32, u8> = HashMap::try_from(&mut reopened_map).unwrap(); + + // Verify that the data is still there + assert_eq!(hash_from_pin.get(&0, 0).unwrap(), 1); + + // Try updating data in the map using the new pin + hash_from_pin.insert(0, 2, 0).unwrap(); + + // Verify that both maps have the same data + assert_eq!(hash_from_bpf.get(&0, 0).unwrap(), 2); + assert_eq!(hash_from_pin.get(&0, 0).unwrap(), 2); +} diff --git a/xtask/public-api/aya.txt b/xtask/public-api/aya.txt index 25480b40..7b38d0eb 100644 --- a/xtask/public-api/aya.txt +++ b/xtask/public-api/aya.txt @@ -1083,6 +1083,7 @@ pub aya::maps::Map::StackTraceMap(aya::maps::MapData) pub aya::maps::Map::Unsupported(aya::maps::MapData) pub aya::maps::Map::XskMap(aya::maps::MapData) impl aya::maps::Map +pub fn aya::maps::Map::from_map_data(map_data: aya::maps::MapData) -> core::result::Result pub fn aya::maps::Map::pin>(&self, path: P) -> core::result::Result<(), aya::pin::PinError> impl core::convert::TryFrom for aya::maps::CpuMap pub type aya::maps::CpuMap::Error = aya::maps::MapError