Skip to content

Commit

Permalink
aya: add MapInfo struct following the same pattern as ProgramInfo
Browse files Browse the repository at this point in the history
This makes the APIs for loading maps and programs more similar.
  • Loading branch information
preuss-adam committed Oct 31, 2023
1 parent ec797b9 commit b138a5a
Show file tree
Hide file tree
Showing 3 changed files with 259 additions and 15 deletions.
232 changes: 217 additions & 15 deletions aya/src/maps/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,13 @@ use obj::maps::InvalidMapTypeError;
use thiserror::Error;

use crate::{
generated::bpf_map_info,
obj::{self, parse_map_info, BpfSectionKind},
pin::PinError,
sys::{
bpf_create_map, bpf_get_object, bpf_map_freeze, bpf_map_get_fd_by_id,
bpf_map_get_info_by_fd, bpf_map_get_next_key, bpf_map_update_elem_ptr, bpf_pin_object,
SyscallError,
iter_map_ids, SyscallError,
},
util::{nr_cpus, KernelVersion},
PinningType, Pod,
Expand Down Expand Up @@ -639,21 +640,14 @@ impl MapData {
call: "BPF_OBJ_GET",
io_error,
})?;
let fd = MapFd(fd);

let info = bpf_map_get_info_by_fd(fd.as_fd())?;

Ok(Self {
obj: parse_map_info(info, PinningType::ByName),
fd,
})
Self::from_fd(fd)
}

/// Loads a map from a map id.
pub fn from_id(id: u32) -> Result<Self, MapError> {
bpf_map_get_fd_by_id(id)
.map_err(MapError::from)
.and_then(Self::from_fd)
let fd = bpf_map_get_fd_by_id(id)?;
Self::from_fd(fd)
}

/// Loads a map from a file descriptor.
Expand All @@ -662,12 +656,10 @@ impl MapData {
/// This API is intended for cases where you have received a valid BPF FD from some other means.
/// For example, you received an FD over Unix Domain Socket.
pub fn from_fd(fd: OwnedFd) -> Result<Self, MapError> {
let info = bpf_map_get_info_by_fd(fd.as_fd())?;

let fd = MapFd(fd);
let MapInfo(info) = MapInfo::new_from_fd(fd.as_fd())?;
Ok(Self {
obj: parse_map_info(info, PinningType::None),
fd,
fd: MapFd(fd),
})
}

Expand Down Expand Up @@ -723,6 +715,11 @@ impl MapData {
let Self { obj, fd: _ } = self;
obj
}

/// Returns the kernel's information about the loaded map.
pub fn info(&self) -> Result<MapInfo, MapError> {
MapInfo::new_from_fd(self.fd.as_fd())
}
}

/// An iterable map
Expand Down Expand Up @@ -911,6 +908,129 @@ impl<T: Pod> Deref for PerCpuValues<T> {
}
}

/// Provides information about a loaded map, like name, id and size information.
#[derive(Debug)]
pub struct MapInfo(bpf_map_info);

