Browse Source

aya: Add loaded_programs() API to list all loaded programs

This uses a Programs iterator to yield all loaded bpf programs using
bpf_prog_get_next_id.

Signed-off-by: Dave Tucker <dave@dtucker.co.uk>
Dave Tucker 2 years ago
parent
commit
de4905a24b
3 changed files with 93 additions and 2 deletions
  1. 1 0
      aya/src/lib.rs
  2. 81 2
      aya/src/programs/mod.rs
  3. 11 0
      aya/src/sys/bpf.rs

+ 1 - 0
aya/src/lib.rs

@@ -51,6 +51,7 @@ pub mod maps;
 use aya_obj as obj;
 pub mod pin;
 pub mod programs;
+pub use programs::loaded_programs;
 mod sys;
 pub mod util;
 

+ 81 - 2
aya/src/programs/mod.rs

@@ -109,8 +109,8 @@ use crate::{
     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, retry_with_verifier_logs,
-        BpfLoadProgramAttrs,
+        bpf_prog_get_fd_by_id, bpf_prog_get_info_by_fd, bpf_prog_get_next_id, bpf_prog_query,
+        retry_with_verifier_logs, BpfLoadProgramAttrs,
     },
     util::VerifierLog,
 };
@@ -912,6 +912,7 @@ impl_try_from_program!(
 );
 
 /// Provides information about a loaded program, like name, id and statistics
+#[derive(Debug)]
 pub struct ProgramInfo(bpf_prog_info);
 
 impl ProgramInfo {
@@ -971,3 +972,81 @@ impl ProgramInfo {
         Ok(ProgramInfo(info))
     }
 }
+
+/// ProgramsIter is an Iterator over loaded eBPF programs.
+pub struct ProgramsIter {
+    current: u32,
+    error: bool,
+}
+
+impl Iterator for ProgramsIter {
+    type Item = Result<ProgramInfo, ProgramError>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.error {
+            return None;
+        }
+        let current = self.current;
+
+        match bpf_prog_get_next_id(current) {
+            Ok(Some(next)) => {
+                self.current = next;
+                Some(
+                    bpf_prog_get_fd_by_id(next)
+                        .map_err(|io_error| ProgramError::SyscallError {
+                            call: "bpf_prog_get_fd_by_id".to_owned(),
+                            io_error,
+                        })
+                        .and_then(|fd| {
+                            bpf_prog_get_info_by_fd(fd)
+                                .map_err(|io_error| ProgramError::SyscallError {
+                                    call: "bpf_prog_get_info_by_fd".to_owned(),
+                                    io_error,
+                                })
+                                .map(ProgramInfo)
+                        }),
+                )
+            }
+            Ok(None) => None,
+            Err((_, io_error)) => {
+                // If getting the next program failed, we have to yield None in our next
+                // iteration to avoid an infinite loop.
+                self.error = true;
+                Some(Err(ProgramError::SyscallError {
+                    call: "bpf_prog_get_fd_by_id".to_owned(),
+                    io_error,
+                }))
+            }
+        }
+    }
+}
+
+/// Returns an iterator over all loaded bpf programs.
+///
+/// This differs from [`crate::Bpf::programs`] since it will return all programs
+/// listed on the host system and not only programs a specific [`crate::Bpf`] instance.
+///
+/// # 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() -> ProgramsIter {
+    ProgramsIter {
+        current: 0,
+        error: false,
+    }
+}

+ 11 - 0
aya/src/sys/bpf.rs

@@ -859,6 +859,17 @@ pub fn sys_bpf(cmd: bpf_cmd, attr: &bpf_attr) -> SysResult {
     syscall(Syscall::Bpf { cmd, attr })
 }
 
+pub(crate) fn bpf_prog_get_next_id(id: u32) -> Result<Option<u32>, (c_long, io::Error)> {
+    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, &attr) {
+        Ok(_) => Ok(Some(unsafe { attr.__bindgen_anon_6.next_id })),
+        Err((_, io_error)) if io_error.raw_os_error() == Some(ENOENT) => Ok(None),
+        Err(e) => Err(e),
+    }
+}
+
 pub(crate) fn retry_with_verifier_logs<F>(
     max_retries: usize,
     log: &mut VerifierLog,