Bläddra i källkod

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.
Tamir Duberstein 1 år sedan
förälder
incheckning
30faa5f68f
5 ändrade filer med 157 tillägg och 110 borttagningar
  1. 21 4
      aya/src/programs/mod.rs
  2. 34 5
      aya/src/sys/bpf.rs
  3. 0 3
      test/README.md
  4. 102 96
      test/integration-test/src/tests/load.rs
  5. 0 2
      test/run.sh

+ 21 - 4
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<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))
+}

+ 34 - 5
aya/src/sys/bpf.rs

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

+ 0 - 3
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.

+ 102 - 96
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<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");
 }

+ 0 - 2
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() {