aya,aya-obj: cache feat probed info fields

Cached probed for ProgramInfo fields instead of exposing it through
global FEATURE. Probing operation happens on cache miss, which happens
when first accessing the field, *and* if the field is 0. No need to
probe if non-zero.
pull/1063/head
Tyrone Wu 3 months ago
parent 27c28a60ed
commit 6075a11bbf
No known key found for this signature in database
GPG Key ID: 978B1A1B79210AD6

@ -47,8 +47,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>,
}
@ -63,8 +61,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 {
@ -75,8 +71,6 @@ impl Features {
bpf_cookie,
cpumap_prog_id,
devmap_prog_id,
prog_info_map_ids,
prog_info_gpl_compatible,
btf,
}
}
@ -119,16 +113,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()

@ -21,7 +21,7 @@ bytes = { workspace = true }
libc = { workspace = true }
log = { workspace = true }
object = { workspace = true, features = ["elf", "read_core", "std", "write"] }
once_cell = { workspace = true }
once_cell = { workspace = true, features = ["race"]}
thiserror = { workspace = true }
tokio = { workspace = true, features = ["rt"], optional = true }

@ -35,9 +35,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},
};
@ -90,8 +90,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);

@ -8,6 +8,7 @@ use std::{
};
use aya_obj::generated::{bpf_prog_info, bpf_prog_type};
use once_cell::race::OnceBool;
use super::{
utils::{boot_time, get_fdinfo},
@ -15,7 +16,9 @@ use super::{
};
use crate::{
sys::{
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, SyscallError,
},
util::bytes_of_bpf_name,
FEATURES,
@ -108,10 +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() {
if self.0.nr_map_ids > 0 {
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))
return Ok(Some(map_ids));
}
static CACHE: OnceBool = OnceBool::new();
if CACHE.get_or_init(|| matches!(is_prog_info_map_ids_supported(), Ok(true))) {
Ok(Some(vec![]))
} else {
Ok(None)
}
@ -140,9 +148,16 @@ impl ProgramInfo {
///
/// Introduced in kernel v4.18.
pub fn gpl_compatible(&self) -> Option<bool> {
FEATURES
.prog_info_gpl_compatible()
.then_some(self.0.gpl_compatible() != 0)
if self.0.gpl_compatible() != 0 {
return Some(true);
}
static CACHE: OnceBool = OnceBool::new();
if CACHE.get_or_init(|| matches!(is_prog_info_license_supported(), Ok(true))) {
Some(false)
} else {
None
}
}
/// The BTF ID for the program.

@ -29,7 +29,7 @@ use crate::{
programs::links::LinkRef,
sys::{syscall, SysResult, Syscall, SyscallError},
util::KernelVersion,
Btf, Pod, VerifierLogLevel, BPF_OBJ_NAME_LEN, FEATURES,
Btf, Pod, VerifierLogLevel, BPF_OBJ_NAME_LEN,
};
pub(crate) fn bpf_create_map(
@ -587,7 +587,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 _;
}
@ -738,62 +738,6 @@ pub(crate) fn is_prog_name_supported() -> bool {
bpf_prog_load(&mut attr).is_ok()
}
/// Tests whether `nr_map_ids` & `map_ids` fields in `bpf_prog_info` is available.
pub(crate) fn is_info_map_ids_supported() -> bool {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
let u = unsafe { &mut attr.__bindgen_anon_3 };
u.prog_type = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32;
let prog: &[u8] = &[
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit
];
let insns = copy_instructions(prog).unwrap();
u.insn_cnt = insns.len() as u32;
u.insns = insns.as_ptr() as u64;
let gpl = b"GPL\0";
u.license = gpl.as_ptr() as u64;
let prog_fd = match bpf_prog_load(&mut 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 {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
let u = unsafe { &mut attr.__bindgen_anon_3 };
u.prog_type = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32;
let prog: &[u8] = &[
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit
];
let insns = copy_instructions(prog).unwrap();
u.insn_cnt = insns.len() as u32;
u.insns = insns.as_ptr() as u64;
let gpl = b"GPL\0";
u.license = gpl.as_ptr() as u64;
let prog_fd = match bpf_prog_load(&mut 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 };
@ -1144,7 +1088,7 @@ pub(super) fn bpf_prog_load(attr: &mut bpf_attr) -> SysResult<crate::MockableFd>
unsafe { fd_sys_bpf(bpf_cmd::BPF_PROG_LOAD, attr) }
}
fn sys_bpf(cmd: bpf_cmd, attr: &mut bpf_attr) -> SysResult<i64> {
pub(super) fn sys_bpf(cmd: bpf_cmd, attr: &mut bpf_attr) -> SysResult<i64> {
syscall(Syscall::Ebpf { cmd, attr })
}

@ -3,16 +3,17 @@
use std::{mem, os::fd::AsRawFd};
use aya_obj::generated::{
bpf_attach_type, bpf_attr, bpf_cmd, bpf_insn, BPF_F_MMAPABLE, BPF_F_NO_PREALLOC,
bpf_attach_type, bpf_attr, bpf_cmd, bpf_insn, bpf_prog_info, BPF_F_MMAPABLE, BPF_F_NO_PREALLOC,
BPF_F_SLEEPABLE,
};
use libc::{E2BIG, EINVAL};
use super::{bpf_prog_load, fd_sys_bpf, SyscallError};
use super::{bpf_prog_load, fd_sys_bpf, sys_bpf, SyscallError};
use crate::{
maps::MapType,
programs::ProgramType,
util::{page_size, KernelVersion},
MockableFd,
};
const RETURN_ZERO_INSNS: &[bpf_insn] = &[
@ -254,3 +255,64 @@ fn dummy_map() -> Result<crate::MockableFd, SyscallError> {
}
})
}
/// Whether `nr_map_ids` & `map_ids` fields in `bpf_prog_info` are supported.
pub(crate) fn is_prog_info_map_ids_supported() -> Result<bool, SyscallError> {
let fd = dummy_prog()?;
// 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)
}
/// Tests whether `bpf_prog_info.gpl_compatible` field is supported.
pub(crate) fn is_prog_info_license_supported() -> Result<bool, SyscallError> {
let fd = dummy_prog()?;
// 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)
}
/// 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 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,
}),
}
}
/// Create a program and returns its fd.
fn dummy_prog() -> Result<crate::MockableFd, SyscallError> {
// 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_3 };
u.prog_type = 1;
u.insn_cnt = 2;
u.insns = RETURN_ZERO_INSNS.as_ptr() as u64;
u.license = GPL_COMPATIBLE.as_ptr() as u64;
bpf_prog_load(&mut attr).map_err(|(_, io_error)| SyscallError {
call: "bpf_prog_load",
io_error,
})
}

