diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index 14a63f0b..cb2019a3 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -71,6 +71,7 @@ use std::{ io, os::unix::io::{AsRawFd, RawFd}, path::{Path, PathBuf}, + time::{Duration, SystemTime, UNIX_EPOCH}, }; use thiserror::Error; @@ -108,6 +109,7 @@ use crate::{ maps::MapError, obj::{self, btf::BtfError, Function}, pin::PinError, + programs::utils::{get_fdinfo, time_since_boot, time_since_epoch}, 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_get_next_id, bpf_prog_query, @@ -937,16 +939,97 @@ impl ProgramInfo { unsafe { std::slice::from_raw_parts(self.0.name.as_ptr() as *const _, length) } } - /// The name of the program as a &str. If the name was not valid unicode, None is returned + /// The name of the program as a &str. If the name was not valid unicode, None is returned. pub fn name_as_str(&self) -> Option<&str> { std::str::from_utf8(self.name()).ok() } - /// The program id for this program. Each program has a unique id. + /// The id for this program. Each program has a unique id. pub fn id(&self) -> u32 { self.0.id } + /// The program tag is a SHA sum of the program's instructions which can be + /// used as an additional program identifier. A program's id can vary every time + /// it's loaded or unloaded, but the tag will remain the same. + pub fn tag(&self) -> u64 { + u64::from_be_bytes(self.0.tag) + } + + /// The type of a bpf program expressed as an integer. To understand the + /// integer to type mappings please see the linux kernel enum + /// [`bpf_prog_type`](https://elixir.bootlin.com/linux/v6.4.4/source/include/uapi/linux/bpf.h#L948). + pub fn program_type(&self) -> u32 { + self.0.type_ + } + + /// Returns true if the program is defined with a GPL-compatible license. + pub fn gpl_compatible(&self) -> bool { + self.0.gpl_compatible() != 0 + } + + /// Returns the ids of the maps maps used by the program. + pub fn map_ids(&self) -> Result<Vec<u32>, ProgramError> { + let fd = self.fd()?; + + let len = self.0.nr_map_ids; + let mut map_ids = vec![0u32; len as usize]; + + bpf_prog_get_info_by_fd(fd, Some(&mut map_ids)).map_err(|io_error| { + ProgramError::SyscallError { + call: "bpf_prog_get_info_by_fd", + io_error, + } + })?; + + unsafe { libc::close(fd) }; + + Ok(map_ids) + } + + /// The btf id for the program. + pub fn btf_id(&self) -> u32 { + self.0.btf_id + } + + /// The size in bytes of the program's translated eBPF bytecode. + pub fn bytes_xlated(&self) -> u32 { + self.0.xlated_prog_len + } + + /// The size in bytes of the program's JIT-compiled machine code. + pub fn bytes_jited(&self) -> u32 { + self.0.jited_prog_len + } + + /// How much memory in bytes has been allocated and locked for the program. + pub fn bytes_memlock(&self) -> Result<u32, ProgramError> { + let fd = self.fd()?; + + let mem = get_fdinfo(fd, "memlock"); + unsafe { libc::close(fd) }; + + mem + } + + /// The number of verified instructions in the program. + pub fn verified_insns(&self) -> u32 { + self.0.verified_insns + } + + /// The time the program was loaded. + /// + /// The load time is specified by the kernel as nanoseconds since system boot, + /// this function converts that u64 value to a [`std::time::SystemTime`] + /// for easy consumption. It is calculated by first finding the realtime of system + /// boot (i.e the [`Duration`] since [`std::time::UNIX_EPOCH`] minus the [`Duration`] + /// since boot), adding the load_time [`Duration`], and finally converting the + /// [`Duration`] to a readable [`SystemTime`] by adding to [`std::time::UNIX_EPOCH`]. + pub fn loaded_at(&self) -> SystemTime { + UNIX_EPOCH + + ((time_since_epoch() - time_since_boot()) + Duration::from_nanos(self.0.load_time)) + } + /// Returns the fd associated with the program. /// /// The returned fd must be closed when no longer needed. diff --git a/aya/src/programs/utils.rs b/aya/src/programs/utils.rs index 33b84095..e088c797 100644 --- a/aya/src/programs/utils.rs +++ b/aya/src/programs/utils.rs @@ -1,5 +1,17 @@ //! Common functions shared between multiple eBPF program types. -use std::{ffi::CStr, io, os::unix::io::RawFd, path::Path}; +use std::{ + ffi::CStr, + fs::File, + io, + io::{BufRead, BufReader}, + os::unix::io::RawFd, + path::Path, + time::Duration, +}; + +// for docs link +#[allow(unused)] +use std::time::UNIX_EPOCH; use crate::{ programs::{FdLink, Link, ProgramData, ProgramError}, @@ -23,7 +35,7 @@ pub(crate) fn attach_raw_tracepoint<T: Link + From<FdLink>>( program_data.links.insert(FdLink::new(pfd).into()) } -/// Find tracefs filesystem path +/// Find tracefs filesystem path. pub(crate) fn find_tracefs_path() -> Result<&'static Path, ProgramError> { lazy_static::lazy_static! { static ref TRACE_FS: Option<&'static Path> = { @@ -51,3 +63,49 @@ pub(crate) fn find_tracefs_path() -> Result<&'static Path, ProgramError> { .as_deref() .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "tracefs not found").into()) } + +/// Get time since boot. The returned duration represents the combined +/// seconds and nanoseconds since the machine was booted. +pub(crate) fn time_since_boot() -> Duration { + let mut time = unsafe { std::mem::zeroed::<libc::timespec>() }; + + let ret = unsafe { libc::clock_gettime(libc::CLOCK_BOOTTIME, &mut time) }; + assert_eq!(ret, 0, "failed to get system bootime"); + let tv_sec = time.tv_sec as u64; + let tv_nsec = time.tv_nsec as u32; + Duration::new(tv_sec, tv_nsec) +} + +/// Get the system-wide real (wall-clock) time. The returned Duration represents +/// the combined seconds and nanoseconds since [`std::time::UNIX_EPOCH`]. +pub(crate) fn time_since_epoch() -> Duration { + let mut time = unsafe { std::mem::zeroed::<libc::timespec>() }; + + let ret = unsafe { libc::clock_gettime(libc::CLOCK_REALTIME, &mut time) }; + assert_eq!(ret, 0, "failed to get system realtime"); + let tv_sec = time.tv_sec as u64; + let tv_nsec = time.tv_nsec as u32; + Duration::new(tv_sec, tv_nsec) +} + +/// Get the specified information from a file descriptor's fdinfo. +pub(crate) fn get_fdinfo(fd: RawFd, key: &str) -> Result<u32, ProgramError> { + let info = File::open(format!("/proc/self/fdinfo/{}", fd))?; + let reader = BufReader::new(info); + for line in reader.lines() { + match line { + Ok(l) => { + if !l.contains(key) { + continue; + } + + let val = l.rsplit_once('\t').unwrap().1; + + return Ok(val.parse().unwrap()); + } + Err(e) => return Err(ProgramError::IOError(e)), + } + } + + Ok(0) +}