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 hash map types:
- BPF_MAP_TYPE_HASH
- BPF_MAP_TYPE_LRU_HASH
- BPF_MAP_TYPE_PERCPU_HASH
- BPF_MAP_TYPE_LRU_PERCPU_HASH

Motivation:
- Simplifies accessing pinned maps in multi-threaded user space
- 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/r1
Adam Schreck 2 months ago committed by Adam Schreck
parent 214fe3c367
commit a83fec2789

@ -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<Self, MapError> {
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::<TestKey>() as u32;
info.value_size = size_of::<TestVal>() 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::<TestKey>() as u32,
value_size: size_of::<TestVal>() 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);
}
}

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

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

@ -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<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>
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

Loading…
Cancel
Save