@ -10,7 +10,10 @@ use std::{fs, panic, path::Path, time::SystemTime};
use aya::{
maps::{loaded_maps, Array, HashMap, IterableMap as _, MapError, MapType},
programs::{loaded_programs, ProgramError, ProgramType, SocketFilter, TracePoint},
sys::enable_stats,
sys::{
enable_stats,
feature_probe::{is_map_supported, is_program_supported},
},
util::KernelVersion,
Ebpf,
};
@ -23,7 +26,11 @@ const BPF_STATS_ENABLED: &str = "/proc/sys/kernel/bpf_stats_enabled";
#[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::SIMPLE_PROG).unwrap();
let prog: &mut SocketFilter = bpf.program_mut("simple_prog").unwrap().try_into().unwrap();
@ -40,9 +47,7 @@ fn test_loaded_programs() {
.raw_os_error()
.is_some_and(|errno| errno == EINVAL)
{
eprintln!(
"ignoring test completely as `loaded_programs()` is not available on the host"
);
eprintln!("skipping test - `loaded_programs()` not supported");
return;
}
}
@ -59,6 +64,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 previously_enabled = is_sysctl_enabled(BPF_JIT_ENABLE);
// Restore to previous state when panic occurs.
@ -136,6 +146,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();
@ -151,7 +166,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;
}
};
@ -180,11 +195,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;
}
@ -202,7 +218,7 @@ fn test_prog_stats() {
let stats_enabled =
stats_fd.is_some() || previously_enabled || enable_sysctl_param(BPF_STATS_ENABLED);
if !stats_enabled {
eprintln!("ignoring test completely as bpf stats could not be enabled on the host");
eprintln!("skipping test - bpf stats could not be enabled");
return;
}
@ -226,6 +242,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();
@ -240,9 +267,7 @@ fn list_loaded_maps() {
.raw_os_error()
.is_some_and(|errno| errno == EINVAL)
{
eprintln!(
"ignoring test completely as `loaded_maps()` is not available on the host"
);
eprintln!("skipping test - `loaded_maps()` not supported");
return;
}
}
@ -280,6 +305,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();

@ -7259,8 +7259,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
@ -8119,8 +8117,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

Loading…
Cancel
Save