From e997f4f93d99c4c28885ef71ee9262e22de4462a Mon Sep 17 00:00:00 2001 From: Adam Schreck Date: Thu, 24 Jul 2025 01:11:15 +0000 Subject: [PATCH] aya: add Map::from_map_data() for pinned map access Enables creation of Map enum variants directly from MapData instances, allowing user-space handles to pinned BPF maps without requiring the original BPF object. Supports multiple BPF map types. Motivation: - Simplifies accessing pinned maps from user space applications. - Avoids full BPF reloads and potential deadlocks. - Matches existing ergonomic APIs like LruHashMap::try_from. - Keeps user code safe and idiomatic. Closes https://github.com/aya-rs/aya/issues/1305. Includes test coverage to validate the new API. --- aya/src/maps/mod.rs | 54 +++++++++++++++++++ test/integration-test/src/tests.rs | 1 + test/integration-test/src/tests/map_pin.rs | 60 ++++++++++++++++++++++ xtask/public-api/aya.txt | 1 + 4 files changed, 116 insertions(+) create mode 100644 test/integration-test/src/tests/map_pin.rs diff --git a/aya/src/maps/mod.rs b/aya/src/maps/mod.rs index 0d3e0ebf..d8593a40 100644 --- a/aya/src/maps/mod.rs +++ b/aya/src/maps/mod.rs @@ -370,6 +370,60 @@ 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. + /// + /// # 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(); + let map = match bpf_map_type::try_from(map_type)? { + bpf_map_type::BPF_MAP_TYPE_HASH => Self::HashMap(map_data), + bpf_map_type::BPF_MAP_TYPE_ARRAY => Self::Array(map_data), + bpf_map_type::BPF_MAP_TYPE_PROG_ARRAY => Self::ProgramArray(map_data), + bpf_map_type::BPF_MAP_TYPE_PERF_EVENT_ARRAY => Self::PerfEventArray(map_data), + bpf_map_type::BPF_MAP_TYPE_PERCPU_HASH => Self::PerCpuHashMap(map_data), + bpf_map_type::BPF_MAP_TYPE_PERCPU_ARRAY => Self::PerCpuArray(map_data), + bpf_map_type::BPF_MAP_TYPE_STACK_TRACE => Self::StackTraceMap(map_data), + bpf_map_type::BPF_MAP_TYPE_LRU_HASH => Self::LruHashMap(map_data), + bpf_map_type::BPF_MAP_TYPE_LRU_PERCPU_HASH => Self::PerCpuLruHashMap(map_data), + bpf_map_type::BPF_MAP_TYPE_LPM_TRIE => Self::LpmTrie(map_data), + bpf_map_type::BPF_MAP_TYPE_DEVMAP => Self::DevMap(map_data), + bpf_map_type::BPF_MAP_TYPE_SOCKMAP => Self::SockMap(map_data), + bpf_map_type::BPF_MAP_TYPE_CPUMAP => Self::CpuMap(map_data), + bpf_map_type::BPF_MAP_TYPE_XSKMAP => Self::XskMap(map_data), + bpf_map_type::BPF_MAP_TYPE_SOCKHASH => Self::SockHash(map_data), + bpf_map_type::BPF_MAP_TYPE_QUEUE => Self::Queue(map_data), + bpf_map_type::BPF_MAP_TYPE_STACK => Self::Stack(map_data), + bpf_map_type::BPF_MAP_TYPE_DEVMAP_HASH => Self::DevMapHash(map_data), + bpf_map_type::BPF_MAP_TYPE_RINGBUF => Self::RingBuf(map_data), + bpf_map_type::BPF_MAP_TYPE_BLOOM_FILTER => Self::BloomFilter(map_data), + bpf_map_type::BPF_MAP_TYPE_CGROUP_ARRAY => Self::Unsupported(map_data), + bpf_map_type::BPF_MAP_TYPE_ARRAY_OF_MAPS => Self::Unsupported(map_data), + bpf_map_type::BPF_MAP_TYPE_HASH_OF_MAPS => Self::Unsupported(map_data), + bpf_map_type::BPF_MAP_TYPE_CGROUP_STORAGE_DEPRECATED => Self::Unsupported(map_data), + bpf_map_type::BPF_MAP_TYPE_REUSEPORT_SOCKARRAY => Self::Unsupported(map_data), + bpf_map_type::BPF_MAP_TYPE_SK_STORAGE => Self::Unsupported(map_data), + bpf_map_type::BPF_MAP_TYPE_STRUCT_OPS => Self::Unsupported(map_data), + bpf_map_type::BPF_MAP_TYPE_INODE_STORAGE => Self::Unsupported(map_data), + bpf_map_type::BPF_MAP_TYPE_TASK_STORAGE => Self::Unsupported(map_data), + bpf_map_type::BPF_MAP_TYPE_USER_RINGBUF => Self::Unsupported(map_data), + bpf_map_type::BPF_MAP_TYPE_CGRP_STORAGE => Self::Unsupported(map_data), + bpf_map_type::BPF_MAP_TYPE_ARENA => Self::Unsupported(map_data), + bpf_map_type::BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE_DEPRECATED => { + Self::Unsupported(map_data) + } + bpf_map_type::BPF_MAP_TYPE_UNSPEC => return Err(MapError::InvalidMapType { map_type }), + bpf_map_type::__MAX_BPF_MAP_TYPE => return Err(MapError::InvalidMapType { map_type }), + }; + Ok(map) + } } // Implements map pinning for different map implementations 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..adf84f78 --- /dev/null +++ b/test/integration-test/src/tests/map_pin.rs @@ -0,0 +1,60 @@ +use std::path::Path; + +use aya::{ + Ebpf, + maps::{HashMap, Map, MapData, MapType}, + programs::{ProgramType, SocketFilter}, + sys::{is_map_supported, is_program_supported}, +}; +use rand::Rng as _; +use scopeguard::defer; + +#[test_log::test] +fn pin_and_reopen_hashmap() { + // This ProgramType and these two MapTypes are needed because the MAP_TEST sample program uses all three. + 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 the eBPF program to create the file descriptor associated with the BAR map. This is + // required to read and write to the map which we test below. + 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(); + + 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(); + + let mut rng = rand::rng(); + let pin_path = Path::new("/sys/fs/bpf/").join(format!( + "test_pin_and_reopen_hashmap_{:x}", + rng.random::() + )); + hash_to_pin.pin(&pin_path).unwrap(); + defer! { + std::fs::remove_file(&pin_path).unwrap(); + } + + // Get fresh reference since pin() will consume hash_to_pin. + let hash_from_bpf: HashMap<_, u32, u8> = HashMap::try_from(bpf.map("BAR").unwrap()).unwrap(); + + // This is the critical part of the test. We reopen the map using the pin and verify both + // references point to the same underlying map data without needing to call bpf.map_mut. + 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(); + assert_eq!(hash_from_pin.get(&0, 0).unwrap(), 1); + + // Try updating data in the map using the pin to verify both maps point and can mutate the same data. + hash_from_pin.insert(0, 2, 0).unwrap(); + 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