From dd5f2c1fb708c0be320f8de69d86ee08e85573b9 Mon Sep 17 00:00:00 2001 From: Tyrone Wu Date: Mon, 28 Oct 2024 02:12:54 +0000 Subject: [PATCH] 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. --- aya-obj/src/obj.rs | 16 ------ aya/src/bpf.rs | 8 ++- aya/src/programs/info.rs | 26 ++++++---- aya/src/sys/bpf.rs | 34 ++----------- aya/src/sys/feature_probe.rs | 66 +++++++++++++++++++++++-- aya/src/sys/mod.rs | 2 +- test/integration-test/src/tests/info.rs | 60 ++++++++++++++++------ xtask/public-api/aya-obj.txt | 4 -- 8 files changed, 131 insertions(+), 85 deletions(-) diff --git a/aya-obj/src/obj.rs b/aya-obj/src/obj.rs index 871e6937..f06270b5 100644 --- a/aya-obj/src/obj.rs +++ b/aya-obj/src/obj.rs @@ -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, } @@ -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, ) -> 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() diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index 9c11defc..18fa60ea 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -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); diff --git a/aya/src/programs/info.rs b/aya/src/programs/info.rs index 876264d1..753d6054 100644 --- a/aya/src/programs/info.rs +++ b/aya/src/programs/info.rs @@ -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>, 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 = 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 { - FEATURES - .prog_info_gpl_compatible() + static CACHE: OnceLock = OnceLock::new(); + CACHE + .get_or_init(|| matches!(is_prog_info_license_supported(), Ok(true))) .then_some(self.0.gpl_compatible() != 0) } diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs index 19c1de1d..b39ce06d 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -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 _; } @@ -807,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::(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::() }; let u = unsafe { &mut attr.__bindgen_anon_3 }; @@ -1144,7 +1116,7 @@ fn sys_bpf(cmd: bpf_cmd, attr: &mut bpf_attr) -> io::Result { }) } -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)) } diff --git a/aya/src/sys/feature_probe.rs b/aya/src/sys/feature_probe.rs index 51eae01d..835653bf 100644 --- a/aya/src/sys/feature_probe.rs +++ b/aya/src/sys/feature_probe.rs @@ -4,11 +4,13 @@ use std::{mem, os::fd::AsRawFd as _}; use aya_obj::{ btf::{Btf, BtfKind}, - generated::{BPF_F_MMAPABLE, BPF_F_NO_PREALLOC, bpf_attr, bpf_cmd, bpf_map_type}, + 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, with_trivial_prog}; +use super::{SyscallError, bpf_prog_load, fd_sys_bpf, unit_sys_bpf, with_trivial_prog}; use crate::{ MockableFd, maps::MapType, @@ -26,7 +28,7 @@ use crate::{ /// 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), /// } /// ``` /// @@ -149,7 +151,7 @@ pub fn is_program_supported(program_type: ProgramType) -> Result println!("hash_of_maps supported :)"), /// Ok(false) => println!("hash_of_maps not supported :("), -/// Err(err) => println!("Uh oh! Unexpected error: {:?}", err), +/// Err(err) => println!("Uh oh! Unexpected error while probing: {:?}", err), /// } /// ``` /// @@ -322,3 +324,59 @@ pub fn is_map_supported(map_type: MapType) -> Result { }), } } + +/// Whether `nr_map_ids` & `map_ids` fields in `bpf_prog_info` are supported. +pub(crate) fn is_prog_info_map_ids_supported() -> Result { + 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::() }; + 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 { + 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::() }; + info.set_gpl_compatible(1); + + probe_bpf_info(fd, info).map_err(ProgramError::from) +} + +/// Probes program and map info. +fn probe_bpf_info(fd: MockableFd, info: T) -> Result { + // SAFETY: all-zero byte-pattern valid for `bpf_attr` + let mut attr = unsafe { mem::zeroed::() }; + 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, + }), + } +} diff --git a/aya/src/sys/mod.rs b/aya/src/sys/mod.rs index c0d2c59c..ee6a3af9 100644 --- a/aya/src/sys/mod.rs +++ b/aya/src/sys/mod.rs @@ -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; diff --git a/test/integration-test/src/tests/info.rs b/test/integration-test/src/tests/info.rs index 99c7af46..cc9ea680 100644 --- a/test/integration-test/src/tests/info.rs +++ b/test/integration-test/src/tests/info.rs @@ -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(); diff --git a/xtask/public-api/aya-obj.txt b/xtask/public-api/aya-obj.txt index 9fce242e..59adfdcf 100644 --- a/xtask/public-api/aya-obj.txt +++ b/xtask/public-api/aya-obj.txt @@ -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