aya: add feature probing for map type

Add API that probes whether kernel supports a map type.
reviewable/pr1063/r26
Tyrone Wu 6 months ago
parent d88f9cdd0e
commit 016dc8e1a4
No known key found for this signature in database
GPG Key ID: 978B1A1B79210AD6

@ -681,7 +681,10 @@ pub(crate) fn bpf_load_btf(
}
// SAFETY: only use for bpf_cmd that return a new file descriptor on success.
unsafe fn fd_sys_bpf(cmd: bpf_cmd, attr: &mut bpf_attr) -> io::Result<crate::MockableFd> {
pub(super) unsafe fn fd_sys_bpf(
cmd: bpf_cmd,
attr: &mut bpf_attr,
) -> io::Result<crate::MockableFd> {
let fd = sys_bpf(cmd, attr)?;
let fd = fd.try_into().map_err(|std::num::TryFromIntError { .. }| {
io::Error::new(

@ -1,10 +1,19 @@
//! Probes and identifies available eBPF features supported by the host kernel.
use aya_obj::btf::{Btf, BtfKind};
use libc::{E2BIG, EINVAL};
use std::{mem, os::fd::AsRawFd as _};
use super::{SyscallError, bpf_prog_load, with_trivial_prog};
use crate::programs::{ProgramError, ProgramType};
use aya_obj::{
btf::{Btf, BtfKind},
generated::{BPF_F_MMAPABLE, BPF_F_NO_PREALLOC, bpf_attr, bpf_cmd, bpf_map_type},
};
use libc::{E2BIG, EBADF, EINVAL};
use super::{SyscallError, bpf_prog_load, fd_sys_bpf, with_trivial_prog};
use crate::{
maps::MapType,
programs::{ProgramError, ProgramType},
util::page_size,
};
/// Whether the host kernel supports the [`ProgramType`].
///
@ -109,3 +118,142 @@ pub fn is_program_supported(program_type: ProgramType) -> Result<bool, ProgramEr
_ => Err(error),
}
}
/// Whether the host kernel supports the [`MapType`].
///
/// # Examples
///
/// ```no_run
/// # use aya::{maps::MapType, sys::is_map_supported};
/// #
/// match is_map_supported(MapType::HashOfMaps) {
/// Ok(true) => println!("hash_of_maps supported :)"),
/// Ok(false) => println!("hash_of_maps not supported :("),
/// Err(err) => println!("Uh oh! Unexpected error: {:?}", err),
/// }
/// ```
///
/// # Errors
///
/// Returns [`SyscallError`] if kernel probing fails with an unexpected error.
///
/// Note that certain errors are expected and handled internally; only
/// unanticipated failures during probing will result in this error.
pub fn is_map_supported(map_type: MapType) -> Result<bool, SyscallError> {
if map_type == MapType::Unspecified {
return Ok(false);
}
// SAFETY: all-zero byte-pattern valid for `bpf_attr`
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
// SAFETY: union access
let u = unsafe { &mut attr.__bindgen_anon_1 };
// To pass `map_alloc_check`/`map_alloc`
let key_size = match map_type {
MapType::LpmTrie | MapType::CgroupStorage | MapType::PerCpuCgroupStorage => 16,
MapType::Queue
| MapType::Stack
| MapType::RingBuf
| MapType::BloomFilter
| MapType::UserRingBuf
| MapType::Arena => 0,
_ => 4,
};
let value_size = match map_type {
MapType::StackTrace | MapType::LpmTrie => 8,
MapType::RingBuf | MapType::UserRingBuf | MapType::Arena => 0,
_ => 4,
};
let max_entries = match map_type {
MapType::CgroupStorage
| MapType::PerCpuCgroupStorage
| MapType::SkStorage
| MapType::InodeStorage
| MapType::TaskStorage
| MapType::CgrpStorage => 0,
MapType::RingBuf | MapType::UserRingBuf => page_size() as u32,
_ => 1,
};
// Ensure that fd doesn't get dropped due to scoping.
let inner_map_fd;
match map_type {
MapType::LpmTrie => u.map_flags = BPF_F_NO_PREALLOC,
MapType::SkStorage
| MapType::InodeStorage
| MapType::TaskStorage
| MapType::CgrpStorage => {
u.map_flags = BPF_F_NO_PREALLOC;
// Intentionally trigger `EBADF` from `btf_get_by_fd()`.
u.btf_fd = u32::MAX;
u.btf_key_type_id = 1;
u.btf_value_type_id = 1;
}
MapType::ArrayOfMaps | MapType::HashOfMaps => {
// SAFETY: all-zero byte-pattern valid for `bpf_attr`
let mut attr_map = unsafe { mem::zeroed::<bpf_attr>() };
// SAFETY: union access
let u_map = unsafe { &mut attr_map.__bindgen_anon_1 };
u_map.map_type = bpf_map_type::BPF_MAP_TYPE_HASH as u32;
u_map.key_size = 1;
u_map.value_size = 1;
u_map.max_entries = 1;
// SAFETY: BPF_MAP_CREATE returns a new file descriptor.
inner_map_fd = unsafe { fd_sys_bpf(bpf_cmd::BPF_MAP_CREATE, &mut attr_map) }.map_err(
|io_error| SyscallError {
call: "bpf_map_create",
io_error,
},
)?;
u.inner_map_fd = inner_map_fd.as_raw_fd() as u32;
}
MapType::StructOps => u.btf_vmlinux_value_type_id = 1,
MapType::Arena => u.map_flags = BPF_F_MMAPABLE,
_ => {}
}
u.map_type = map_type as u32;
u.key_size = key_size;
u.value_size = value_size;
u.max_entries = max_entries;
// SAFETY: BPF_MAP_CREATE returns a new file descriptor.
let io_error = match unsafe { fd_sys_bpf(bpf_cmd::BPF_MAP_CREATE, &mut attr) } {
Ok(_) => return Ok(true),
Err(io_error) => io_error,
};
match io_error.raw_os_error() {
Some(EINVAL) => Ok(false),
Some(E2BIG)
if matches!(
map_type,
MapType::SkStorage
| MapType::StructOps
| MapType::InodeStorage
| MapType::TaskStorage
| MapType::CgrpStorage
) =>
{
Ok(false)
}
Some(EBADF)
if matches!(
map_type,
MapType::SkStorage
| MapType::InodeStorage
| MapType::TaskStorage
| MapType::CgrpStorage
) =>
{
Ok(true)
}
// `ENOTSUPP` from `bpf_struct_ops_map_alloc()` for struct_ops.
Some(524) if map_type == MapType::StructOps => Ok(true),
_ => Err(SyscallError {
call: "bpf_map_create",
io_error,
}),
}
}

@ -18,7 +18,7 @@ use aya_obj::generated::{bpf_attr, bpf_cmd, perf_event_attr};
pub(crate) use bpf::*;
#[cfg(test)]
pub(crate) use fake::*;
pub use feature_probe::is_program_supported;
pub use feature_probe::{is_map_supported, is_program_supported};
#[doc(hidden)]
pub use netlink::netlink_set_link_up;
pub(crate) use netlink::*;

@ -1,6 +1,12 @@
//! Test feature probing against kernel version.
use aya::{Btf, programs::ProgramType, sys::is_program_supported, util::KernelVersion};
use aya::{
Btf,
maps::MapType,
programs::ProgramType,
sys::{is_map_supported, is_program_supported},
util::KernelVersion,
};
use procfs::kernel_config;
use crate::utils::kernel_assert;
@ -130,3 +136,92 @@ fn probe_supported_programs() {
kern_version = KernelVersion::new(6, 4, 0);
kernel_assert!(is_supported!(ProgramType::Netfilter), kern_version);
}
#[test]
fn probe_supported_maps() {
let mut kern_version: KernelVersion;
macro_rules! is_supported {
($map_type:expr) => {
is_map_supported($map_type).unwrap()
};
}
kern_version = KernelVersion::new(3, 19, 0);
kernel_assert!(is_supported!(MapType::Hash), kern_version);
kernel_assert!(is_supported!(MapType::Array), kern_version);
kern_version = KernelVersion::new(4, 2, 0);
kernel_assert!(is_supported!(MapType::ProgramArray), kern_version);
kern_version = KernelVersion::new(4, 3, 0);
kernel_assert!(is_supported!(MapType::PerfEventArray), kern_version);
kern_version = KernelVersion::new(4, 6, 0);
kernel_assert!(is_supported!(MapType::PerCpuHash), kern_version);
kernel_assert!(is_supported!(MapType::PerCpuArray), kern_version);
kernel_assert!(is_supported!(MapType::StackTrace), kern_version);
kern_version = KernelVersion::new(4, 8, 0);
kernel_assert!(is_supported!(MapType::CgroupArray), kern_version);
kern_version = KernelVersion::new(4, 10, 0);
kernel_assert!(is_supported!(MapType::LruHash), kern_version);
kernel_assert!(is_supported!(MapType::LruPerCpuHash), kern_version);
kern_version = KernelVersion::new(4, 11, 0);
kernel_assert!(is_supported!(MapType::LpmTrie), kern_version);
kern_version = KernelVersion::new(4, 12, 0);
kernel_assert!(is_supported!(MapType::ArrayOfMaps), kern_version);
kernel_assert!(is_supported!(MapType::HashOfMaps), kern_version);
kern_version = KernelVersion::new(4, 14, 0);
kernel_assert!(is_supported!(MapType::DevMap), kern_version);
kernel_assert!(is_supported!(MapType::SockMap), kern_version);
kern_version = KernelVersion::new(4, 15, 0);
kernel_assert!(is_supported!(MapType::CpuMap), kern_version);
kern_version = KernelVersion::new(4, 18, 0);
kernel_assert!(is_supported!(MapType::XskMap), kern_version);
kernel_assert!(is_supported!(MapType::SockHash), kern_version);
kern_version = KernelVersion::new(4, 19, 0);
kernel_assert!(is_supported!(MapType::CgroupStorage), kern_version);
kernel_assert!(is_supported!(MapType::ReuseportSockArray), kern_version);
kern_version = KernelVersion::new(4, 20, 0);
kernel_assert!(is_supported!(MapType::PerCpuCgroupStorage), kern_version);
kernel_assert!(is_supported!(MapType::Queue), kern_version);
kernel_assert!(is_supported!(MapType::Stack), kern_version);
kern_version = KernelVersion::new(5, 2, 0);
kernel_assert!(is_supported!(MapType::SkStorage), kern_version);
kern_version = KernelVersion::new(5, 4, 0);
kernel_assert!(is_supported!(MapType::DevMapHash), kern_version);
kern_version = KernelVersion::new(5, 6, 0);
kernel_assert!(is_supported!(MapType::StructOps), kern_version);
kern_version = KernelVersion::new(5, 8, 0);
kernel_assert!(is_supported!(MapType::RingBuf), kern_version);
kern_version = KernelVersion::new(5, 10, 0);
kernel_assert!(is_supported!(MapType::InodeStorage), kern_version); // Requires `CONFIG_BPF_LSM=y`
kern_version = KernelVersion::new(5, 11, 0);
kernel_assert!(is_supported!(MapType::TaskStorage), kern_version);
kern_version = KernelVersion::new(5, 16, 0);
kernel_assert!(is_supported!(MapType::BloomFilter), kern_version);
kern_version = KernelVersion::new(6, 1, 0);
kernel_assert!(is_supported!(MapType::UserRingBuf), kern_version);
kern_version = KernelVersion::new(6, 2, 0);
kernel_assert!(is_supported!(MapType::CgrpStorage), kern_version);
kern_version = KernelVersion::new(6, 9, 0);
kernel_assert!(is_supported!(MapType::Arena), kern_version);
}

@ -10076,6 +10076,7 @@ pub fn aya::sys::SyscallError::borrow_mut(&mut self) -> &mut T
impl<T> core::convert::From<T> for aya::sys::SyscallError
pub fn aya::sys::SyscallError::from(t: T) -> T
pub fn aya::sys::enable_stats(stats_type: aya::sys::Stats) -> core::result::Result<std::os::fd::owned::OwnedFd, aya::sys::SyscallError>
pub fn aya::sys::is_map_supported(map_type: aya::maps::MapType) -> core::result::Result<bool, aya::sys::SyscallError>
pub fn aya::sys::is_program_supported(program_type: aya::programs::ProgramType) -> core::result::Result<bool, aya::programs::ProgramError>
pub mod aya::util
pub struct aya::util::KernelVersion

Loading…
Cancel
Save