From 6075a11bbf9d27abddd55533398deb3665edb2b9 Mon Sep 17 00:00:00 2001 From: Tyrone Wu Date: Tue, 22 Oct 2024 00:52:14 +0000 Subject: [PATCH] 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. --- aya-obj/src/obj.rs | 16 ------ aya/Cargo.toml | 2 +- aya/src/bpf.rs | 8 ++- aya/src/programs/info.rs | 27 +++++++--- aya/src/sys/bpf.rs | 62 ++--------------------- aya/src/sys/feature_probe.rs | 66 ++++++++++++++++++++++++- test/integration-test/src/tests/info.rs | 64 ++++++++++++++++++------ xtask/public-api/aya-obj.txt | 4 -- 8 files changed, 142 insertions(+), 107 deletions(-) diff --git a/aya-obj/src/obj.rs b/aya-obj/src/obj.rs index c6ea882d..6b3339c1 100644 --- a/aya-obj/src/obj.rs +++ b/aya-obj/src/obj.rs @@ -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, } @@ -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, ) -> 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() diff --git a/aya/Cargo.toml b/aya/Cargo.toml index ef82d9e5..6e372940 100644 --- a/aya/Cargo.toml +++ b/aya/Cargo.toml @@ -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 } diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index c50efb9a..7f5b345a 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -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); diff --git a/aya/src/programs/info.rs b/aya/src/programs/info.rs index 3bfa89f3..2aff583f 100644 --- a/aya/src/programs/info.rs +++ b/aya/src/programs/info.rs @@ -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>, 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 { - 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. diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs index c503d14d..023438fd 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -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::() }; - 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::() }; - 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::(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 +1088,7 @@ pub(super) fn bpf_prog_load(attr: &mut bpf_attr) -> SysResult unsafe { fd_sys_bpf(bpf_cmd::BPF_PROG_LOAD, attr) } } -fn sys_bpf(cmd: bpf_cmd, attr: &mut bpf_attr) -> SysResult { +pub(super) fn sys_bpf(cmd: bpf_cmd, attr: &mut bpf_attr) -> SysResult { syscall(Syscall::Ebpf { cmd, attr }) } diff --git a/aya/src/sys/feature_probe.rs b/aya/src/sys/feature_probe.rs index 8b6cd1d5..3d03b223 100644 --- a/aya/src/sys/feature_probe.rs +++ b/aya/src/sys/feature_probe.rs @@ -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 { } }) } + +/// 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 = dummy_prog()?; + + // 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) +} + +/// Tests whether `bpf_prog_info.gpl_compatible` field is supported. +pub(crate) fn is_prog_info_license_supported() -> Result { + let fd = dummy_prog()?; + + // 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) +} + +/// 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 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 { + // SAFETY: all-zero byte-pattern valid for `bpf_attr` + let mut attr = unsafe { mem::zeroed::() }; + // 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, + }) +} diff --git a/test/integration-test/src/tests/info.rs b/test/integration-test/src/tests/info.rs index 6a84100d..acca0e00 100644 --- a/test/integration-test/src/tests/info.rs +++ b/test/integration-test/src/tests/info.rs @@ -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(); diff --git a/xtask/public-api/aya-obj.txt b/xtask/public-api/aya-obj.txt index c3597242..1bc747a6 100644 --- a/xtask/public-api/aya-obj.txt +++ b/xtask/public-api/aya-obj.txt @@ -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