mirror of https://github.com/aya-rs/aya
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.0pull/1007/head
parent
ab000ad7c3
commit
cb8e478800
@ -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 {}
|
||||||
|
}
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue