Add links iterator

This is not yet exposed in documentation, but is complete enough for use
in tests, removing the dependency on bpftool.

Updates #645.
pull/712/head
Tamir Duberstein 1 year ago
parent 7bb9b7f5a5
commit 30faa5f68f

@ -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<Item = Result<ProgramInfo, ProgramErro
})
.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)]
pub fn loaded_links() -> impl Iterator<Item = Result<bpf_link_info, ProgramError>> {
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))
}

@ -508,6 +508,20 @@ pub(crate) fn bpf_map_get_info_by_fd(fd: RawFd) -> Result<bpf_map_info, SyscallE
bpf_obj_get_info_by_fd::<bpf_map_info>(fd)
}
pub(crate) fn bpf_link_get_fd_by_id(link_id: u32) -> Result<OwnedFd, SyscallError> {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
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<bpf_link_info, SyscallError> {
let fd = unsafe { BorrowedFd::borrow_raw(fd) };
bpf_obj_get_info_by_fd::<bpf_link_info>(fd)
@ -909,11 +923,15 @@ fn sys_bpf(cmd: bpf_cmd, attr: &mut bpf_attr) -> SysResult<c_long> {
syscall(Syscall::Bpf { cmd, attr })
}
fn bpf_prog_get_next_id(id: u32) -> Result<Option<u32>, SyscallError> {
fn bpf_obj_get_next_id(
id: u32,
cmd: bpf_cmd,
name: &'static str,
) -> Result<Option<u32>, SyscallError> {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
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<Option<u32>, 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<Option<u32>, SyscallError> {
}
}
pub(crate) fn iter_prog_ids() -> impl Iterator<Item = Result<u32, SyscallError>> {
fn iter_obj_ids(
cmd: bpf_cmd,
name: &'static str,
) -> impl Iterator<Item = Result<u32, SyscallError>> {
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<Item = Result<u32, SyscallError>>
})
}
pub(crate) fn iter_prog_ids() -> impl Iterator<Item = Result<u32, SyscallError>> {
iter_obj_ids(bpf_cmd::BPF_PROG_GET_NEXT_ID, "bpf_prog_get_next_id")
}
pub(crate) fn iter_link_ids() -> impl Iterator<Item = Result<u32, SyscallError>> {
iter_obj_ids(bpf_cmd::BPF_LINK_GET_NEXT_ID, "bpf_link_get_next_id")
}
pub(crate) fn retry_with_verifier_logs<T>(
max_retries: usize,
f: impl Fn(&mut [u8]) -> SysResult<T>,

@ -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.

@ -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<Item = Option<u32>> + '_ {
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");
}

@ -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() {

Loading…
Cancel
Save