Compare commits

...

2 Commits

Author SHA1 Message Date
Tyrone Wu dd5f2c1fb7
aya,aya-obj: cache feat probed info fields
Cached probed for ProgramInfo fields instead of exposing it through
global FEATURE. Probing occurs on cache miss, which happens when first
accessing the field, *and* if the field is 0.
4 days ago
Tyrone Wu e2da6f8eb7
aya: add feature probing for map type
Add API that probes whether kernel supports a map type.
4 days ago

@ -45,8 +45,6 @@ pub struct Features {
bpf_cookie: bool,
cpumap_prog_id: bool,
devmap_prog_id: bool,
prog_info_map_ids: bool,
prog_info_gpl_compatible: bool,
btf: Option<BtfFeatures>,
}
@ -61,8 +59,6 @@ impl Features {
bpf_cookie: bool,
cpumap_prog_id: bool,
devmap_prog_id: bool,
prog_info_map_ids: bool,
prog_info_gpl_compatible: bool,
btf: Option<BtfFeatures>,
) -> Self {
Self {
@ -73,8 +69,6 @@ impl Features {
bpf_cookie,
cpumap_prog_id,
devmap_prog_id,
prog_info_map_ids,
prog_info_gpl_compatible,
btf,
}
}
@ -117,16 +111,6 @@ impl Features {
self.devmap_prog_id
}
/// Returns whether `bpf_prog_info` supports `nr_map_ids` & `map_ids` fields.
pub fn prog_info_map_ids(&self) -> bool {
self.prog_info_map_ids
}
/// Returns whether `bpf_prog_info` supports `gpl_compatible` field.
pub fn prog_info_gpl_compatible(&self) -> bool {
self.prog_info_gpl_compatible
}
/// If BTF is supported, returns which BTF features are supported.
pub fn btf(&self) -> Option<&BtfFeatures> {
self.btf.as_ref()

@ -32,9 +32,9 @@ use crate::{
bpf_load_btf, is_bpf_cookie_supported, is_bpf_global_data_supported,
is_btf_datasec_supported, is_btf_decl_tag_supported, is_btf_enum64_supported,
is_btf_float_supported, is_btf_func_global_supported, is_btf_func_supported,
is_btf_supported, is_btf_type_tag_supported, is_info_gpl_compatible_supported,
is_info_map_ids_supported, is_perf_link_supported, is_probe_read_kernel_supported,
is_prog_id_supported, is_prog_name_supported, retry_with_verifier_logs,
is_btf_supported, is_btf_type_tag_supported, is_perf_link_supported,
is_probe_read_kernel_supported, is_prog_id_supported, is_prog_name_supported,
retry_with_verifier_logs,
},
util::{bytes_of, bytes_of_slice, nr_cpus, page_size},
};
@ -82,8 +82,6 @@ fn detect_features() -> Features {
is_bpf_cookie_supported(),
is_prog_id_supported(BPF_MAP_TYPE_CPUMAP),
is_prog_id_supported(BPF_MAP_TYPE_DEVMAP),
is_info_map_ids_supported(),
is_info_gpl_compatible_supported(),
btf,
);
debug!("BPF Feature Detection: {:#?}", f);

@ -4,6 +4,7 @@ use std::{
ffi::CString,
os::fd::{AsFd as _, BorrowedFd},
path::Path,
sync::OnceLock,
time::{Duration, SystemTime},
};
@ -16,7 +17,9 @@ use super::{
use crate::{
FEATURES,
sys::{
SyscallError, bpf_get_object, bpf_prog_get_fd_by_id, bpf_prog_get_info_by_fd, iter_prog_ids,
SyscallError, bpf_get_object, bpf_prog_get_fd_by_id, bpf_prog_get_info_by_fd,
feature_probe::{is_prog_info_license_supported, is_prog_info_map_ids_supported},
iter_prog_ids,
},
util::bytes_of_bpf_name,
};
@ -108,13 +111,15 @@ impl ProgramInfo {
///
/// Introduced in kernel v4.15.
pub fn map_ids(&self) -> Result<Option<Vec<u32>>, ProgramError> {
if FEATURES.prog_info_map_ids() {
let mut map_ids = vec![0u32; self.0.nr_map_ids as usize];
bpf_prog_get_info_by_fd(self.fd()?.as_fd(), &mut map_ids)?;
Ok(Some(map_ids))
} else {
Ok(None)
}
static CACHE: OnceLock<bool> = OnceLock::new();
CACHE
.get_or_init(|| matches!(is_prog_info_map_ids_supported(), Ok(true)))
.then(|| {
let mut map_ids = vec![0u32; self.0.nr_map_ids as usize];
bpf_prog_get_info_by_fd(self.fd()?.as_fd(), &mut map_ids)?;
Ok(map_ids)
})
.transpose()
}
/// The name of the program as was provided when it was load. This is limited to 16 bytes.
@ -140,8 +145,9 @@ impl ProgramInfo {
///
/// Introduced in kernel v4.18.
pub fn gpl_compatible(&self) -> Option<bool> {
FEATURES
.prog_info_gpl_compatible()
static CACHE: OnceLock<bool> = OnceLock::new();
CACHE
.get_or_init(|| matches!(is_prog_info_license_supported(), Ok(true)))
.then_some(self.0.gpl_compatible() != 0)
}

@ -25,7 +25,7 @@ use aya_obj::{
use libc::{ENOENT, ENOSPC};
use crate::{
Btf, FEATURES, Pod, VerifierLogLevel,
Btf, Pod, VerifierLogLevel,
maps::{MapData, PerCpuValues},
programs::{ProgramType, links::LinkRef},
sys::{Syscall, SyscallError, syscall},
@ -593,7 +593,7 @@ pub(crate) fn bpf_prog_get_info_by_fd(
// An `E2BIG` error can occur on kernels below v4.15 when handing over a large struct where the
// extra space is not all-zero bytes.
bpf_obj_get_info_by_fd(fd, |info: &mut bpf_prog_info| {
if FEATURES.prog_info_map_ids() {
if !map_ids.is_empty() {
info.nr_map_ids = map_ids.len() as _;
info.map_ids = map_ids.as_mut_ptr() as _;
}
@ -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(
@ -804,34 +807,6 @@ where
op(&mut attr)
}
/// Tests whether `nr_map_ids` & `map_ids` fields in `bpf_prog_info` is available.
pub(crate) fn is_info_map_ids_supported() -> bool {
with_trivial_prog(ProgramType::TracePoint, |attr| {
let prog_fd = match bpf_prog_load(attr) {
Ok(fd) => fd,
Err(_) => return false,
};
bpf_obj_get_info_by_fd(prog_fd.as_fd(), |info: &mut bpf_prog_info| {
info.nr_map_ids = 1
})
.is_ok()
})
}
/// Tests whether `gpl_compatible` field in `bpf_prog_info` is available.
pub(crate) fn is_info_gpl_compatible_supported() -> bool {
with_trivial_prog(ProgramType::TracePoint, |attr| {
let prog_fd = match bpf_prog_load(attr) {
Ok(fd) => fd,
Err(_) => return false,
};
if let Ok::<bpf_prog_info, _>(info) = bpf_obj_get_info_by_fd(prog_fd.as_fd(), |_| {}) {
return info.gpl_compatible() != 0;
}
false
})
}
pub(crate) fn is_probe_read_kernel_supported() -> bool {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
let u = unsafe { &mut attr.__bindgen_anon_3 };
@ -1141,7 +1116,7 @@ fn sys_bpf(cmd: bpf_cmd, attr: &mut bpf_attr) -> io::Result<i64> {
})
}
fn unit_sys_bpf(cmd: bpf_cmd, attr: &mut bpf_attr) -> io::Result<()> {
pub(super) fn unit_sys_bpf(cmd: bpf_cmd, attr: &mut bpf_attr) -> io::Result<()> {
sys_bpf(cmd, attr).map(|code| assert_eq!(code, 0))
}

@ -1,10 +1,22 @@
//! 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, bpf_prog_info,
},
};
use libc::{E2BIG, EBADF, EINVAL};
use super::{SyscallError, bpf_prog_load, fd_sys_bpf, unit_sys_bpf, with_trivial_prog};
use crate::{
MockableFd,
maps::MapType,
programs::{ProgramError, ProgramType},
util::page_size,
};
/// Whether the host kernel supports the [`ProgramType`].
///
@ -16,7 +28,7 @@ use crate::programs::{ProgramError, ProgramType};
/// match is_program_supported(ProgramType::Xdp) {
/// Ok(true) => println!("XDP supported :)"),
/// Ok(false) => println!("XDP not supported :("),
/// Err(err) => println!("Uh oh! Unexpected error: {:?}", err),
/// Err(err) => println!("Uh oh! Unexpected error while probing: {:?}", err),
/// }
/// ```
///
@ -128,3 +140,243 @@ 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 while probing: {:?}", 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> {
// Each `bpf_map_ops` struct contains their own `.map_alloc()` & `.map_alloc_check()` that does
// field validation on map_create.
let (key_size, value_size, max_entries) = match map_type {
MapType::Unspecified => return Ok(false),
MapType::Hash // https://elixir.bootlin.com/linux/v3.19/source/kernel/bpf/hashtab.c#L349
| MapType::PerCpuHash // https://elixir.bootlin.com/linux/v4.6/source/kernel/bpf/hashtab.c#L726
| MapType::LruHash // https://elixir.bootlin.com/linux/v4.10/source/kernel/bpf/hashtab.c#L1032
| MapType::LruPerCpuHash // https://elixir.bootlin.com/linux/v4.10/source/kernel/bpf/hashtab.c#L1133
=> (1, 1, 1),
MapType::Array // https://elixir.bootlin.com/linux/v3.19/source/kernel/bpf/arraymap.c#L138
| MapType::PerCpuArray // https://elixir.bootlin.com/linux/v4.6/source/kernel/bpf/arraymap.c#L283
=> (4, 1, 1),
MapType::ProgramArray // https://elixir.bootlin.com/linux/v4.2/source/kernel/bpf/arraymap.c#L239
| MapType::PerfEventArray // https://elixir.bootlin.com/linux/v4.3/source/kernel/bpf/arraymap.c#L312
| MapType::CgroupArray // https://elixir.bootlin.com/linux/v4.8/source/kernel/bpf/arraymap.c#L562
| MapType::ArrayOfMaps // https://elixir.bootlin.com/linux/v4.12/source/kernel/bpf/arraymap.c#L595
| MapType::DevMap // https://elixir.bootlin.com/linux/v4.14/source/kernel/bpf/devmap.c#L360
| MapType::SockMap // https://elixir.bootlin.com/linux/v4.14/source/kernel/bpf/sockmap.c#L874
| MapType::CpuMap // https://elixir.bootlin.com/linux/v4.15/source/kernel/bpf/cpumap.c#L589
| MapType::XskMap // https://elixir.bootlin.com/linux/v4.18/source/kernel/bpf/xskmap.c#L224
| MapType::ReuseportSockArray // https://elixir.bootlin.com/linux/v4.20/source/kernel/bpf/reuseport_array.c#L357
| MapType::DevMapHash // https://elixir.bootlin.com/linux/v5.4/source/kernel/bpf/devmap.c#L713
=> (4, 4, 1),
MapType::StackTrace // https://elixir.bootlin.com/linux/v4.6/source/kernel/bpf/stackmap.c#L272
=> (4, 8, 1),
MapType::LpmTrie // https://elixir.bootlin.com/linux/v4.11/source/kernel/bpf/lpm_trie.c#L509
=> (8, 1, 1),
MapType::HashOfMaps // https://elixir.bootlin.com/linux/v4.12/source/kernel/bpf/hashtab.c#L1301
| MapType::SockHash // https://elixir.bootlin.com/linux/v4.18/source/kernel/bpf/sockmap.c#L2507
=> (1, 4, 1),
MapType::CgroupStorage // https://elixir.bootlin.com/linux/v4.19/source/kernel/bpf/local_storage.c#L246
| MapType::PerCpuCgroupStorage // https://elixir.bootlin.com/linux/v4.20/source/kernel/bpf/local_storage.c#L313
=> (16, 1, 0),
MapType::Queue // https://elixir.bootlin.com/linux/v4.20/source/kernel/bpf/queue_stack_maps.c#L267
| MapType::Stack // https://elixir.bootlin.com/linux/v4.20/source/kernel/bpf/queue_stack_maps.c#L280
| MapType::BloomFilter // https://elixir.bootlin.com/linux/v5.16/source/kernel/bpf/bloom_filter.c#L193
=> (0, 1, 1),
MapType::SkStorage // https://elixir.bootlin.com/linux/v5.2/source/net/core/bpf_sk_storage.c#L779
| MapType::InodeStorage // https://elixir.bootlin.com/linux/v5.10/source/kernel/bpf/bpf_inode_storage.c#L239
| MapType::TaskStorage // https://elixir.bootlin.com/linux/v5.11/source/kernel/bpf/bpf_task_storage.c#L285
| MapType::CgrpStorage // https://elixir.bootlin.com/linux/v6.2/source/kernel/bpf/bpf_cgrp_storage.c#L216
=> (4, 1, 0),
MapType::StructOps // https://elixir.bootlin.com/linux/v5.6/source/kernel/bpf/bpf_struct_ops.c#L607
=> (4, 0, 1),
MapType::RingBuf // https://elixir.bootlin.com/linux/v5.8/source/kernel/bpf/ringbuf.c#L296
| MapType::UserRingBuf // https://elixir.bootlin.com/linux/v6.1/source/kernel/bpf/ringbuf.c#L356
// `max_entries` required to be multiple of kernel page size & power of 2: https://elixir.bootlin.com/linux/v5.8/source/kernel/bpf/ringbuf.c#L160
=> (0, 0, page_size() as u32),
MapType::Arena // https://elixir.bootlin.com/linux/v6.9/source/kernel/bpf/arena.c#L380
=> (0, 0, 1),
};
// 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 };
u.map_type = map_type as u32;
u.key_size = key_size;
u.value_size = value_size;
u.max_entries = max_entries;
// Ensure that fd doesn't get dropped due to scoping for for *_of_maps type.
let inner_map_fd: MockableFd;
match map_type {
// lpm_trie required to not be pre-alloced: https://elixir.bootlin.com/linux/v4.11/source/kernel/bpf/lpm_trie.c#L419
MapType::LpmTrie => u.map_flags = BPF_F_NO_PREALLOC,
// For these types, we aim to intentionally trigger `EBADF` by supplying invalid btf attach
// data to verify the map type's existance. Otherwise, negative support will produce
// `EINVAL` instead.
MapType::SkStorage
| MapType::InodeStorage
| MapType::TaskStorage
| MapType::CgrpStorage => {
// These types required to not be pre-alloced:
// - sk_storage: https://elixir.bootlin.com/linux/v5.2/source/net/core/bpf_sk_storage.c#L604
// - inode_storage: https://elixir.bootlin.com/linux/v5.10/source/kernel/bpf/bpf_local_storage.c#L525
// - task_storage: https://elixir.bootlin.com/linux/v5.11/source/kernel/bpf/bpf_local_storage.c#L527
// - cgrp_storage: https://elixir.bootlin.com/linux/v6.2/source/kernel/bpf/bpf_local_storage.c#L539
u.map_flags = BPF_F_NO_PREALLOC;
// Will trigger `EBADF` from `btf_get_by_fd()` https://elixir.bootlin.com/linux/v5.2/source/kernel/bpf/btf.c#L3428
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;
}
// We aim to intentionally trigger `ENOTSUPP` by setting an invalid, non-zero
// `btf_vmlinux_value_type_id`. Negative support produce `EINVAL` instead.
MapType::StructOps => u.btf_vmlinux_value_type_id = 1,
// arena required to be mmapable: https://elixir.bootlin.com/linux/v6.9/source/kernel/bpf/arena.c#L103
MapType::Arena => u.map_flags = BPF_F_MMAPABLE,
_ => {}
}
// 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,
};
// sk_storage, struct_ops, inode_storage, task_storage, & cgrp_storage requires further
// examination to verify support.
match io_error.raw_os_error() {
Some(EINVAL) => Ok(false),
// These types use fields that may not exist at the kernel's current version.
// Supplying `bpf_attr` fields unknown to the kernel triggers `E2BIG` from
// `bpf_check_uarg_tail_zero()` https://elixir.bootlin.com/linux/v4.18/source/kernel/bpf/syscall.c#L71.
Some(E2BIG)
if matches!(
map_type,
MapType::SkStorage
| MapType::StructOps
| MapType::InodeStorage
| MapType::TaskStorage
| MapType::CgrpStorage
) =>
{
Ok(false)
}
// For these types, `EBADF` from `btf_get_by_fd()` https://elixir.bootlin.com/linux/v5.2/source/kernel/bpf/btf.c#L3428
// indicates that map_create advanced far enough in the validation to recognize the type
// before being rejected.
// Otherwise, negative support produces `EINVAL`, meaning it was immediately rejected.
Some(EBADF)
if matches!(
map_type,
MapType::SkStorage
| MapType::InodeStorage
| MapType::TaskStorage
| MapType::CgrpStorage
) =>
{
Ok(true)
}
// `ENOTSUPP` from `bpf_struct_ops_map_alloc()` https://elixir.bootlin.com/linux/v5.6/source/kernel/bpf/bpf_struct_ops.c#L557
// indicates that map_create advanced far enough in the validation to recognize the type
// before being rejected.
// Otherwise, negative support produces `EINVAL`, meaning it was immediately rejected.
Some(524) if map_type == MapType::StructOps => Ok(true),
_ => Err(SyscallError {
call: "bpf_map_create",
io_error,
}),
}
}
/// Whether `nr_map_ids` & `map_ids` fields in `bpf_prog_info` are supported.
pub(crate) fn is_prog_info_map_ids_supported() -> Result<bool, ProgramError> {
let fd = with_trivial_prog(ProgramType::SocketFilter, |attr| {
bpf_prog_load(attr).map_err(|io_error| {
ProgramError::SyscallError(SyscallError {
call: "bpf_prog_load",
io_error,
})
})
})?;
// SAFETY: all-zero byte-pattern valid for `bpf_prog_info`
let mut info = unsafe { mem::zeroed::<bpf_prog_info>() };
info.nr_map_ids = 1;
probe_bpf_info(fd, info).map_err(ProgramError::from)
}
/// Tests whether `bpf_prog_info.gpl_compatible` field is supported.
pub(crate) fn is_prog_info_license_supported() -> Result<bool, ProgramError> {
let fd = with_trivial_prog(ProgramType::SocketFilter, |attr| {
bpf_prog_load(attr).map_err(|io_error| {
ProgramError::SyscallError(SyscallError {
call: "bpf_prog_load",
io_error,
})
})
})?;
// SAFETY: all-zero byte-pattern valid for `bpf_prog_info`
let mut info = unsafe { mem::zeroed::<bpf_prog_info>() };
info.set_gpl_compatible(1);
probe_bpf_info(fd, info).map_err(ProgramError::from)
}
/// Probes program and map info.
fn probe_bpf_info<T>(fd: MockableFd, info: T) -> Result<bool, SyscallError> {
// SAFETY: all-zero byte-pattern valid for `bpf_attr`
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
attr.info.bpf_fd = fd.as_raw_fd() as u32;
attr.info.info_len = mem::size_of_val(&info) as u32;
attr.info.info = &info as *const _ as u64;
let io_error = match unit_sys_bpf(bpf_cmd::BPF_OBJ_GET_INFO_BY_FD, &mut attr) {
Ok(()) => return Ok(true),
Err(io_error) => io_error,
};
match io_error.raw_os_error() {
// `E2BIG` from `bpf_check_uarg_tail_zero()`
Some(E2BIG) => Ok(false),
_ => Err(SyscallError {
call: "bpf_obj_get_info_by_fd",
io_error,
}),
}
}

@ -1,7 +1,7 @@
//! A collection of system calls for performing eBPF related operations.
mod bpf;
mod feature_probe;
pub(crate) mod feature_probe;
mod netlink;
mod perf_event;
@ -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;
@ -129,3 +135,91 @@ fn probe_supported_programs() {
let kern_version = KernelVersion::new(6, 4, 0);
kernel_assert!(is_supported!(ProgramType::Netfilter), kern_version);
}
#[test]
fn probe_supported_maps() {
macro_rules! is_supported {
($map_type:expr) => {
is_map_supported($map_type).unwrap()
};
}
let kern_version = KernelVersion::new(3, 19, 0);
kernel_assert!(is_supported!(MapType::Hash), kern_version);
kernel_assert!(is_supported!(MapType::Array), kern_version);
let kern_version = KernelVersion::new(4, 2, 0);
kernel_assert!(is_supported!(MapType::ProgramArray), kern_version);
let kern_version = KernelVersion::new(4, 3, 0);
kernel_assert!(is_supported!(MapType::PerfEventArray), kern_version);
let 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);
let kern_version = KernelVersion::new(4, 8, 0);
kernel_assert!(is_supported!(MapType::CgroupArray), kern_version);
let kern_version = KernelVersion::new(4, 10, 0);
kernel_assert!(is_supported!(MapType::LruHash), kern_version);
kernel_assert!(is_supported!(MapType::LruPerCpuHash), kern_version);
let kern_version = KernelVersion::new(4, 11, 0);
kernel_assert!(is_supported!(MapType::LpmTrie), kern_version);
let kern_version = KernelVersion::new(4, 12, 0);
kernel_assert!(is_supported!(MapType::ArrayOfMaps), kern_version);
kernel_assert!(is_supported!(MapType::HashOfMaps), kern_version);
let kern_version = KernelVersion::new(4, 14, 0);
kernel_assert!(is_supported!(MapType::DevMap), kern_version);
kernel_assert!(is_supported!(MapType::SockMap), kern_version);
let kern_version = KernelVersion::new(4, 15, 0);
kernel_assert!(is_supported!(MapType::CpuMap), kern_version);
let kern_version = KernelVersion::new(4, 18, 0);
kernel_assert!(is_supported!(MapType::XskMap), kern_version);
kernel_assert!(is_supported!(MapType::SockHash), kern_version);
let kern_version = KernelVersion::new(4, 19, 0);
kernel_assert!(is_supported!(MapType::CgroupStorage), kern_version);
kernel_assert!(is_supported!(MapType::ReuseportSockArray), kern_version);
let 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);
let kern_version = KernelVersion::new(5, 2, 0);
kernel_assert!(is_supported!(MapType::SkStorage), kern_version);
let kern_version = KernelVersion::new(5, 4, 0);
kernel_assert!(is_supported!(MapType::DevMapHash), kern_version);
let kern_version = KernelVersion::new(5, 6, 0);
kernel_assert!(is_supported!(MapType::StructOps), kern_version);
let kern_version = KernelVersion::new(5, 8, 0);
kernel_assert!(is_supported!(MapType::RingBuf), kern_version);
let kern_version = KernelVersion::new(5, 10, 0);
kernel_assert!(is_supported!(MapType::InodeStorage), kern_version); // Requires `CONFIG_BPF_LSM=y`
let kern_version = KernelVersion::new(5, 11, 0);
kernel_assert!(is_supported!(MapType::TaskStorage), kern_version);
let kern_version = KernelVersion::new(5, 16, 0);
kernel_assert!(is_supported!(MapType::BloomFilter), kern_version);
let kern_version = KernelVersion::new(6, 1, 0);
kernel_assert!(is_supported!(MapType::UserRingBuf), kern_version);
let kern_version = KernelVersion::new(6, 2, 0);
kernel_assert!(is_supported!(MapType::CgrpStorage), kern_version);
let kern_version = KernelVersion::new(6, 9, 0);
kernel_assert!(is_supported!(MapType::Arena), kern_version);
}

@ -12,6 +12,7 @@ use aya::{
Ebpf,
maps::{Array, HashMap, IterableMap as _, MapError, MapType, loaded_maps},
programs::{ProgramError, ProgramType, SocketFilter, TracePoint, UProbe, loaded_programs},
sys::{is_map_supported, is_program_supported},
util::KernelVersion,
};
use libc::EINVAL;
@ -20,7 +21,11 @@ use crate::utils::{kernel_assert, kernel_assert_eq};
#[test]
fn test_loaded_programs() {
// Load a program.
if !is_program_supported(ProgramType::SocketFilter).unwrap() {
eprintln!("skipping test - socket_filter program not supported");
return;
}
// Since we are only testing the programs for their metadata, there is no need to "attach" them.
let mut bpf = Ebpf::load(crate::TEST).unwrap();
let prog: &mut UProbe = bpf.program_mut("test_uprobe").unwrap().try_into().unwrap();
@ -33,9 +38,7 @@ fn test_loaded_programs() {
if let ProgramError::SyscallError(err) = &err {
// Skip entire test since feature not available
if err.io_error.raw_os_error() == Some(EINVAL) {
eprintln!(
"ignoring test completely as `loaded_programs()` is not available on the host"
);
eprintln!("skipping test - `loaded_programs()` not supported");
return;
}
}
@ -71,6 +74,11 @@ fn test_loaded_programs() {
#[test]
fn test_program_info() {
if !is_program_supported(ProgramType::SocketFilter).unwrap() {
eprintln!("skipping test - socket_filter program not supported");
return;
}
// Kernels below v4.15 have been observed to have `bpf_jit_enable` disabled by default.
let _guard = ensure_sysctl_enabled("/proc/sys/net/core/bpf_jit_enable");
@ -129,6 +137,11 @@ fn test_program_info() {
#[test]
fn test_loaded_at() {
if !is_program_supported(ProgramType::SocketFilter).unwrap() {
eprintln!("skipping test - socket_filter program not supported");
return;
}
let mut bpf: Ebpf = Ebpf::load(crate::SIMPLE_PROG).unwrap();
let prog: &mut SocketFilter = bpf.program_mut("simple_prog").unwrap().try_into().unwrap();
@ -144,9 +157,7 @@ fn test_loaded_at() {
let loaded_at = match prog.info().unwrap().loaded_at() {
Some(time) => time,
None => {
eprintln!(
"ignoring test completely as `load_time` field of `bpf_prog_info` is not available on the host"
);
eprintln!("skipping test - `bpf_prog_info.load_time` field not supported");
return;
}
};
@ -175,11 +186,12 @@ fn test_loaded_at() {
#[test]
fn test_prog_stats() {
// Test depends on whether trace point exists.
if !is_program_supported(ProgramType::TracePoint).unwrap() {
eprintln!("skipping test - tracepoint program not supported");
return;
}
if !Path::new("/sys/kernel/debug/tracing/events/syscalls/sys_enter_bpf").exists() {
eprintln!(
"ignoring test completely as `syscalls/sys_enter_bpf` is not available on the host"
);
eprintln!("skipping test - `syscalls/sys_enter_bpf` not available");
return;
}
@ -200,6 +212,17 @@ fn test_prog_stats() {
#[test]
fn list_loaded_maps() {
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();
@ -210,9 +233,7 @@ fn list_loaded_maps() {
if let Err(err) = maps.peek().unwrap() {
if let MapError::SyscallError(err) = &err {
if err.io_error.raw_os_error() == Some(EINVAL) {
eprintln!(
"ignoring test completely as `loaded_maps()` is not available on the host"
);
eprintln!("skipping test - `loaded_maps()` not supported");
return;
}
}
@ -250,6 +271,17 @@ fn list_loaded_maps() {
#[test]
fn test_map_info() {
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;
}
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();

@ -8678,8 +8678,6 @@ pub fn aya_obj::Features::bpf_probe_read_kernel(&self) -> bool
pub fn aya_obj::Features::btf(&self) -> core::option::Option<&aya_obj::btf::BtfFeatures>
pub fn aya_obj::Features::cpumap_prog_id(&self) -> bool
pub fn aya_obj::Features::devmap_prog_id(&self) -> bool
pub fn aya_obj::Features::prog_info_gpl_compatible(&self) -> bool
pub fn aya_obj::Features::prog_info_map_ids(&self) -> bool
impl core::default::Default for aya_obj::Features
pub fn aya_obj::Features::default() -> aya_obj::Features
impl core::fmt::Debug for aya_obj::Features
@ -9541,8 +9539,6 @@ pub fn aya_obj::Features::bpf_probe_read_kernel(&self) -> bool
pub fn aya_obj::Features::btf(&self) -> core::option::Option<&aya_obj::btf::BtfFeatures>
pub fn aya_obj::Features::cpumap_prog_id(&self) -> bool
pub fn aya_obj::Features::devmap_prog_id(&self) -> bool
pub fn aya_obj::Features::prog_info_gpl_compatible(&self) -> bool
pub fn aya_obj::Features::prog_info_map_ids(&self) -> bool
impl core::default::Default for aya_obj::Features
pub fn aya_obj::Features::default() -> aya_obj::Features
impl core::fmt::Debug for aya_obj::Features

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