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.
reviewable/pr1063/r20
Tyrone Wu 6 months ago
parent 2f0158c812
commit 3ace956c5b
No known key found for this signature in database
GPG Key ID: 978B1A1B79210AD6

@ -45,8 +45,6 @@ pub struct Features {
bpf_cookie: bool, bpf_cookie: bool,
cpumap_prog_id: bool, cpumap_prog_id: bool,
devmap_prog_id: bool, devmap_prog_id: bool,
prog_info_map_ids: bool,
prog_info_gpl_compatible: bool,
btf: Option<BtfFeatures>, btf: Option<BtfFeatures>,
} }
@ -61,8 +59,6 @@ impl Features {
bpf_cookie: bool, bpf_cookie: bool,
cpumap_prog_id: bool, cpumap_prog_id: bool,
devmap_prog_id: bool, devmap_prog_id: bool,
prog_info_map_ids: bool,
prog_info_gpl_compatible: bool,
btf: Option<BtfFeatures>, btf: Option<BtfFeatures>,
) -> Self { ) -> Self {
Self { Self {
@ -73,8 +69,6 @@ impl Features {
bpf_cookie, bpf_cookie,
cpumap_prog_id, cpumap_prog_id,
devmap_prog_id, devmap_prog_id,
prog_info_map_ids,
prog_info_gpl_compatible,
btf, btf,
} }
} }
@ -117,16 +111,6 @@ impl Features {
self.devmap_prog_id 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. /// If BTF is supported, returns which BTF features are supported.
pub fn btf(&self) -> Option<&BtfFeatures> { pub fn btf(&self) -> Option<&BtfFeatures> {
self.btf.as_ref() self.btf.as_ref()

@ -32,9 +32,9 @@ use crate::{
bpf_load_btf, is_bpf_cookie_supported, is_bpf_global_data_supported, 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_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_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_btf_supported, is_btf_type_tag_supported, is_perf_link_supported,
is_info_map_ids_supported, is_perf_link_supported, is_probe_read_kernel_supported, is_probe_read_kernel_supported, is_prog_id_supported, is_prog_name_supported,
is_prog_id_supported, is_prog_name_supported, retry_with_verifier_logs, retry_with_verifier_logs,
}, },
util::{bytes_of, bytes_of_slice, nr_cpus, page_size}, util::{bytes_of, bytes_of_slice, nr_cpus, page_size},
}; };
@ -82,8 +82,6 @@ fn detect_features() -> Features {
is_bpf_cookie_supported(), is_bpf_cookie_supported(),
is_prog_id_supported(BPF_MAP_TYPE_CPUMAP), is_prog_id_supported(BPF_MAP_TYPE_CPUMAP),
is_prog_id_supported(BPF_MAP_TYPE_DEVMAP), is_prog_id_supported(BPF_MAP_TYPE_DEVMAP),
is_info_map_ids_supported(),
is_info_gpl_compatible_supported(),
btf, btf,
); );
debug!("BPF Feature Detection: {:#?}", f); debug!("BPF Feature Detection: {:#?}", f);

