diff --git a/aya-obj/src/obj.rs b/aya-obj/src/obj.rs index a3b9b441..479daf32 100644 --- a/aya-obj/src/obj.rs +++ b/aya-obj/src/obj.rs @@ -46,8 +46,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, } @@ -62,8 +60,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 { @@ -74,8 +70,6 @@ impl Features { bpf_cookie, cpumap_prog_id, devmap_prog_id, - prog_info_map_ids, - prog_info_gpl_compatible, btf, } } @@ -118,16 +112,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 059c3c16..da098e52 100644 --- a/aya/Cargo.toml +++ b/aya/Cargo.toml @@ -22,7 +22,7 @@ hashbrown = { 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 acce6c16..37afefc2 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -37,9 +37,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}, }; @@ -92,8 +92,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 d06972bf..92338518 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -33,7 +33,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_iter(link_fd: BorrowedFd<'_>) -> SysResult { @@ -618,7 +618,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 _; } @@ -769,62 +769,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 }; @@ -1179,7 +1123,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 894d48d1..54b15f2e 100644 --- a/aya/src/sys/feature_probe.rs +++ b/aya/src/sys/feature_probe.rs @@ -5,17 +5,18 @@ use std::{io::ErrorKind, mem, os::fd::AsRawFd}; use aya_obj::{ btf::{Btf, BtfError, BtfKind}, generated::{ - bpf_attach_type, bpf_attr, bpf_cmd, bpf_insn, bpf_map_type, BPF_F_MMAPABLE, + bpf_attach_type, bpf_attr, bpf_cmd, bpf_insn, bpf_map_type, bpf_prog_info, BPF_F_MMAPABLE, BPF_F_NO_PREALLOC, BPF_F_SLEEPABLE, }, }; use libc::{E2BIG, EBADF, 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::{ProgramError, ProgramType}, util::{page_size, KernelVersion}, + MockableFd, }; const RETURN_ZERO_INSNS: &[bpf_insn] = &[ @@ -232,6 +233,26 @@ 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 = create_minimal_program(ProgramType::SocketFilter, &mut [])?; + // 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 = create_minimal_program(ProgramType::SocketFilter, &mut [])?; + // 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) +} + /// Create a minimal program with the specified type. /// Types not created for `Extension` and `StructOps`. fn create_minimal_program( @@ -290,3 +311,25 @@ fn create_minimal_program( }) }) } + +/// 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, + }), + } +} 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 fad28f9c..8c757571 100644 --- a/xtask/public-api/aya-obj.txt +++ b/xtask/public-api/aya-obj.txt @@ -7466,8 +7466,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 @@ -8328,8 +8326,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