impl MapInfo {
fn new_from_fd(fd: BorrowedFd<'_>) -> Result<Self, MapError> {
let info = bpf_map_get_info_by_fd(fd.as_fd())?;
Ok(Self(info))
}

/// Loads map info from a map id.
pub fn from_id(id: u32) -> Result<Self, MapError> {
bpf_map_get_fd_by_id(id)
.map_err(MapError::from)
.and_then(|fd| Self::new_from_fd(fd.as_fd()))
}

/// The name of the map.
pub fn name(&self) -> &[u8] {
let length = self
.0
.name
.iter()
.rposition(|ch| *ch != 0)
.map(|pos| pos + 1)
.unwrap_or(0);

// The name field is defined as [std::os::raw::c_char; 16]. c_char may be signed or
// unsigned depending on the platform; that's why we're using from_raw_parts here.
unsafe { std::slice::from_raw_parts(self.0.name.as_ptr() as *const _, length) }
}

/// The name of the map as a &str. If the name is not valid unicode, None is returned.
pub fn name_as_str(&self) -> Option<&str> {
std::str::from_utf8(self.name()).ok()
}

/// The id for this map. Each map has a unique id.
pub fn id(&self) -> u32 {
self.0.id
}

/// The map type as defined by the linux kernel enum
/// [`bpf_map_type`](https://elixir.bootlin.com/linux/v6.4.4/source/include/uapi/linux/bpf.h#L905).
pub fn map_type(&self) -> u32 {
self.0.type_
}

/// The key size for this map.
pub fn key_size(&self) -> u32 {
self.0.key_size
}

/// The value size for this map.
pub fn value_size(&self) -> u32 {
self.0.value_size
}

/// The maximum number of entries in this map.
pub fn max_entries(&self) -> u32 {
self.0.max_entries
}

/// The flags for this map.
pub fn map_flags(&self) -> u32 {
self.0.map_flags
}

/// Returns a file descriptor referencing the map.
///
/// The returned file descriptor can be closed at any time and doing so does
/// not influence the life cycle of the map.
pub fn fd(&self) -> Result<MapFd, MapError> {
let Self(info) = self;
let fd = bpf_map_get_fd_by_id(info.id)?;
Ok(MapFd(fd))
}

/// Loads a map from a pinned path in bpffs.
pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, MapError> {
use std::os::unix::ffi::OsStrExt as _;

// TODO: avoid this unwrap by adding a new error variant.
let path_string = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap();
let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError {
call: "BPF_OBJ_GET",
io_error,
})?;

Self::new_from_fd(fd.as_fd())
}
}

/// Returns an iterator over all loaded bpf maps.
///
/// This differs from [`crate::Bpf::maps`] since it will return all maps
/// listed on the host system and not only maps for a specific [`crate::Bpf`] instance.
///
/// # Example
/// ```
/// # use aya::maps::loaded_maps;
///
/// for m in loaded_maps() {
/// match m {
/// Ok(map) => println!("{:?}", map.name_as_str()),
/// Err(e) => println!("Error iterating maps: {:?}", e),
/// }
/// }
/// ```
///
/// # Errors
///
/// Returns [`MapError::SyscallError`] if any of the syscalls required to either get
/// next map id, get the map fd, or the [`MapInfo`] fail. In cases where
/// iteration can't be performed, for example the caller does not have the necessary privileges,
/// a single item will be yielded containing the error that occurred.
pub fn loaded_maps() -> impl Iterator<Item = Result<MapInfo, MapError>> {
iter_map_ids().map(|id| {
let id = id?;
MapInfo::from_id(id)
})
}

#[cfg(test)]
mod tests {
use std::os::fd::AsRawFd as _;
Expand Down Expand Up @@ -994,6 +1114,88 @@ mod tests {
);
}

#[test]
fn test_name() {
use crate::generated::bpf_map_info;

const TEST_NAME: &str = "foo";

override_syscall(|call| match call {
Syscall::Bpf {
cmd: bpf_cmd::BPF_MAP_CREATE,
..
} => Ok(42),
Syscall::Bpf {
cmd: bpf_cmd::BPF_OBJ_GET_INFO_BY_FD,
attr,
} => {
assert_eq!(
unsafe { attr.info.info_len },
mem::size_of::<bpf_map_info>() as u32
);
let map_info = unsafe { &mut *(attr.info.info as *mut bpf_map_info) };
map_info.name[..TEST_NAME.len()]
.copy_from_slice(unsafe { std::mem::transmute(TEST_NAME) });
Ok(0)
}
_ => Err((-1, io::Error::from_raw_os_error(EFAULT))),
});

let map_data = MapData::create(new_obj_map(), TEST_NAME, None).unwrap();
assert_eq!(TEST_NAME, map_data.info().unwrap().name_as_str().unwrap());
}

