From cb8e47880082ccfcd75b02209b686e15426e9b6a Mon Sep 17 00:00:00 2001 From: tyrone-wu Date: Tue, 30 Jul 2024 21:30:00 +0000 Subject: [PATCH 1/4] aya,integration-test: improve integration tests for info API Improves the existing integraiton tests for `loaded_programs()` and `loaded_maps()` in consideration for older kernels: - Opt for `SocketFilter` program in tests since XDP requires v4.8 and fragments requires v5.18. - For assertion tests, first perform the assertion, if the assertion fails, then it checks the host kernel version to see if it is above the minimum version requirement. If not, then continue with test, otherwise fail. For assertions that are skipped, they're logged in stderr which can be observed with `-- --nocapture`. This also fixes the `bpf_prog_get_info_by_fd()` call for kernels below v4.15. If calling syscall on kernels below v4.15, it can produce an `E2BIG` error because `check_uarg_tail_zero()` expects the entire struct to all-zero bytes (which is caused from the map info). Instead, we first attempt the syscall with the map info filled, if it returns `E2BIG`, then perform syscall again with empty closure. Also adds doc for which version a kernel feature was introduced for better awareness. The tests have been verified kernel versions: - 4.13.0 - 4.15.0 - 6.1.0 --- aya/src/maps/mod.rs | 2 + aya/src/programs/mod.rs | 6 +- aya/src/sys/bpf.rs | 25 +++- aya/src/util.rs | 7 + test/integration-ebpf/Cargo.toml | 4 + test/integration-ebpf/src/map_test.rs | 33 +++-- test/integration-ebpf/src/simple_prog.rs | 19 +++ test/integration-test/src/lib.rs | 1 + test/integration-test/src/tests.rs | 1 + test/integration-test/src/tests/info.rs | 181 +++++++++++++++++++++++ test/integration-test/src/tests/smoke.rs | 59 +------- test/integration-test/src/utils.rs | 59 ++++++++ xtask/public-api/aya.txt | 4 + 13 files changed, 325 insertions(+), 76 deletions(-) create mode 100644 test/integration-ebpf/src/simple_prog.rs create mode 100644 test/integration-test/src/tests/info.rs diff --git a/aya/src/maps/mod.rs b/aya/src/maps/mod.rs index 431064d8..00585a13 100644 --- a/aya/src/maps/mod.rs +++ b/aya/src/maps/mod.rs @@ -1039,6 +1039,8 @@ impl MapInfo { /// This differs from [`crate::Ebpf::maps`] since it will return all maps /// listed on the host system and not only maps for a specific [`crate::Ebpf`] instance. /// +/// Uses kernel v4.13 features. +/// /// # Example /// ``` /// # use aya::maps::loaded_maps; diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index 898070e5..81b94f78 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -961,7 +961,9 @@ macro_rules! impl_info { ($($struct_name:ident),+ $(,)?) => { $( impl $struct_name { - /// Returns the file descriptor of this Program. + /// Returns metadata information of this program. + /// + /// Uses kernel v4.13 features. pub fn info(&self) -> Result { let ProgramFd(fd) = self.fd()?; @@ -1142,6 +1144,8 @@ impl ProgramInfo { /// This differs from [`crate::Ebpf::programs`] since it will return all programs /// listed on the host system and not only programs a specific [`crate::Ebpf`] instance. /// +/// Uses kernel v4.13 features. +/// /// # Example /// ``` /// # use aya::programs::loaded_programs; diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs index 01bad1a3..058de40e 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -8,7 +8,7 @@ use std::{ }; use assert_matches::assert_matches; -use libc::{ENOENT, ENOSPC}; +use libc::{E2BIG, ENOENT, ENOSPC}; use obj::{ btf::{BtfEnum64, Enum64}, generated::bpf_stats_type, @@ -499,6 +499,7 @@ pub(crate) fn bpf_prog_query( ret } +/// Introduced in kernel v4.13. pub(crate) fn bpf_prog_get_fd_by_id(prog_id: u32) -> Result { let mut attr = unsafe { mem::zeroed::() }; @@ -513,6 +514,7 @@ pub(crate) fn bpf_prog_get_fd_by_id(prog_id: u32) -> Result( fd: BorrowedFd<'_>, init: F, @@ -541,16 +543,31 @@ fn bpf_obj_get_info_by_fd( } } +/// Introduced in kernel v4.13. pub(crate) fn bpf_prog_get_info_by_fd( fd: BorrowedFd<'_>, map_ids: &mut [u32], ) -> Result { - bpf_obj_get_info_by_fd(fd, |info: &mut bpf_prog_info| { + // Attempt syscall with the map info filled. + let mut info = bpf_obj_get_info_by_fd(fd, |info: &mut bpf_prog_info| { info.nr_map_ids = map_ids.len() as _; info.map_ids = map_ids.as_mut_ptr() as _; - }) + }); + + // 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. + if let Err(err) = &info { + if let Some(errno) = &err.io_error.raw_os_error() { + if errno == &E2BIG { + info = bpf_obj_get_info_by_fd(fd, |_| {}); + } + } + } + + info } +/// Introduced in kernel v4.13. pub(crate) fn bpf_map_get_fd_by_id(map_id: u32) -> Result { let mut attr = unsafe { mem::zeroed::() }; @@ -1093,6 +1110,7 @@ fn iter_obj_ids( }) } +/// Introduced in kernel v4.13. pub(crate) fn iter_prog_ids() -> impl Iterator> { iter_obj_ids(bpf_cmd::BPF_PROG_GET_NEXT_ID, "bpf_prog_get_next_id") } @@ -1101,6 +1119,7 @@ pub(crate) fn iter_link_ids() -> impl Iterator> iter_obj_ids(bpf_cmd::BPF_LINK_GET_NEXT_ID, "bpf_link_get_next_id") } +/// Introduced in kernel v4.13. pub(crate) fn iter_map_ids() -> impl Iterator> { iter_obj_ids(bpf_cmd::BPF_MAP_GET_NEXT_ID, "bpf_map_get_next_id") } diff --git a/aya/src/util.rs b/aya/src/util.rs index 17371460..18de6002 100644 --- a/aya/src/util.rs +++ b/aya/src/util.rs @@ -3,6 +3,7 @@ use std::{ collections::BTreeMap, error::Error, ffi::{CStr, CString}, + fmt::Display, fs::{self, File}, io::{self, BufRead, BufReader}, mem, @@ -177,6 +178,12 @@ impl KernelVersion { } } +impl Display for KernelVersion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}.{}.{}", self.major, self.minor, self.patch) + } +} + const ONLINE_CPUS: &str = "/sys/devices/system/cpu/online"; pub(crate) const POSSIBLE_CPUS: &str = "/sys/devices/system/cpu/possible"; diff --git a/test/integration-ebpf/Cargo.toml b/test/integration-ebpf/Cargo.toml index 2c5e7b2e..a69595da 100644 --- a/test/integration-ebpf/Cargo.toml +++ b/test/integration-ebpf/Cargo.toml @@ -64,3 +64,7 @@ path = "src/ring_buf.rs" [[bin]] name = "memmove_test" path = "src/memmove_test.rs" + +[[bin]] +name = "simple_prog" +path = "src/simple_prog.rs" diff --git a/test/integration-ebpf/src/map_test.rs b/test/integration-ebpf/src/map_test.rs index 242207b8..e3fc28bc 100644 --- a/test/integration-ebpf/src/map_test.rs +++ b/test/integration-ebpf/src/map_test.rs @@ -1,29 +1,34 @@ +// Socket Filter program for testing with an arbitrary program with maps. +// This is mainly used in tests with consideration for old kernels. + #![no_std] #![no_main] use aya_ebpf::{ - bindings::xdp_action, - macros::{map, xdp}, - maps::Array, - programs::XdpContext, + macros::{map, socket_filter}, + maps::{Array, HashMap}, + programs::SkBuffContext, }; +// Introduced in kernel v3.19. #[map] static FOO: Array = Array::::with_max_entries(10, 0); +// Introduced in kernel v3.19. #[map(name = "BAR")] -static BAZ: Array = Array::::with_max_entries(10, 0); +static BAZ: HashMap = HashMap::::with_max_entries(8, 0); -#[xdp(frags)] -pub fn pass(ctx: XdpContext) -> u32 { - match unsafe { try_pass(ctx) } { - Ok(ret) => ret, - Err(_) => xdp_action::XDP_ABORTED, - } -} +// Introduced in kernel v3.19. +#[socket_filter] +pub fn simple_prog(_ctx: SkBuffContext) -> i64 { + // So that these maps show up under the `map_ids` field. + FOO.get(0); + // If we use the literal value `0` instead of the local variable `i`, then an additional + // `.rodata` map will be associated with the program. + let i = 0; + BAZ.get_ptr(&i); -unsafe fn try_pass(_ctx: XdpContext) -> Result { - Ok(xdp_action::XDP_PASS) + 0 } #[cfg(not(test))] diff --git a/test/integration-ebpf/src/simple_prog.rs b/test/integration-ebpf/src/simple_prog.rs new file mode 100644 index 00000000..98725dce --- /dev/null +++ b/test/integration-ebpf/src/simple_prog.rs @@ -0,0 +1,19 @@ +// Socket Filter program for testing with an arbitrary program. +// This is mainly used in tests with consideration for old kernels. + +#![no_std] +#![no_main] + +use aya_ebpf::{macros::socket_filter, programs::SkBuffContext}; + +// Introduced in kernel v3.19. +#[socket_filter] +pub fn simple_prog(_ctx: SkBuffContext) -> i64 { + 0 +} + +#[cfg(not(test))] +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} diff --git a/test/integration-test/src/lib.rs b/test/integration-test/src/lib.rs index 4b55f3a1..fc31e6c2 100644 --- a/test/integration-test/src/lib.rs +++ b/test/integration-test/src/lib.rs @@ -23,6 +23,7 @@ pub const REDIRECT: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/re pub const XDP_SEC: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/xdp_sec")); pub const RING_BUF: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/ring_buf")); pub const MEMMOVE_TEST: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/memmove_test")); +pub const SIMPLE_PROG: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/simple_prog")); #[cfg(test)] mod tests; diff --git a/test/integration-test/src/tests.rs b/test/integration-test/src/tests.rs index f37d54bb..db99e095 100644 --- a/test/integration-test/src/tests.rs +++ b/test/integration-test/src/tests.rs @@ -1,6 +1,7 @@ mod bpf_probe_read; mod btf_relocations; mod elf; +mod info; mod load; mod log; mod rbpf; diff --git a/test/integration-test/src/tests/info.rs b/test/integration-test/src/tests/info.rs new file mode 100644 index 00000000..e44d720d --- /dev/null +++ b/test/integration-test/src/tests/info.rs @@ -0,0 +1,181 @@ +//! Tests the Info API. + +use std::{fs, time::SystemTime}; + +use aya::{ + maps::{loaded_maps, MapError}, + programs::{loaded_programs, ProgramError, SocketFilter}, + util::KernelVersion, + Ebpf, +}; +use aya_obj::generated::{bpf_map_type, bpf_prog_type}; +use libc::EINVAL; + +use crate::utils::{kernel_assert, kernel_assert_eq}; + +const BPF_JIT_ENABLE: &str = "/proc/sys/net/core/bpf_jit_enable"; + +#[test] +fn list_loaded_programs() { + // Kernels below v4.15 have been observed to have `bpf_jit_enable` disabled by default. + let jit_enabled = enable_jit(); + + // Load a program. + // 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(); + prog.load().unwrap(); + + // Ensure the `loaded_programs()` api does not panic and grab the last loaded program in the + // iter, which should be our test program. + let prog = match loaded_programs().last().unwrap() { + Ok(prog) => prog, + Err(err) => { + if let ProgramError::SyscallError(err) = &err { + // Skip entire test since feature not available + if err + .io_error + .raw_os_error() + .is_some_and(|errno| errno == EINVAL) + { + eprintln!("ignoring test completely as `loaded_programs()` is not available on the host"); + return; + } + } + panic!("{err}"); + } + }; + + // Test `bpf_prog_info` fields. + kernel_assert_eq!( + bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32, + prog.program_type(), + KernelVersion::new(4, 13, 0), + ); + kernel_assert!(prog.id() > 0, KernelVersion::new(4, 13, 0)); + kernel_assert!(prog.tag() > 0, KernelVersion::new(4, 13, 0)); + if jit_enabled { + kernel_assert!(prog.size_jitted() > 0, KernelVersion::new(4, 13, 0)); + } + kernel_assert!(prog.size_translated() > 0, KernelVersion::new(4, 13, 0)); + let uptime = SystemTime::now().duration_since(prog.loaded_at()).unwrap(); + kernel_assert!(uptime.as_nanos() > 0, KernelVersion::new(4, 15, 0)); + let maps = prog.map_ids().unwrap(); + kernel_assert!(maps.is_empty(), KernelVersion::new(4, 15, 0)); + let name = prog.name_as_str().unwrap(); + kernel_assert_eq!("simple_prog", name, KernelVersion::new(4, 15, 0)); + kernel_assert!(prog.gpl_compatible(), KernelVersion::new(4, 18, 0)); + kernel_assert!( + prog.verified_instruction_count() > 0, + KernelVersion::new(5, 16, 0) + ); + + // We can't reliably test these fields since `0` can be interpreted as the actual value or + // unavailable. + prog.btf_id(); + + // Ensure rest of the fields do not panic. + prog.memory_locked().unwrap(); + prog.fd().unwrap(); +} + +#[test] +fn list_loaded_maps() { + // 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(); + prog.load().unwrap(); + + // Ensure the loaded_maps() api doesn't panic and retrieve loaded maps. + let mut maps = loaded_maps().peekable(); + if let Err(err) = maps.peek().unwrap() { + if let MapError::SyscallError(err) = &err { + if err + .io_error + .raw_os_error() + .is_some_and(|errno| errno == EINVAL) + { + eprintln!( + "ignoring test completely as `loaded_maps()` is not available on the host" + ); + return; + } + } + panic!("{err}"); + } + let mut maps: Vec<_> = maps.filter_map(|m| m.ok()).collect(); + + // There's not a good way to extract our maps of interest with load order being + // non-deterministic. Since we are trying to be more considerate of older kernels, we should + // only rely on v4.13 feats. + // Expected sort order should be: `BAR`, `aya_global` (if ran local), `FOO` + maps.sort_unstable_by_key(|m| (m.map_type(), m.id())); + + // Ensure program has the 2 maps. + if let Ok(info) = prog.info() { + let map_ids = info.map_ids().unwrap(); + kernel_assert_eq!(2, map_ids.len(), KernelVersion::new(4, 15, 0)); + + for id in map_ids.iter() { + assert!( + maps.iter().any(|m| m.id() == *id), + "expected `loaded_maps()` to have `map_ids` from program" + ); + } + } + + // Test `bpf_map_info` fields. + let hash = maps.first().unwrap(); + kernel_assert_eq!( + bpf_map_type::BPF_MAP_TYPE_HASH as u32, + hash.map_type(), + KernelVersion::new(4, 13, 0) + ); + kernel_assert!(hash.id() > 0, KernelVersion::new(4, 13, 0)); + kernel_assert_eq!(4, hash.key_size(), KernelVersion::new(4, 13, 0)); + kernel_assert_eq!(1, hash.value_size(), KernelVersion::new(4, 13, 0)); + kernel_assert_eq!(8, hash.max_entries(), KernelVersion::new(4, 13, 0)); + kernel_assert_eq!( + "BAR", + hash.name_as_str().unwrap(), + KernelVersion::new(4, 15, 0) + ); + + hash.map_flags(); + hash.fd().unwrap(); + + let array = maps.last().unwrap(); + kernel_assert_eq!( + bpf_map_type::BPF_MAP_TYPE_ARRAY as u32, + array.map_type(), + KernelVersion::new(4, 13, 0) + ); + kernel_assert!(array.id() > 0, KernelVersion::new(4, 13, 0)); + kernel_assert_eq!(4, array.key_size(), KernelVersion::new(4, 13, 0)); + kernel_assert_eq!(4, array.value_size(), KernelVersion::new(4, 13, 0)); + kernel_assert_eq!(10, array.max_entries(), KernelVersion::new(4, 13, 0)); + kernel_assert_eq!( + "FOO", + array.name_as_str().unwrap(), + KernelVersion::new(4, 15, 0) + ); + + array.map_flags(); + array.fd().unwrap(); +} + +/// Enable program to be JIT-compiled if not already enabled. +fn enable_jit() -> bool { + match fs::read_to_string(BPF_JIT_ENABLE) { + Ok(contents) => { + if contents.chars().next().is_some_and(|c| c == '0') { + let failed = fs::write(BPF_JIT_ENABLE, b"1").is_err(); + if failed { + return false; + } + } + true + } + Err(_) => false, + } +} diff --git a/test/integration-test/src/tests/smoke.rs b/test/integration-test/src/tests/smoke.rs index b5bb30a2..0d57aea9 100644 --- a/test/integration-test/src/tests/smoke.rs +++ b/test/integration-test/src/tests/smoke.rs @@ -1,6 +1,5 @@ use aya::{ - maps::loaded_maps, - programs::{loaded_programs, Extension, TracePoint, Xdp, XdpFlags}, + programs::{Extension, TracePoint, Xdp, XdpFlags}, util::KernelVersion, Ebpf, EbpfLoader, }; @@ -70,59 +69,3 @@ fn extension() { .load(pass.fd().unwrap().try_clone().unwrap(), "xdp_pass") .unwrap(); } - -#[test] -fn list_loaded_programs() { - // Load a program. - let mut bpf = Ebpf::load(crate::PASS).unwrap(); - let dispatcher: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); - dispatcher.load().unwrap(); - dispatcher.attach("lo", XdpFlags::default()).unwrap(); - - // Ensure the loaded_programs() api doesn't panic. - let prog = loaded_programs() - .map(|p| p.unwrap()) - .find(|p| p.name_as_str().unwrap() == "pass") - .unwrap(); - - // Ensure all relevant helper functions don't panic. - prog.name(); - prog.id(); - prog.tag(); - prog.program_type(); - prog.gpl_compatible(); - prog.map_ids().unwrap(); - prog.btf_id(); - prog.size_translated(); - prog.memory_locked().unwrap(); - prog.verified_instruction_count(); - prog.loaded_at(); - prog.fd().unwrap(); - prog.run_time(); - prog.run_count(); -} - -#[test] -fn list_loaded_maps() { - // Load a program with maps. - let mut bpf = Ebpf::load(crate::MAP_TEST).unwrap(); - let dispatcher: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); - dispatcher.load().unwrap(); - dispatcher.attach("lo", XdpFlags::default()).unwrap(); - - // Ensure the loaded_maps() api doesn't panic and retrieve a map. - let map = loaded_maps() - .map(|m| m.unwrap()) - .find(|m| m.name_as_str().unwrap() == "FOO") - .unwrap(); - - // Ensure all relevant helper functions don't panic. - map.name(); - map.id(); - map.map_type(); - map.key_size(); - map.value_size(); - map.max_entries(); - map.map_flags(); - map.fd().unwrap(); -} diff --git a/test/integration-test/src/utils.rs b/test/integration-test/src/utils.rs index 28b68d4a..27c41ad8 100644 --- a/test/integration-test/src/utils.rs +++ b/test/integration-test/src/utils.rs @@ -70,3 +70,62 @@ impl Drop for NetNsGuard { println!("Exited network namespace {}", self.name); } } + +/// Performs `assert!` macro. If the assertion fails and host kernel version +/// is above feature version, then fail test. +macro_rules! kernel_assert { + ($cond:expr, $version:expr $(,)?) => { + let pass: bool = $cond; + if !pass { + let feat_version: aya::util::KernelVersion = $version; + let current = aya::util::KernelVersion::current().unwrap(); + let cond_literal = stringify!($cond); + if current >= feat_version { + // Host kernel is expected to have the feat but does not + panic!( + r#" assertion `{cond_literal}` failed: expected host kernel v{current} to have v{feat_version} feature"#, + ); + } else { + // Continue with tests since host is not expected to have feat + eprintln!( + r#"ignoring assertion at {}:{} + assertion `{cond_literal}` failed: continuing since host kernel v{current} is not expected to have v{feat_version} feature"#, + file!(), line!(), + ); + } + } + }; +} + +pub(crate) use kernel_assert; + +/// Performs `assert_eq!` macro. If the assertion fails and host kernel version +/// is above feature version, then fail test. +macro_rules! kernel_assert_eq { + ($left:expr, $right:expr, $version:expr $(,)?) => { + if $left != $right { + let feat_version: aya::util::KernelVersion = $version; + let current = aya::util::KernelVersion::current().unwrap(); + if current >= feat_version { + // Host kernel is expected to have the feat but does not + panic!( + r#" assertion `left == right` failed: expected host kernel v{current} to have v{feat_version} feature + left: {:?} + right: {:?}"#, + $left, $right, + ); + } else { + // Continue with tests since host is not expected to have feat + eprintln!( + r#"ignoring assertion at {}:{} + assertion `left == right` failed: continuing since host kernel v{current} is not expected to have v{feat_version} feature + left: {:?} + right: {:?}"#, + file!(), line!(), $left, $right, + ); + } + } + }; +} + +pub(crate) use kernel_assert_eq; diff --git a/xtask/public-api/aya.txt b/xtask/public-api/aya.txt index b47fc579..0024ca71 100644 --- a/xtask/public-api/aya.txt +++ b/xtask/public-api/aya.txt @@ -8765,6 +8765,8 @@ impl core::cmp::PartialOrd for aya::util::KernelVersion pub fn aya::util::KernelVersion::partial_cmp(&self, other: &aya::util::KernelVersion) -> core::option::Option impl core::fmt::Debug for aya::util::KernelVersion pub fn aya::util::KernelVersion::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::fmt::Display for aya::util::KernelVersion +pub fn aya::util::KernelVersion::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result impl core::marker::Copy for aya::util::KernelVersion impl core::marker::StructuralPartialEq for aya::util::KernelVersion impl core::marker::Freeze for aya::util::KernelVersion @@ -8789,6 +8791,8 @@ impl alloc::borrow::ToOwned for aya::util::KernelVersion where T: core::clone pub type aya::util::KernelVersion::Owned = T pub fn aya::util::KernelVersion::clone_into(&self, target: &mut T) pub fn aya::util::KernelVersion::to_owned(&self) -> T +impl alloc::string::ToString for aya::util::KernelVersion where T: core::fmt::Display + core::marker::Sized +pub fn aya::util::KernelVersion::to_string(&self) -> alloc::string::String impl core::any::Any for aya::util::KernelVersion where T: 'static + core::marker::Sized pub fn aya::util::KernelVersion::type_id(&self) -> core::any::TypeId impl core::borrow::Borrow for aya::util::KernelVersion where T: core::marker::Sized From 1634fa7188e40ed75da53517f1fdb7396c348c34 Mon Sep 17 00:00:00 2001 From: tyrone-wu Date: Wed, 17 Jul 2024 20:23:01 +0000 Subject: [PATCH 2/4] aya-obj: add conversion u32 to enum type for prog, link, & attach type Add conversion from u32 to program type, link type, and attach type. Additionally, remove duplicate match statement for u32 conversion to `BPF_MAP_TYPE_BLOOM_FILTER` & `BPF_MAP_TYPE_CGRP_STORAGE`. New error `InvalidTypeBinding` is created to represent when a parsed/received value binding to a type is invalid. This is used in the new conversions added here, and also replaces `InvalidMapTypeError` in `TryFrom` for `bpf_map_type`. --- aya-obj/src/lib.rs | 1 + aya-obj/src/links.rs | 100 ++++++++++++++++++++++++++++++++++ aya-obj/src/maps.rs | 15 ++--- aya-obj/src/obj.rs | 6 ++ aya-obj/src/programs/mod.rs | 1 + aya-obj/src/programs/types.rs | 51 +++++++++++++++++ aya/src/maps/mod.rs | 14 ++--- xtask/public-api/aya-obj.txt | 84 +++++++++++++++++++--------- xtask/public-api/aya.txt | 4 +- 9 files changed, 229 insertions(+), 47 deletions(-) create mode 100644 aya-obj/src/links.rs create mode 100644 aya-obj/src/programs/types.rs diff --git a/aya-obj/src/lib.rs b/aya-obj/src/lib.rs index ea0e5670..c6e3a45c 100644 --- a/aya-obj/src/lib.rs +++ b/aya-obj/src/lib.rs @@ -87,6 +87,7 @@ mod std { pub mod btf; pub mod generated; +pub mod links; pub mod maps; pub mod obj; pub mod programs; diff --git a/aya-obj/src/links.rs b/aya-obj/src/links.rs new file mode 100644 index 00000000..b01e60db --- /dev/null +++ b/aya-obj/src/links.rs @@ -0,0 +1,100 @@ +//! Link type bindings. + +use crate::{ + generated::{bpf_attach_type, bpf_link_type}, + InvalidTypeBinding, +}; + +impl TryFrom for bpf_link_type { + type Error = InvalidTypeBinding; + + fn try_from(link_type: u32) -> Result { + use bpf_link_type::*; + Ok(match link_type { + x if x == BPF_LINK_TYPE_UNSPEC as u32 => BPF_LINK_TYPE_UNSPEC, + x if x == BPF_LINK_TYPE_RAW_TRACEPOINT as u32 => BPF_LINK_TYPE_RAW_TRACEPOINT, + x if x == BPF_LINK_TYPE_TRACING as u32 => BPF_LINK_TYPE_TRACING, + x if x == BPF_LINK_TYPE_CGROUP as u32 => BPF_LINK_TYPE_CGROUP, + x if x == BPF_LINK_TYPE_ITER as u32 => BPF_LINK_TYPE_ITER, + x if x == BPF_LINK_TYPE_NETNS as u32 => BPF_LINK_TYPE_NETNS, + x if x == BPF_LINK_TYPE_XDP as u32 => BPF_LINK_TYPE_XDP, + x if x == BPF_LINK_TYPE_PERF_EVENT as u32 => BPF_LINK_TYPE_PERF_EVENT, + x if x == BPF_LINK_TYPE_KPROBE_MULTI as u32 => BPF_LINK_TYPE_KPROBE_MULTI, + x if x == BPF_LINK_TYPE_STRUCT_OPS as u32 => BPF_LINK_TYPE_STRUCT_OPS, + x if x == BPF_LINK_TYPE_NETFILTER as u32 => BPF_LINK_TYPE_NETFILTER, + x if x == BPF_LINK_TYPE_TCX as u32 => BPF_LINK_TYPE_TCX, + x if x == BPF_LINK_TYPE_UPROBE_MULTI as u32 => BPF_LINK_TYPE_UPROBE_MULTI, + x if x == BPF_LINK_TYPE_NETKIT as u32 => BPF_LINK_TYPE_NETKIT, + _ => return Err(InvalidTypeBinding { value: link_type }), + }) + } +} + +impl TryFrom for bpf_attach_type { + type Error = InvalidTypeBinding; + + fn try_from(attach_type: u32) -> Result { + use bpf_attach_type::*; + Ok(match attach_type { + x if x == BPF_CGROUP_INET_INGRESS as u32 => BPF_CGROUP_INET_INGRESS, + x if x == BPF_CGROUP_INET_EGRESS as u32 => BPF_CGROUP_INET_EGRESS, + x if x == BPF_CGROUP_INET_SOCK_CREATE as u32 => BPF_CGROUP_INET_SOCK_CREATE, + x if x == BPF_CGROUP_SOCK_OPS as u32 => BPF_CGROUP_SOCK_OPS, + x if x == BPF_SK_SKB_STREAM_PARSER as u32 => BPF_SK_SKB_STREAM_PARSER, + x if x == BPF_SK_SKB_STREAM_VERDICT as u32 => BPF_SK_SKB_STREAM_VERDICT, + x if x == BPF_CGROUP_DEVICE as u32 => BPF_CGROUP_DEVICE, + x if x == BPF_SK_MSG_VERDICT as u32 => BPF_SK_MSG_VERDICT, + x if x == BPF_CGROUP_INET4_BIND as u32 => BPF_CGROUP_INET4_BIND, + x if x == BPF_CGROUP_INET6_BIND as u32 => BPF_CGROUP_INET6_BIND, + x if x == BPF_CGROUP_INET4_CONNECT as u32 => BPF_CGROUP_INET4_CONNECT, + x if x == BPF_CGROUP_INET6_CONNECT as u32 => BPF_CGROUP_INET6_CONNECT, + x if x == BPF_CGROUP_INET4_POST_BIND as u32 => BPF_CGROUP_INET4_POST_BIND, + x if x == BPF_CGROUP_INET6_POST_BIND as u32 => BPF_CGROUP_INET6_POST_BIND, + x if x == BPF_CGROUP_UDP4_SENDMSG as u32 => BPF_CGROUP_UDP4_SENDMSG, + x if x == BPF_CGROUP_UDP6_SENDMSG as u32 => BPF_CGROUP_UDP6_SENDMSG, + x if x == BPF_LIRC_MODE2 as u32 => BPF_LIRC_MODE2, + x if x == BPF_FLOW_DISSECTOR as u32 => BPF_FLOW_DISSECTOR, + x if x == BPF_CGROUP_SYSCTL as u32 => BPF_CGROUP_SYSCTL, + x if x == BPF_CGROUP_UDP4_RECVMSG as u32 => BPF_CGROUP_UDP4_RECVMSG, + x if x == BPF_CGROUP_UDP6_RECVMSG as u32 => BPF_CGROUP_UDP6_RECVMSG, + x if x == BPF_CGROUP_GETSOCKOPT as u32 => BPF_CGROUP_GETSOCKOPT, + x if x == BPF_CGROUP_SETSOCKOPT as u32 => BPF_CGROUP_SETSOCKOPT, + x if x == BPF_TRACE_RAW_TP as u32 => BPF_TRACE_RAW_TP, + x if x == BPF_TRACE_FENTRY as u32 => BPF_TRACE_FENTRY, + x if x == BPF_TRACE_FEXIT as u32 => BPF_TRACE_FEXIT, + x if x == BPF_MODIFY_RETURN as u32 => BPF_MODIFY_RETURN, + x if x == BPF_LSM_MAC as u32 => BPF_LSM_MAC, + x if x == BPF_TRACE_ITER as u32 => BPF_TRACE_ITER, + x if x == BPF_CGROUP_INET4_GETPEERNAME as u32 => BPF_CGROUP_INET4_GETPEERNAME, + x if x == BPF_CGROUP_INET6_GETPEERNAME as u32 => BPF_CGROUP_INET6_GETPEERNAME, + x if x == BPF_CGROUP_INET4_GETSOCKNAME as u32 => BPF_CGROUP_INET4_GETSOCKNAME, + x if x == BPF_CGROUP_INET6_GETSOCKNAME as u32 => BPF_CGROUP_INET6_GETSOCKNAME, + x if x == BPF_XDP_DEVMAP as u32 => BPF_XDP_DEVMAP, + x if x == BPF_CGROUP_INET_SOCK_RELEASE as u32 => BPF_CGROUP_INET_SOCK_RELEASE, + x if x == BPF_XDP_CPUMAP as u32 => BPF_XDP_CPUMAP, + x if x == BPF_SK_LOOKUP as u32 => BPF_SK_LOOKUP, + x if x == BPF_XDP as u32 => BPF_XDP, + x if x == BPF_SK_SKB_VERDICT as u32 => BPF_SK_SKB_VERDICT, + x if x == BPF_SK_REUSEPORT_SELECT as u32 => BPF_SK_REUSEPORT_SELECT, + x if x == BPF_SK_REUSEPORT_SELECT_OR_MIGRATE as u32 => { + BPF_SK_REUSEPORT_SELECT_OR_MIGRATE + } + x if x == BPF_PERF_EVENT as u32 => BPF_PERF_EVENT, + x if x == BPF_TRACE_KPROBE_MULTI as u32 => BPF_TRACE_KPROBE_MULTI, + x if x == BPF_LSM_CGROUP as u32 => BPF_LSM_CGROUP, + x if x == BPF_STRUCT_OPS as u32 => BPF_STRUCT_OPS, + x if x == BPF_NETFILTER as u32 => BPF_NETFILTER, + x if x == BPF_TCX_INGRESS as u32 => BPF_TCX_INGRESS, + x if x == BPF_TCX_EGRESS as u32 => BPF_TCX_EGRESS, + x if x == BPF_TRACE_UPROBE_MULTI as u32 => BPF_TRACE_UPROBE_MULTI, + x if x == BPF_CGROUP_UNIX_CONNECT as u32 => BPF_CGROUP_UNIX_CONNECT, + x if x == BPF_CGROUP_UNIX_SENDMSG as u32 => BPF_CGROUP_UNIX_SENDMSG, + x if x == BPF_CGROUP_UNIX_RECVMSG as u32 => BPF_CGROUP_UNIX_RECVMSG, + x if x == BPF_CGROUP_UNIX_GETPEERNAME as u32 => BPF_CGROUP_UNIX_GETPEERNAME, + x if x == BPF_CGROUP_UNIX_GETSOCKNAME as u32 => BPF_CGROUP_UNIX_GETSOCKNAME, + x if x == BPF_NETKIT_PRIMARY as u32 => BPF_NETKIT_PRIMARY, + x if x == BPF_NETKIT_PEER as u32 => BPF_NETKIT_PEER, + _ => return Err(InvalidTypeBinding { value: attach_type }), + }) + } +} diff --git a/aya-obj/src/maps.rs b/aya-obj/src/maps.rs index ca85bba7..3eb82409 100644 --- a/aya-obj/src/maps.rs +++ b/aya-obj/src/maps.rs @@ -5,16 +5,10 @@ use core::mem; #[cfg(not(feature = "std"))] use crate::std; -use crate::EbpfSectionKind; - -/// Invalid map type encontered -pub struct InvalidMapTypeError { - /// The map type - pub map_type: u32, -} +use crate::{EbpfSectionKind, InvalidTypeBinding}; impl TryFrom for crate::generated::bpf_map_type { - type Error = InvalidMapTypeError; + type Error = InvalidTypeBinding; fn try_from(map_type: u32) -> Result { use crate::generated::bpf_map_type::*; @@ -31,7 +25,6 @@ impl TryFrom for crate::generated::bpf_map_type { x if x == BPF_MAP_TYPE_LRU_HASH as u32 => BPF_MAP_TYPE_LRU_HASH, x if x == BPF_MAP_TYPE_LRU_PERCPU_HASH as u32 => BPF_MAP_TYPE_LRU_PERCPU_HASH, x if x == BPF_MAP_TYPE_LPM_TRIE as u32 => BPF_MAP_TYPE_LPM_TRIE, - x if x == BPF_MAP_TYPE_BLOOM_FILTER as u32 => BPF_MAP_TYPE_BLOOM_FILTER, x if x == BPF_MAP_TYPE_ARRAY_OF_MAPS as u32 => BPF_MAP_TYPE_ARRAY_OF_MAPS, x if x == BPF_MAP_TYPE_HASH_OF_MAPS as u32 => BPF_MAP_TYPE_HASH_OF_MAPS, x if x == BPF_MAP_TYPE_DEVMAP as u32 => BPF_MAP_TYPE_DEVMAP, @@ -42,7 +35,6 @@ impl TryFrom for crate::generated::bpf_map_type { x if x == BPF_MAP_TYPE_CGROUP_STORAGE_DEPRECATED as u32 => { BPF_MAP_TYPE_CGROUP_STORAGE_DEPRECATED } - x if x == BPF_MAP_TYPE_CGRP_STORAGE as u32 => BPF_MAP_TYPE_CGRP_STORAGE, x if x == BPF_MAP_TYPE_REUSEPORT_SOCKARRAY as u32 => BPF_MAP_TYPE_REUSEPORT_SOCKARRAY, x if x == BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE_DEPRECATED as u32 => { BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE_DEPRECATED @@ -58,7 +50,8 @@ impl TryFrom for crate::generated::bpf_map_type { x if x == BPF_MAP_TYPE_BLOOM_FILTER as u32 => BPF_MAP_TYPE_BLOOM_FILTER, x if x == BPF_MAP_TYPE_USER_RINGBUF as u32 => BPF_MAP_TYPE_USER_RINGBUF, x if x == BPF_MAP_TYPE_CGRP_STORAGE as u32 => BPF_MAP_TYPE_CGRP_STORAGE, - _ => return Err(InvalidMapTypeError { map_type }), + x if x == BPF_MAP_TYPE_ARENA as u32 => BPF_MAP_TYPE_ARENA, + _ => return Err(InvalidTypeBinding { value: map_type }), }) } } diff --git a/aya-obj/src/obj.rs b/aya-obj/src/obj.rs index 429388a0..be1170f9 100644 --- a/aya-obj/src/obj.rs +++ b/aya-obj/src/obj.rs @@ -987,6 +987,12 @@ pub enum ParseError { NoBTF, } +/// Invalid bindings to the bpf type from the parsed/received value. +pub struct InvalidTypeBinding { + /// The value parsed/received. + pub value: T, +} + /// The kind of an ELF section. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum EbpfSectionKind { diff --git a/aya-obj/src/programs/mod.rs b/aya-obj/src/programs/mod.rs index 6b66b005..b6e91e7a 100644 --- a/aya-obj/src/programs/mod.rs +++ b/aya-obj/src/programs/mod.rs @@ -3,6 +3,7 @@ pub mod cgroup_sock; pub mod cgroup_sock_addr; pub mod cgroup_sockopt; +mod types; pub mod xdp; pub use cgroup_sock::CgroupSockAttachType; diff --git a/aya-obj/src/programs/types.rs b/aya-obj/src/programs/types.rs new file mode 100644 index 00000000..792f562b --- /dev/null +++ b/aya-obj/src/programs/types.rs @@ -0,0 +1,51 @@ +//! Program type bindings. + +use crate::{ + generated::bpf_prog_type::{self, *}, + InvalidTypeBinding, +}; + +impl TryFrom for bpf_prog_type { + type Error = InvalidTypeBinding; + + fn try_from(prog_type: u32) -> Result { + Ok(match prog_type { + x if x == BPF_PROG_TYPE_UNSPEC as u32 => BPF_PROG_TYPE_UNSPEC, + x if x == BPF_PROG_TYPE_SOCKET_FILTER as u32 => BPF_PROG_TYPE_SOCKET_FILTER, + x if x == BPF_PROG_TYPE_KPROBE as u32 => BPF_PROG_TYPE_KPROBE, + x if x == BPF_PROG_TYPE_SCHED_CLS as u32 => BPF_PROG_TYPE_SCHED_CLS, + x if x == BPF_PROG_TYPE_SCHED_ACT as u32 => BPF_PROG_TYPE_SCHED_ACT, + x if x == BPF_PROG_TYPE_TRACEPOINT as u32 => BPF_PROG_TYPE_TRACEPOINT, + x if x == BPF_PROG_TYPE_XDP as u32 => BPF_PROG_TYPE_XDP, + x if x == BPF_PROG_TYPE_PERF_EVENT as u32 => BPF_PROG_TYPE_PERF_EVENT, + x if x == BPF_PROG_TYPE_CGROUP_SKB as u32 => BPF_PROG_TYPE_CGROUP_SKB, + x if x == BPF_PROG_TYPE_CGROUP_SOCK as u32 => BPF_PROG_TYPE_CGROUP_SOCK, + x if x == BPF_PROG_TYPE_LWT_IN as u32 => BPF_PROG_TYPE_LWT_IN, + x if x == BPF_PROG_TYPE_LWT_OUT as u32 => BPF_PROG_TYPE_LWT_OUT, + x if x == BPF_PROG_TYPE_LWT_XMIT as u32 => BPF_PROG_TYPE_LWT_XMIT, + x if x == BPF_PROG_TYPE_SOCK_OPS as u32 => BPF_PROG_TYPE_SOCK_OPS, + x if x == BPF_PROG_TYPE_SK_SKB as u32 => BPF_PROG_TYPE_SK_SKB, + x if x == BPF_PROG_TYPE_CGROUP_DEVICE as u32 => BPF_PROG_TYPE_CGROUP_DEVICE, + x if x == BPF_PROG_TYPE_SK_MSG as u32 => BPF_PROG_TYPE_SK_MSG, + x if x == BPF_PROG_TYPE_RAW_TRACEPOINT as u32 => BPF_PROG_TYPE_RAW_TRACEPOINT, + x if x == BPF_PROG_TYPE_CGROUP_SOCK_ADDR as u32 => BPF_PROG_TYPE_CGROUP_SOCK_ADDR, + x if x == BPF_PROG_TYPE_LWT_SEG6LOCAL as u32 => BPF_PROG_TYPE_LWT_SEG6LOCAL, + x if x == BPF_PROG_TYPE_LIRC_MODE2 as u32 => BPF_PROG_TYPE_LIRC_MODE2, + x if x == BPF_PROG_TYPE_SK_REUSEPORT as u32 => BPF_PROG_TYPE_SK_REUSEPORT, + x if x == BPF_PROG_TYPE_FLOW_DISSECTOR as u32 => BPF_PROG_TYPE_FLOW_DISSECTOR, + x if x == BPF_PROG_TYPE_CGROUP_SYSCTL as u32 => BPF_PROG_TYPE_CGROUP_SYSCTL, + x if x == BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE as u32 => { + BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE + } + x if x == BPF_PROG_TYPE_CGROUP_SOCKOPT as u32 => BPF_PROG_TYPE_CGROUP_SOCKOPT, + x if x == BPF_PROG_TYPE_TRACING as u32 => BPF_PROG_TYPE_TRACING, + x if x == BPF_PROG_TYPE_STRUCT_OPS as u32 => BPF_PROG_TYPE_STRUCT_OPS, + x if x == BPF_PROG_TYPE_EXT as u32 => BPF_PROG_TYPE_EXT, + x if x == BPF_PROG_TYPE_LSM as u32 => BPF_PROG_TYPE_LSM, + x if x == BPF_PROG_TYPE_SK_LOOKUP as u32 => BPF_PROG_TYPE_SK_LOOKUP, + x if x == BPF_PROG_TYPE_SYSCALL as u32 => BPF_PROG_TYPE_SYSCALL, + x if x == BPF_PROG_TYPE_NETFILTER as u32 => BPF_PROG_TYPE_NETFILTER, + _ => return Err(InvalidTypeBinding { value: prog_type }), + }) + } +} diff --git a/aya/src/maps/mod.rs b/aya/src/maps/mod.rs index 00585a13..e914d052 100644 --- a/aya/src/maps/mod.rs +++ b/aya/src/maps/mod.rs @@ -59,10 +59,9 @@ use std::{ ptr, }; -use aya_obj::generated::bpf_map_type; +use aya_obj::{generated::bpf_map_type, InvalidTypeBinding}; use libc::{getrlimit, rlim_t, rlimit, RLIMIT_MEMLOCK, RLIM_INFINITY}; use log::warn; -use obj::maps::InvalidMapTypeError; use thiserror::Error; use crate::{ @@ -203,13 +202,10 @@ pub enum MapError { }, } -// Note that this is not just derived using #[from] because InvalidMapTypeError cannot implement -// Error due the the fact that aya-obj is no_std and error_in_core is not stabilized -// (https://github.com/rust-lang/rust/issues/103765). -impl From for MapError { - fn from(e: InvalidMapTypeError) -> Self { - let InvalidMapTypeError { map_type } = e; - Self::InvalidMapType { map_type } +impl From> for MapError { + fn from(e: InvalidTypeBinding) -> Self { + let InvalidTypeBinding { value } = e; + Self::InvalidMapType { map_type: value } } } diff --git a/xtask/public-api/aya-obj.txt b/xtask/public-api/aya-obj.txt index 10e93ce2..5014c91c 100644 --- a/xtask/public-api/aya-obj.txt +++ b/xtask/public-api/aya-obj.txt @@ -1458,6 +1458,9 @@ impl core::convert::From aya_obj::generated::bpf_attach_type impl core::convert::From for aya_obj::generated::bpf_attach_type pub fn aya_obj::generated::bpf_attach_type::from(value: aya_obj::programs::xdp::XdpAttachType) -> Self +impl core::convert::TryFrom for aya_obj::generated::bpf_attach_type +pub type aya_obj::generated::bpf_attach_type::Error = aya_obj::InvalidTypeBinding +pub fn aya_obj::generated::bpf_attach_type::try_from(attach_type: u32) -> core::result::Result impl core::fmt::Debug for aya_obj::generated::bpf_attach_type pub fn aya_obj::generated::bpf_attach_type::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result impl core::hash::Hash for aya_obj::generated::bpf_attach_type @@ -1593,6 +1596,9 @@ pub fn aya_obj::generated::bpf_link_type::clone(&self) -> aya_obj::generated::bp impl core::cmp::Eq for aya_obj::generated::bpf_link_type impl core::cmp::PartialEq for aya_obj::generated::bpf_link_type pub fn aya_obj::generated::bpf_link_type::eq(&self, other: &aya_obj::generated::bpf_link_type) -> bool +impl core::convert::TryFrom for aya_obj::generated::bpf_link_type +pub type aya_obj::generated::bpf_link_type::Error = aya_obj::InvalidTypeBinding +pub fn aya_obj::generated::bpf_link_type::try_from(link_type: u32) -> core::result::Result impl core::fmt::Debug for aya_obj::generated::bpf_link_type pub fn aya_obj::generated::bpf_link_type::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result impl core::hash::Hash for aya_obj::generated::bpf_link_type @@ -1673,7 +1679,7 @@ impl core::cmp::Eq for aya_obj::generated::bpf_map_type impl core::cmp::PartialEq for aya_obj::generated::bpf_map_type pub fn aya_obj::generated::bpf_map_type::eq(&self, other: &aya_obj::generated::bpf_map_type) -> bool impl core::convert::TryFrom for aya_obj::generated::bpf_map_type -pub type aya_obj::generated::bpf_map_type::Error = aya_obj::maps::InvalidMapTypeError +pub type aya_obj::generated::bpf_map_type::Error = aya_obj::InvalidTypeBinding pub fn aya_obj::generated::bpf_map_type::try_from(map_type: u32) -> core::result::Result impl core::fmt::Debug for aya_obj::generated::bpf_map_type pub fn aya_obj::generated::bpf_map_type::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result @@ -1749,6 +1755,9 @@ pub fn aya_obj::generated::bpf_prog_type::clone(&self) -> aya_obj::generated::bp impl core::cmp::Eq for aya_obj::generated::bpf_prog_type impl core::cmp::PartialEq for aya_obj::generated::bpf_prog_type pub fn aya_obj::generated::bpf_prog_type::eq(&self, other: &aya_obj::generated::bpf_prog_type) -> bool +impl core::convert::TryFrom for aya_obj::generated::bpf_prog_type +pub type aya_obj::generated::bpf_prog_type::Error = aya_obj::InvalidTypeBinding +pub fn aya_obj::generated::bpf_prog_type::try_from(prog_type: u32) -> core::result::Result impl core::fmt::Debug for aya_obj::generated::bpf_prog_type pub fn aya_obj::generated::bpf_prog_type::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result impl core::hash::Hash for aya_obj::generated::bpf_prog_type @@ -6603,6 +6612,7 @@ pub type aya_obj::generated::_bindgen_ty_7 = core::ffi::c_uint pub type aya_obj::generated::_bindgen_ty_8 = core::ffi::c_uint pub type aya_obj::generated::_bindgen_ty_9 = core::ffi::c_uint pub type aya_obj::generated::_bindgen_ty_92 = core::ffi::c_uint +pub mod aya_obj::links pub mod aya_obj::maps pub enum aya_obj::maps::Map pub aya_obj::maps::Map::Btf(aya_obj::maps::BtfMap) @@ -6806,30 +6816,6 @@ impl core::clone::CloneToUninit for aya_obj::maps::BtfMapDef where T: core::c pub unsafe fn aya_obj::maps::BtfMapDef::clone_to_uninit(&self, dst: *mut T) impl core::convert::From for aya_obj::maps::BtfMapDef pub fn aya_obj::maps::BtfMapDef::from(t: T) -> T -pub struct aya_obj::maps::InvalidMapTypeError -pub aya_obj::maps::InvalidMapTypeError::map_type: u32 -impl core::marker::Freeze for aya_obj::maps::InvalidMapTypeError -impl core::marker::Send for aya_obj::maps::InvalidMapTypeError -impl core::marker::Sync for aya_obj::maps::InvalidMapTypeError -impl core::marker::Unpin for aya_obj::maps::InvalidMapTypeError -impl core::panic::unwind_safe::RefUnwindSafe for aya_obj::maps::InvalidMapTypeError -impl core::panic::unwind_safe::UnwindSafe for aya_obj::maps::InvalidMapTypeError -impl core::convert::Into for aya_obj::maps::InvalidMapTypeError where U: core::convert::From -pub fn aya_obj::maps::InvalidMapTypeError::into(self) -> U -impl core::convert::TryFrom for aya_obj::maps::InvalidMapTypeError where U: core::convert::Into -pub type aya_obj::maps::InvalidMapTypeError::Error = core::convert::Infallible -pub fn aya_obj::maps::InvalidMapTypeError::try_from(value: U) -> core::result::Result>::Error> -impl core::convert::TryInto for aya_obj::maps::InvalidMapTypeError where U: core::convert::TryFrom -pub type aya_obj::maps::InvalidMapTypeError::Error = >::Error -pub fn aya_obj::maps::InvalidMapTypeError::try_into(self) -> core::result::Result>::Error> -impl core::any::Any for aya_obj::maps::InvalidMapTypeError where T: 'static + core::marker::Sized -pub fn aya_obj::maps::InvalidMapTypeError::type_id(&self) -> core::any::TypeId -impl core::borrow::Borrow for aya_obj::maps::InvalidMapTypeError where T: core::marker::Sized -pub fn aya_obj::maps::InvalidMapTypeError::borrow(&self) -> &T -impl core::borrow::BorrowMut for aya_obj::maps::InvalidMapTypeError where T: core::marker::Sized -pub fn aya_obj::maps::InvalidMapTypeError::borrow_mut(&mut self) -> &mut T -impl core::convert::From for aya_obj::maps::InvalidMapTypeError -pub fn aya_obj::maps::InvalidMapTypeError::from(t: T) -> T pub struct aya_obj::maps::LegacyMap pub aya_obj::maps::LegacyMap::data: alloc::vec::Vec pub aya_obj::maps::LegacyMap::def: aya_obj::maps::bpf_map_def @@ -7191,6 +7177,30 @@ impl core::clone::CloneToUninit for aya_obj::Function where T: core::clone::C pub unsafe fn aya_obj::Function::clone_to_uninit(&self, dst: *mut T) impl core::convert::From for aya_obj::Function pub fn aya_obj::Function::from(t: T) -> T +pub struct aya_obj::obj::InvalidTypeBinding +pub aya_obj::obj::InvalidTypeBinding::value: T +impl core::marker::Freeze for aya_obj::InvalidTypeBinding where T: core::marker::Freeze +impl core::marker::Send for aya_obj::InvalidTypeBinding where T: core::marker::Send +impl core::marker::Sync for aya_obj::InvalidTypeBinding where T: core::marker::Sync +impl core::marker::Unpin for aya_obj::InvalidTypeBinding where T: core::marker::Unpin +impl core::panic::unwind_safe::RefUnwindSafe for aya_obj::InvalidTypeBinding where T: core::panic::unwind_safe::RefUnwindSafe +impl core::panic::unwind_safe::UnwindSafe for aya_obj::InvalidTypeBinding where T: core::panic::unwind_safe::UnwindSafe +impl core::convert::Into for aya_obj::InvalidTypeBinding where U: core::convert::From +pub fn aya_obj::InvalidTypeBinding::into(self) -> U +impl core::convert::TryFrom for aya_obj::InvalidTypeBinding where U: core::convert::Into +pub type aya_obj::InvalidTypeBinding::Error = core::convert::Infallible +pub fn aya_obj::InvalidTypeBinding::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for aya_obj::InvalidTypeBinding where U: core::convert::TryFrom +pub type aya_obj::InvalidTypeBinding::Error = >::Error +pub fn aya_obj::InvalidTypeBinding::try_into(self) -> core::result::Result>::Error> +impl core::any::Any for aya_obj::InvalidTypeBinding where T: 'static + core::marker::Sized +pub fn aya_obj::InvalidTypeBinding::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for aya_obj::InvalidTypeBinding where T: core::marker::Sized +pub fn aya_obj::InvalidTypeBinding::borrow(&self) -> &T +impl core::borrow::BorrowMut for aya_obj::InvalidTypeBinding where T: core::marker::Sized +pub fn aya_obj::InvalidTypeBinding::borrow_mut(&mut self) -> &mut T +impl core::convert::From for aya_obj::InvalidTypeBinding +pub fn aya_obj::InvalidTypeBinding::from(t: T) -> T pub struct aya_obj::obj::Object pub aya_obj::obj::Object::btf: core::option::Option pub aya_obj::obj::Object::btf_ext: core::option::Option @@ -8025,6 +8035,30 @@ impl core::clone::CloneToUninit for aya_obj::Function where T: core::clone::C pub unsafe fn aya_obj::Function::clone_to_uninit(&self, dst: *mut T) impl core::convert::From for aya_obj::Function pub fn aya_obj::Function::from(t: T) -> T +pub struct aya_obj::InvalidTypeBinding +pub aya_obj::InvalidTypeBinding::value: T +impl core::marker::Freeze for aya_obj::InvalidTypeBinding where T: core::marker::Freeze +impl core::marker::Send for aya_obj::InvalidTypeBinding where T: core::marker::Send +impl core::marker::Sync for aya_obj::InvalidTypeBinding where T: core::marker::Sync +impl core::marker::Unpin for aya_obj::InvalidTypeBinding where T: core::marker::Unpin +impl core::panic::unwind_safe::RefUnwindSafe for aya_obj::InvalidTypeBinding where T: core::panic::unwind_safe::RefUnwindSafe +impl core::panic::unwind_safe::UnwindSafe for aya_obj::InvalidTypeBinding where T: core::panic::unwind_safe::UnwindSafe +impl core::convert::Into for aya_obj::InvalidTypeBinding where U: core::convert::From +pub fn aya_obj::InvalidTypeBinding::into(self) -> U +impl core::convert::TryFrom for aya_obj::InvalidTypeBinding where U: core::convert::Into +pub type aya_obj::InvalidTypeBinding::Error = core::convert::Infallible +pub fn aya_obj::InvalidTypeBinding::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for aya_obj::InvalidTypeBinding where U: core::convert::TryFrom +pub type aya_obj::InvalidTypeBinding::Error = >::Error +pub fn aya_obj::InvalidTypeBinding::try_into(self) -> core::result::Result>::Error> +impl core::any::Any for aya_obj::InvalidTypeBinding where T: 'static + core::marker::Sized +pub fn aya_obj::InvalidTypeBinding::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for aya_obj::InvalidTypeBinding where T: core::marker::Sized +pub fn aya_obj::InvalidTypeBinding::borrow(&self) -> &T +impl core::borrow::BorrowMut for aya_obj::InvalidTypeBinding where T: core::marker::Sized +pub fn aya_obj::InvalidTypeBinding::borrow_mut(&mut self) -> &mut T +impl core::convert::From for aya_obj::InvalidTypeBinding +pub fn aya_obj::InvalidTypeBinding::from(t: T) -> T pub struct aya_obj::Object pub aya_obj::Object::btf: core::option::Option pub aya_obj::Object::btf_ext: core::option::Option diff --git a/xtask/public-api/aya.txt b/xtask/public-api/aya.txt index 0024ca71..0f46ef74 100644 --- a/xtask/public-api/aya.txt +++ b/xtask/public-api/aya.txt @@ -1371,8 +1371,8 @@ impl core::convert::From for aya::programs::ProgramError pub fn aya::programs::ProgramError::from(source: aya::maps::MapError) -> Self impl core::convert::From for aya::maps::MapError pub fn aya::maps::MapError::from(source: aya::sys::SyscallError) -> Self -impl core::convert::From for aya::maps::MapError -pub fn aya::maps::MapError::from(e: aya_obj::maps::InvalidMapTypeError) -> Self +impl core::convert::From> for aya::maps::MapError +pub fn aya::maps::MapError::from(e: aya_obj::obj::InvalidTypeBinding) -> Self impl core::convert::From for aya::maps::MapError pub fn aya::maps::MapError::from(source: std::io::error::Error) -> Self impl core::error::Error for aya::maps::MapError From 88f5ac31142f1657b41b1ee0f217dcd9125b210a Mon Sep 17 00:00:00 2001 From: tyrone-wu Date: Fri, 2 Aug 2024 16:16:24 +0000 Subject: [PATCH 3/4] aya,obj,int-test: revamp ProgramInfo be more friendly with older kernels Purpose of this commit is to add detections for whether a field is available in `ProgramInfo`. - For `program_type()`, we return the new enum `ProgramType` instead of the integer representation. - For fields that we know cannot be zero, we return `Option` type. - For `name_as_str()`, it now also uses the feature probe `bpf_name()` to detect if field is available or not. - Two additional feature probes are added for the fields: - `prog_info_map_ids()` probe -> `map_ids()` field - `prog_info_gpl_compatible()` probe -> `gpl_compatible()` field With the `prog_info_map_ids()` probe, the previous implementation that I had for `bpf_prog_get_info_by_fd()` is shortened to use the probe instead of having to make 2 potential syscalls. The `test_loaded_at()` test is also moved into info tests since it is better related to the info tests. `aya::programs::Programs::prog_type(&self)` now returns `ProgramType` instead of the generated FFI from aya-obj. Also previously, `loaded_programs()` could be accessed either through `aya` or `aya::programs`. To avoid confusion and duplicate export of the item, the function should now only be exposed through `aya::programs`. --- aya-log/src/lib.rs | 9 +- aya-obj/src/obj.rs | 16 + aya/src/bpf.rs | 12 +- aya/src/lib.rs | 1 - aya/src/programs/info.rs | 548 ++++++++++++++++++++++++ aya/src/programs/mod.rs | 268 ++---------- aya/src/sys/bpf.rs | 80 +++- test/integration-test/src/tests/info.rs | 274 +++++++++--- test/integration-test/src/tests/load.rs | 46 +- xtask/public-api/aya-obj.txt | 4 + xtask/public-api/aya.txt | 95 +++- 11 files changed, 974 insertions(+), 379 deletions(-) create mode 100644 aya/src/programs/info.rs diff --git a/aya-log/src/lib.rs b/aya-log/src/lib.rs index 9dcbb2f5..6a2ca27a 100644 --- a/aya-log/src/lib.rs +++ b/aya-log/src/lib.rs @@ -60,12 +60,11 @@ use std::{ const MAP_NAME: &str = "AYA_LOGS"; use aya::{ - loaded_programs, maps::{ perf::{AsyncPerfEventArray, Events, PerfBufferError}, Map, MapData, MapError, MapInfo, }, - programs::ProgramError, + programs::{loaded_programs, ProgramError}, util::online_cpus, Ebpf, Pod, }; @@ -137,13 +136,15 @@ impl EbpfLogger { ) -> Result { let program_info = loaded_programs() .filter_map(|info| info.ok()) - .find(|info| info.id() == program_id) + .find(|info| info.id().is_some_and(|id| id.get() == program_id)) .ok_or(Error::ProgramNotFound)?; + let map = program_info .map_ids() .map_err(Error::ProgramError)? + .expect("`map_ids` field in `bpf_prog_info` not available") .iter() - .filter_map(|id| MapInfo::from_id(*id).ok()) + .filter_map(|id| MapInfo::from_id(id.get()).ok()) .find(|map_info| match map_info.name_as_str() { Some(name) => name == MAP_NAME, None => false, diff --git a/aya-obj/src/obj.rs b/aya-obj/src/obj.rs index be1170f9..4e203fe1 100644 --- a/aya-obj/src/obj.rs +++ b/aya-obj/src/obj.rs @@ -47,6 +47,8 @@ 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,6 +63,8 @@ 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 { @@ -71,6 +75,8 @@ impl Features { bpf_cookie, cpumap_prog_id, devmap_prog_id, + prog_info_map_ids, + prog_info_gpl_compatible, btf, } } @@ -110,6 +116,16 @@ 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 99d8e0d2..8a189476 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -21,8 +21,8 @@ use thiserror::Error; use crate::{ generated::{ - bpf_map_type, bpf_map_type::*, AYA_PERF_EVENT_IOC_DISABLE, AYA_PERF_EVENT_IOC_ENABLE, - AYA_PERF_EVENT_IOC_SET_BPF, + bpf_map_type::{self, *}, + AYA_PERF_EVENT_IOC_DISABLE, AYA_PERF_EVENT_IOC_ENABLE, AYA_PERF_EVENT_IOC_SET_BPF, }, maps::{Map, MapData, MapError}, obj::{ @@ -39,9 +39,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_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_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, }, util::{bytes_of, bytes_of_slice, page_size, possible_cpus, POSSIBLE_CPUS}, }; @@ -96,6 +96,8 @@ 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/lib.rs b/aya/src/lib.rs index c35bf597..3278d3dc 100644 --- a/aya/src/lib.rs +++ b/aya/src/lib.rs @@ -91,7 +91,6 @@ use aya_obj::generated; pub use bpf::*; pub use obj::btf::{Btf, BtfError}; pub use object::Endianness; -pub use programs::loaded_programs; #[doc(hidden)] pub use sys::netlink_set_link_up; diff --git a/aya/src/programs/info.rs b/aya/src/programs/info.rs new file mode 100644 index 00000000..dbcfba3f --- /dev/null +++ b/aya/src/programs/info.rs @@ -0,0 +1,548 @@ +//! Metadata information about an eBPF program. + +use std::{ + ffi::CString, + num::{NonZeroU32, NonZeroU64}, + os::fd::{AsFd as _, BorrowedFd}, + path::Path, + time::{Duration, SystemTime}, +}; + +use aya_obj::generated::{bpf_prog_info, bpf_prog_type}; + +use super::{ + utils::{boot_time, get_fdinfo}, + ProgramError, ProgramFd, +}; +use crate::{ + sys::{ + bpf_get_object, bpf_prog_get_fd_by_id, bpf_prog_get_info_by_fd, iter_prog_ids, SyscallError, + }, + util::bytes_of_bpf_name, + FEATURES, +}; + +/// Provides metadata information about a loaded eBPF program. +/// +/// Introduced in kernel v4.13. +#[doc(alias = "bpf_prog_info")] +#[derive(Debug)] +pub struct ProgramInfo(pub(crate) bpf_prog_info); + +impl ProgramInfo { + pub(crate) fn new_from_fd(fd: BorrowedFd<'_>) -> Result { + let info = bpf_prog_get_info_by_fd(fd, &mut [])?; + Ok(Self(info)) + } + + /// The type of program. + /// + /// Introduced in kernel v4.13. + pub fn program_type(&self) -> Result { + bpf_prog_type::try_from(self.0.type_) + .unwrap_or(bpf_prog_type::__MAX_BPF_PROG_TYPE) + .try_into() + } + + /// The unique ID for this program. + /// + /// `None` is returned if the field is not available. + /// + /// Introduced in kernel v4.13. + pub fn id(&self) -> Option { + NonZeroU32::new(self.0.id) + } + + /// The program tag. + /// + /// The tag is a SHA sum of the program's instructions which be used as an alternative to + /// [`Self::id()`]. A program's ID can vary every time it's loaded or unloaded, but the tag + /// will remain the same. + /// + /// `None` is returned if the field is not available. + /// + /// Introduced in kernel v4.13. + pub fn tag(&self) -> Option { + NonZeroU64::new(u64::from_be_bytes(self.0.tag)) + } + + /// The size in bytes of the program's JIT-compiled machine code. + /// + /// Note that this field is only updated when BPF JIT compiler is enabled. Kernels v4.15 and + /// above may already have it enabled by default. + /// + /// `None` is returned if the field is not available, or if the JIT compiler is not enabled. + /// + /// Introduced in kernel v4.13. + pub fn size_jitted(&self) -> Option { + NonZeroU32::new(self.0.jited_prog_len) + } + + /// The size in bytes of the program's translated eBPF bytecode. + /// + /// The translated bytecode is after it has been passed though the verifier where it was + /// possibly modified by the kernel. + /// + /// `None` is returned if the field is not available. + /// + /// Introduced in kernel v4.15. + pub fn size_translated(&self) -> Option { + NonZeroU32::new(self.0.xlated_prog_len) + } + + /// The time when the program was loaded. + /// + /// `None` is returned if the field is not available. + /// + /// Introduced in kernel v4.15. + pub fn loaded_at(&self) -> Option { + if self.0.load_time > 0 { + Some(boot_time() + Duration::from_nanos(self.0.load_time)) + } else { + None + } + } + + /// The user ID of the process who loaded the program. + /// + /// `None` is returned if the field is not available. + /// + /// Introduced in kernel v4.15. + pub fn created_by_uid(&self) -> Option { + // This field was introduced in the same commit as `load_time`. + if self.0.load_time > 0 { + Some(self.0.created_by_uid) + } else { + None + } + } + + /// The IDs of the maps used by the program. + /// + /// `None` is returned if the field is not available. + /// + /// 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 + .into_iter() + .map(|id| NonZeroU32::new(id).unwrap()) + .collect(), + )) + } else { + Ok(None) + } + } + + /// The name of the program as was provided when it was load. This is limited to 16 bytes. + /// + /// Introduced in kernel v4.15. + pub fn name(&self) -> &[u8] { + bytes_of_bpf_name(&self.0.name) + } + + /// The name of the program as a &str. + /// + /// `None` is returned if the name was not valid unicode or if field is not available. + /// + /// Introduced in kernel v4.15. + pub fn name_as_str(&self) -> Option<&str> { + let name = std::str::from_utf8(self.name()).ok(); + if let Some(name_str) = name { + if FEATURES.bpf_name() || !name_str.is_empty() { + return name; + } + } + None + } + + /// Returns true if the program is defined with a GPL-compatible license. + /// + /// `None` is returned if the field is not available. + /// + /// Introduced in kernel v4.18. + pub fn gpl_compatible(&self) -> Option { + if FEATURES.prog_info_gpl_compatible() { + Some(self.0.gpl_compatible() != 0) + } else { + None + } + } + + /// The BTF ID for the program. + /// + /// Introduced in kernel v5.0. + pub fn btf_id(&self) -> Option { + NonZeroU32::new(self.0.btf_id) + } + + /// The accumulated time that the program has been actively running. + /// + /// This is not to be confused with the duration since the program was + /// first loaded on the host. + /// + /// Note this field is only updated for as long as + /// [`enable_stats`](crate::sys::enable_stats) is enabled + /// with [`Stats::RunTime`](crate::sys::Stats::RunTime). + /// + /// Introduced in kernel v5.1. + pub fn run_time(&self) -> Duration { + Duration::from_nanos(self.0.run_time_ns) + } + + /// The accumulated execution count of the program. + /// + /// Note this field is only updated for as long as + /// [`enable_stats`](crate::sys::enable_stats) is enabled + /// with [`Stats::RunTime`](crate::sys::Stats::RunTime). + /// + /// Introduced in kernel v5.1. + pub fn run_count(&self) -> u64 { + self.0.run_cnt + } + + /// The number of verified instructions in the program. + /// + /// This may be less than the total number of instructions in the compiled program due to dead + /// code elimination in the verifier. + /// + /// `None` is returned if the field is not available. + /// + /// Introduced in kernel v5.16. + pub fn verified_instruction_count(&self) -> Option { + NonZeroU32::new(self.0.verified_insns) + } + + /// How much memory in bytes has been allocated and locked for the program. + pub fn memory_locked(&self) -> Result { + get_fdinfo(self.fd()?.as_fd(), "memlock") + } + + /// Returns a file descriptor referencing the program. + /// + /// The returned file descriptor can be closed at any time and doing so does + /// not influence the life cycle of the program. + /// + /// Uses kernel v4.13 features. + pub fn fd(&self) -> Result { + let Self(info) = self; + let fd = bpf_prog_get_fd_by_id(info.id)?; + Ok(ProgramFd(fd)) + } + + /// Loads a program from a pinned path in bpffs. + /// + /// Uses kernel v4.4 and v4.13 features. + pub fn from_pin>(path: P) -> Result { + use std::os::unix::ffi::OsStrExt as _; + + // TODO: avoid this unwrap by adding a new error variant. + let path_string = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap(); + let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError { + call: "BPF_OBJ_GET", + io_error, + })?; + + Self::new_from_fd(fd.as_fd()) + } +} + +/// Returns information about a loaded program with the [`ProgramInfo`] structure. +/// +/// This information is populated at load time by the kernel and can be used +/// to correlate a given [`crate::programs::Program`] to it's corresponding [`ProgramInfo`] +/// metadata. +macro_rules! impl_info { + ($($struct_name:ident),+ $(,)?) => { + $( + impl $struct_name { + /// Returns metadata information of this program. + /// + /// Uses kernel v4.13 features. + pub fn info(&self) -> Result { + let ProgramFd(fd) = self.fd()?; + ProgramInfo::new_from_fd(fd.as_fd()) + } + } + )+ + } +} + +pub(crate) use impl_info; + +/// Returns an iterator of [`ProgramInfo`] over all eBPF programs loaded on the host. +/// +/// Unlike [`Ebpf::programs`](crate::Ebpf::programs), this includes **all** programs on the host +/// system, not just those tied to a specific [`crate::Ebpf`] instance. +/// +/// Uses kernel v4.13 features. +/// +/// # Example +/// ``` +/// # use aya::programs::loaded_programs; +/// # +/// for p in loaded_programs() { +/// match p { +/// Ok(program) => println!("{}", String::from_utf8_lossy(program.name())), +/// Err(e) => println!("Error iterating programs: {:?}", e), +/// } +/// } +/// ``` +/// +/// # Errors +/// +/// Returns [`ProgramError::SyscallError`] if any of the syscalls required to either get +/// next program id, get the program fd, or the [`ProgramInfo`] fail. +/// +/// In cases where iteration can't be performed, for example the caller does not have the necessary +/// privileges, a single item will be yielded containing the error that occurred. +pub fn loaded_programs() -> impl Iterator> { + iter_prog_ids() + .map(|id| { + let id = id?; + bpf_prog_get_fd_by_id(id) + }) + .map(|fd| { + let fd = fd?; + bpf_prog_get_info_by_fd(fd.as_fd(), &mut []) + }) + .map(|result| result.map(ProgramInfo).map_err(Into::into)) +} + +/// The type of eBPF program. +#[non_exhaustive] +#[doc(alias = "bpf_prog_type")] +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum ProgramType { + /// An unspecified program type. + Unspecified = bpf_prog_type::BPF_PROG_TYPE_UNSPEC as isize, + /// A Socket Filter program type. See [`SocketFilter`](super::socket_filter::SocketFilter) + /// for the program implementation. + /// + /// Introduced in kernel v3.19. + #[doc(alias = "BPF_PROG_TYPE_SOCKET_FILTER")] + SocketFilter = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as isize, + /// A Kernel Probe program type. See [`KProbe`](super::kprobe::KProbe) and + /// [`UProbe`](super::uprobe::UProbe) for the program implementations. + /// + /// Introduced in kernel v4.1. + #[doc(alias = "BPF_PROG_TYPE_KPROBE")] + KProbe = bpf_prog_type::BPF_PROG_TYPE_KPROBE as isize, + /// A Traffic Control (TC) Classifier program type. See + /// [`SchedClassifier`](super::tc::SchedClassifier) for the program implementation. + /// + /// Introduced in kernel v4.1. + #[doc(alias = "BPF_PROG_TYPE_SCHED_CLS")] + SchedClassifier = bpf_prog_type::BPF_PROG_TYPE_SCHED_CLS as isize, + /// A Traffic Control (TC) Action program type. + /// + /// Introduced in kernel v4.1. + #[doc(alias = "BPF_PROG_TYPE_SCHED_ACT")] + SchedAction = bpf_prog_type::BPF_PROG_TYPE_SCHED_ACT as isize, + /// A Tracepoint program type. See [`TracePoint`](super::trace_point::TracePoint) for the + /// program implementation. + /// + /// Introduced in kernel v4.7. + #[doc(alias = "BPF_PROG_TYPE_TRACEPOINT")] + TracePoint = bpf_prog_type::BPF_PROG_TYPE_TRACEPOINT as isize, + /// An Express Data Path (XDP) program type. See [`Xdp`](super::xdp::Xdp) for the program + /// implementation. + /// + /// Introduced in kernel v4.8. + #[doc(alias = "BPF_PROG_TYPE_XDP")] + Xdp = bpf_prog_type::BPF_PROG_TYPE_XDP as isize, + /// A Perf Event program type. See [`PerfEvent`](super::perf_event::PerfEvent) for the program + /// implementation. + /// + /// Introduced in kernel v4.9. + #[doc(alias = "BPF_PROG_TYPE_PERF_EVENT")] + PerfEvent = bpf_prog_type::BPF_PROG_TYPE_PERF_EVENT as isize, + /// A cGroup Socket Buffer program type. See [`CgroupSkb`](super::cgroup_skb::CgroupSkb) for + /// the program implementation. + /// + /// Introduced in kernel v4.10. + #[doc(alias = "BPF_PROG_TYPE_CGROUP_SKB")] + CgroupSkb = bpf_prog_type::BPF_PROG_TYPE_CGROUP_SKB as isize, + /// A cGroup Socket program type. See [`CgroupSock`](super::cgroup_sock::CgroupSock) for the + /// program implementation. + /// + /// Introduced in kernel v4.10. + #[doc(alias = "BPF_PROG_TYPE_CGROUP_SOCK")] + CgroupSock = bpf_prog_type::BPF_PROG_TYPE_CGROUP_SOCK as isize, + /// A Lightweight Tunnel (LWT) Input program type. + /// + /// Introduced in kernel v4.10. + #[doc(alias = "BPF_PROG_TYPE_LWT_IN")] + LwtInput = bpf_prog_type::BPF_PROG_TYPE_LWT_IN as isize, + /// A Lightweight Tunnel (LWT) Output program type. + /// + /// Introduced in kernel v4.10. + #[doc(alias = "BPF_PROG_TYPE_LWT_OUT")] + LwtOutput = bpf_prog_type::BPF_PROG_TYPE_LWT_OUT as isize, + /// A Lightweight Tunnel (LWT) Transmit program type. + /// + /// Introduced in kernel v4.10. + #[doc(alias = "BPF_PROG_TYPE_LWT_XMIT")] + LwtXmit = bpf_prog_type::BPF_PROG_TYPE_LWT_XMIT as isize, + /// A Socket Operation program type. See [`SockOps`](super::sock_ops::SockOps) for the program + /// implementation. + /// + /// Introduced in kernel v4.13. + #[doc(alias = "BPF_PROG_TYPE_SOCK_OPS")] + SockOps = bpf_prog_type::BPF_PROG_TYPE_SOCK_OPS as isize, + /// A Socket-to-Socket Buffer program type. See [`SkSkb`](super::sk_skb::SkSkb) for the program + /// implementation. + /// + /// Introduced in kernel v4.14. + #[doc(alias = "BPF_PROG_TYPE_SK_SKB")] + SkSkb = bpf_prog_type::BPF_PROG_TYPE_SK_SKB as isize, + /// A cGroup Device program type. See [`CgroupDevice`](super::cgroup_device::CgroupDevice) + /// for the program implementation. + /// + /// Introduced in kernel v4.15. + #[doc(alias = "BPF_PROG_TYPE_CGROUP_DEVICE")] + CgroupDevice = bpf_prog_type::BPF_PROG_TYPE_CGROUP_DEVICE as isize, + /// A Socket Message program type. See [`SkMsg`](super::sk_msg::SkMsg) for the program + /// implementation. + /// + /// Introduced in kernel v4.17. + #[doc(alias = "BPF_PROG_TYPE_SK_MSG")] + SkMsg = bpf_prog_type::BPF_PROG_TYPE_SK_MSG as isize, + /// A Raw Tracepoint program type. See [`RawTracePoint`](super::raw_trace_point::RawTracePoint) + /// for the program implementation. + /// + /// Introduced in kernel v4.17. + #[doc(alias = "BPF_PROG_TYPE_RAW_TRACEPOINT")] + RawTracePoint = bpf_prog_type::BPF_PROG_TYPE_RAW_TRACEPOINT as isize, + /// A cGroup Socket Address program type. See + /// [`CgroupSockAddr`](super::cgroup_sock_addr::CgroupSockAddr) for the program implementation. + /// + /// Introduced in kernel v4.17. + #[doc(alias = "BPF_PROG_TYPE_CGROUP_SOCK_ADDR")] + CgroupSockAddr = bpf_prog_type::BPF_PROG_TYPE_CGROUP_SOCK_ADDR as isize, + /// A Lightweight Tunnel (LWT) Seg6local program type. + /// + /// Introduced in kernel v4.18. + #[doc(alias = "BPF_PROG_TYPE_LWT_SEG6LOCAL")] + LwtSeg6local = bpf_prog_type::BPF_PROG_TYPE_LWT_SEG6LOCAL as isize, + /// A Linux Infrared Remote Control (LIRC) Mode2 program type. See + /// [`LircMode2`](super::lirc_mode2::LircMode2) for the program implementation. + /// + /// Introduced in kernel v4.18. + #[doc(alias = "BPF_PROG_TYPE_LIRC_MODE2")] + LircMode2 = bpf_prog_type::BPF_PROG_TYPE_LIRC_MODE2 as isize, + /// A Socket Reuseport program type. + /// + /// Introduced in kernel v4.19. + #[doc(alias = "BPF_PROG_TYPE_SK_REUSEPORT")] + SkReuseport = bpf_prog_type::BPF_PROG_TYPE_SK_REUSEPORT as isize, + /// A Flow Dissector program type. + /// + /// Introduced in kernel v4.20. + #[doc(alias = "BPF_PROG_TYPE_FLOW_DISSECTOR")] + FlowDissector = bpf_prog_type::BPF_PROG_TYPE_FLOW_DISSECTOR as isize, + /// A cGroup Sysctl program type. See [`CgroupSysctl`](super::cgroup_sysctl::CgroupSysctl) for + /// the program implementation. + /// + /// Introduced in kernel v5.2. + #[doc(alias = "BPF_PROG_TYPE_CGROUP_SYSCTL")] + CgroupSysctl = bpf_prog_type::BPF_PROG_TYPE_CGROUP_SYSCTL as isize, + /// A Writable Raw Tracepoint program type. + /// + /// Introduced in kernel v5.2. + #[doc(alias = "BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE")] + RawTracePointWritable = bpf_prog_type::BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE as isize, + /// A cGroup Socket Option program type. See [`CgroupSockopt`](super::cgroup_sockopt::CgroupSockopt) + /// for the program implementation. + /// + /// Introduced in kernel v5.3. + #[doc(alias = "BPF_PROG_TYPE_CGROUP_SOCKOPT")] + CgroupSockopt = bpf_prog_type::BPF_PROG_TYPE_CGROUP_SOCKOPT as isize, + /// A Tracing program type. See [`FEntry`](super::fentry::FEntry), [`FExit`](super::fexit::FExit), + /// and [`BtfTracePoint`](super::tp_btf::BtfTracePoint) for the program implementations. + /// + /// Introduced in kernel v5.5. + #[doc(alias = "BPF_PROG_TYPE_TRACING")] + Tracing = bpf_prog_type::BPF_PROG_TYPE_TRACING as isize, + /// A Struct Ops program type. + /// + /// Introduced in kernel v5.6. + #[doc(alias = "BPF_PROG_TYPE_STRUCT_OPS")] + StructOps = bpf_prog_type::BPF_PROG_TYPE_STRUCT_OPS as isize, + /// A Extension program type. See [`Extension`](super::extension::Extension) for the program + /// implementation. + /// + /// Introduced in kernel v5.6. + #[doc(alias = "BPF_PROG_TYPE_EXT")] + Extension = bpf_prog_type::BPF_PROG_TYPE_EXT as isize, + /// A Linux Security Module (LSM) program type. See [`Lsm`](super::lsm::Lsm) for the program + /// implementation. + /// + /// Introduced in kernel v5.7. + #[doc(alias = "BPF_PROG_TYPE_LSM")] + Lsm = bpf_prog_type::BPF_PROG_TYPE_LSM as isize, + /// A Socket Lookup program type. See [`SkLookup`](super::sk_lookup::SkLookup) for the program + /// implementation. + /// + /// Introduced in kernel v5.9. + #[doc(alias = "BPF_PROG_TYPE_SK_LOOKUP")] + SkLookup = bpf_prog_type::BPF_PROG_TYPE_SK_LOOKUP as isize, + /// A Syscall program type. + /// + /// Introduced in kernel v5.14. + #[doc(alias = "BPF_PROG_TYPE_SYSCALL")] + Syscall = bpf_prog_type::BPF_PROG_TYPE_SYSCALL as isize, + /// A Netfilter program type. + /// + /// Introduced in kernel v6.4. + #[doc(alias = "BPF_PROG_TYPE_NETFILTER")] + Netfilter = bpf_prog_type::BPF_PROG_TYPE_NETFILTER as isize, +} + +impl TryFrom for ProgramType { + type Error = ProgramError; + + fn try_from(prog_type: bpf_prog_type) -> Result { + use bpf_prog_type::*; + Ok(match prog_type { + BPF_PROG_TYPE_UNSPEC => Self::Unspecified, + BPF_PROG_TYPE_SOCKET_FILTER => Self::SocketFilter, + BPF_PROG_TYPE_KPROBE => Self::KProbe, + BPF_PROG_TYPE_SCHED_CLS => Self::SchedClassifier, + BPF_PROG_TYPE_SCHED_ACT => Self::SchedAction, + BPF_PROG_TYPE_TRACEPOINT => Self::TracePoint, + BPF_PROG_TYPE_XDP => Self::Xdp, + BPF_PROG_TYPE_PERF_EVENT => Self::PerfEvent, + BPF_PROG_TYPE_CGROUP_SKB => Self::CgroupSkb, + BPF_PROG_TYPE_CGROUP_SOCK => Self::CgroupSock, + BPF_PROG_TYPE_LWT_IN => Self::LwtInput, + BPF_PROG_TYPE_LWT_OUT => Self::LwtOutput, + BPF_PROG_TYPE_LWT_XMIT => Self::LwtXmit, + BPF_PROG_TYPE_SOCK_OPS => Self::SockOps, + BPF_PROG_TYPE_SK_SKB => Self::SkSkb, + BPF_PROG_TYPE_CGROUP_DEVICE => Self::CgroupDevice, + BPF_PROG_TYPE_SK_MSG => Self::SkMsg, + BPF_PROG_TYPE_RAW_TRACEPOINT => Self::RawTracePoint, + BPF_PROG_TYPE_CGROUP_SOCK_ADDR => Self::CgroupSockAddr, + BPF_PROG_TYPE_LWT_SEG6LOCAL => Self::LwtSeg6local, + BPF_PROG_TYPE_LIRC_MODE2 => Self::LircMode2, + BPF_PROG_TYPE_SK_REUSEPORT => Self::SkReuseport, + BPF_PROG_TYPE_FLOW_DISSECTOR => Self::FlowDissector, + BPF_PROG_TYPE_CGROUP_SYSCTL => Self::CgroupSysctl, + BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE => Self::RawTracePointWritable, + BPF_PROG_TYPE_CGROUP_SOCKOPT => Self::CgroupSockopt, + BPF_PROG_TYPE_TRACING => Self::Tracing, + BPF_PROG_TYPE_STRUCT_OPS => Self::StructOps, + BPF_PROG_TYPE_EXT => Self::Extension, + BPF_PROG_TYPE_LSM => Self::Lsm, + BPF_PROG_TYPE_SK_LOOKUP => Self::SkLookup, + BPF_PROG_TYPE_SYSCALL => Self::Syscall, + BPF_PROG_TYPE_NETFILTER => Self::Netfilter, + __MAX_BPF_PROG_TYPE => return Err(ProgramError::UnexpectedProgramType), + }) + } +} diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index 81b94f78..d0ce82d9 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -37,6 +37,7 @@ //! [`maps`]: crate::maps // modules we don't export +mod info; mod probe; mod utils; @@ -71,13 +72,13 @@ pub mod xdp; use std::{ ffi::CString, io, - num::NonZeroU32, os::fd::{AsFd, AsRawFd, BorrowedFd}, path::{Path, PathBuf}, sync::Arc, - time::{Duration, SystemTime}, }; +use info::impl_info; +pub use info::{loaded_programs, ProgramInfo, ProgramType}; use libc::ENOSPC; use thiserror::Error; @@ -115,18 +116,13 @@ use crate::{ maps::MapError, obj::{self, btf::BtfError, VerifierLog}, pin::PinError, - programs::{ - links::*, - perf_attach::*, - utils::{boot_time, get_fdinfo}, - }, + programs::{links::*, perf_attach::*}, sys::{ bpf_btf_get_fd_by_id, bpf_get_object, bpf_link_get_fd_by_id, bpf_link_get_info_by_fd, - bpf_load_program, bpf_pin_object, bpf_prog_get_fd_by_id, bpf_prog_get_info_by_fd, - bpf_prog_query, iter_link_ids, iter_prog_ids, retry_with_verifier_logs, - EbpfLoadProgramAttrs, SyscallError, + bpf_load_program, bpf_pin_object, bpf_prog_get_fd_by_id, bpf_prog_query, iter_link_ids, + retry_with_verifier_logs, EbpfLoadProgramAttrs, SyscallError, }, - util::{bytes_of_bpf_name, KernelVersion}, + util::KernelVersion, VerifierLogLevel, }; @@ -242,7 +238,7 @@ impl AsFd for ProgramFd { } } -/// eBPF program type. +/// The various eBPF programs. #[derive(Debug)] pub enum Program { /// A [`KProbe`] program @@ -296,34 +292,30 @@ pub enum Program { } impl Program { - /// Returns the low level program type. - pub fn prog_type(&self) -> bpf_prog_type { - use crate::generated::bpf_prog_type::*; + /// Returns the program type. + pub fn prog_type(&self) -> ProgramType { match self { - Self::KProbe(_) => BPF_PROG_TYPE_KPROBE, - Self::UProbe(_) => BPF_PROG_TYPE_KPROBE, - Self::TracePoint(_) => BPF_PROG_TYPE_TRACEPOINT, - Self::SocketFilter(_) => BPF_PROG_TYPE_SOCKET_FILTER, - Self::Xdp(_) => BPF_PROG_TYPE_XDP, - Self::SkMsg(_) => BPF_PROG_TYPE_SK_MSG, - Self::SkSkb(_) => BPF_PROG_TYPE_SK_SKB, - Self::SockOps(_) => BPF_PROG_TYPE_SOCK_OPS, - Self::SchedClassifier(_) => BPF_PROG_TYPE_SCHED_CLS, - Self::CgroupSkb(_) => BPF_PROG_TYPE_CGROUP_SKB, - Self::CgroupSysctl(_) => BPF_PROG_TYPE_CGROUP_SYSCTL, - Self::CgroupSockopt(_) => BPF_PROG_TYPE_CGROUP_SOCKOPT, - Self::LircMode2(_) => BPF_PROG_TYPE_LIRC_MODE2, - Self::PerfEvent(_) => BPF_PROG_TYPE_PERF_EVENT, - Self::RawTracePoint(_) => BPF_PROG_TYPE_RAW_TRACEPOINT, - Self::Lsm(_) => BPF_PROG_TYPE_LSM, - Self::BtfTracePoint(_) => BPF_PROG_TYPE_TRACING, - Self::FEntry(_) => BPF_PROG_TYPE_TRACING, - Self::FExit(_) => BPF_PROG_TYPE_TRACING, - Self::Extension(_) => BPF_PROG_TYPE_EXT, - Self::CgroupSockAddr(_) => BPF_PROG_TYPE_CGROUP_SOCK_ADDR, - Self::SkLookup(_) => BPF_PROG_TYPE_SK_LOOKUP, - Self::CgroupSock(_) => BPF_PROG_TYPE_CGROUP_SOCK, - Self::CgroupDevice(_) => BPF_PROG_TYPE_CGROUP_DEVICE, + Self::KProbe(_) | Self::UProbe(_) => ProgramType::KProbe, + Self::TracePoint(_) => ProgramType::TracePoint, + Self::SocketFilter(_) => ProgramType::SocketFilter, + Self::Xdp(_) => ProgramType::Xdp, + Self::SkMsg(_) => ProgramType::SkMsg, + Self::SkSkb(_) => ProgramType::SkSkb, + Self::SockOps(_) => ProgramType::SockOps, + Self::SchedClassifier(_) => ProgramType::SchedClassifier, + Self::CgroupSkb(_) => ProgramType::CgroupSkb, + Self::CgroupSysctl(_) => ProgramType::CgroupSysctl, + Self::CgroupSockopt(_) => ProgramType::CgroupSockopt, + Self::LircMode2(_) => ProgramType::LircMode2, + Self::PerfEvent(_) => ProgramType::PerfEvent, + Self::RawTracePoint(_) => ProgramType::RawTracePoint, + Self::Lsm(_) => ProgramType::Lsm, + Self::BtfTracePoint(_) | Self::FEntry(_) | Self::FExit(_) => ProgramType::Tracing, + Self::Extension(_) => ProgramType::Extension, + Self::CgroupSockAddr(_) => ProgramType::CgroupSockAddr, + Self::SkLookup(_) => ProgramType::SkLookup, + Self::CgroupSock(_) => ProgramType::CgroupSock, + Self::CgroupDevice(_) => ProgramType::CgroupDevice, } } @@ -952,28 +944,6 @@ impl_try_from_program!( CgroupDevice, ); -/// Returns information about a loaded program with the [`ProgramInfo`] structure. -/// -/// This information is populated at load time by the kernel and can be used -/// to correlate a given [`Program`] to it's corresponding [`ProgramInfo`] -/// metadata. -macro_rules! impl_info { - ($($struct_name:ident),+ $(,)?) => { - $( - impl $struct_name { - /// Returns metadata information of this program. - /// - /// Uses kernel v4.13 features. - pub fn info(&self) -> Result { - let ProgramFd(fd) = self.fd()?; - - ProgramInfo::new_from_fd(fd.as_fd()) - } - } - )+ - } -} - impl_info!( KProbe, UProbe, @@ -1001,182 +971,6 @@ impl_info!( CgroupDevice, ); -/// Provides information about a loaded program, like name, id and statistics -#[doc(alias = "bpf_prog_info")] -#[derive(Debug)] -pub struct ProgramInfo(bpf_prog_info); - -impl ProgramInfo { - fn new_from_fd(fd: BorrowedFd<'_>) -> Result { - let info = bpf_prog_get_info_by_fd(fd, &mut [])?; - Ok(Self(info)) - } - - /// The name of the program as was provided when it was load. This is limited to 16 bytes - pub fn name(&self) -> &[u8] { - bytes_of_bpf_name(&self.0.name) - } - - /// The name of the program as a &str. If the name was not valid unicode, None is returned. - pub fn name_as_str(&self) -> Option<&str> { - std::str::from_utf8(self.name()).ok() - } - - /// The id for this program. Each program has a unique id. - pub fn id(&self) -> u32 { - self.0.id - } - - /// The program tag. - /// - /// The program tag is a SHA sum of the program's instructions which be used as an alternative to - /// [`Self::id()`]". A program's id can vary every time it's loaded or unloaded, but the tag - /// will remain the same. - pub fn tag(&self) -> u64 { - u64::from_be_bytes(self.0.tag) - } - - /// The program type as defined by the linux kernel enum - /// [`bpf_prog_type`](https://elixir.bootlin.com/linux/v6.4.4/source/include/uapi/linux/bpf.h#L948). - pub fn program_type(&self) -> u32 { - self.0.type_ - } - - /// Returns true if the program is defined with a GPL-compatible license. - pub fn gpl_compatible(&self) -> bool { - self.0.gpl_compatible() != 0 - } - - /// The ids of the maps used by the program. - pub fn map_ids(&self) -> Result, ProgramError> { - let ProgramFd(fd) = self.fd()?; - let mut map_ids = vec![0u32; self.0.nr_map_ids as usize]; - - bpf_prog_get_info_by_fd(fd.as_fd(), &mut map_ids)?; - - Ok(map_ids) - } - - /// The btf id for the program. - pub fn btf_id(&self) -> Option { - NonZeroU32::new(self.0.btf_id) - } - - /// The size in bytes of the program's translated eBPF bytecode, which is - /// the bytecode after it has been passed though the verifier where it was - /// possibly modified by the kernel. - pub fn size_translated(&self) -> u32 { - self.0.xlated_prog_len - } - - /// The size in bytes of the program's JIT-compiled machine code. - pub fn size_jitted(&self) -> u32 { - self.0.jited_prog_len - } - - /// How much memory in bytes has been allocated and locked for the program. - pub fn memory_locked(&self) -> Result { - get_fdinfo(self.fd()?.as_fd(), "memlock") - } - - /// The number of verified instructions in the program. - /// - /// This may be less than the total number of instructions in the compiled - /// program due to dead code elimination in the verifier. - pub fn verified_instruction_count(&self) -> u32 { - self.0.verified_insns - } - - /// The time the program was loaded. - pub fn loaded_at(&self) -> SystemTime { - boot_time() + Duration::from_nanos(self.0.load_time) - } - - /// Returns a file descriptor referencing the program. - /// - /// The returned file descriptor can be closed at any time and doing so does - /// not influence the life cycle of the program. - pub fn fd(&self) -> Result { - let Self(info) = self; - let fd = bpf_prog_get_fd_by_id(info.id)?; - Ok(ProgramFd(fd)) - } - - /// The accumulated time that the program has been actively running. - /// - /// This is not to be confused with the duration since the program was - /// first loaded on the host. - /// - /// Note this field is only updated for as long as - /// [`enable_stats`](crate::sys::enable_stats) is enabled - /// with [`Stats::RunTime`](crate::sys::Stats::RunTime). - pub fn run_time(&self) -> Duration { - Duration::from_nanos(self.0.run_time_ns) - } - - /// The accumulated execution count of the program. - /// - /// Note this field is only updated for as long as - /// [`enable_stats`](crate::sys::enable_stats) is enabled - /// with [`Stats::RunTime`](crate::sys::Stats::RunTime). - pub fn run_count(&self) -> u64 { - self.0.run_cnt - } - - /// Loads a program from a pinned path in bpffs. - pub fn from_pin>(path: P) -> Result { - use std::os::unix::ffi::OsStrExt as _; - - // TODO: avoid this unwrap by adding a new error variant. - let path_string = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap(); - let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError { - call: "BPF_OBJ_GET", - io_error, - })?; - - let info = bpf_prog_get_info_by_fd(fd.as_fd(), &mut [])?; - Ok(Self(info)) - } -} - -/// Returns an iterator over all loaded bpf programs. -/// -/// This differs from [`crate::Ebpf::programs`] since it will return all programs -/// listed on the host system and not only programs a specific [`crate::Ebpf`] instance. -/// -/// Uses kernel v4.13 features. -/// -/// # Example -/// ``` -/// # use aya::programs::loaded_programs; -/// -/// for p in loaded_programs() { -/// match p { -/// Ok(program) => println!("{}", String::from_utf8_lossy(program.name())), -/// Err(e) => println!("Error iterating programs: {:?}", e), -/// } -/// } -/// ``` -/// -/// # Errors -/// -/// Returns [`ProgramError::SyscallError`] if any of the syscalls required to either get -/// next program id, get the program fd, or the [`ProgramInfo`] fail. In cases where -/// iteration can't be performed, for example the caller does not have the necessary privileges, -/// a single item will be yielded containing the error that occurred. -pub fn loaded_programs() -> impl Iterator> { - iter_prog_ids() - .map(|id| { - let id = id?; - bpf_prog_get_fd_by_id(id) - }) - .map(|fd| { - let fd = fd?; - bpf_prog_get_info_by_fd(fd.as_fd(), &mut []) - }) - .map(|result| result.map(ProgramInfo).map_err(Into::into)) -} - // TODO(https://github.com/aya-rs/aya/issues/645): this API is currently used in tests. Stabilize // and remove doc(hidden). #[doc(hidden)] diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs index 058de40e..8d07b70c 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -8,7 +8,7 @@ use std::{ }; use assert_matches::assert_matches; -use libc::{E2BIG, ENOENT, ENOSPC}; +use libc::{ENOENT, ENOSPC}; use obj::{ btf::{BtfEnum64, Enum64}, generated::bpf_stats_type, @@ -32,7 +32,7 @@ use crate::{ }, sys::{syscall, SysResult, Syscall, SyscallError}, util::KernelVersion, - Btf, Pod, VerifierLogLevel, BPF_OBJ_NAME_LEN, + Btf, Pod, VerifierLogLevel, BPF_OBJ_NAME_LEN, FEATURES, }; pub(crate) fn bpf_create_map( @@ -105,6 +105,7 @@ pub(crate) fn bpf_pin_object(fd: BorrowedFd<'_>, path: &CStr) -> SysResult sys_bpf(bpf_cmd::BPF_OBJ_PIN, &mut attr) } +/// Introduced in kernel v4.4. pub(crate) fn bpf_get_object(path: &CStr) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_4 }; @@ -548,23 +549,14 @@ pub(crate) fn bpf_prog_get_info_by_fd( fd: BorrowedFd<'_>, map_ids: &mut [u32], ) -> Result { - // Attempt syscall with the map info filled. - let mut info = bpf_obj_get_info_by_fd(fd, |info: &mut bpf_prog_info| { - info.nr_map_ids = map_ids.len() as _; - info.map_ids = map_ids.as_mut_ptr() as _; - }); - // 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. - if let Err(err) = &info { - if let Some(errno) = &err.io_error.raw_os_error() { - if errno == &E2BIG { - info = bpf_obj_get_info_by_fd(fd, |_| {}); - } + bpf_obj_get_info_by_fd(fd, |info: &mut bpf_prog_info| { + if FEATURES.prog_info_map_ids() { + info.nr_map_ids = map_ids.len() as _; + info.map_ids = map_ids.as_mut_ptr() as _; } - } - - info + }) } /// Introduced in kernel v4.13. @@ -711,6 +703,62 @@ 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 }; diff --git a/test/integration-test/src/tests/info.rs b/test/integration-test/src/tests/info.rs index e44d720d..16770136 100644 --- a/test/integration-test/src/tests/info.rs +++ b/test/integration-test/src/tests/info.rs @@ -1,82 +1,229 @@ //! Tests the Info API. -use std::{fs, time::SystemTime}; +use std::{fs, panic, path::Path, time::SystemTime}; use aya::{ maps::{loaded_maps, MapError}, - programs::{loaded_programs, ProgramError, SocketFilter}, + programs::{loaded_programs, ProgramError, ProgramType, SocketFilter, TracePoint}, + sys::enable_stats, util::KernelVersion, Ebpf, }; -use aya_obj::generated::{bpf_map_type, bpf_prog_type}; +use aya_obj::generated::bpf_map_type; use libc::EINVAL; use crate::utils::{kernel_assert, kernel_assert_eq}; const BPF_JIT_ENABLE: &str = "/proc/sys/net/core/bpf_jit_enable"; +const BPF_STATS_ENABLED: &str = "/proc/sys/kernel/bpf_stats_enabled"; #[test] -fn list_loaded_programs() { - // Kernels below v4.15 have been observed to have `bpf_jit_enable` disabled by default. - let jit_enabled = enable_jit(); - +fn test_loaded_programs() { // Load a program. // 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(); prog.load().unwrap(); + let test_prog = prog.info().unwrap(); - // Ensure the `loaded_programs()` api does not panic and grab the last loaded program in the - // iter, which should be our test program. - let prog = match loaded_programs().last().unwrap() { - Ok(prog) => prog, - Err(err) => { - if let ProgramError::SyscallError(err) = &err { - // Skip entire test since feature not available - if err - .io_error - .raw_os_error() - .is_some_and(|errno| errno == EINVAL) - { - eprintln!("ignoring test completely as `loaded_programs()` is not available on the host"); - return; - } + // Ensure loaded program doesn't panic + let mut programs = loaded_programs().peekable(); + if let Err(err) = programs.peek().unwrap() { + if let ProgramError::SyscallError(err) = &err { + // Skip entire test since feature not available + if err + .io_error + .raw_os_error() + .is_some_and(|errno| errno == EINVAL) + { + eprintln!( + "ignoring test completely as `loaded_programs()` is not available on the host" + ); + return; } - panic!("{err}"); } - }; + panic!("{err}"); + } + + // Loaded programs should contain our test program + let mut programs = programs.filter_map(|prog| prog.ok()); + kernel_assert!( + programs.any(|prog| prog.id() == test_prog.id()), + KernelVersion::new(4, 13, 0) + ); +} + +#[test] +fn test_program_info() { + // 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. + let prev_panic = panic::take_hook(); + panic::set_hook(Box::new(move |panic_info| { + if !previously_enabled { + disable_sysctl_param(BPF_JIT_ENABLE); + } + prev_panic(panic_info); + })); + let jit_enabled = previously_enabled || enable_sysctl_param(BPF_JIT_ENABLE); + + let mut bpf = Ebpf::load(crate::SIMPLE_PROG).unwrap(); + let prog: &mut SocketFilter = bpf.program_mut("simple_prog").unwrap().try_into().unwrap(); + prog.load().unwrap(); + let test_prog = prog.info().unwrap(); // Test `bpf_prog_info` fields. kernel_assert_eq!( - bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32, - prog.program_type(), + ProgramType::SocketFilter, + test_prog.program_type().unwrap_or(ProgramType::Unspecified), KernelVersion::new(4, 13, 0), ); - kernel_assert!(prog.id() > 0, KernelVersion::new(4, 13, 0)); - kernel_assert!(prog.tag() > 0, KernelVersion::new(4, 13, 0)); + kernel_assert!(test_prog.id().is_some(), KernelVersion::new(4, 13, 0)); + kernel_assert!(test_prog.tag().is_some(), KernelVersion::new(4, 13, 0)); if jit_enabled { - kernel_assert!(prog.size_jitted() > 0, KernelVersion::new(4, 13, 0)); + kernel_assert!( + test_prog.size_jitted().is_some(), + KernelVersion::new(4, 13, 0), + ); } - kernel_assert!(prog.size_translated() > 0, KernelVersion::new(4, 13, 0)); - let uptime = SystemTime::now().duration_since(prog.loaded_at()).unwrap(); - kernel_assert!(uptime.as_nanos() > 0, KernelVersion::new(4, 15, 0)); - let maps = prog.map_ids().unwrap(); - kernel_assert!(maps.is_empty(), KernelVersion::new(4, 15, 0)); - let name = prog.name_as_str().unwrap(); - kernel_assert_eq!("simple_prog", name, KernelVersion::new(4, 15, 0)); - kernel_assert!(prog.gpl_compatible(), KernelVersion::new(4, 18, 0)); kernel_assert!( - prog.verified_instruction_count() > 0, - KernelVersion::new(5, 16, 0) + test_prog.size_translated().is_some(), + KernelVersion::new(4, 13, 0), + ); + kernel_assert!( + test_prog.loaded_at().is_some(), + KernelVersion::new(4, 15, 0), + ); + kernel_assert!( + test_prog.created_by_uid().is_some_and(|uid| uid == 0), + KernelVersion::new(4, 15, 0), + ); + let maps = test_prog.map_ids().unwrap(); + kernel_assert!( + maps.is_some_and(|ids| ids.is_empty()), + KernelVersion::new(4, 15, 0), + ); + kernel_assert!( + test_prog + .name_as_str() + .is_some_and(|name| name == "simple_prog"), + KernelVersion::new(4, 15, 0), + ); + kernel_assert!( + test_prog.gpl_compatible().is_some_and(|gpl| gpl), + KernelVersion::new(4, 18, 0), + ); + kernel_assert!( + test_prog.verified_instruction_count().is_some(), + KernelVersion::new(5, 16, 0), ); // We can't reliably test these fields since `0` can be interpreted as the actual value or // unavailable. - prog.btf_id(); + test_prog.btf_id(); // Ensure rest of the fields do not panic. - prog.memory_locked().unwrap(); - prog.fd().unwrap(); + test_prog.memory_locked().unwrap(); + test_prog.fd().unwrap(); + + // Restore to previous state + if !previously_enabled { + disable_sysctl_param(BPF_JIT_ENABLE); + } +} + +#[test] +fn test_loaded_at() { + let mut bpf: Ebpf = Ebpf::load(crate::SIMPLE_PROG).unwrap(); + let prog: &mut SocketFilter = bpf.program_mut("simple_prog").unwrap().try_into().unwrap(); + + // SystemTime is not monotonic, which can cause this test to flake. We don't expect the clock + // timestamp to continuously jump around, so we add some retries. If the test is ever correct, + // we know that the value returned by loaded_at() was reasonable relative to SystemTime::now(). + let mut failures = Vec::new(); + for _ in 0..5 { + let t1 = SystemTime::now(); + prog.load().unwrap(); + + let t2 = SystemTime::now(); + 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"); + return; + } + }; + prog.unload().unwrap(); + + let range = t1..t2; + if range.contains(&loaded_at) { + failures.clear(); + break; + } + failures.push(LoadedAtRange(loaded_at, range)); + } + assert!( + failures.is_empty(), + "loaded_at was not in range: {failures:?}", + ); + + struct LoadedAtRange(SystemTime, std::ops::Range); + impl std::fmt::Debug for LoadedAtRange { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self(loaded_at, range) = self; + write!(f, "{range:?}.contains({loaded_at:?})") + } + } +} + +#[test] +fn test_prog_stats() { + // Test depends on whether trace point exists. + 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" + ); + return; + } + + let stats_fd = enable_stats(aya::sys::Stats::RunTime).ok(); + // Restore to previous state when panic occurs. + let previously_enabled = is_sysctl_enabled(BPF_STATS_ENABLED); + let prev_panic = panic::take_hook(); + panic::set_hook(Box::new(move |panic_info| { + if !previously_enabled { + disable_sysctl_param(BPF_STATS_ENABLED); + } + prev_panic(panic_info); + })); + + 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"); + return; + } + + let mut bpf = Ebpf::load(crate::TEST).unwrap(); + let prog: &mut TracePoint = bpf + .program_mut("test_tracepoint") + .unwrap() + .try_into() + .unwrap(); + prog.load().unwrap(); + prog.attach("syscalls", "sys_enter_bpf").unwrap(); + let test_prog = prog.info().unwrap(); + + kernel_assert!( + test_prog.run_time().as_nanos() > 0, + KernelVersion::new(5, 1, 0) + ); + kernel_assert!(test_prog.run_count() > 0, KernelVersion::new(5, 1, 0)); + + // Restore to previous state + if !previously_enabled { + disable_sysctl_param(BPF_STATS_ENABLED); + } } #[test] @@ -108,19 +255,22 @@ fn list_loaded_maps() { // There's not a good way to extract our maps of interest with load order being // non-deterministic. Since we are trying to be more considerate of older kernels, we should // only rely on v4.13 feats. - // Expected sort order should be: `BAR`, `aya_global` (if ran local), `FOO` + // Expected sort order should be: `BAR`, `aya_global` (if avail), `FOO` maps.sort_unstable_by_key(|m| (m.map_type(), m.id())); // Ensure program has the 2 maps. if let Ok(info) = prog.info() { let map_ids = info.map_ids().unwrap(); - kernel_assert_eq!(2, map_ids.len(), KernelVersion::new(4, 15, 0)); + kernel_assert!(map_ids.is_some(), KernelVersion::new(4, 15, 0)); - for id in map_ids.iter() { - assert!( - maps.iter().any(|m| m.id() == *id), - "expected `loaded_maps()` to have `map_ids` from program" - ); + if let Some(map_ids) = map_ids { + assert_eq!(2, map_ids.len()); + for id in map_ids.iter() { + assert!( + maps.iter().any(|m| m.id() == id.get()), + "expected `loaded_maps()` to have `map_ids` from program" + ); + } } } @@ -164,18 +314,20 @@ fn list_loaded_maps() { array.fd().unwrap(); } -/// Enable program to be JIT-compiled if not already enabled. -fn enable_jit() -> bool { - match fs::read_to_string(BPF_JIT_ENABLE) { - Ok(contents) => { - if contents.chars().next().is_some_and(|c| c == '0') { - let failed = fs::write(BPF_JIT_ENABLE, b"1").is_err(); - if failed { - return false; - } - } - true - } +/// Whether sysctl parameter is enabled in the `/proc` file. +fn is_sysctl_enabled(path: &str) -> bool { + match fs::read_to_string(path) { + Ok(contents) => contents.chars().next().is_some_and(|c| c == '1'), Err(_) => false, } } + +/// Enable sysctl parameter through procfs. +fn enable_sysctl_param(path: &str) -> bool { + fs::write(path, b"1").is_ok() +} + +/// Disable sysctl parameter through procfs. +fn disable_sysctl_param(path: &str) -> bool { + fs::write(path, b"0").is_ok() +} diff --git a/test/integration-test/src/tests/load.rs b/test/integration-test/src/tests/load.rs index 8ff13757..60025092 100644 --- a/test/integration-test/src/tests/load.rs +++ b/test/integration-test/src/tests/load.rs @@ -1,10 +1,4 @@ -use std::{ - convert::TryInto as _, - fs::remove_file, - path::Path, - thread, - time::{Duration, SystemTime}, -}; +use std::{convert::TryInto as _, fs::remove_file, path::Path, thread, time::Duration}; use aya::{ maps::Array, @@ -144,7 +138,7 @@ fn poll_loaded_program_id(name: &str) -> impl Iterator> + '_ // program in the middle of a `loaded_programs()` call. loaded_programs() .filter_map(|prog| prog.ok()) - .find_map(|prog| (prog.name() == name.as_bytes()).then(|| prog.id())) + .find_map(|prog| (prog.name() == name.as_bytes()).then(|| prog.id().unwrap().get())) }) } @@ -221,42 +215,6 @@ fn unload_xdp() { assert_unloaded("pass"); } -#[test] -fn test_loaded_at() { - let mut bpf = Ebpf::load(crate::TEST).unwrap(); - let prog: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); - - // SystemTime is not monotonic, which can cause this test to flake. We don't expect the clock - // timestamp to continuously jump around, so we add some retries. If the test is ever correct, - // we know that the value returned by loaded_at() was reasonable relative to SystemTime::now(). - let mut failures = Vec::new(); - for _ in 0..5 { - let t1 = SystemTime::now(); - prog.load().unwrap(); - let t2 = SystemTime::now(); - let loaded_at = prog.info().unwrap().loaded_at(); - prog.unload().unwrap(); - let range = t1..t2; - if range.contains(&loaded_at) { - failures.clear(); - break; - } - failures.push(LoadedAtRange(loaded_at, range)); - } - assert!( - failures.is_empty(), - "loaded_at was not in range: {failures:?}", - ); - - struct LoadedAtRange(SystemTime, std::ops::Range); - impl std::fmt::Debug for LoadedAtRange { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let Self(loaded_at, range) = self; - write!(f, "{range:?}.contains({loaded_at:?})") - } - } -} - #[test] fn unload_kprobe() { let mut bpf = Ebpf::load(crate::TEST).unwrap(); diff --git a/xtask/public-api/aya-obj.txt b/xtask/public-api/aya-obj.txt index 5014c91c..f46d106f 100644 --- a/xtask/public-api/aya-obj.txt +++ b/xtask/public-api/aya-obj.txt @@ -7109,6 +7109,8 @@ 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 @@ -7967,6 +7969,8 @@ 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 diff --git a/xtask/public-api/aya.txt b/xtask/public-api/aya.txt index 0f46ef74..b26f09d0 100644 --- a/xtask/public-api/aya.txt +++ b/xtask/public-api/aya.txt @@ -6644,7 +6644,7 @@ impl aya::programs::Program pub fn aya::programs::Program::fd(&self) -> core::result::Result<&aya::programs::ProgramFd, aya::programs::ProgramError> pub fn aya::programs::Program::info(&self) -> core::result::Result pub fn aya::programs::Program::pin>(&mut self, path: P) -> core::result::Result<(), aya::pin::PinError> -pub fn aya::programs::Program::prog_type(&self) -> aya_obj::generated::linux_bindings_x86_64::bpf_prog_type +pub fn aya::programs::Program::prog_type(&self) -> aya::programs::ProgramType pub fn aya::programs::Program::unload(self) -> core::result::Result<(), aya::programs::ProgramError> impl core::fmt::Debug for aya::programs::Program pub fn aya::programs::Program::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result @@ -6892,6 +6892,79 @@ impl core::borrow::BorrowMut for aya::programs::ProgramError where T: core pub fn aya::programs::ProgramError::borrow_mut(&mut self) -> &mut T impl core::convert::From for aya::programs::ProgramError pub fn aya::programs::ProgramError::from(t: T) -> T +#[non_exhaustive] pub enum aya::programs::ProgramType +pub aya::programs::ProgramType::CgroupDevice = 15 +pub aya::programs::ProgramType::CgroupSkb = 8 +pub aya::programs::ProgramType::CgroupSock = 9 +pub aya::programs::ProgramType::CgroupSockAddr = 18 +pub aya::programs::ProgramType::CgroupSockopt = 25 +pub aya::programs::ProgramType::CgroupSysctl = 23 +pub aya::programs::ProgramType::Extension = 28 +pub aya::programs::ProgramType::FlowDissector = 22 +pub aya::programs::ProgramType::KProbe = 2 +pub aya::programs::ProgramType::LircMode2 = 20 +pub aya::programs::ProgramType::Lsm = 29 +pub aya::programs::ProgramType::LwtInput = 10 +pub aya::programs::ProgramType::LwtOutput = 11 +pub aya::programs::ProgramType::LwtSeg6local = 19 +pub aya::programs::ProgramType::LwtXmit = 12 +pub aya::programs::ProgramType::Netfilter = 32 +pub aya::programs::ProgramType::PerfEvent = 7 +pub aya::programs::ProgramType::RawTracePoint = 17 +pub aya::programs::ProgramType::RawTracePointWritable = 24 +pub aya::programs::ProgramType::SchedAction = 4 +pub aya::programs::ProgramType::SchedClassifier = 3 +pub aya::programs::ProgramType::SkLookup = 30 +pub aya::programs::ProgramType::SkMsg = 16 +pub aya::programs::ProgramType::SkReuseport = 21 +pub aya::programs::ProgramType::SkSkb = 14 +pub aya::programs::ProgramType::SockOps = 13 +pub aya::programs::ProgramType::SocketFilter = 1 +pub aya::programs::ProgramType::StructOps = 27 +pub aya::programs::ProgramType::Syscall = 31 +pub aya::programs::ProgramType::TracePoint = 5 +pub aya::programs::ProgramType::Tracing = 26 +pub aya::programs::ProgramType::Unspecified = 0 +pub aya::programs::ProgramType::Xdp = 6 +impl core::clone::Clone for aya::programs::ProgramType +pub fn aya::programs::ProgramType::clone(&self) -> aya::programs::ProgramType +impl core::cmp::PartialEq for aya::programs::ProgramType +pub fn aya::programs::ProgramType::eq(&self, other: &aya::programs::ProgramType) -> bool +impl core::convert::TryFrom for aya::programs::ProgramType +pub type aya::programs::ProgramType::Error = aya::programs::ProgramError +pub fn aya::programs::ProgramType::try_from(prog_type: aya_obj::generated::linux_bindings_x86_64::bpf_prog_type) -> core::result::Result +impl core::fmt::Debug for aya::programs::ProgramType +pub fn aya::programs::ProgramType::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::marker::Copy for aya::programs::ProgramType +impl core::marker::StructuralPartialEq for aya::programs::ProgramType +impl core::marker::Freeze for aya::programs::ProgramType +impl core::marker::Send for aya::programs::ProgramType +impl core::marker::Sync for aya::programs::ProgramType +impl core::marker::Unpin for aya::programs::ProgramType +impl core::panic::unwind_safe::RefUnwindSafe for aya::programs::ProgramType +impl core::panic::unwind_safe::UnwindSafe for aya::programs::ProgramType +impl core::convert::Into for aya::programs::ProgramType where U: core::convert::From +pub fn aya::programs::ProgramType::into(self) -> U +impl core::convert::TryFrom for aya::programs::ProgramType where U: core::convert::Into +pub type aya::programs::ProgramType::Error = core::convert::Infallible +pub fn aya::programs::ProgramType::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for aya::programs::ProgramType where U: core::convert::TryFrom +pub type aya::programs::ProgramType::Error = >::Error +pub fn aya::programs::ProgramType::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for aya::programs::ProgramType where T: core::clone::Clone +pub type aya::programs::ProgramType::Owned = T +pub fn aya::programs::ProgramType::clone_into(&self, target: &mut T) +pub fn aya::programs::ProgramType::to_owned(&self) -> T +impl core::any::Any for aya::programs::ProgramType where T: 'static + core::marker::Sized +pub fn aya::programs::ProgramType::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for aya::programs::ProgramType where T: core::marker::Sized +pub fn aya::programs::ProgramType::borrow(&self) -> &T +impl core::borrow::BorrowMut for aya::programs::ProgramType where T: core::marker::Sized +pub fn aya::programs::ProgramType::borrow_mut(&mut self) -> &mut T +impl core::clone::CloneToUninit for aya::programs::ProgramType where T: core::clone::Clone +pub unsafe fn aya::programs::ProgramType::clone_to_uninit(&self, dst: *mut T) +impl core::convert::From for aya::programs::ProgramType +pub fn aya::programs::ProgramType::from(t: T) -> T pub enum aya::programs::SamplePolicy pub aya::programs::SamplePolicy::Frequency(u64) pub aya::programs::SamplePolicy::Period(u64) @@ -7913,22 +7986,23 @@ pub fn aya::programs::ProgramFd::from(t: T) -> T pub struct aya::programs::ProgramInfo(_) impl aya::programs::ProgramInfo pub fn aya::programs::ProgramInfo::btf_id(&self) -> core::option::Option +pub fn aya::programs::ProgramInfo::created_by_uid(&self) -> core::option::Option pub fn aya::programs::ProgramInfo::fd(&self) -> core::result::Result pub fn aya::programs::ProgramInfo::from_pin>(path: P) -> core::result::Result -pub fn aya::programs::ProgramInfo::gpl_compatible(&self) -> bool -pub fn aya::programs::ProgramInfo::id(&self) -> u32 -pub fn aya::programs::ProgramInfo::loaded_at(&self) -> std::time::SystemTime -pub fn aya::programs::ProgramInfo::map_ids(&self) -> core::result::Result, aya::programs::ProgramError> +pub fn aya::programs::ProgramInfo::gpl_compatible(&self) -> core::option::Option +pub fn aya::programs::ProgramInfo::id(&self) -> core::option::Option +pub fn aya::programs::ProgramInfo::loaded_at(&self) -> core::option::Option +pub fn aya::programs::ProgramInfo::map_ids(&self) -> core::result::Result>, aya::programs::ProgramError> pub fn aya::programs::ProgramInfo::memory_locked(&self) -> core::result::Result pub fn aya::programs::ProgramInfo::name(&self) -> &[u8] pub fn aya::programs::ProgramInfo::name_as_str(&self) -> core::option::Option<&str> -pub fn aya::programs::ProgramInfo::program_type(&self) -> u32 +pub fn aya::programs::ProgramInfo::program_type(&self) -> core::result::Result pub fn aya::programs::ProgramInfo::run_count(&self) -> u64 pub fn aya::programs::ProgramInfo::run_time(&self) -> core::time::Duration -pub fn aya::programs::ProgramInfo::size_jitted(&self) -> u32 -pub fn aya::programs::ProgramInfo::size_translated(&self) -> u32 -pub fn aya::programs::ProgramInfo::tag(&self) -> u64 -pub fn aya::programs::ProgramInfo::verified_instruction_count(&self) -> u32 +pub fn aya::programs::ProgramInfo::size_jitted(&self) -> core::option::Option +pub fn aya::programs::ProgramInfo::size_translated(&self) -> core::option::Option +pub fn aya::programs::ProgramInfo::tag(&self) -> core::option::Option +pub fn aya::programs::ProgramInfo::verified_instruction_count(&self) -> core::option::Option impl core::fmt::Debug for aya::programs::ProgramInfo pub fn aya::programs::ProgramInfo::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result impl core::marker::Freeze for aya::programs::ProgramInfo @@ -9093,7 +9167,6 @@ impl aya::Pod for u8 impl aya::Pod for aya::maps::lpm_trie::Key impl aya::Pod for [T; N] pub fn aya::features() -> &'static aya_obj::obj::Features -pub fn aya::loaded_programs() -> impl core::iter::traits::iterator::Iterator> pub type aya::Bpf = aya::Ebpf pub type aya::BpfError = aya::EbpfError pub type aya::BpfLoader<'a> = aya::EbpfLoader<'a> From fbb09304a2de0d8baf7ea20c9727fcd2e4fb7f41 Mon Sep 17 00:00:00 2001 From: tyrone-wu Date: Fri, 2 Aug 2024 22:25:04 +0000 Subject: [PATCH 4/4] aya,int-test: revamp MapInfo be more friendly with older kernels Adds detection for whether a field is available in `MapInfo`: - For `map_type()`, we treturn new enum `MapType` instead of the integer representation. - For fields that can't be zero, we return `Option` type. - For `name_as_str()`, it now uses the feature probe `bpf_name()` to detect if field is available. Although the feature probe checks for program name, it can also be used for map name since they were both introduced in the same commit. --- aya-log/src/lib.rs | 2 +- aya-obj/src/obj.rs | 5 +- aya/src/maps/info.rs | 413 ++++++++++++++++++++++++ aya/src/maps/mod.rs | 133 +------- test/integration-test/src/tests/info.rs | 113 ++++--- xtask/public-api/aya.txt | 84 ++++- 6 files changed, 577 insertions(+), 173 deletions(-) create mode 100644 aya/src/maps/info.rs diff --git a/aya-log/src/lib.rs b/aya-log/src/lib.rs index 6a2ca27a..421ce2a0 100644 --- a/aya-log/src/lib.rs +++ b/aya-log/src/lib.rs @@ -150,7 +150,7 @@ impl EbpfLogger { None => false, }) .ok_or(Error::MapNotFound)?; - let map = MapData::from_id(map.id()).map_err(Error::MapError)?; + let map = MapData::from_id(map.id().unwrap().get()).map_err(Error::MapError)?; Self::read_logs_async(Map::PerfEventArray(map), logger)?; diff --git a/aya-obj/src/obj.rs b/aya-obj/src/obj.rs index 4e203fe1..e4be87da 100644 --- a/aya-obj/src/obj.rs +++ b/aya-obj/src/obj.rs @@ -81,7 +81,10 @@ impl Features { } } - /// Returns whether BPF program names are supported. + /// Returns whether BPF program names and map names are supported. + /// + /// Although the feature probe performs the check for program name, we can use this to also + /// detect if map name is supported since they were both introduced in the same commit. pub fn bpf_name(&self) -> bool { self.bpf_name } diff --git a/aya/src/maps/info.rs b/aya/src/maps/info.rs new file mode 100644 index 00000000..1d15dc6f --- /dev/null +++ b/aya/src/maps/info.rs @@ -0,0 +1,413 @@ +//! Metadata information about an eBPF map. + +use std::{ + ffi::CString, + num::NonZeroU32, + os::fd::{AsFd as _, BorrowedFd}, + path::Path, +}; + +use aya_obj::generated::{bpf_map_info, bpf_map_type}; + +use super::{MapError, MapFd}; +use crate::{ + sys::{ + bpf_get_object, bpf_map_get_fd_by_id, bpf_map_get_info_by_fd, iter_map_ids, SyscallError, + }, + util::bytes_of_bpf_name, + FEATURES, +}; + +/// Provides Provides metadata information about a loaded eBPF map. +#[doc(alias = "bpf_map_info")] +#[derive(Debug)] +pub struct MapInfo(pub(crate) bpf_map_info); + +impl MapInfo { + pub(crate) fn new_from_fd(fd: BorrowedFd<'_>) -> Result { + let info = bpf_map_get_info_by_fd(fd.as_fd())?; + Ok(Self(info)) + } + + /// Loads map info from a map ID. + /// + /// Uses kernel v4.13 features. + pub fn from_id(id: u32) -> Result { + bpf_map_get_fd_by_id(id) + .map_err(MapError::from) + .and_then(|fd| Self::new_from_fd(fd.as_fd())) + } + + /// The type of map. + /// + /// Introduced in kernel v4.13. + pub fn map_type(&self) -> Result { + bpf_map_type::try_from(self.0.type_) + .unwrap_or(bpf_map_type::__MAX_BPF_MAP_TYPE) + .try_into() + } + + /// The unique ID for this map. + /// + /// `None` is returned if the field is not available. + /// + /// Introduced in kernel v4.13. + pub fn id(&self) -> Option { + NonZeroU32::new(self.0.id) + } + + /// The key size for this map in bytes. + /// + /// `None` is returned if the field is not available. + /// + /// Introduced in kernel v4.13. + pub fn key_size(&self) -> Option { + NonZeroU32::new(self.0.key_size) + } + + /// The value size for this map in bytes. + /// + /// `None` is returned if the field is not available. + /// + /// Introduced in kernel v4.13. + pub fn value_size(&self) -> Option { + NonZeroU32::new(self.0.value_size) + } + + /// The maximum number of entries in this map. + /// + /// `None` is returned if the field is not available. + /// + /// Introduced in kernel v4.13. + pub fn max_entries(&self) -> Option { + NonZeroU32::new(self.0.max_entries) + } + + /// The flags used in loading this map. + /// + /// Introduced in kernel v4.13. + pub fn map_flags(&self) -> u32 { + self.0.map_flags + } + + /// The name of the map, limited to 16 bytes. + /// + /// Introduced in kernel v4.15. + pub fn name(&self) -> &[u8] { + bytes_of_bpf_name(&self.0.name) + } + + /// The name of the map as a &str. + /// + /// `None` is returned if the name was not valid unicode or if field is not available. + /// + /// Introduced in kernel v4.15. + pub fn name_as_str(&self) -> Option<&str> { + let name = std::str::from_utf8(self.name()).ok(); + if let Some(name_str) = name { + // Char in program name was introduced in the same commit as map name + if FEATURES.bpf_name() || !name_str.is_empty() { + return name; + } + } + None + } + + /// Returns a file descriptor referencing the map. + /// + /// The returned file descriptor can be closed at any time and doing so does + /// not influence the life cycle of the map. + /// + /// Uses kernel v4.13 features. + pub fn fd(&self) -> Result { + let Self(info) = self; + let fd = bpf_map_get_fd_by_id(info.id)?; + Ok(MapFd::from_fd(fd)) + } + + /// Loads a map from a pinned path in bpffs. + /// + /// Uses kernel v4.4 and v4.13 features. + pub fn from_pin>(path: P) -> Result { + use std::os::unix::ffi::OsStrExt as _; + + // TODO: avoid this unwrap by adding a new error variant. + let path_string = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap(); + let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError { + call: "BPF_OBJ_GET", + io_error, + })?; + + Self::new_from_fd(fd.as_fd()) + } +} + +/// Returns an iterator of [`MapInfo`] over all eBPF maps on the host. +/// +/// Unlike [`Ebpf::maps`](crate::Ebpf::maps), this includes all maps on the host system, not +/// just those tied to a specific [`crate::Ebpf`] instance. +/// +/// Uses kernel v4.13 features. +/// +/// # Example +/// ``` +/// # use aya::maps::loaded_maps; +/// # +/// for m in loaded_maps() { +/// match m { +/// Ok(map) => println!("{:?}", map.name_as_str()), +/// Err(e) => println!("Error iterating maps: {:?}", e), +/// } +/// } +/// ``` +/// +/// # Errors +/// +/// Returns [`MapError::SyscallError`] if any of the syscalls required to either get +/// next map id, get the map fd, or the [`MapInfo`] fail. +/// +/// In cases where iteration can't be performed, for example the caller does not have the necessary +/// privileges, a single item will be yielded containing the error that occurred. +pub fn loaded_maps() -> impl Iterator> { + iter_map_ids().map(|id| { + let id = id?; + MapInfo::from_id(id) + }) +} + +/// The type of eBPF map. +#[non_exhaustive] +#[doc(alias = "bpf_map_type")] +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum MapType { + /// An unspecified program type. + Unspecified = bpf_map_type::BPF_MAP_TYPE_UNSPEC as isize, + /// A Hash map type. See [`HashMap`](super::hash_map::HashMap) for the map implementation. + /// + /// Introduced in kernel v3.19. + #[doc(alias = "BPF_MAP_TYPE_HASH")] + Hash = bpf_map_type::BPF_MAP_TYPE_HASH as isize, + /// An Array map type. See [`Array`](super::array::Array) for the map implementation. + /// + /// Introduced in kernel v3.19. + #[doc(alias = "BPF_MAP_TYPE_ARRAY")] + Array = bpf_map_type::BPF_MAP_TYPE_ARRAY as isize, + /// A Program Array map type. See [`ProgramArray`](super::array::ProgramArray) for the map + /// implementation. + /// + /// Introduced in kernel v4.2. + #[doc(alias = "BPF_MAP_TYPE_PROG_ARRAY")] + ProgramArray = bpf_map_type::BPF_MAP_TYPE_PROG_ARRAY as isize, + /// A Perf Event Array map type. See [`PerfEventArray`](super::perf::PerfEventArray) and + /// [`AsyncPerfEventArray`](super::perf::AsyncPerfEventArray) for the map implementations. + /// + /// Introduced in kernel v4.3. + #[doc(alias = "BPF_MAP_TYPE_PERF_EVENT_ARRAY")] + PerfEventArray = bpf_map_type::BPF_MAP_TYPE_PERF_EVENT_ARRAY as isize, + /// A per-CPU Hash map type. See [`PerCpuHashMap`](super::hash_map::PerCpuHashMap) for the map + /// implementation. + /// + /// Introduced in kernel v4.6. + #[doc(alias = "BPF_MAP_TYPE_PERCPU_HASH")] + PerCpuHash = bpf_map_type::BPF_MAP_TYPE_PERCPU_HASH as isize, + /// A per-CPU Array map type. See [`PerCpuArray`](super::array::PerCpuArray) for the map + /// implementation. + /// + /// Introduced in kernel v4.6. + #[doc(alias = "BPF_MAP_TYPE_PERCPU_ARRAY")] + PerCpuArray = bpf_map_type::BPF_MAP_TYPE_PERCPU_ARRAY as isize, + /// A Stack Trace map type. See [`StackTraceMap`](super::stack_trace::StackTraceMap) for the map + /// implementation. + /// + /// Introduced in kernel v4.6. + #[doc(alias = "BPF_MAP_TYPE_STACK_TRACE")] + StackTrace = bpf_map_type::BPF_MAP_TYPE_STACK_TRACE as isize, + /// A cGroup Array map type. + /// + /// Introduced in kernel v4.8. + #[doc(alias = "BPF_MAP_TYPE_CGROUP_ARRAY")] + CgroupArray = bpf_map_type::BPF_MAP_TYPE_CGROUP_ARRAY as isize, + /// A Least Recently Used (LRU) Hash map type. See [`HashMap`](super::hash_map::HashMap) for + /// the map implementation. + /// + /// Introduced in kernel v4.10. + #[doc(alias = "BPF_MAP_TYPE_LRU_HASH")] + LruHash = bpf_map_type::BPF_MAP_TYPE_LRU_HASH as isize, + /// A Least Recently Used (LRU) per-CPU Hash map type. See + /// [`PerCpuHashMap`](super::hash_map::PerCpuHashMap) for the map implementation. + /// + /// Introduced in kernel v4.10. + #[doc(alias = "BPF_MAP_TYPE_LRU_PERCPU_HASH")] + LruPerCpuHash = bpf_map_type::BPF_MAP_TYPE_LRU_PERCPU_HASH as isize, + /// A Longest Prefix Match (LPM) Trie map type. See [`LpmTrie`](super::lpm_trie::LpmTrie) for + /// the map implementation. + /// + /// Introduced in kernel v4.11. + #[doc(alias = "BPF_MAP_TYPE_LPM_TRIE")] + LpmTrie = bpf_map_type::BPF_MAP_TYPE_LPM_TRIE as isize, + /// An Array of Maps map type. + /// + /// Introduced in kernel v4.12. + #[doc(alias = "BPF_MAP_TYPE_ARRAY_OF_MAPS")] + ArrayOfMaps = bpf_map_type::BPF_MAP_TYPE_ARRAY_OF_MAPS as isize, + /// A Hash of Maps map type. + /// + /// Introduced in kernel v4.12. + #[doc(alias = "BPF_MAP_TYPE_HASH_OF_MAPS")] + HashOfMaps = bpf_map_type::BPF_MAP_TYPE_HASH_OF_MAPS as isize, + /// A Device Map type. See [`DevMap`](super::xdp::DevMap) for the map implementation. + /// + /// Introduced in kernel v4.14. + #[doc(alias = "BPF_MAP_TYPE_DEVMAP")] + DevMap = bpf_map_type::BPF_MAP_TYPE_DEVMAP as isize, + /// A Socket Map type. See [`SockMap`](super::sock::SockMap) for the map implementation. + /// + /// Introduced in kernel v4.14. + #[doc(alias = "BPF_MAP_TYPE_SOCKMAP")] + SockMap = bpf_map_type::BPF_MAP_TYPE_SOCKMAP as isize, + /// A CPU Map type. See [`CpuMap`](super::xdp::CpuMap) for the map implementation. + /// + /// Introduced in kernel v4.15. + #[doc(alias = "BPF_MAP_TYPE_CPUMAP")] + CpuMap = bpf_map_type::BPF_MAP_TYPE_CPUMAP as isize, + /// An XDP Socket Map type. See [`XskMap`](super::xdp::XskMap) for the map implementation. + /// + /// Introduced in kernel v4.18. + #[doc(alias = "BPF_MAP_TYPE_XSKMAP")] + XskMap = bpf_map_type::BPF_MAP_TYPE_XSKMAP as isize, + /// A Socket Hash map type. See [`SockHash`](super::sock::SockHash) for the map implementation. + /// + /// Introduced in kernel v4.18. + #[doc(alias = "BPF_MAP_TYPE_SOCKHASH")] + SockHash = bpf_map_type::BPF_MAP_TYPE_SOCKHASH as isize, + /// A cGroup Storage map type. + /// + /// Introduced in kernel v4.19. + // #[deprecated] + #[doc(alias = "BPF_MAP_TYPE_CGROUP_STORAGE")] + #[doc(alias = "BPF_MAP_TYPE_CGROUP_STORAGE_DEPRECATED")] + CgroupStorage = bpf_map_type::BPF_MAP_TYPE_CGROUP_STORAGE as isize, + /// A Reuseport Socket Array map type. + /// + /// Introduced in kernel v4.19. + #[doc(alias = "BPF_MAP_TYPE_REUSEPORT_SOCKARRAY")] + ReuseportSockArray = bpf_map_type::BPF_MAP_TYPE_REUSEPORT_SOCKARRAY as isize, + /// A per-CPU cGroup Storage map type. + /// + /// Introduced in kernel v4.20. + #[doc(alias = "BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE")] + #[doc(alias = "BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE_DEPRECATED")] + PerCpuCgroupStorage = bpf_map_type::BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE as isize, + /// A Queue map type. See [`Queue`](super::queue::Queue) for the map implementation. + /// + /// Introduced in kernel v4.20. + #[doc(alias = "BPF_MAP_TYPE_QUEUE")] + Queue = bpf_map_type::BPF_MAP_TYPE_QUEUE as isize, + /// A Stack map type. See [`Stack`](super::stack::Stack) for the map implementation. + /// + /// Introduced in kernel v4.20. + #[doc(alias = "BPF_MAP_TYPE_STACK")] + Stack = bpf_map_type::BPF_MAP_TYPE_STACK as isize, + /// A Socket-local Storage map type. + /// + /// Introduced in kernel v5.2. + #[doc(alias = "BPF_MAP_TYPE_SK_STORAGE")] + SkStorage = bpf_map_type::BPF_MAP_TYPE_SK_STORAGE as isize, + /// A Device Hash Map type. See [`DevMapHash`](super::xdp::DevMapHash) for the map + /// implementation. + /// + /// Introduced in kernel v5.4. + #[doc(alias = "BPF_MAP_TYPE_DEVMAP_HASH")] + DevMapHash = bpf_map_type::BPF_MAP_TYPE_DEVMAP_HASH as isize, + /// A Struct Ops map type. + /// + /// Introduced in kernel v5.6. + #[doc(alias = "BPF_MAP_TYPE_STRUCT_OPS")] + StructOps = bpf_map_type::BPF_MAP_TYPE_STRUCT_OPS as isize, + /// A Ring Buffer map type. See [`RingBuf`](super::ring_buf::RingBuf) for the map + /// implementation. + /// + /// Introduced in kernel v5.8. + #[doc(alias = "BPF_MAP_TYPE_RINGBUF")] + RingBuf = bpf_map_type::BPF_MAP_TYPE_RINGBUF as isize, + /// An Inode Storage map type. + /// + /// Introduced in kernel v5.10. + #[doc(alias = "BPF_MAP_TYPE_INODE_STORAGE")] + InodeStorage = bpf_map_type::BPF_MAP_TYPE_INODE_STORAGE as isize, + /// A Task Storage map type. + /// + /// Introduced in kernel v5.11. + #[doc(alias = "BPF_MAP_TYPE_TASK_STORAGE")] + TaskStorage = bpf_map_type::BPF_MAP_TYPE_TASK_STORAGE as isize, + /// A Bloom Filter map type. See [`BloomFilter`](super::bloom_filter::BloomFilter) for the map + /// implementation. + /// + /// Introduced in kernel v5.16. + #[doc(alias = "BPF_MAP_TYPE_BLOOM_FILTER")] + BloomFilter = bpf_map_type::BPF_MAP_TYPE_BLOOM_FILTER as isize, + /// A User Ring Buffer map type. + /// + /// Introduced in kernel v6.1. + #[doc(alias = "BPF_MAP_TYPE_USER_RINGBUF")] + UserRingBuf = bpf_map_type::BPF_MAP_TYPE_USER_RINGBUF as isize, + /// A cGroup Storage map type. + /// + /// Introduced in kernel v6.2. + #[doc(alias = "BPF_MAP_TYPE_CGRP_STORAGE")] + CgrpStorage = bpf_map_type::BPF_MAP_TYPE_CGRP_STORAGE as isize, + /// An Arena map type. + /// + /// Introduced in kernel v6.9. + #[doc(alias = "BPF_MAP_TYPE_ARENA")] + Arena = bpf_map_type::BPF_MAP_TYPE_ARENA as isize, +} + +impl TryFrom for MapType { + type Error = MapError; + + fn try_from(map_type: bpf_map_type) -> Result { + use bpf_map_type::*; + Ok(match map_type { + BPF_MAP_TYPE_UNSPEC => Self::Unspecified, + BPF_MAP_TYPE_HASH => Self::Hash, + BPF_MAP_TYPE_ARRAY => Self::Array, + BPF_MAP_TYPE_PROG_ARRAY => Self::ProgramArray, + BPF_MAP_TYPE_PERF_EVENT_ARRAY => Self::PerfEventArray, + BPF_MAP_TYPE_PERCPU_HASH => Self::PerCpuHash, + BPF_MAP_TYPE_PERCPU_ARRAY => Self::PerCpuArray, + BPF_MAP_TYPE_STACK_TRACE => Self::StackTrace, + BPF_MAP_TYPE_CGROUP_ARRAY => Self::CgroupArray, + BPF_MAP_TYPE_LRU_HASH => Self::LruHash, + BPF_MAP_TYPE_LRU_PERCPU_HASH => Self::LruPerCpuHash, + BPF_MAP_TYPE_LPM_TRIE => Self::LpmTrie, + BPF_MAP_TYPE_ARRAY_OF_MAPS => Self::ArrayOfMaps, + BPF_MAP_TYPE_HASH_OF_MAPS => Self::HashOfMaps, + BPF_MAP_TYPE_DEVMAP => Self::DevMap, + BPF_MAP_TYPE_SOCKMAP => Self::SockMap, + BPF_MAP_TYPE_CPUMAP => Self::CpuMap, + BPF_MAP_TYPE_XSKMAP => Self::XskMap, + BPF_MAP_TYPE_SOCKHASH => Self::SockHash, + BPF_MAP_TYPE_CGROUP_STORAGE_DEPRECATED => Self::CgroupStorage, + BPF_MAP_TYPE_REUSEPORT_SOCKARRAY => Self::ReuseportSockArray, + BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE_DEPRECATED => Self::PerCpuCgroupStorage, + BPF_MAP_TYPE_QUEUE => Self::Queue, + BPF_MAP_TYPE_STACK => Self::Stack, + BPF_MAP_TYPE_SK_STORAGE => Self::SkStorage, + BPF_MAP_TYPE_DEVMAP_HASH => Self::DevMapHash, + BPF_MAP_TYPE_STRUCT_OPS => Self::StructOps, + BPF_MAP_TYPE_RINGBUF => Self::RingBuf, + BPF_MAP_TYPE_INODE_STORAGE => Self::InodeStorage, + BPF_MAP_TYPE_TASK_STORAGE => Self::TaskStorage, + BPF_MAP_TYPE_BLOOM_FILTER => Self::BloomFilter, + BPF_MAP_TYPE_USER_RINGBUF => Self::UserRingBuf, + BPF_MAP_TYPE_CGRP_STORAGE => Self::CgrpStorage, + BPF_MAP_TYPE_ARENA => Self::Arena, + __MAX_BPF_MAP_TYPE => { + return Err(MapError::InvalidMapType { + map_type: map_type as u32, + }) + } + }) + } +} diff --git a/aya/src/maps/mod.rs b/aya/src/maps/mod.rs index e914d052..752bd9ff 100644 --- a/aya/src/maps/mod.rs +++ b/aya/src/maps/mod.rs @@ -65,21 +65,20 @@ use log::warn; use thiserror::Error; use crate::{ - generated::bpf_map_info, obj::{self, parse_map_info, EbpfSectionKind}, pin::PinError, sys::{ - bpf_create_map, bpf_get_object, bpf_map_freeze, bpf_map_get_fd_by_id, - bpf_map_get_info_by_fd, bpf_map_get_next_key, bpf_map_update_elem_ptr, bpf_pin_object, - iter_map_ids, SyscallError, + bpf_create_map, bpf_get_object, bpf_map_freeze, bpf_map_get_fd_by_id, bpf_map_get_next_key, + bpf_map_update_elem_ptr, bpf_pin_object, SyscallError, }, - util::{bytes_of_bpf_name, nr_cpus, KernelVersion}, + util::{nr_cpus, KernelVersion}, PinningType, Pod, }; pub mod array; pub mod bloom_filter; pub mod hash_map; +mod info; pub mod lpm_trie; pub mod perf; pub mod queue; @@ -92,6 +91,7 @@ pub mod xdp; pub use array::{Array, PerCpuArray, ProgramArray}; pub use bloom_filter::BloomFilter; pub use hash_map::{HashMap, PerCpuHashMap}; +pub use info::{loaded_maps, MapInfo, MapType}; pub use lpm_trie::LpmTrie; #[cfg(any(feature = "async_tokio", feature = "async_std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "async_tokio", feature = "async_std"))))] @@ -947,121 +947,6 @@ impl Deref for PerCpuValues { } } -/// Provides information about a loaded map, like name, id and size. -#[derive(Debug)] -pub struct MapInfo(bpf_map_info); - -impl MapInfo { - fn new_from_fd(fd: BorrowedFd<'_>) -> Result { - let info = bpf_map_get_info_by_fd(fd.as_fd())?; - Ok(Self(info)) - } - - /// Loads map info from a map id. - pub fn from_id(id: u32) -> Result { - bpf_map_get_fd_by_id(id) - .map_err(MapError::from) - .and_then(|fd| Self::new_from_fd(fd.as_fd())) - } - - /// The name of the map, limited to 16 bytes. - pub fn name(&self) -> &[u8] { - bytes_of_bpf_name(&self.0.name) - } - - /// The name of the map as a &str. If the name is not valid unicode, None is returned. - pub fn name_as_str(&self) -> Option<&str> { - std::str::from_utf8(self.name()).ok() - } - - /// The id for this map. Each map has a unique id. - pub fn id(&self) -> u32 { - self.0.id - } - - /// The map type as defined by the linux kernel enum - /// [`bpf_map_type`](https://elixir.bootlin.com/linux/v6.4.4/source/include/uapi/linux/bpf.h#L905). - pub fn map_type(&self) -> u32 { - self.0.type_ - } - - /// The key size for this map. - pub fn key_size(&self) -> u32 { - self.0.key_size - } - - /// The value size for this map. - pub fn value_size(&self) -> u32 { - self.0.value_size - } - - /// The maximum number of entries in this map. - pub fn max_entries(&self) -> u32 { - self.0.max_entries - } - - /// The flags for this map. - pub fn map_flags(&self) -> u32 { - self.0.map_flags - } - - /// Returns a file descriptor referencing the map. - /// - /// The returned file descriptor can be closed at any time and doing so does - /// not influence the life cycle of the map. - pub fn fd(&self) -> Result { - let Self(info) = self; - let fd = bpf_map_get_fd_by_id(info.id)?; - Ok(MapFd::from_fd(fd)) - } - - /// Loads a map from a pinned path in bpffs. - pub fn from_pin>(path: P) -> Result { - use std::os::unix::ffi::OsStrExt as _; - - // TODO: avoid this unwrap by adding a new error variant. - let path_string = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap(); - let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError { - call: "BPF_OBJ_GET", - io_error, - })?; - - Self::new_from_fd(fd.as_fd()) - } -} - -/// Returns an iterator over all loaded bpf maps. -/// -/// This differs from [`crate::Ebpf::maps`] since it will return all maps -/// listed on the host system and not only maps for a specific [`crate::Ebpf`] instance. -/// -/// Uses kernel v4.13 features. -/// -/// # Example -/// ``` -/// # use aya::maps::loaded_maps; -/// -/// for m in loaded_maps() { -/// match m { -/// Ok(map) => println!("{:?}", map.name_as_str()), -/// Err(e) => println!("Error iterating maps: {:?}", e), -/// } -/// } -/// ``` -/// -/// # Errors -/// -/// Returns [`MapError::SyscallError`] if any of the syscalls required to either get -/// next map id, get the map fd, or the [`MapInfo`] fail. In cases where -/// iteration can't be performed, for example the caller does not have the necessary privileges, -/// a single item will be yielded containing the error that occurred. -pub fn loaded_maps() -> impl Iterator> { - iter_map_ids().map(|id| { - let id = id?; - MapInfo::from_id(id) - }) -} - #[cfg(test)] mod test_utils { use crate::{ @@ -1332,11 +1217,11 @@ mod tests { .map(|map_info| { let map_info = map_info.unwrap(); ( - map_info.id(), - map_info.key_size(), - map_info.value_size(), + map_info.id().unwrap().get(), + map_info.key_size().unwrap().get(), + map_info.value_size().unwrap().get(), map_info.map_flags(), - map_info.max_entries(), + map_info.max_entries().unwrap().get(), map_info.fd().unwrap().as_fd().as_raw_fd(), ) }) diff --git a/test/integration-test/src/tests/info.rs b/test/integration-test/src/tests/info.rs index 16770136..e90b31b1 100644 --- a/test/integration-test/src/tests/info.rs +++ b/test/integration-test/src/tests/info.rs @@ -3,13 +3,12 @@ use std::{fs, panic, path::Path, time::SystemTime}; use aya::{ - maps::{loaded_maps, MapError}, + maps::{loaded_maps, Array, HashMap, IterableMap as _, MapError, MapType}, programs::{loaded_programs, ProgramError, ProgramType, SocketFilter, TracePoint}, sys::enable_stats, util::KernelVersion, Ebpf, }; -use aya_obj::generated::bpf_map_type; use libc::EINVAL; use crate::utils::{kernel_assert, kernel_assert_eq}; @@ -233,7 +232,7 @@ fn list_loaded_maps() { let prog: &mut SocketFilter = bpf.program_mut("simple_prog").unwrap().try_into().unwrap(); prog.load().unwrap(); - // Ensure the loaded_maps() api doesn't panic and retrieve loaded maps. + // Ensure the loaded_maps() api doesn't panic let mut maps = loaded_maps().peekable(); if let Err(err) = maps.peek().unwrap() { if let MapError::SyscallError(err) = &err { @@ -250,64 +249,94 @@ fn list_loaded_maps() { } panic!("{err}"); } - let mut maps: Vec<_> = maps.filter_map(|m| m.ok()).collect(); - // There's not a good way to extract our maps of interest with load order being - // non-deterministic. Since we are trying to be more considerate of older kernels, we should - // only rely on v4.13 feats. - // Expected sort order should be: `BAR`, `aya_global` (if avail), `FOO` - maps.sort_unstable_by_key(|m| (m.map_type(), m.id())); - - // Ensure program has the 2 maps. - if let Ok(info) = prog.info() { - let map_ids = info.map_ids().unwrap(); - kernel_assert!(map_ids.is_some(), KernelVersion::new(4, 15, 0)); - - if let Some(map_ids) = map_ids { + // Loaded maps should contain our test maps + let maps: Vec<_> = maps.filter_map(|m| m.ok()).collect(); + if let Ok(info) = &prog.info() { + if let Some(map_ids) = info.map_ids().unwrap() { assert_eq!(2, map_ids.len()); for id in map_ids.iter() { assert!( - maps.iter().any(|m| m.id() == id.get()), - "expected `loaded_maps()` to have `map_ids` from program" + maps.iter().any(|m| &m.id().unwrap() == id), + "expected `loaded_maps()` to have `map_ids` from program", ); } } } + let hash: HashMap<_, u32, u8> = HashMap::try_from(bpf.map("BAR").unwrap()).unwrap(); + let hash_id = hash.map().info().unwrap().id(); + kernel_assert!( + maps.iter().any(|map| map.id() == hash_id), + KernelVersion::new(4, 13, 0), + ); + + let array: Array<_, u32> = Array::try_from(bpf.map("FOO").unwrap()).unwrap(); + let array_id = array.map().info().unwrap().id(); + kernel_assert!( + maps.iter().any(|map| map.id() == array_id), + KernelVersion::new(4, 13, 0), + ); +} + +#[test] +fn test_map_info() { + 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(); + // Test `bpf_map_info` fields. - let hash = maps.first().unwrap(); + let hash: HashMap<_, u32, u8> = HashMap::try_from(bpf.map("BAR").unwrap()).unwrap(); + let hash = hash.map().info().unwrap(); kernel_assert_eq!( - bpf_map_type::BPF_MAP_TYPE_HASH as u32, - hash.map_type(), - KernelVersion::new(4, 13, 0) + MapType::Hash, + hash.map_type().unwrap_or(MapType::Unspecified), + KernelVersion::new(4, 13, 0), ); - kernel_assert!(hash.id() > 0, KernelVersion::new(4, 13, 0)); - kernel_assert_eq!(4, hash.key_size(), KernelVersion::new(4, 13, 0)); - kernel_assert_eq!(1, hash.value_size(), KernelVersion::new(4, 13, 0)); - kernel_assert_eq!(8, hash.max_entries(), KernelVersion::new(4, 13, 0)); - kernel_assert_eq!( - "BAR", - hash.name_as_str().unwrap(), - KernelVersion::new(4, 15, 0) + kernel_assert!(hash.id().is_some(), KernelVersion::new(4, 13, 0)); + kernel_assert!( + hash.key_size().is_some_and(|size| size.get() == 4), + KernelVersion::new(4, 13, 0), + ); + kernel_assert!( + hash.value_size().is_some_and(|size| size.get() == 1), + KernelVersion::new(4, 13, 0), + ); + kernel_assert!( + hash.max_entries().is_some_and(|size| size.get() == 8), + KernelVersion::new(4, 13, 0), + ); + kernel_assert!( + hash.name_as_str().is_some_and(|name| name == "BAR"), + KernelVersion::new(4, 15, 0), ); hash.map_flags(); hash.fd().unwrap(); - let array = maps.last().unwrap(); + let array: Array<_, u32> = Array::try_from(bpf.map("FOO").unwrap()).unwrap(); + let array = array.map().info().unwrap(); kernel_assert_eq!( - bpf_map_type::BPF_MAP_TYPE_ARRAY as u32, - array.map_type(), - KernelVersion::new(4, 13, 0) + MapType::Array, + array.map_type().unwrap_or(MapType::Unspecified), + KernelVersion::new(4, 13, 0), ); - kernel_assert!(array.id() > 0, KernelVersion::new(4, 13, 0)); - kernel_assert_eq!(4, array.key_size(), KernelVersion::new(4, 13, 0)); - kernel_assert_eq!(4, array.value_size(), KernelVersion::new(4, 13, 0)); - kernel_assert_eq!(10, array.max_entries(), KernelVersion::new(4, 13, 0)); - kernel_assert_eq!( - "FOO", - array.name_as_str().unwrap(), - KernelVersion::new(4, 15, 0) + kernel_assert!(array.id().is_some(), KernelVersion::new(4, 13, 0)); + kernel_assert!( + array.key_size().is_some_and(|size| size.get() == 4), + KernelVersion::new(4, 13, 0), + ); + kernel_assert!( + array.value_size().is_some_and(|size| size.get() == 4), + KernelVersion::new(4, 13, 0), + ); + kernel_assert!( + array.max_entries().is_some_and(|size| size.get() == 10), + KernelVersion::new(4, 13, 0), + ); + kernel_assert!( + array.name_as_str().is_some_and(|name| name == "FOO"), + KernelVersion::new(4, 15, 0), ); array.map_flags(); diff --git a/xtask/public-api/aya.txt b/xtask/public-api/aya.txt index b26f09d0..2b0c37fd 100644 --- a/xtask/public-api/aya.txt +++ b/xtask/public-api/aya.txt @@ -1405,6 +1405,80 @@ impl core::borrow::BorrowMut for aya::maps::MapError where T: core::marker pub fn aya::maps::MapError::borrow_mut(&mut self) -> &mut T impl core::convert::From for aya::maps::MapError pub fn aya::maps::MapError::from(t: T) -> T +#[non_exhaustive] pub enum aya::maps::MapType +pub aya::maps::MapType::Arena = 33 +pub aya::maps::MapType::Array = 2 +pub aya::maps::MapType::ArrayOfMaps = 12 +pub aya::maps::MapType::BloomFilter = 30 +pub aya::maps::MapType::CgroupArray = 8 +pub aya::maps::MapType::CgroupStorage = 19 +pub aya::maps::MapType::CgrpStorage = 32 +pub aya::maps::MapType::CpuMap = 16 +pub aya::maps::MapType::DevMap = 14 +pub aya::maps::MapType::DevMapHash = 25 +pub aya::maps::MapType::Hash = 1 +pub aya::maps::MapType::HashOfMaps = 13 +pub aya::maps::MapType::InodeStorage = 28 +pub aya::maps::MapType::LpmTrie = 11 +pub aya::maps::MapType::LruHash = 9 +pub aya::maps::MapType::LruPerCpuHash = 10 +pub aya::maps::MapType::PerCpuArray = 6 +pub aya::maps::MapType::PerCpuCgroupStorage = 21 +pub aya::maps::MapType::PerCpuHash = 5 +pub aya::maps::MapType::PerfEventArray = 4 +pub aya::maps::MapType::ProgramArray = 3 +pub aya::maps::MapType::Queue = 22 +pub aya::maps::MapType::ReuseportSockArray = 20 +pub aya::maps::MapType::RingBuf = 27 +pub aya::maps::MapType::SkStorage = 24 +pub aya::maps::MapType::SockHash = 18 +pub aya::maps::MapType::SockMap = 15 +pub aya::maps::MapType::Stack = 23 +pub aya::maps::MapType::StackTrace = 7 +pub aya::maps::MapType::StructOps = 26 +pub aya::maps::MapType::TaskStorage = 29 +pub aya::maps::MapType::Unspecified = 0 +pub aya::maps::MapType::UserRingBuf = 31 +pub aya::maps::MapType::XskMap = 17 +impl core::clone::Clone for aya::maps::MapType +pub fn aya::maps::MapType::clone(&self) -> aya::maps::MapType +impl core::cmp::PartialEq for aya::maps::MapType +pub fn aya::maps::MapType::eq(&self, other: &aya::maps::MapType) -> bool +impl core::convert::TryFrom for aya::maps::MapType +pub type aya::maps::MapType::Error = aya::maps::MapError +pub fn aya::maps::MapType::try_from(map_type: aya_obj::generated::linux_bindings_x86_64::bpf_map_type) -> core::result::Result +impl core::fmt::Debug for aya::maps::MapType +pub fn aya::maps::MapType::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::marker::Copy for aya::maps::MapType +impl core::marker::StructuralPartialEq for aya::maps::MapType +impl core::marker::Freeze for aya::maps::MapType +impl core::marker::Send for aya::maps::MapType +impl core::marker::Sync for aya::maps::MapType +impl core::marker::Unpin for aya::maps::MapType +impl core::panic::unwind_safe::RefUnwindSafe for aya::maps::MapType +impl core::panic::unwind_safe::UnwindSafe for aya::maps::MapType +impl core::convert::Into for aya::maps::MapType where U: core::convert::From +pub fn aya::maps::MapType::into(self) -> U +impl core::convert::TryFrom for aya::maps::MapType where U: core::convert::Into +pub type aya::maps::MapType::Error = core::convert::Infallible +pub fn aya::maps::MapType::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for aya::maps::MapType where U: core::convert::TryFrom +pub type aya::maps::MapType::Error = >::Error +pub fn aya::maps::MapType::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for aya::maps::MapType where T: core::clone::Clone +pub type aya::maps::MapType::Owned = T +pub fn aya::maps::MapType::clone_into(&self, target: &mut T) +pub fn aya::maps::MapType::to_owned(&self) -> T +impl core::any::Any for aya::maps::MapType where T: 'static + core::marker::Sized +pub fn aya::maps::MapType::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for aya::maps::MapType where T: core::marker::Sized +pub fn aya::maps::MapType::borrow(&self) -> &T +impl core::borrow::BorrowMut for aya::maps::MapType where T: core::marker::Sized +pub fn aya::maps::MapType::borrow_mut(&mut self) -> &mut T +impl core::clone::CloneToUninit for aya::maps::MapType where T: core::clone::Clone +pub unsafe fn aya::maps::MapType::clone_to_uninit(&self, dst: *mut T) +impl core::convert::From for aya::maps::MapType +pub fn aya::maps::MapType::from(t: T) -> T pub struct aya::maps::Array impl, V: aya::Pod> aya::maps::array::Array pub fn aya::maps::array::Array::get(&self, index: &u32, flags: u64) -> core::result::Result @@ -1801,14 +1875,14 @@ impl aya::maps::MapInfo pub fn aya::maps::MapInfo::fd(&self) -> core::result::Result pub fn aya::maps::MapInfo::from_id(id: u32) -> core::result::Result pub fn aya::maps::MapInfo::from_pin>(path: P) -> core::result::Result -pub fn aya::maps::MapInfo::id(&self) -> u32 -pub fn aya::maps::MapInfo::key_size(&self) -> u32 +pub fn aya::maps::MapInfo::id(&self) -> core::option::Option +pub fn aya::maps::MapInfo::key_size(&self) -> core::option::Option pub fn aya::maps::MapInfo::map_flags(&self) -> u32 -pub fn aya::maps::MapInfo::map_type(&self) -> u32 -pub fn aya::maps::MapInfo::max_entries(&self) -> u32 +pub fn aya::maps::MapInfo::map_type(&self) -> core::result::Result +pub fn aya::maps::MapInfo::max_entries(&self) -> core::option::Option pub fn aya::maps::MapInfo::name(&self) -> &[u8] pub fn aya::maps::MapInfo::name_as_str(&self) -> core::option::Option<&str> -pub fn aya::maps::MapInfo::value_size(&self) -> u32 +pub fn aya::maps::MapInfo::value_size(&self) -> core::option::Option impl core::fmt::Debug for aya::maps::MapInfo pub fn aya::maps::MapInfo::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result impl core::marker::Freeze for aya::maps::MapInfo