Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add helpers for querying info from existing bpf programs #675

Merged
merged 3 commits into from
Nov 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
266 changes: 253 additions & 13 deletions aya/src/maps/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,15 @@ 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_info_by_fd,
bpf_map_get_next_key, bpf_map_update_elem_ptr, bpf_pin_object, SyscallError,
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,
},
util::{nr_cpus, KernelVersion},
util::{bytes_of_bpf_name, nr_cpus, KernelVersion},
PinningType, Pod,
};

Expand Down Expand Up @@ -638,14 +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())?;
Self::from_fd(fd)
}

Ok(Self {
obj: parse_map_info(info, PinningType::ByName),
fd,
})
/// Loads a map from a map id.
pub fn from_id(id: u32) -> Result<Self, MapError> {
let fd = bpf_map_get_fd_by_id(id)?;
Self::from_fd(fd)
}

/// Loads a map from a file descriptor.
Expand All @@ -654,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 @@ -715,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 @@ -903,6 +908,119 @@ 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(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 @@ -935,6 +1053,38 @@ mod tests {
})
}

#[test]
fn test_from_map_id() {
override_syscall(|call| match call {
Syscall::Bpf {
cmd: bpf_cmd::BPF_MAP_GET_FD_BY_ID,
attr,
} => {
assert_eq!(
unsafe { attr.__bindgen_anon_6.__bindgen_anon_1.map_id },
1234
);
Ok(42)
}
Syscall::Bpf {
cmd: bpf_cmd::BPF_OBJ_GET_INFO_BY_FD,
attr,
} => {
assert_eq!(unsafe { attr.info.bpf_fd }, 42);
Ok(0)
}
_ => Err((-1, io::Error::from_raw_os_error(EFAULT))),
});

assert_matches!(
MapData::from_id(1234),
Ok(MapData {
obj: _,
fd,
}) => assert_eq!(fd.as_fd().as_raw_fd(), 42)
);
}

#[test]
fn test_create() {
override_syscall(|call| match call {
Expand All @@ -954,6 +1104,96 @@ mod tests {
);
}

#[test]
// Syscall overrides are performing integer-to-pointer conversions, which
// should be done with `ptr::from_exposed_addr` in Rust nightly, but we have
// to support stable as well.
#[cfg_attr(miri, ignore)]
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]
// Syscall overrides are performing integer-to-pointer conversions, which
// should be done with `ptr::from_exposed_addr` in Rust nightly, but we have
// to support stable as well.
#[cfg_attr(miri, ignore)]
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 }) as c_long),
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
14 changes: 2 additions & 12 deletions aya/src/programs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ use crate::{
bpf_prog_query, iter_link_ids, iter_prog_ids, retry_with_verifier_logs,
BpfLoadProgramAttrs, SyscallError,
},
util::KernelVersion,
util::{bytes_of_bpf_name, KernelVersion},
VerifierLogLevel,
};

Expand Down Expand Up @@ -1002,17 +1002,7 @@ impl ProgramInfo {

/// The name of the program as was provided when it was load. This is limited to 16 bytes
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) }
bytes_of_bpf_name(&self.0.name)
}

/// The name of the program as a &str. If the name was not valid unicode, None is returned.
Expand Down
19 changes: 19 additions & 0 deletions aya/src/sys/bpf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,21 @@ pub(crate) fn bpf_prog_get_info_by_fd(
})
}

pub(crate) fn bpf_map_get_fd_by_id(map_id: u32) -> Result<OwnedFd, SyscallError> {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };

attr.__bindgen_anon_6.__bindgen_anon_1.map_id = map_id;

// SAFETY: BPF_MAP_GET_FD_BY_ID returns a new file descriptor.
unsafe { fd_sys_bpf(bpf_cmd::BPF_MAP_GET_FD_BY_ID, &mut attr) }.map_err(|(code, io_error)| {
assert_eq!(code, -1);
SyscallError {
call: "bpf_map_get_fd_by_id",
io_error,
}
})
}

pub(crate) fn bpf_map_get_info_by_fd(fd: BorrowedFd<'_>) -> Result<bpf_map_info, SyscallError> {
bpf_obj_get_info_by_fd(fd, |_| {})
}
Expand Down Expand Up @@ -1034,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
9 changes: 9 additions & 0 deletions aya/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,15 @@ pub(crate) fn bytes_of_slice<T: Pod>(val: &[T]) -> &[u8] {
unsafe { slice::from_raw_parts(val.as_ptr().cast(), size) }
}

pub(crate) fn bytes_of_bpf_name(bpf_name: &[core::ffi::c_char; 16]) -> &[u8] {
let length = bpf_name
.iter()
.rposition(|ch| *ch != 0)
.map(|pos| pos + 1)
.unwrap_or(0);
unsafe { std::slice::from_raw_parts(bpf_name.as_ptr() as *const _, length) }
}

#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
Expand Down
Loading