#[test]
fn test_loaded_maps() {
use crate::generated::bpf_map_info;

override_syscall(|call| match call {
Syscall::Bpf {
cmd: bpf_cmd::BPF_MAP_GET_NEXT_ID,
attr,
} => unsafe {
let id = attr.__bindgen_anon_6.__bindgen_anon_1.start_id;
if id < 5 {
attr.__bindgen_anon_6.next_id = id + 1;
Ok(0)
} else {
Err((-1, io::Error::from_raw_os_error(libc::ENOENT)))
}
},
Syscall::Bpf {
cmd: bpf_cmd::BPF_MAP_GET_FD_BY_ID,
attr,
} => Ok((1000 + unsafe { attr.__bindgen_anon_6.__bindgen_anon_1.map_id }).into()),
Syscall::Bpf {
cmd: bpf_cmd::BPF_OBJ_GET_INFO_BY_FD,
attr,
} => {
let map_info = unsafe { &mut *(attr.info.info as *mut bpf_map_info) };
map_info.id = unsafe { attr.info.bpf_fd } - 1000;
map_info.key_size = 32;
map_info.value_size = 64;
map_info.map_flags = 1234;
map_info.max_entries = 99;
Ok(0)
}
_ => Err((-1, io::Error::from_raw_os_error(EFAULT))),
});

let loaded_maps: Vec<_> = loaded_maps().collect();
assert_eq!(loaded_maps.len(), 5);

for (i, map_info) in loaded_maps.into_iter().enumerate() {
let i = i + 1;
let map_info = map_info.unwrap();
assert_eq!(map_info.id(), i as u32);
assert_eq!(map_info.key_size(), 32);
assert_eq!(map_info.value_size(), 64);
assert_eq!(map_info.map_flags(), 1234);
assert_eq!(map_info.max_entries(), 99);
assert_eq!(map_info.fd().unwrap().as_fd().as_raw_fd(), 1000 + i as i32);
}
}

#[test]
fn test_create_failed() {
override_syscall(|_| Err((-42, io::Error::from_raw_os_error(EFAULT))));
Expand Down
4 changes: 4 additions & 0 deletions aya/src/sys/bpf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,10 @@ pub(crate) fn iter_link_ids() -> impl Iterator<Item = Result<u32, SyscallError>>
iter_obj_ids(bpf_cmd::BPF_LINK_GET_NEXT_ID, "bpf_link_get_next_id")
}

pub(crate) fn iter_map_ids() -> impl Iterator<Item = Result<u32, SyscallError>> {
iter_obj_ids(bpf_cmd::BPF_MAP_GET_NEXT_ID, "bpf_map_get_next_id")
}