@ -4,6 +4,7 @@ use std::{
ffi::CString, ffi::CString,
os::fd::{AsFd as _, BorrowedFd}, os::fd::{AsFd as _, BorrowedFd},
path::Path, path::Path,
sync::OnceLock,
time::{Duration, SystemTime}, time::{Duration, SystemTime},
}; };
@ -16,7 +17,9 @@ use super::{
use crate::{ use crate::{
FEATURES, FEATURES,
sys::{ 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, util::bytes_of_bpf_name,
}; };
@ -108,13 +111,15 @@ impl ProgramInfo {
/// ///
/// Introduced in kernel v4.15. /// Introduced in kernel v4.15.
pub fn map_ids(&self) -> Result<Option<Vec<u32>>, ProgramError> { pub fn map_ids(&self) -> Result<Option<Vec<u32>>, ProgramError> {
if FEATURES.prog_info_map_ids() { static CACHE: OnceLock<bool> = OnceLock::new();
let mut map_ids = vec![0u32; self.0.nr_map_ids as usize]; CACHE
bpf_prog_get_info_by_fd(self.fd()?.as_fd(), &mut map_ids)?; .get_or_init(|| matches!(is_prog_info_map_ids_supported(), Ok(true)))
Ok(Some(map_ids)) .then(|| {
} else { let mut map_ids = vec![0u32; self.0.nr_map_ids as usize];
Ok(None) 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. /// 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. /// Introduced in kernel v4.18.
pub fn gpl_compatible(&self) -> Option<bool> { pub fn gpl_compatible(&self) -> Option<bool> {
FEATURES static CACHE: OnceLock<bool> = OnceLock::new();
.prog_info_gpl_compatible() CACHE
.get_or_init(|| matches!(is_prog_info_license_supported(), Ok(true)))
.then_some(self.0.gpl_compatible() != 0) .then_some(self.0.gpl_compatible() != 0)
} }

@ -25,7 +25,7 @@ use aya_obj::{
use libc::{ENOENT, ENOSPC}; use libc::{ENOENT, ENOSPC};
use crate::{ use crate::{
Btf, FEATURES, Pod, VerifierLogLevel, Btf, Pod, VerifierLogLevel,
maps::{MapData, PerCpuValues}, maps::{MapData, PerCpuValues},
programs::links::LinkRef, programs::links::LinkRef,
sys::{Syscall, SyscallError, syscall}, 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 // 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. // extra space is not all-zero bytes.
bpf_obj_get_info_by_fd(fd, |info: &mut bpf_prog_info| { 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.nr_map_ids = map_ids.len() as _;
info.map_ids = map_ids.as_mut_ptr() as _; info.map_ids = map_ids.as_mut_ptr() as _;
} }
@ -751,34 +751,6 @@ where
op(&mut attr) 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(|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(|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 { pub(crate) fn is_probe_read_kernel_supported() -> bool {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() }; let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
let u = unsafe { &mut attr.__bindgen_anon_3 }; let u = unsafe { &mut attr.__bindgen_anon_3 };
@ -1088,7 +1060,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)) sys_bpf(cmd, attr).map(|code| assert_eq!(code, 0))
} }

@ -6,13 +6,14 @@ use aya_obj::{
btf::{Btf, BtfError, BtfKind}, btf::{Btf, BtfError, BtfKind},
generated::{ generated::{
BPF_F_MMAPABLE, BPF_F_NO_PREALLOC, BPF_F_SLEEPABLE, bpf_attach_type, bpf_attr, bpf_cmd, BPF_F_MMAPABLE, BPF_F_NO_PREALLOC, BPF_F_SLEEPABLE, bpf_attach_type, bpf_attr, bpf_cmd,
bpf_map_type, bpf_map_type, bpf_prog_info,
}, },
}; };
use libc::{E2BIG, EBADF, EINVAL}; use libc::{E2BIG, EBADF, EINVAL};
use super::{SyscallError, bpf_prog_load, fd_sys_bpf, with_trivial_prog}; use super::{SyscallError, bpf_prog_load, fd_sys_bpf, unit_sys_bpf, with_trivial_prog};
use crate::{ use crate::{
MockableFd,
maps::MapType, maps::MapType,
programs::{ProgramError, ProgramType}, programs::{ProgramError, ProgramType},
util::{KernelVersion, page_size}, util::{KernelVersion, page_size},
@ -226,6 +227,26 @@ pub fn is_map_supported(map_type: MapType) -> Result<bool, 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, ProgramError> {
let fd = create_minimal_program(ProgramType::SocketFilter, &mut [])?;
// 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 = create_minimal_program(ProgramType::SocketFilter, &mut [])?;
// 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)
}
/// Create a minimal program with the specified type. /// Create a minimal program with the specified type.
/// Types not created for `Extension` and `StructOps`. /// Types not created for `Extension` and `StructOps`.
fn create_minimal_program( fn create_minimal_program(
@ -281,3 +302,25 @@ fn create_minimal_program(
}) })
}) })
} }
/// 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,
}),
}
}

