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.
reviewable/pr1306/r12
Adam Schreck 2 months ago committed by Adam Schreck
parent 214fe3c367
commit e997f4f93d

@ -370,6 +370,60 @@ impl Map {
Self::XskMap(map) => map.pin(path), 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<Self, MapError> {
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 // Implements map pinning for different map implementations

@ -7,6 +7,7 @@ mod iter;
mod linear_data_structures; mod linear_data_structures;
mod load; mod load;
mod log; mod log;
mod map_pin;
mod raw_tracepoint; mod raw_tracepoint;
mod rbpf; mod rbpf;
mod relocations; mod relocations;

@ -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::<u64>()
));
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);
}

@ -1083,6 +1083,7 @@ pub aya::maps::Map::StackTraceMap(aya::maps::MapData)
pub aya::maps::Map::Unsupported(aya::maps::MapData) pub aya::maps::Map::Unsupported(aya::maps::MapData)
pub aya::maps::Map::XskMap(aya::maps::MapData) pub aya::maps::Map::XskMap(aya::maps::MapData)
impl aya::maps::Map impl aya::maps::Map
pub fn aya::maps::Map::from_map_data(map_data: aya::maps::MapData) -> core::result::Result<Self, aya::maps::MapError>
pub fn aya::maps::Map::pin<P: core::convert::AsRef<std::path::Path>>(&self, path: P) -> core::result::Result<(), aya::pin::PinError> pub fn aya::maps::Map::pin<P: core::convert::AsRef<std::path::Path>>(&self, path: P) -> core::result::Result<(), aya::pin::PinError>
impl core::convert::TryFrom<aya::maps::Map> for aya::maps::CpuMap<aya::maps::MapData> impl core::convert::TryFrom<aya::maps::Map> for aya::maps::CpuMap<aya::maps::MapData>
pub type aya::maps::CpuMap<aya::maps::MapData>::Error = aya::maps::MapError pub type aya::maps::CpuMap<aya::maps::MapData>::Error = aya::maps::MapError

Loading…
Cancel
Save