pub(crate) fn retry_with_verifier_logs<T>(
max_retries: usize,
f: impl Fn(&mut [u8]) -> SysResult<T>,
Expand Down
38 changes: 38 additions & 0 deletions xtask/public-api/aya.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1688,6 +1688,7 @@ pub fn aya::maps::MapData::fd(&self) -> &aya::maps::MapFd
pub fn aya::maps::MapData::from_fd(fd: std::os::fd::owned::OwnedFd) -> core::result::Result<Self, aya::maps::MapError>
pub fn aya::maps::MapData::from_id(id: u32) -> core::result::Result<Self, aya::maps::MapError>
pub fn aya::maps::MapData::from_pin<P: core::convert::AsRef<std::path::Path>>(path: P) -> core::result::Result<Self, aya::maps::MapError>
pub fn aya::maps::MapData::info(&self) -> core::result::Result<aya::maps::MapInfo, aya::maps::MapError>
pub fn aya::maps::MapData::pin<P: core::convert::AsRef<std::path::Path>>(&mut self, path: P) -> core::result::Result<(), aya::pin::PinError>
impl core::fmt::Debug for aya::maps::MapData
pub fn aya::maps::MapData::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
Expand Down Expand Up @@ -1738,6 +1739,42 @@ impl<T> core::borrow::BorrowMut<T> for aya::maps::MapFd where T: core::marker::S
pub fn aya::maps::MapFd::borrow_mut(&mut self) -> &mut T
impl<T> core::convert::From<T> for aya::maps::MapFd
pub fn aya::maps::MapFd::from(t: T) -> T
pub struct aya::maps::MapInfo(_)
impl aya::maps::MapInfo
pub fn aya::maps::MapInfo::fd(&self) -> core::result::Result<aya::maps::MapFd, aya::maps::MapError>
pub fn aya::maps::MapInfo::from_id(id: u32) -> core::result::Result<Self, aya::maps::MapError>
pub fn aya::maps::MapInfo::from_pin<P: core::convert::AsRef<std::path::Path>>(path: P) -> core::result::Result<Self, aya::maps::MapError>
pub fn aya::maps::MapInfo::id(&self) -> u32
pub fn aya::maps::MapInfo::key_size(&self) -> u32
pub fn aya::maps::MapInfo::map_flags(&self) -> u32
pub fn aya::maps::MapInfo::map_type(&self) -> u32
pub fn aya::maps::MapInfo::max_entries(&self) -> u32
pub fn aya::maps::MapInfo::name(&self) -> &[u8]
pub fn aya::maps::MapInfo::name_as_str(&self) -> core::option::Option<&str>
pub fn aya::maps::MapInfo::value_size(&self) -> u32
impl core::fmt::Debug for aya::maps::MapInfo
pub fn aya::maps::MapInfo::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
impl core::marker::Send for aya::maps::MapInfo
impl core::marker::Sync for aya::maps::MapInfo
impl core::marker::Unpin for aya::maps::MapInfo
impl core::panic::unwind_safe::RefUnwindSafe for aya::maps::MapInfo
impl core::panic::unwind_safe::UnwindSafe for aya::maps::MapInfo
impl<T, U> core::convert::Into<U> for aya::maps::MapInfo where U: core::convert::From<T>
pub fn aya::maps::MapInfo::into(self) -> U
impl<T, U> core::convert::TryFrom<U> for aya::maps::MapInfo where U: core::convert::Into<T>
pub type aya::maps::MapInfo::Error = core::convert::Infallible
pub fn aya::maps::MapInfo::try_from(value: U) -> core::result::Result<T, <T as core::convert::TryFrom<U>>::Error>
impl<T, U> core::convert::TryInto<U> for aya::maps::MapInfo where U: core::convert::TryFrom<T>
pub type aya::maps::MapInfo::Error = <U as core::convert::TryFrom<T>>::Error
pub fn aya::maps::MapInfo::try_into(self) -> core::result::Result<U, <U as core::convert::TryFrom<T>>::Error>
impl<T> core::any::Any for aya::maps::MapInfo where T: 'static + core::marker::Sized
pub fn aya::maps::MapInfo::type_id(&self) -> core::any::TypeId
impl<T> core::borrow::Borrow<T> for aya::maps::MapInfo where T: core::marker::Sized
pub fn aya::maps::MapInfo::borrow(&self) -> &T
impl<T> core::borrow::BorrowMut<T> for aya::maps::MapInfo where T: core::marker::Sized
pub fn aya::maps::MapInfo::borrow_mut(&mut self) -> &mut T
impl<T> core::convert::From<T> for aya::maps::MapInfo
pub fn aya::maps::MapInfo::from(t: T) -> T
pub struct aya::maps::MapIter<'coll, K: aya::Pod, V, I: aya::maps::IterableMap<K, V>>
impl<K: aya::Pod, V, I: aya::maps::IterableMap<K, V>> core::iter::traits::iterator::Iterator for aya::maps::MapIter<'_, K, V, I>
pub type aya::maps::MapIter<'_, K, V, I>::Item = core::result::Result<(K, V), aya::maps::MapError>
Expand Down Expand Up @@ -2289,6 +2326,7 @@ pub fn aya::maps::PerCpuArray<T, V>::map(&self) -> &aya::maps::MapData
impl<T: core::borrow::Borrow<aya::maps::MapData>> aya::maps::IterableMap<u32, aya::maps::stack_trace::StackTrace> for aya::maps::stack_trace::StackTraceMap<T>
pub fn aya::maps::stack_trace::StackTraceMap<T>::get(&self, index: &u32) -> core::result::Result<aya::maps::stack_trace::StackTrace, aya::maps::MapError>
pub fn aya::maps::stack_trace::StackTraceMap<T>::map(&self) -> &aya::maps::MapData
pub fn aya::maps::loaded_maps() -> impl core::iter::traits::iterator::Iterator<Item = core::result::Result<aya::maps::MapInfo, aya::maps::MapError>>
pub mod aya::pin
pub enum aya::pin::PinError
pub aya::pin::PinError::InvalidPinPath
Expand Down

0 comments on commit b138a5a

Please sign in to comment.