@ -12,6 +12,7 @@ use aya::{
Ebpf, Ebpf,
maps::{Array, HashMap, IterableMap as _, MapError, MapType, loaded_maps}, maps::{Array, HashMap, IterableMap as _, MapError, MapType, loaded_maps},
programs::{ProgramError, ProgramType, SocketFilter, TracePoint, UProbe, loaded_programs}, programs::{ProgramError, ProgramType, SocketFilter, TracePoint, UProbe, loaded_programs},
sys::feature_probe::{is_map_supported, is_program_supported},
util::KernelVersion, util::KernelVersion,
}; };
use libc::EINVAL; use libc::EINVAL;
@ -20,7 +21,11 @@ use crate::utils::{kernel_assert, kernel_assert_eq};
#[test] #[test]
fn test_loaded_programs() { 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. // 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 mut bpf = Ebpf::load(crate::TEST).unwrap();
let prog: &mut UProbe = bpf.program_mut("test_uprobe").unwrap().try_into().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 { if let ProgramError::SyscallError(err) = &err {
// Skip entire test since feature not available // Skip entire test since feature not available
if err.io_error.raw_os_error() == Some(EINVAL) { if err.io_error.raw_os_error() == Some(EINVAL) {
eprintln!( eprintln!("skipping test - `loaded_programs()` not supported");
"ignoring test completely as `loaded_programs()` is not available on the host"
);
return; return;
} }
} }
@ -71,6 +74,11 @@ fn test_loaded_programs() {
#[test] #[test]
fn test_program_info() { 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. // 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"); let _guard = ensure_sysctl_enabled("/proc/sys/net/core/bpf_jit_enable");
@ -129,6 +137,11 @@ fn test_program_info() {
#[test] #[test]
fn test_loaded_at() { 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 mut bpf: Ebpf = Ebpf::load(crate::SIMPLE_PROG).unwrap();
let prog: &mut SocketFilter = bpf.program_mut("simple_prog").unwrap().try_into().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() { let loaded_at = match prog.info().unwrap().loaded_at() {
Some(time) => time, Some(time) => time,
None => { None => {
eprintln!( eprintln!("skipping test - `bpf_prog_info.load_time` field not supported");
"ignoring test completely as `load_time` field of `bpf_prog_info` is not available on the host"
);
return; return;
} }
}; };
@ -175,11 +186,12 @@ fn test_loaded_at() {
#[test] #[test]
fn test_prog_stats() { 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() { if !Path::new("/sys/kernel/debug/tracing/events/syscalls/sys_enter_bpf").exists() {
eprintln!( eprintln!("skipping test - `syscalls/sys_enter_bpf` not available");
"ignoring test completely as `syscalls/sys_enter_bpf` is not available on the host"
);
return; return;
} }
@ -200,6 +212,17 @@ fn test_prog_stats() {
#[test] #[test]
fn list_loaded_maps() { 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. // Load a program with maps.
let mut bpf: Ebpf = Ebpf::load(crate::MAP_TEST).unwrap(); let mut bpf: Ebpf = Ebpf::load(crate::MAP_TEST).unwrap();
let prog: &mut SocketFilter = bpf.program_mut("simple_prog").unwrap().try_into().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 Err(err) = maps.peek().unwrap() {
if let MapError::SyscallError(err) = &err { if let MapError::SyscallError(err) = &err {
if err.io_error.raw_os_error() == Some(EINVAL) { if err.io_error.raw_os_error() == Some(EINVAL) {
eprintln!( eprintln!("skipping test - `loaded_maps()` not supported");
"ignoring test completely as `loaded_maps()` is not available on the host"
);
return; return;
} }
} }
@ -250,6 +271,17 @@ fn list_loaded_maps() {
#[test] #[test]
fn test_map_info() { 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 mut bpf: Ebpf = Ebpf::load(crate::MAP_TEST).unwrap();
let prog: &mut SocketFilter = bpf.program_mut("simple_prog").unwrap().try_into().unwrap(); let prog: &mut SocketFilter = bpf.program_mut("simple_prog").unwrap().try_into().unwrap();
prog.load().unwrap(); prog.load().unwrap();

@ -7725,8 +7725,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::btf(&self) -> core::option::Option<&aya_obj::btf::BtfFeatures>
pub fn aya_obj::Features::cpumap_prog_id(&self) -> bool 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::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 impl core::default::Default for aya_obj::Features
pub fn aya_obj::Features::default() -> aya_obj::Features pub fn aya_obj::Features::default() -> aya_obj::Features
impl core::fmt::Debug for aya_obj::Features impl core::fmt::Debug for aya_obj::Features
@ -8588,8 +8586,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::btf(&self) -> core::option::Option<&aya_obj::btf::BtfFeatures>
pub fn aya_obj::Features::cpumap_prog_id(&self) -> bool 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::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 impl core::default::Default for aya_obj::Features
pub fn aya_obj::Features::default() -> aya_obj::Features pub fn aya_obj::Features::default() -> aya_obj::Features
impl core::fmt::Debug for aya_obj::Features impl core::fmt::Debug for aya_obj::Features

Loading…
Cancel
Save