diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index 95a45f62..ed90c788 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -104,14 +104,15 @@ pub use uprobe::{UProbe, UProbeError}; pub use xdp::{Xdp, XdpError, XdpFlags}; use crate::{ - generated::{bpf_attach_type, bpf_prog_info, bpf_prog_type}, + generated::{bpf_attach_type, bpf_link_info, bpf_prog_info, bpf_prog_type}, maps::MapError, obj::{self, btf::BtfError, Function, VerifierLog}, pin::PinError, sys::{ - bpf_btf_get_fd_by_id, bpf_get_object, bpf_load_program, bpf_pin_object, - bpf_prog_get_fd_by_id, bpf_prog_get_info_by_fd, bpf_prog_query, iter_prog_ids, - retry_with_verifier_logs, BpfLoadProgramAttrs, SyscallError, + 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, + BpfLoadProgramAttrs, SyscallError, }, util::KernelVersion, VerifierLogLevel, @@ -998,3 +999,19 @@ pub fn loaded_programs() -> impl Iterator impl Iterator> { + iter_link_ids() + .map(|id| { + let id = id?; + bpf_link_get_fd_by_id(id) + }) + .map(|fd| { + let fd = fd?; + bpf_link_get_info_by_fd(fd.as_raw_fd()) + }) + .map(|result| result.map_err(Into::into)) +} diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs index c6ee9e30..e88771a7 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -508,6 +508,20 @@ pub(crate) fn bpf_map_get_info_by_fd(fd: RawFd) -> Result(fd) } +pub(crate) fn bpf_link_get_fd_by_id(link_id: u32) -> Result { + let mut attr = unsafe { mem::zeroed::() }; + + attr.__bindgen_anon_6.__bindgen_anon_1.link_id = link_id; + // SAFETY: BPF_LINK_GET_FD_BY_ID returns a new file descriptor. + unsafe { fd_sys_bpf(bpf_cmd::BPF_LINK_GET_FD_BY_ID, &mut attr) }.map_err(|(code, io_error)| { + assert_eq!(code, -1); + SyscallError { + call: "bpf_link_get_fd_by_id", + io_error, + } + }) +} + pub(crate) fn bpf_link_get_info_by_fd(fd: RawFd) -> Result { let fd = unsafe { BorrowedFd::borrow_raw(fd) }; bpf_obj_get_info_by_fd::(fd) @@ -909,11 +923,15 @@ fn sys_bpf(cmd: bpf_cmd, attr: &mut bpf_attr) -> SysResult { syscall(Syscall::Bpf { cmd, attr }) } -fn bpf_prog_get_next_id(id: u32) -> Result, SyscallError> { +fn bpf_obj_get_next_id( + id: u32, + cmd: bpf_cmd, + name: &'static str, +) -> Result, SyscallError> { let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_6 }; u.__bindgen_anon_1.start_id = id; - match sys_bpf(bpf_cmd::BPF_PROG_GET_NEXT_ID, &mut attr) { + match sys_bpf(cmd, &mut attr) { Ok(code) => { assert_eq!(code, 0); Ok(Some(unsafe { attr.__bindgen_anon_6.next_id })) @@ -924,7 +942,7 @@ fn bpf_prog_get_next_id(id: u32) -> Result, SyscallError> { Ok(None) } else { Err(SyscallError { - call: "bpf_prog_get_next_id", + call: name, io_error, }) } @@ -932,12 +950,15 @@ fn bpf_prog_get_next_id(id: u32) -> Result, SyscallError> { } } -pub(crate) fn iter_prog_ids() -> impl Iterator> { +fn iter_obj_ids( + cmd: bpf_cmd, + name: &'static str, +) -> impl Iterator> { let mut current_id = Some(0); iter::from_fn(move || { let next_id = { let current_id = current_id?; - bpf_prog_get_next_id(current_id).transpose() + bpf_obj_get_next_id(current_id, cmd, name).transpose() }; current_id = next_id.as_ref().and_then(|next_id| match next_id { Ok(next_id) => Some(*next_id), @@ -947,6 +968,14 @@ pub(crate) fn iter_prog_ids() -> impl Iterator> }) } +pub(crate) fn iter_prog_ids() -> impl Iterator> { + iter_obj_ids(bpf_cmd::BPF_PROG_GET_NEXT_ID, "bpf_prog_get_next_id") +} + +pub(crate) fn iter_link_ids() -> impl Iterator> { + iter_obj_ids(bpf_cmd::BPF_LINK_GET_NEXT_ID, "bpf_link_get_next_id") +} + pub(crate) fn retry_with_verifier_logs( max_retries: usize, f: impl Fn(&mut [u8]) -> SysResult, diff --git a/test/README.md b/test/README.md index 0a6d1b84..a9521c0b 100644 --- a/test/README.md +++ b/test/README.md @@ -11,7 +11,6 @@ To run locally all you need is: 1. Rust nightly 1. `cargo install bpf-linker` -1. `bpftool` [^1] ### Other OSs @@ -52,5 +51,3 @@ Tests should follow these guidelines: - You may add a new module, or use an existing one. - Test functions should not return `anyhow::Result<()>` since this produces errors without stack traces. Prefer to `panic!` instead. - -[^1]: TODO(https://github.com/aya-rs/aya/issues/645): Remove this dependency. diff --git a/test/integration-test/src/tests/load.rs b/test/integration-test/src/tests/load.rs index 4c366e46..ece72daf 100644 --- a/test/integration-test/src/tests/load.rs +++ b/test/integration-test/src/tests/load.rs @@ -1,17 +1,17 @@ -use std::{convert::TryInto as _, process::Command, thread, time}; +use std::{convert::TryInto as _, thread, time}; use aya::{ maps::Array, programs::{ links::{FdLink, PinnedLink}, - loaded_programs, KProbe, TracePoint, UProbe, Xdp, XdpFlags, + loaded_links, loaded_programs, KProbe, TracePoint, UProbe, Xdp, XdpFlags, }, util::KernelVersion, Bpf, }; -const MAX_RETRIES: u32 = 100; -const RETRY_DURATION_MS: u64 = 10; +const MAX_RETRIES: usize = 100; +const RETRY_DURATION: time::Duration = time::Duration::from_millis(10); #[test] fn long_name() { @@ -50,61 +50,67 @@ fn multiple_btf_maps() { assert_eq!(val_2, 42); } -fn is_linked(prog_id: &u32) -> bool { - let output = Command::new("bpftool").args(["link"]).output(); - let output = output.expect("Failed to run 'bpftool link'"); - let stdout = String::from_utf8(output.stdout).unwrap(); - stdout.contains(&prog_id.to_string()) -} - -macro_rules! assert_loaded_and_linked { - ($name:literal, $loaded:expr) => { - for i in 0..(MAX_RETRIES + 1) { +fn poll_loaded_program_id(name: &str) -> impl Iterator> + '_ { + std::iter::once(true) + .chain(std::iter::repeat(false)) + .map(|first| { + if !first { + thread::sleep(RETRY_DURATION); + } // Ignore race failures which can happen when the tests delete a // program in the middle of a `loaded_programs()` call. - let id = loaded_programs() + loaded_programs() .filter_map(|prog| prog.ok()) - .find(|prog| prog.name() == $name.as_bytes()) - .map(|prog| Some(prog.id())); - let mut linked = false; - if let Some(prog_id) = id { - linked = is_linked(&prog_id.unwrap()); - if linked == $loaded { - break; - } - } - - if i == MAX_RETRIES { - panic!( - "Expected (loaded/linked: {}) but found (id: {}, linked: {}", - $loaded, - id.is_some(), - linked - ); - } - thread::sleep(time::Duration::from_millis(RETRY_DURATION_MS)); - } - }; + .find_map(|prog| (prog.name() == name.as_bytes()).then(|| prog.id())) + }) } -macro_rules! assert_loaded { - ($name:literal, $loaded:expr) => { - for i in 0..(MAX_RETRIES + 1) { +#[track_caller] +fn assert_loaded_and_linked(name: &str) { + let (attempts_used, prog_id) = poll_loaded_program_id(name) + .take(MAX_RETRIES) + .enumerate() + .find_map(|(i, id)| id.map(|id| (i, id))) + .unwrap_or_else(|| panic!("{name} not loaded after {MAX_RETRIES}")); + let poll_loaded_link_id = std::iter::once(true) + .chain(std::iter::repeat(false)) + .map(|first| { + if !first { + thread::sleep(RETRY_DURATION); + } // Ignore race failures which can happen when the tests delete a // program in the middle of a `loaded_programs()` call. - let state = loaded_programs() - .filter_map(|prog| prog.ok()) - .any(|prog| prog.name() == $name.as_bytes()); + loaded_links() + .filter_map(|link| link.ok()) + .find_map(|link| (link.prog_id == prog_id).then_some(link.id)) + }); + assert!( + poll_loaded_link_id + .take(MAX_RETRIES) + .skip(attempts_used) + .any(|id| id.is_some()), + "{name} not linked after {MAX_RETRIES}" + ); +} - if state == $loaded { - break; - } - if i == MAX_RETRIES { - panic!("Expected loaded: {} but was loaded: {}", $loaded, state); - } - thread::sleep(time::Duration::from_millis(RETRY_DURATION_MS)); - } - }; +#[track_caller] +fn assert_loaded(name: &str) { + assert!( + poll_loaded_program_id(name) + .take(MAX_RETRIES) + .any(|id| id.is_some()), + "{name} not loaded after {MAX_RETRIES}" + ) +} + +#[track_caller] +fn assert_unloaded(name: &str) { + assert!( + poll_loaded_program_id(name) + .take(MAX_RETRIES) + .any(|id| id.is_none()), + "{name} still loaded after {MAX_RETRIES}" + ) } #[test] @@ -112,24 +118,24 @@ fn unload_xdp() { let mut bpf = Bpf::load(crate::TEST).unwrap(); let prog: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); prog.load().unwrap(); - assert_loaded!("pass", true); + assert_loaded("pass"); let link = prog.attach("lo", XdpFlags::default()).unwrap(); { let _link_owned = prog.take_link(link).unwrap(); prog.unload().unwrap(); - assert_loaded_and_linked!("pass", true); + assert_loaded_and_linked("pass"); }; - assert_loaded!("pass", false); + assert_unloaded("pass"); prog.load().unwrap(); - assert_loaded!("pass", true); + assert_loaded("pass"); prog.attach("lo", XdpFlags::default()).unwrap(); - assert_loaded!("pass", true); + assert_loaded("pass"); prog.unload().unwrap(); - assert_loaded!("pass", false); + assert_unloaded("pass"); } #[test] @@ -137,24 +143,24 @@ fn unload_kprobe() { let mut bpf = Bpf::load(crate::TEST).unwrap(); let prog: &mut KProbe = bpf.program_mut("test_kprobe").unwrap().try_into().unwrap(); prog.load().unwrap(); - assert_loaded!("test_kprobe", true); + assert_loaded("test_kprobe"); let link = prog.attach("try_to_wake_up", 0).unwrap(); { let _link_owned = prog.take_link(link).unwrap(); prog.unload().unwrap(); - assert_loaded_and_linked!("test_kprobe", true); + assert_loaded_and_linked("test_kprobe"); }; - assert_loaded!("test_kprobe", false); + assert_unloaded("test_kprobe"); prog.load().unwrap(); - assert_loaded!("test_kprobe", true); + assert_loaded("test_kprobe"); prog.attach("try_to_wake_up", 0).unwrap(); - assert_loaded!("test_kprobe", true); + assert_loaded("test_kprobe"); prog.unload().unwrap(); - assert_loaded!("test_kprobe", false); + assert_unloaded("test_kprobe"); } #[test] @@ -167,25 +173,25 @@ fn basic_tracepoint() { .unwrap(); prog.load().unwrap(); - assert_loaded!("test_tracepoint", true); + assert_loaded("test_tracepoint"); let link = prog.attach("syscalls", "sys_enter_kill").unwrap(); { let _link_owned = prog.take_link(link).unwrap(); prog.unload().unwrap(); - assert_loaded_and_linked!("test_tracepoint", true); + assert_loaded_and_linked("test_tracepoint"); }; - assert_loaded!("test_tracepoint", false); + assert_unloaded("test_tracepoint"); prog.load().unwrap(); - assert_loaded!("test_tracepoint", true); + assert_loaded("test_tracepoint"); prog.attach("syscalls", "sys_enter_kill").unwrap(); - assert_loaded!("test_tracepoint", true); + assert_loaded("test_tracepoint"); prog.unload().unwrap(); - assert_loaded!("test_tracepoint", false); + assert_unloaded("test_tracepoint"); } #[test] @@ -194,25 +200,25 @@ fn basic_uprobe() { let prog: &mut UProbe = bpf.program_mut("test_uprobe").unwrap().try_into().unwrap(); prog.load().unwrap(); - assert_loaded!("test_uprobe", true); + assert_loaded("test_uprobe"); let link = prog.attach(Some("sleep"), 0, "libc", None).unwrap(); { let _link_owned = prog.take_link(link).unwrap(); prog.unload().unwrap(); - assert_loaded_and_linked!("test_uprobe", true); + assert_loaded_and_linked("test_uprobe"); }; - assert_loaded!("test_uprobe", false); + assert_unloaded("test_uprobe"); prog.load().unwrap(); - assert_loaded!("test_uprobe", true); + assert_loaded("test_uprobe"); prog.attach(Some("sleep"), 0, "libc", None).unwrap(); - assert_loaded!("test_uprobe", true); + assert_loaded("test_uprobe"); prog.unload().unwrap(); - assert_loaded!("test_uprobe", false); + assert_unloaded("test_uprobe"); } #[test] @@ -228,22 +234,22 @@ fn pin_link() { prog.load().unwrap(); let link_id = prog.attach("lo", XdpFlags::default()).unwrap(); let link = prog.take_link(link_id).unwrap(); - assert_loaded!("pass", true); + assert_loaded("pass"); let fd_link: FdLink = link.try_into().unwrap(); let pinned = fd_link.pin("/sys/fs/bpf/aya-xdp-test-lo").unwrap(); // because of the pin, the program is still attached prog.unload().unwrap(); - assert_loaded!("pass", true); + assert_loaded("pass"); // delete the pin, but the program is still attached let new_link = pinned.unpin().unwrap(); - assert_loaded!("pass", true); + assert_loaded("pass"); // finally when new_link is dropped we're detached drop(new_link); - assert_loaded!("pass", false); + assert_unloaded("pass"); } #[test] @@ -263,7 +269,7 @@ fn pin_lifecycle() { } // should still be loaded since prog was pinned - assert_loaded!("pass", true); + assert_loaded("pass"); // 2. Load program from bpffs but don't attach it { @@ -271,7 +277,7 @@ fn pin_lifecycle() { } // should still be loaded since prog was pinned - assert_loaded!("pass", true); + assert_loaded("pass"); // 3. Load program from bpffs and attach { @@ -286,7 +292,7 @@ fn pin_lifecycle() { } // should still be loaded since link was pinned - assert_loaded_and_linked!("pass", true); + assert_loaded_and_linked("pass"); // 4. Load a new version of the program, unpin link, and atomically replace old program { @@ -299,11 +305,11 @@ fn pin_lifecycle() { .unpin() .unwrap(); prog.attach_to_link(link.try_into().unwrap()).unwrap(); - assert_loaded!("pass", true); + assert_loaded("pass"); } // program should be unloaded - assert_loaded!("pass", false); + assert_unloaded("pass"); } #[test] @@ -321,7 +327,7 @@ fn pin_lifecycle_tracepoint() { } // should still be loaded since prog was pinned - assert_loaded!("test_tracepoint", true); + assert_loaded("test_tracepoint"); // 2. Load program from bpffs but don't attach it { @@ -329,7 +335,7 @@ fn pin_lifecycle_tracepoint() { } // should still be loaded since prog was pinned - assert_loaded!("test_tracepoint", true); + assert_loaded("test_tracepoint"); // 3. Load program from bpffs and attach { @@ -346,7 +352,7 @@ fn pin_lifecycle_tracepoint() { } // should still be loaded since link was pinned - assert_loaded_and_linked!("test_tracepoint", true); + assert_loaded_and_linked("test_tracepoint"); // 4. unpin link, and make sure everything is unloaded { @@ -357,7 +363,7 @@ fn pin_lifecycle_tracepoint() { } // program should be unloaded - assert_loaded!("test_tracepoint", false); + assert_unloaded("test_tracepoint"); } #[test] @@ -371,7 +377,7 @@ fn pin_lifecycle_kprobe() { } // should still be loaded since prog was pinned - assert_loaded!("test_kprobe", true); + assert_loaded("test_kprobe"); // 2. Load program from bpffs but don't attach it { @@ -383,7 +389,7 @@ fn pin_lifecycle_kprobe() { } // should still be loaded since prog was pinned - assert_loaded!("test_kprobe", true); + assert_loaded("test_kprobe"); // 3. Load program from bpffs and attach { @@ -404,7 +410,7 @@ fn pin_lifecycle_kprobe() { } // should still be loaded since link was pinned - assert_loaded_and_linked!("test_kprobe", true); + assert_loaded_and_linked("test_kprobe"); // 4. unpin link, and make sure everything is unloaded { @@ -415,7 +421,7 @@ fn pin_lifecycle_kprobe() { } // program should be unloaded - assert_loaded!("test_kprobe", false); + assert_unloaded("test_kprobe"); } #[test] @@ -429,7 +435,7 @@ fn pin_lifecycle_uprobe() { } // should still be loaded since prog was pinned - assert_loaded!("test_uprobe", true); + assert_loaded("test_uprobe"); // 2. Load program from bpffs but don't attach it { @@ -441,7 +447,7 @@ fn pin_lifecycle_uprobe() { } // should still be loaded since prog was pinned - assert_loaded!("test_uprobe", true); + assert_loaded("test_uprobe"); // 3. Load program from bpffs and attach { @@ -462,7 +468,7 @@ fn pin_lifecycle_uprobe() { } // should still be loaded since link was pinned - assert_loaded_and_linked!("test_uprobe", true); + assert_loaded_and_linked("test_uprobe"); // 4. unpin link, and make sure everything is unloaded { @@ -473,5 +479,5 @@ fn pin_lifecycle_uprobe() { } // program should be unloaded - assert_loaded!("test_uprobe", false); + assert_unloaded("test_uprobe"); } diff --git a/test/run.sh b/test/run.sh index 4cbd48b1..26c98918 100755 --- a/test/run.sh +++ b/test/run.sh @@ -190,8 +190,6 @@ EOF echo "Enabling testing repositories" exec_vm sudo dnf config-manager --set-enabled updates-testing exec_vm sudo dnf config-manager --set-enabled updates-testing-modular - echo "Installing dependencies" - exec_vm sudo dnf install -qy bpftool } scp_vm() {