Skip to content

Commit

Permalink
aya,int-test: revamp MapInfo be more friendly with older kernels
Browse files Browse the repository at this point in the history
Adds detection for whether a field is available in `MapInfo`:
- For `map_type()`, we treturn the `bpf_map_type` enum instead of the
  integer representation.
- For fields that can't be zero, we return `Option<NonZero*>` type.
- For `name_as_str()`, it now uses the feature probe `bpf_name()` to
  detect if field is available.
  Although the feature probe checks for program name, it can also be
  used for map name since they were both introduced in the same commit.
  • Loading branch information
tyrone-wu committed Aug 7, 2024
1 parent 06291f4 commit f6ff1c8
Show file tree
Hide file tree
Showing 6 changed files with 261 additions and 170 deletions.
2 changes: 1 addition & 1 deletion aya-log/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ impl EbpfLogger {
None => false,
})
.ok_or(Error::MapNotFound)?;
let map = MapData::from_id(map.id()).map_err(Error::MapError)?;
let map = MapData::from_id(map.id().unwrap().get()).map_err(Error::MapError)?;

Self::read_logs_async(Map::PerfEventArray(map), logger)?;

Expand Down
5 changes: 4 additions & 1 deletion aya-obj/src/obj.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ impl Features {
}
}

/// Returns whether BPF program names are supported.
/// Returns whether BPF program names and map names are supported.
///
/// Although the feature probe performs the check for program name, we can use this to also
/// detect if map name is supported since they were both introduced in the same commit.
pub fn bpf_name(&self) -> bool {
self.bpf_name
}
Expand Down
173 changes: 173 additions & 0 deletions aya/src/maps/info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
//! Metadata information about an eBPF map.

use std::{
ffi::CString,
num::NonZeroU32,
os::fd::{AsFd as _, BorrowedFd},
path::Path,
};

use aya_obj::generated::{bpf_map_info, bpf_map_type};

use super::{MapError, MapFd};
use crate::{
sys::{
bpf_get_object, bpf_map_get_fd_by_id, bpf_map_get_info_by_fd, iter_map_ids, SyscallError,
},
util::bytes_of_bpf_name,
FEATURES,
};

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

impl MapInfo {
pub(crate) 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.
///
/// Uses kernel v4.13 features.
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 map type as defined by the linux kernel enum [`bpf_map_type`].
///
/// Introduced in kernel v4.13.
pub fn map_type(&self) -> bpf_map_type {
bpf_map_type::try_from(self.0.type_).unwrap_or(bpf_map_type::__MAX_BPF_MAP_TYPE)
}

/// The unique ID for this map.
///
/// `None` is returned if the field is not available.
///
/// Introduced in kernel v4.13.
pub fn id(&self) -> Option<NonZeroU32> {
NonZeroU32::new(self.0.id)
}

/// The key size for this map in bytes.
///
/// `None` is returned if the field is not available.
///
/// Introduced in kernel v4.13.
pub fn key_size(&self) -> Option<NonZeroU32> {
NonZeroU32::new(self.0.key_size)
}

/// The value size for this map in bytes.
///
/// `None` is returned if the field is not available.
///
/// Introduced in kernel v4.13.
pub fn value_size(&self) -> Option<NonZeroU32> {
NonZeroU32::new(self.0.value_size)
}

/// The maximum number of entries in this map.
///
/// `None` is returned if the field is not available.
///
/// Introduced in kernel v4.13.
pub fn max_entries(&self) -> Option<NonZeroU32> {
NonZeroU32::new(self.0.max_entries)
}

/// The flags used in loading this map.
///
/// Introduced in kernel v4.13.
pub fn map_flags(&self) -> u32 {
self.0.map_flags
}

/// The name of the map, limited to 16 bytes.
///
/// Introduced in kernel v4.15.
pub fn name(&self) -> &[u8] {
bytes_of_bpf_name(&self.0.name)
}

/// The name of the map as a &str.
///
/// `None` is returned if the name was not valid unicode or if field is not available.
///
/// Introduced in kernel v4.15.
pub fn name_as_str(&self) -> Option<&str> {
let name = std::str::from_utf8(self.name()).ok();
if let Some(name_str) = name {
// Char in program name was introduced in the same commit as map name
if FEATURES.bpf_name() || !name_str.is_empty() {
return name;
}
}
None
}

/// 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.
///
/// Uses kernel v4.13 features.
pub fn fd(&self) -> Result<MapFd, MapError> {
let Self(info) = self;
let fd = bpf_map_get_fd_by_id(info.id)?;
Ok(MapFd::from_fd(fd))
}

/// Loads a map from a pinned path in bpffs.
///
/// Uses kernel v4.4 and v4.13 features.
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::Ebpf::maps`] since it will return all maps
/// listed on the host system and not only maps for a specific [`crate::Ebpf`] instance.
///
/// Uses kernel v4.13 features.
///
/// # 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)
})
}
133 changes: 9 additions & 124 deletions aya/src/maps/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,21 +66,20 @@ use obj::maps::InvalidMapTypeError;
use thiserror::Error;

use crate::{
generated::bpf_map_info,
obj::{self, parse_map_info, EbpfSectionKind},
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,
iter_map_ids, SyscallError,
bpf_create_map, bpf_get_object, bpf_map_freeze, bpf_map_get_fd_by_id, bpf_map_get_next_key,
bpf_map_update_elem_ptr, bpf_pin_object, SyscallError,
},
util::{bytes_of_bpf_name, nr_cpus, KernelVersion},
util::{nr_cpus, KernelVersion},
PinningType, Pod,
};

pub mod array;
pub mod bloom_filter;
pub mod hash_map;
mod info;
pub mod lpm_trie;
pub mod perf;
pub mod queue;
Expand All @@ -93,6 +92,7 @@ pub mod xdp;
pub use array::{Array, PerCpuArray, ProgramArray};
pub use bloom_filter::BloomFilter;
pub use hash_map::{HashMap, PerCpuHashMap};
pub use info::{loaded_maps, MapInfo};
pub use lpm_trie::LpmTrie;
#[cfg(any(feature = "async_tokio", feature = "async_std"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "async_tokio", feature = "async_std"))))]
Expand Down Expand Up @@ -951,121 +951,6 @@ impl<T: Pod> Deref for PerCpuValues<T> {
}
}

/// Provides information about a loaded map, like name, id and size.
#[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, limited to 16 bytes.
pub fn name(&self) -> &[u8] {
bytes_of_bpf_name(&self.0.name)
}

/// 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::from_fd(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::Ebpf::maps`] since it will return all maps
/// listed on the host system and not only maps for a specific [`crate::Ebpf`] instance.
///
/// Uses kernel v4.13 features.
///
/// # 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 test_utils {
use crate::{
Expand Down Expand Up @@ -1336,11 +1221,11 @@ mod tests {
.map(|map_info| {
let map_info = map_info.unwrap();
(
map_info.id(),
map_info.key_size(),
map_info.value_size(),
map_info.id().unwrap().get(),
map_info.key_size().unwrap().get(),
map_info.value_size().unwrap().get(),
map_info.map_flags(),
map_info.max_entries(),
map_info.max_entries().unwrap().get(),
map_info.fd().unwrap().as_fd().as_raw_fd(),
)
})
Expand Down
Loading

0 comments on commit f6ff1c8

Please sign in to comment.