aya,obj,int-test: revamp ProgramInfo be more friendly with older kernels

Purpose of this commit is to add detections for whether a field is
available in `ProgramInfo`.
- For `program_type()`, we return the new enum `ProgramType` instead of
  the integer representation.
- For fields that we know cannot be zero, we return `Option<NonZero*>`
  type.
- For `name_as_str()`, it now also uses the feature probe `bpf_name()`
  to detect if field is available or not.
- Two additional feature probes are added for the fields:
  - `prog_info_map_ids()` probe -> `map_ids()` field
  - `prog_info_gpl_compatible()` probe -> `gpl_compatible()` field

With the `prog_info_map_ids()` probe, the previous implementation that
I had for `bpf_prog_get_info_by_fd()` is shortened to use the probe
instead of having to make 2 potential syscalls.

The `test_loaded_at()` test is also moved into info tests since it is
better related to the info tests.

`aya::programs::Programs::prog_type(&self)` now returns `ProgramType`
instead of the generated FFI from aya-obj.

Also previously, `loaded_programs()` could be accessed either through
`aya` or `aya::programs`. To avoid confusion and duplicate export of
the item, the function should now only be exposed through
`aya::programs`.
pull/1007/head
tyrone-wu 2 months ago
parent 1634fa7188
commit 88f5ac3114
No known key found for this signature in database
GPG Key ID: 978B1A1B79210AD6

@ -60,12 +60,11 @@ use std::{
const MAP_NAME: &str = "AYA_LOGS";
use aya::{
loaded_programs,
maps::{
perf::{AsyncPerfEventArray, Events, PerfBufferError},
Map, MapData, MapError, MapInfo,
},
programs::ProgramError,
programs::{loaded_programs, ProgramError},
util::online_cpus,
Ebpf, Pod,
};
@ -137,13 +136,15 @@ impl EbpfLogger {
) -> Result<EbpfLogger, Error> {
let program_info = loaded_programs()
.filter_map(|info| info.ok())
.find(|info| info.id() == program_id)
.find(|info| info.id().is_some_and(|id| id.get() == program_id))
.ok_or(Error::ProgramNotFound)?;
let map = program_info
.map_ids()
.map_err(Error::ProgramError)?
.expect("`map_ids` field in `bpf_prog_info` not available")
.iter()
.filter_map(|id| MapInfo::from_id(*id).ok())
.filter_map(|id| MapInfo::from_id(id.get()).ok())
.find(|map_info| match map_info.name_as_str() {
Some(name) => name == MAP_NAME,
None => false,

@ -47,6 +47,8 @@ pub struct Features {
bpf_cookie: bool,
cpumap_prog_id: bool,
devmap_prog_id: bool,
prog_info_map_ids: bool,
prog_info_gpl_compatible: bool,
btf: Option<BtfFeatures>,
}
@ -61,6 +63,8 @@ impl Features {
bpf_cookie: bool,
cpumap_prog_id: bool,
devmap_prog_id: bool,
prog_info_map_ids: bool,
prog_info_gpl_compatible: bool,
btf: Option<BtfFeatures>,
) -> Self {
Self {
@ -71,6 +75,8 @@ impl Features {
bpf_cookie,
cpumap_prog_id,
devmap_prog_id,
prog_info_map_ids,
prog_info_gpl_compatible,
btf,
}
}
@ -110,6 +116,16 @@ impl Features {
self.devmap_prog_id
}
/// Returns whether `bpf_prog_info` supports `nr_map_ids` & `map_ids` fields.
pub fn prog_info_map_ids(&self) -> bool {
self.prog_info_map_ids
}
/// Returns whether `bpf_prog_info` supports `gpl_compatible` field.
pub fn prog_info_gpl_compatible(&self) -> bool {
self.prog_info_gpl_compatible
}
/// If BTF is supported, returns which BTF features are supported.
pub fn btf(&self) -> Option<&BtfFeatures> {
self.btf.as_ref()

@ -21,8 +21,8 @@ use thiserror::Error;
use crate::{
generated::{
bpf_map_type, bpf_map_type::*, AYA_PERF_EVENT_IOC_DISABLE, AYA_PERF_EVENT_IOC_ENABLE,
AYA_PERF_EVENT_IOC_SET_BPF,
bpf_map_type::{self, *},
AYA_PERF_EVENT_IOC_DISABLE, AYA_PERF_EVENT_IOC_ENABLE, AYA_PERF_EVENT_IOC_SET_BPF,
},
maps::{Map, MapData, MapError},
obj::{
@ -39,9 +39,9 @@ use crate::{
bpf_load_btf, is_bpf_cookie_supported, is_bpf_global_data_supported,
is_btf_datasec_supported, is_btf_decl_tag_supported, is_btf_enum64_supported,
is_btf_float_supported, is_btf_func_global_supported, is_btf_func_supported,
is_btf_supported, is_btf_type_tag_supported, is_perf_link_supported,
is_probe_read_kernel_supported, is_prog_id_supported, is_prog_name_supported,
retry_with_verifier_logs,
is_btf_supported, is_btf_type_tag_supported, is_info_gpl_compatible_supported,
is_info_map_ids_supported, is_perf_link_supported, is_probe_read_kernel_supported,
is_prog_id_supported, is_prog_name_supported, retry_with_verifier_logs,
},
util::{bytes_of, bytes_of_slice, page_size, possible_cpus, POSSIBLE_CPUS},
};
@ -96,6 +96,8 @@ fn detect_features() -> Features {
is_bpf_cookie_supported(),
is_prog_id_supported(BPF_MAP_TYPE_CPUMAP),
is_prog_id_supported(BPF_MAP_TYPE_DEVMAP),
is_info_map_ids_supported(),
is_info_gpl_compatible_supported(),
btf,
);
debug!("BPF Feature Detection: {:#?}", f);

@ -91,7 +91,6 @@ use aya_obj::generated;
pub use bpf::*;
pub use obj::btf::{Btf, BtfError};
pub use object::Endianness;
pub use programs::loaded_programs;
#[doc(hidden)]
pub use sys::netlink_set_link_up;

@ -0,0 +1,548 @@
//! Metadata information about an eBPF program.
use std::{
ffi::CString,
num::{NonZeroU32, NonZeroU64},
os::fd::{AsFd as _, BorrowedFd},
path::Path,
time::{Duration, SystemTime},
};
use aya_obj::generated::{bpf_prog_info, bpf_prog_type};
use super::{
utils::{boot_time, get_fdinfo},
ProgramError, ProgramFd,
};
use crate::{
sys::{
bpf_get_object, bpf_prog_get_fd_by_id, bpf_prog_get_info_by_fd, iter_prog_ids, SyscallError,
},
util::bytes_of_bpf_name,
FEATURES,
};
/// Provides metadata information about a loaded eBPF program.
///
/// Introduced in kernel v4.13.
#[doc(alias = "bpf_prog_info")]
#[derive(Debug)]
pub struct ProgramInfo(pub(crate) bpf_prog_info);
impl ProgramInfo {
pub(crate) fn new_from_fd(fd: BorrowedFd<'_>) -> Result<Self, ProgramError> {
let info = bpf_prog_get_info_by_fd(fd, &mut [])?;
Ok(Self(info))
}
/// The type of program.
///
/// Introduced in kernel v4.13.
pub fn program_type(&self) -> Result<ProgramType, ProgramError> {
bpf_prog_type::try_from(self.0.type_)
.unwrap_or(bpf_prog_type::__MAX_BPF_PROG_TYPE)
.try_into()
}
/// The unique ID for this program.
///
/// `None` is returned if the field is not available.
///
/// Introduced in kernel v4.13.
pub fn id(&self) -> Option<NonZeroU32> {
NonZeroU32::new(self.0.id)
}
/// The program tag.
///
/// The tag is a SHA sum of the program's instructions which be used as an alternative to
/// [`Self::id()`]. A program's ID can vary every time it's loaded or unloaded, but the tag
/// will remain the same.
///
/// `None` is returned if the field is not available.
///
/// Introduced in kernel v4.13.
pub fn tag(&self) -> Option<NonZeroU64> {
NonZeroU64::new(u64::from_be_bytes(self.0.tag))
}
/// The size in bytes of the program's JIT-compiled machine code.
///
/// Note that this field is only updated when BPF JIT compiler is enabled. Kernels v4.15 and
/// above may already have it enabled by default.
///
/// `None` is returned if the field is not available, or if the JIT compiler is not enabled.
///
/// Introduced in kernel v4.13.
pub fn size_jitted(&self) -> Option<NonZeroU32> {
NonZeroU32::new(self.0.jited_prog_len)
}
/// The size in bytes of the program's translated eBPF bytecode.
///
/// The translated bytecode is after it has been passed though the verifier where it was
/// possibly modified by the kernel.
///
/// `None` is returned if the field is not available.
///
/// Introduced in kernel v4.15.
pub fn size_translated(&self) -> Option<NonZeroU32> {
NonZeroU32::new(self.0.xlated_prog_len)
}
/// The time when the program was loaded.
///
/// `None` is returned if the field is not available.
///
/// Introduced in kernel v4.15.
pub fn loaded_at(&self) -> Option<SystemTime> {
if self.0.load_time > 0 {
Some(boot_time() + Duration::from_nanos(self.0.load_time))
} else {
None
}
}
/// The user ID of the process who loaded the program.
///
/// `None` is returned if the field is not available.
///
/// Introduced in kernel v4.15.
pub fn created_by_uid(&self) -> Option<u32> {
// This field was introduced in the same commit as `load_time`.
if self.0.load_time > 0 {
Some(self.0.created_by_uid)
} else {
None
}
}
/// The IDs of the maps used by the program.
///
/// `None` is returned if the field is not available.
///
/// Introduced in kernel v4.15.
pub fn map_ids(&self) -> Result<Option<Vec<NonZeroU32>>, ProgramError> {
if FEATURES.prog_info_map_ids() {
let mut map_ids = vec![0u32; self.0.nr_map_ids as usize];
bpf_prog_get_info_by_fd(self.fd()?.as_fd(), &mut map_ids)?;
Ok(Some(
map_ids
.into_iter()
.map(|id| NonZeroU32::new(id).unwrap())
.collect(),
))
} else {
Ok(None)
}
}
/// The name of the program as was provided when it was load. This is limited to 16 bytes.
///
/// Introduced in kernel v4.15.
pub fn name(&self) -> &[u8] {
bytes_of_bpf_name(&self.0.name)
}
/// The name of the program as a &str.
///
/// `None` is returned if the name was not valid unicode or if field is not available.
///
/// Introduced in kernel v4.15.
pub fn name_as_str(&self) -> Option<&str> {
let name = std::str::from_utf8(self.name()).ok();
if let Some(name_str) = name {
if FEATURES.bpf_name() || !name_str.is_empty() {
return name;
}
}
None
}
/// Returns true if the program is defined with a GPL-compatible license.
///
/// `None` is returned if the field is not available.
///
/// Introduced in kernel v4.18.
pub fn gpl_compatible(&self) -> Option<bool> {
if FEATURES.prog_info_gpl_compatible() {
Some(self.0.gpl_compatible() != 0)
} else {
None
}
}
/// The BTF ID for the program.
///
/// Introduced in kernel v5.0.
pub fn btf_id(&self) -> Option<NonZeroU32> {
NonZeroU32::new(self.0.btf_id)
}
/// The accumulated time that the program has been actively running.
///
/// This is not to be confused with the duration since the program was
/// first loaded on the host.
///
/// Note this field is only updated for as long as
/// [`enable_stats`](crate::sys::enable_stats) is enabled
/// with [`Stats::RunTime`](crate::sys::Stats::RunTime).
///
/// Introduced in kernel v5.1.
pub fn run_time(&self) -> Duration {
Duration::from_nanos(self.0.run_time_ns)
}
/// The accumulated execution count of the program.
///
/// Note this field is only updated for as long as
/// [`enable_stats`](crate::sys::enable_stats) is enabled
/// with [`Stats::RunTime`](crate::sys::Stats::RunTime).
///
/// Introduced in kernel v5.1.
pub fn run_count(&self) -> u64 {
self.0.run_cnt
}
/// The number of verified instructions in the program.
///
/// This may be less than the total number of instructions in the compiled program due to dead
/// code elimination in the verifier.
///
/// `None` is returned if the field is not available.
///
/// Introduced in kernel v5.16.
pub fn verified_instruction_count(&self) -> Option<NonZeroU32> {
NonZeroU32::new(self.0.verified_insns)
}
/// How much memory in bytes has been allocated and locked for the program.
pub fn memory_locked(&self) -> Result<u32, ProgramError> {
get_fdinfo(self.fd()?.as_fd(), "memlock")
}
/// Returns a file descriptor referencing the program.
///
/// The returned file descriptor can be closed at any time and doing so does
/// not influence the life cycle of the program.
///
/// Uses kernel v4.13 features.
pub fn fd(&self) -> Result<ProgramFd, ProgramError> {
let Self(info) = self;
let fd = bpf_prog_get_fd_by_id(info.id)?;
Ok(ProgramFd(fd))
}
/// Loads a program from a pinned path in bpffs.
///
/// Uses kernel v4.4 and v4.13 features.
pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, ProgramError> {
use std::os::unix::ffi::OsStrExt as _;
// TODO: avoid this unwrap by adding a new error variant.
let path_string = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap();
let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError {
call: "BPF_OBJ_GET",
io_error,
})?;
Self::new_from_fd(fd.as_fd())
}
}
/// Returns information about a loaded program with the [`ProgramInfo`] structure.
///
/// This information is populated at load time by the kernel and can be used
/// to correlate a given [`crate::programs::Program`] to it's corresponding [`ProgramInfo`]
/// metadata.
macro_rules! impl_info {
($($struct_name:ident),+ $(,)?) => {
$(
impl $struct_name {
/// Returns metadata information of this program.
///
/// Uses kernel v4.13 features.
pub fn info(&self) -> Result<ProgramInfo, ProgramError> {
let ProgramFd(fd) = self.fd()?;
ProgramInfo::new_from_fd(fd.as_fd())
}
}
)+
}
}
pub(crate) use impl_info;
/// Returns an iterator of [`ProgramInfo`] over all eBPF programs loaded on the host.
///
/// Unlike [`Ebpf::programs`](crate::Ebpf::programs), this includes **all** programs on the host
/// system, not just those tied to a specific [`crate::Ebpf`] instance.
///
/// Uses kernel v4.13 features.
///
/// # 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() -> impl Iterator<Item = Result<ProgramInfo, ProgramError>> {
iter_prog_ids()
.map(|id| {
let id = id?;
bpf_prog_get_fd_by_id(id)
})
.map(|fd| {
let fd = fd?;
bpf_prog_get_info_by_fd(fd.as_fd(), &mut [])
})
.map(|result| result.map(ProgramInfo).map_err(Into::into))
}
/// The type of eBPF program.
#[non_exhaustive]
#[doc(alias = "bpf_prog_type")]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ProgramType {
/// An unspecified program type.
Unspecified = bpf_prog_type::BPF_PROG_TYPE_UNSPEC as isize,
/// A Socket Filter program type. See [`SocketFilter`](super::socket_filter::SocketFilter)
/// for the program implementation.
///
/// Introduced in kernel v3.19.
#[doc(alias = "BPF_PROG_TYPE_SOCKET_FILTER")]
SocketFilter = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as isize,
/// A Kernel Probe program type. See [`KProbe`](super::kprobe::KProbe) and
/// [`UProbe`](super::uprobe::UProbe) for the program implementations.
///
/// Introduced in kernel v4.1.
#[doc(alias = "BPF_PROG_TYPE_KPROBE")]
KProbe = bpf_prog_type::BPF_PROG_TYPE_KPROBE as isize,
/// A Traffic Control (TC) Classifier program type. See
/// [`SchedClassifier`](super::tc::SchedClassifier) for the program implementation.
///
/// Introduced in kernel v4.1.
#[doc(alias = "BPF_PROG_TYPE_SCHED_CLS")]
SchedClassifier = bpf_prog_type::BPF_PROG_TYPE_SCHED_CLS as isize,
/// A Traffic Control (TC) Action program type.
///
/// Introduced in kernel v4.1.
#[doc(alias = "BPF_PROG_TYPE_SCHED_ACT")]
SchedAction = bpf_prog_type::BPF_PROG_TYPE_SCHED_ACT as isize,
/// A Tracepoint program type. See [`TracePoint`](super::trace_point::TracePoint) for the
/// program implementation.
///
/// Introduced in kernel v4.7.
#[doc(alias = "BPF_PROG_TYPE_TRACEPOINT")]
TracePoint = bpf_prog_type::BPF_PROG_TYPE_TRACEPOINT as isize,
/// An Express Data Path (XDP) program type. See [`Xdp`](super::xdp::Xdp) for the program
/// implementation.
///
/// Introduced in kernel v4.8.
#[doc(alias = "BPF_PROG_TYPE_XDP")]
Xdp = bpf_prog_type::BPF_PROG_TYPE_XDP as isize,
/// A Perf Event program type. See [`PerfEvent`](super::perf_event::PerfEvent) for the program
/// implementation.
///
/// Introduced in kernel v4.9.
#[doc(alias = "BPF_PROG_TYPE_PERF_EVENT")]
PerfEvent = bpf_prog_type::BPF_PROG_TYPE_PERF_EVENT as isize,
/// A cGroup Socket Buffer program type. See [`CgroupSkb`](super::cgroup_skb::CgroupSkb) for
/// the program implementation.
///
/// Introduced in kernel v4.10.
#[doc(alias = "BPF_PROG_TYPE_CGROUP_SKB")]
CgroupSkb = bpf_prog_type::BPF_PROG_TYPE_CGROUP_SKB as isize,
/// A cGroup Socket program type. See [`CgroupSock`](super::cgroup_sock::CgroupSock) for the
/// program implementation.
///
/// Introduced in kernel v4.10.
#[doc(alias = "BPF_PROG_TYPE_CGROUP_SOCK")]
CgroupSock = bpf_prog_type::BPF_PROG_TYPE_CGROUP_SOCK as isize,
/// A Lightweight Tunnel (LWT) Input program type.
///
/// Introduced in kernel v4.10.
#[doc(alias = "BPF_PROG_TYPE_LWT_IN")]
LwtInput = bpf_prog_type::BPF_PROG_TYPE_LWT_IN as isize,
/// A Lightweight Tunnel (LWT) Output program type.
///
/// Introduced in kernel v4.10.
#[doc(alias = "BPF_PROG_TYPE_LWT_OUT")]
LwtOutput = bpf_prog_type::BPF_PROG_TYPE_LWT_OUT as isize,
/// A Lightweight Tunnel (LWT) Transmit program type.
///
/// Introduced in kernel v4.10.
#[doc(alias = "BPF_PROG_TYPE_LWT_XMIT")]
LwtXmit = bpf_prog_type::BPF_PROG_TYPE_LWT_XMIT as isize,
/// A Socket Operation program type. See [`SockOps`](super::sock_ops::SockOps) for the program
/// implementation.
///
/// Introduced in kernel v4.13.
#[doc(alias = "BPF_PROG_TYPE_SOCK_OPS")]
SockOps = bpf_prog_type::BPF_PROG_TYPE_SOCK_OPS as isize,
/// A Socket-to-Socket Buffer program type. See [`SkSkb`](super::sk_skb::SkSkb) for the program
/// implementation.
///
/// Introduced in kernel v4.14.
#[doc(alias = "BPF_PROG_TYPE_SK_SKB")]
SkSkb = bpf_prog_type::BPF_PROG_TYPE_SK_SKB as isize,
/// A cGroup Device program type. See [`CgroupDevice`](super::cgroup_device::CgroupDevice)
/// for the program implementation.
///
/// Introduced in kernel v4.15.
#[doc(alias = "BPF_PROG_TYPE_CGROUP_DEVICE")]
CgroupDevice = bpf_prog_type::BPF_PROG_TYPE_CGROUP_DEVICE as isize,
/// A Socket Message program type. See [`SkMsg`](super::sk_msg::SkMsg) for the program
/// implementation.
///
/// Introduced in kernel v4.17.
#[doc(alias = "BPF_PROG_TYPE_SK_MSG")]
SkMsg = bpf_prog_type::BPF_PROG_TYPE_SK_MSG as isize,
/// A Raw Tracepoint program type. See [`RawTracePoint`](super::raw_trace_point::RawTracePoint)
/// for the program implementation.
///
/// Introduced in kernel v4.17.
#[doc(alias = "BPF_PROG_TYPE_RAW_TRACEPOINT")]
RawTracePoint = bpf_prog_type::BPF_PROG_TYPE_RAW_TRACEPOINT as isize,
/// A cGroup Socket Address program type. See
/// [`CgroupSockAddr`](super::cgroup_sock_addr::CgroupSockAddr) for the program implementation.
///
/// Introduced in kernel v4.17.
#[doc(alias = "BPF_PROG_TYPE_CGROUP_SOCK_ADDR")]
CgroupSockAddr = bpf_prog_type::BPF_PROG_TYPE_CGROUP_SOCK_ADDR as isize,
/// A Lightweight Tunnel (LWT) Seg6local program type.
///
/// Introduced in kernel v4.18.
#[doc(alias = "BPF_PROG_TYPE_LWT_SEG6LOCAL")]
LwtSeg6local = bpf_prog_type::BPF_PROG_TYPE_LWT_SEG6LOCAL as isize,
/// A Linux Infrared Remote Control (LIRC) Mode2 program type. See
/// [`LircMode2`](super::lirc_mode2::LircMode2) for the program implementation.
///
/// Introduced in kernel v4.18.
#[doc(alias = "BPF_PROG_TYPE_LIRC_MODE2")]
LircMode2 = bpf_prog_type::BPF_PROG_TYPE_LIRC_MODE2 as isize,
/// A Socket Reuseport program type.
///
/// Introduced in kernel v4.19.
#[doc(alias = "BPF_PROG_TYPE_SK_REUSEPORT")]
SkReuseport = bpf_prog_type::BPF_PROG_TYPE_SK_REUSEPORT as isize,
/// A Flow Dissector program type.
///
/// Introduced in kernel v4.20.
#[doc(alias = "BPF_PROG_TYPE_FLOW_DISSECTOR")]
FlowDissector = bpf_prog_type::BPF_PROG_TYPE_FLOW_DISSECTOR as isize,
/// A cGroup Sysctl program type. See [`CgroupSysctl`](super::cgroup_sysctl::CgroupSysctl) for
/// the program implementation.
///
/// Introduced in kernel v5.2.
#[doc(alias = "BPF_PROG_TYPE_CGROUP_SYSCTL")]
CgroupSysctl = bpf_prog_type::BPF_PROG_TYPE_CGROUP_SYSCTL as isize,
/// A Writable Raw Tracepoint program type.
///
/// Introduced in kernel v5.2.
#[doc(alias = "BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE")]
RawTracePointWritable = bpf_prog_type::BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE as isize,
/// A cGroup Socket Option program type. See [`CgroupSockopt`](super::cgroup_sockopt::CgroupSockopt)
/// for the program implementation.
///
/// Introduced in kernel v5.3.
#[doc(alias = "BPF_PROG_TYPE_CGROUP_SOCKOPT")]
CgroupSockopt = bpf_prog_type::BPF_PROG_TYPE_CGROUP_SOCKOPT as isize,
/// A Tracing program type. See [`FEntry`](super::fentry::FEntry), [`FExit`](super::fexit::FExit),
/// and [`BtfTracePoint`](super::tp_btf::BtfTracePoint) for the program implementations.
///
/// Introduced in kernel v5.5.
#[doc(alias = "BPF_PROG_TYPE_TRACING")]
Tracing = bpf_prog_type::BPF_PROG_TYPE_TRACING as isize,
/// A Struct Ops program type.
///
/// Introduced in kernel v5.6.
#[doc(alias = "BPF_PROG_TYPE_STRUCT_OPS")]
StructOps = bpf_prog_type::BPF_PROG_TYPE_STRUCT_OPS as isize,
/// A Extension program type. See [`Extension`](super::extension::Extension) for the program
/// implementation.
///
/// Introduced in kernel v5.6.
#[doc(alias = "BPF_PROG_TYPE_EXT")]
Extension = bpf_prog_type::BPF_PROG_TYPE_EXT as isize,
/// A Linux Security Module (LSM) program type. See [`Lsm`](super::lsm::Lsm) for the program
/// implementation.
///
/// Introduced in kernel v5.7.
#[doc(alias = "BPF_PROG_TYPE_LSM")]
Lsm = bpf_prog_type::BPF_PROG_TYPE_LSM as isize,
/// A Socket Lookup program type. See [`SkLookup`](super::sk_lookup::SkLookup) for the program
/// implementation.
///
/// Introduced in kernel v5.9.
#[doc(alias = "BPF_PROG_TYPE_SK_LOOKUP")]
SkLookup = bpf_prog_type::BPF_PROG_TYPE_SK_LOOKUP as isize,
/// A Syscall program type.
///
/// Introduced in kernel v5.14.
#[doc(alias = "BPF_PROG_TYPE_SYSCALL")]
Syscall = bpf_prog_type::BPF_PROG_TYPE_SYSCALL as isize,
/// A Netfilter program type.
///
/// Introduced in kernel v6.4.
#[doc(alias = "BPF_PROG_TYPE_NETFILTER")]
Netfilter = bpf_prog_type::BPF_PROG_TYPE_NETFILTER as isize,
}
impl TryFrom<bpf_prog_type> for ProgramType {
type Error = ProgramError;
fn try_from(prog_type: bpf_prog_type) -> Result<Self, Self::Error> {
use bpf_prog_type::*;
Ok(match prog_type {
BPF_PROG_TYPE_UNSPEC => Self::Unspecified,
BPF_PROG_TYPE_SOCKET_FILTER => Self::SocketFilter,
BPF_PROG_TYPE_KPROBE => Self::KProbe,
BPF_PROG_TYPE_SCHED_CLS => Self::SchedClassifier,
BPF_PROG_TYPE_SCHED_ACT => Self::SchedAction,
BPF_PROG_TYPE_TRACEPOINT => Self::TracePoint,
BPF_PROG_TYPE_XDP => Self::Xdp,
BPF_PROG_TYPE_PERF_EVENT => Self::PerfEvent,
BPF_PROG_TYPE_CGROUP_SKB => Self::CgroupSkb,
BPF_PROG_TYPE_CGROUP_SOCK => Self::CgroupSock,
BPF_PROG_TYPE_LWT_IN => Self::LwtInput,
BPF_PROG_TYPE_LWT_OUT => Self::LwtOutput,
BPF_PROG_TYPE_LWT_XMIT => Self::LwtXmit,
BPF_PROG_TYPE_SOCK_OPS => Self::SockOps,
BPF_PROG_TYPE_SK_SKB => Self::SkSkb,
BPF_PROG_TYPE_CGROUP_DEVICE => Self::CgroupDevice,
BPF_PROG_TYPE_SK_MSG => Self::SkMsg,
BPF_PROG_TYPE_RAW_TRACEPOINT => Self::RawTracePoint,
BPF_PROG_TYPE_CGROUP_SOCK_ADDR => Self::CgroupSockAddr,
BPF_PROG_TYPE_LWT_SEG6LOCAL => Self::LwtSeg6local,
BPF_PROG_TYPE_LIRC_MODE2 => Self::LircMode2,
BPF_PROG_TYPE_SK_REUSEPORT => Self::SkReuseport,
BPF_PROG_TYPE_FLOW_DISSECTOR => Self::FlowDissector,
BPF_PROG_TYPE_CGROUP_SYSCTL => Self::CgroupSysctl,
BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE => Self::RawTracePointWritable,
BPF_PROG_TYPE_CGROUP_SOCKOPT => Self::CgroupSockopt,
BPF_PROG_TYPE_TRACING => Self::Tracing,
BPF_PROG_TYPE_STRUCT_OPS => Self::StructOps,
BPF_PROG_TYPE_EXT => Self::Extension,
BPF_PROG_TYPE_LSM => Self::Lsm,
BPF_PROG_TYPE_SK_LOOKUP => Self::SkLookup,
BPF_PROG_TYPE_SYSCALL => Self::Syscall,
BPF_PROG_TYPE_NETFILTER => Self::Netfilter,
__MAX_BPF_PROG_TYPE => return Err(ProgramError::UnexpectedProgramType),
})
}
}

@ -37,6 +37,7 @@
//! [`maps`]: crate::maps
// modules we don't export
mod info;
mod probe;
mod utils;
@ -71,13 +72,13 @@ pub mod xdp;
use std::{
ffi::CString,
io,
num::NonZeroU32,
os::fd::{AsFd, AsRawFd, BorrowedFd},
path::{Path, PathBuf},
sync::Arc,
time::{Duration, SystemTime},
};
use info::impl_info;
pub use info::{loaded_programs, ProgramInfo, ProgramType};
use libc::ENOSPC;
use thiserror::Error;
@ -115,18 +116,13 @@ use crate::{
maps::MapError,
obj::{self, btf::BtfError, VerifierLog},
pin::PinError,
programs::{
links::*,
perf_attach::*,
utils::{boot_time, get_fdinfo},
},
programs::{links::*, perf_attach::*},
sys::{
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,
EbpfLoadProgramAttrs, SyscallError,
bpf_load_program, bpf_pin_object, bpf_prog_get_fd_by_id, bpf_prog_query, iter_link_ids,
retry_with_verifier_logs, EbpfLoadProgramAttrs, SyscallError,
},
util::{bytes_of_bpf_name, KernelVersion},
util::KernelVersion,
VerifierLogLevel,
};
@ -242,7 +238,7 @@ impl AsFd for ProgramFd {
}
}
/// eBPF program type.
/// The various eBPF programs.
#[derive(Debug)]
pub enum Program {
/// A [`KProbe`] program
@ -296,34 +292,30 @@ pub enum Program {
}
impl Program {
/// Returns the low level program type.
pub fn prog_type(&self) -> bpf_prog_type {
use crate::generated::bpf_prog_type::*;
/// Returns the program type.
pub fn prog_type(&self) -> ProgramType {
match self {
Self::KProbe(_) => BPF_PROG_TYPE_KPROBE,
Self::UProbe(_) => BPF_PROG_TYPE_KPROBE,
Self::TracePoint(_) => BPF_PROG_TYPE_TRACEPOINT,
Self::SocketFilter(_) => BPF_PROG_TYPE_SOCKET_FILTER,
Self::Xdp(_) => BPF_PROG_TYPE_XDP,
Self::SkMsg(_) => BPF_PROG_TYPE_SK_MSG,
Self::SkSkb(_) => BPF_PROG_TYPE_SK_SKB,
Self::SockOps(_) => BPF_PROG_TYPE_SOCK_OPS,
Self::SchedClassifier(_) => BPF_PROG_TYPE_SCHED_CLS,
Self::CgroupSkb(_) => BPF_PROG_TYPE_CGROUP_SKB,
Self::CgroupSysctl(_) => BPF_PROG_TYPE_CGROUP_SYSCTL,
Self::CgroupSockopt(_) => BPF_PROG_TYPE_CGROUP_SOCKOPT,
Self::LircMode2(_) => BPF_PROG_TYPE_LIRC_MODE2,
Self::PerfEvent(_) => BPF_PROG_TYPE_PERF_EVENT,
Self::RawTracePoint(_) => BPF_PROG_TYPE_RAW_TRACEPOINT,
Self::Lsm(_) => BPF_PROG_TYPE_LSM,
Self::BtfTracePoint(_) => BPF_PROG_TYPE_TRACING,
Self::FEntry(_) => BPF_PROG_TYPE_TRACING,
Self::FExit(_) => BPF_PROG_TYPE_TRACING,
Self::Extension(_) => BPF_PROG_TYPE_EXT,
Self::CgroupSockAddr(_) => BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
Self::SkLookup(_) => BPF_PROG_TYPE_SK_LOOKUP,
Self::CgroupSock(_) => BPF_PROG_TYPE_CGROUP_SOCK,
Self::CgroupDevice(_) => BPF_PROG_TYPE_CGROUP_DEVICE,
Self::KProbe(_) | Self::UProbe(_) => ProgramType::KProbe,
Self::TracePoint(_) => ProgramType::TracePoint,
Self::SocketFilter(_) => ProgramType::SocketFilter,
Self::Xdp(_) => ProgramType::Xdp,
Self::SkMsg(_) => ProgramType::SkMsg,
Self::SkSkb(_) => ProgramType::SkSkb,
Self::SockOps(_) => ProgramType::SockOps,
Self::SchedClassifier(_) => ProgramType::SchedClassifier,
Self::CgroupSkb(_) => ProgramType::CgroupSkb,
Self::CgroupSysctl(_) => ProgramType::CgroupSysctl,
Self::CgroupSockopt(_) => ProgramType::CgroupSockopt,
Self::LircMode2(_) => ProgramType::LircMode2,
Self::PerfEvent(_) => ProgramType::PerfEvent,
Self::RawTracePoint(_) => ProgramType::RawTracePoint,
Self::Lsm(_) => ProgramType::Lsm,
Self::BtfTracePoint(_) | Self::FEntry(_) | Self::FExit(_) => ProgramType::Tracing,
Self::Extension(_) => ProgramType::Extension,
Self::CgroupSockAddr(_) => ProgramType::CgroupSockAddr,
Self::SkLookup(_) => ProgramType::SkLookup,
Self::CgroupSock(_) => ProgramType::CgroupSock,
Self::CgroupDevice(_) => ProgramType::CgroupDevice,
}
}
@ -952,28 +944,6 @@ impl_try_from_program!(
CgroupDevice,
);
/// Returns information about a loaded program with the [`ProgramInfo`] structure.
///
/// This information is populated at load time by the kernel and can be used
/// to correlate a given [`Program`] to it's corresponding [`ProgramInfo`]
/// metadata.
macro_rules! impl_info {
($($struct_name:ident),+ $(,)?) => {
$(
impl $struct_name {
/// Returns metadata information of this program.
///
/// Uses kernel v4.13 features.
pub fn info(&self) -> Result<ProgramInfo, ProgramError> {
let ProgramFd(fd) = self.fd()?;
ProgramInfo::new_from_fd(fd.as_fd())
}
}
)+
}
}
impl_info!(
KProbe,
UProbe,
@ -1001,182 +971,6 @@ impl_info!(
CgroupDevice,
);
/// Provides information about a loaded program, like name, id and statistics
#[doc(alias = "bpf_prog_info")]
#[derive(Debug)]
pub struct ProgramInfo(bpf_prog_info);
impl ProgramInfo {
fn new_from_fd(fd: BorrowedFd<'_>) -> Result<Self, ProgramError> {
let info = bpf_prog_get_info_by_fd(fd, &mut [])?;
Ok(Self(info))
}
/// The name of the program as was provided when it was load. This is limited to 16 bytes
pub fn name(&self) -> &[u8] {
bytes_of_bpf_name(&self.0.name)
}
/// 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 id for this program. Each program has a unique id.
pub fn id(&self) -> u32 {
self.0.id
}
/// The program tag.
///
/// The program tag is a SHA sum of the program's instructions which be used as an alternative to
/// [`Self::id()`]". 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 program type as defined by 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
}
/// The ids of the maps used by the program.
pub fn map_ids(&self) -> Result<Vec<u32>, ProgramError> {
let ProgramFd(fd) = self.fd()?;
let mut map_ids = vec![0u32; self.0.nr_map_ids as usize];
bpf_prog_get_info_by_fd(fd.as_fd(), &mut map_ids)?;
Ok(map_ids)
}
/// The btf id for the program.
pub fn btf_id(&self) -> Option<NonZeroU32> {
NonZeroU32::new(self.0.btf_id)
}
/// The size in bytes of the program's translated eBPF bytecode, which is
/// the bytecode after it has been passed though the verifier where it was
/// possibly modified by the kernel.
pub fn size_translated(&self) -> u32 {
self.0.xlated_prog_len
}
/// The size in bytes of the program's JIT-compiled machine code.
pub fn size_jitted(&self) -> u32 {
self.0.jited_prog_len
}
/// How much memory in bytes has been allocated and locked for the program.
pub fn memory_locked(&self) -> Result<u32, ProgramError> {
get_fdinfo(self.fd()?.as_fd(), "memlock")
}
/// The number of verified instructions in the program.
///
/// This may be less than the total number of instructions in the compiled
/// program due to dead code elimination in the verifier.
pub fn verified_instruction_count(&self) -> u32 {
self.0.verified_insns
}
/// The time the program was loaded.
pub fn loaded_at(&self) -> SystemTime {
boot_time() + Duration::from_nanos(self.0.load_time)
}
/// Returns a file descriptor referencing the program.
///
/// The returned file descriptor can be closed at any time and doing so does
/// not influence the life cycle of the program.
pub fn fd(&self) -> Result<ProgramFd, ProgramError> {
let Self(info) = self;
let fd = bpf_prog_get_fd_by_id(info.id)?;
Ok(ProgramFd(fd))
}
/// The accumulated time that the program has been actively running.
///
/// This is not to be confused with the duration since the program was
/// first loaded on the host.
///
/// Note this field is only updated for as long as
/// [`enable_stats`](crate::sys::enable_stats) is enabled
/// with [`Stats::RunTime`](crate::sys::Stats::RunTime).
pub fn run_time(&self) -> Duration {
Duration::from_nanos(self.0.run_time_ns)
}
/// The accumulated execution count of the program.
///
/// Note this field is only updated for as long as
/// [`enable_stats`](crate::sys::enable_stats) is enabled
/// with [`Stats::RunTime`](crate::sys::Stats::RunTime).
pub fn run_count(&self) -> u64 {
self.0.run_cnt
}
/// Loads a program from a pinned path in bpffs.
pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, ProgramError> {
use std::os::unix::ffi::OsStrExt as _;
// TODO: avoid this unwrap by adding a new error variant.
let path_string = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap();
let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError {
call: "BPF_OBJ_GET",
io_error,
})?;
let info = bpf_prog_get_info_by_fd(fd.as_fd(), &mut [])?;
Ok(Self(info))
}
}
/// Returns an iterator over all loaded bpf programs.
///
/// This differs from [`crate::Ebpf::programs`] since it will return all programs
/// listed on the host system and not only programs a specific [`crate::Ebpf`] instance.
///
/// Uses kernel v4.13 features.
///
/// # 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() -> impl Iterator<Item = Result<ProgramInfo, ProgramError>> {
iter_prog_ids()
.map(|id| {
let id = id?;
bpf_prog_get_fd_by_id(id)
})
.map(|fd| {
let fd = fd?;
bpf_prog_get_info_by_fd(fd.as_fd(), &mut [])
})
.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)]

@ -8,7 +8,7 @@ use std::{
};
use assert_matches::assert_matches;
use libc::{E2BIG, ENOENT, ENOSPC};
use libc::{ENOENT, ENOSPC};
use obj::{
btf::{BtfEnum64, Enum64},
generated::bpf_stats_type,
@ -32,7 +32,7 @@ use crate::{
},
sys::{syscall, SysResult, Syscall, SyscallError},
util::KernelVersion,
Btf, Pod, VerifierLogLevel, BPF_OBJ_NAME_LEN,
Btf, Pod, VerifierLogLevel, BPF_OBJ_NAME_LEN, FEATURES,
};
pub(crate) fn bpf_create_map(
@ -105,6 +105,7 @@ pub(crate) fn bpf_pin_object(fd: BorrowedFd<'_>, path: &CStr) -> SysResult<i64>
sys_bpf(bpf_cmd::BPF_OBJ_PIN, &mut attr)
}
/// Introduced in kernel v4.4.
pub(crate) fn bpf_get_object(path: &CStr) -> SysResult<crate::MockableFd> {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
let u = unsafe { &mut attr.__bindgen_anon_4 };
@ -548,23 +549,14 @@ pub(crate) fn bpf_prog_get_info_by_fd(
fd: BorrowedFd<'_>,
map_ids: &mut [u32],
) -> Result<bpf_prog_info, SyscallError> {
// Attempt syscall with the map info filled.
let mut info = bpf_obj_get_info_by_fd(fd, |info: &mut bpf_prog_info| {
info.nr_map_ids = map_ids.len() as _;
info.map_ids = map_ids.as_mut_ptr() as _;
});
// An `E2BIG` error can occur on kernels below v4.15 when handing over a large struct where the
// extra space is not all-zero bytes.
if let Err(err) = &info {
if let Some(errno) = &err.io_error.raw_os_error() {
if errno == &E2BIG {
info = bpf_obj_get_info_by_fd(fd, |_| {});
}
bpf_obj_get_info_by_fd(fd, |info: &mut bpf_prog_info| {
if FEATURES.prog_info_map_ids() {
info.nr_map_ids = map_ids.len() as _;
info.map_ids = map_ids.as_mut_ptr() as _;
}
}
info
})
}
/// Introduced in kernel v4.13.
@ -711,6 +703,62 @@ pub(crate) fn is_prog_name_supported() -> bool {
bpf_prog_load(&mut attr).is_ok()
}
/// Tests whether `nr_map_ids` & `map_ids` fields in `bpf_prog_info` is available.
pub(crate) fn is_info_map_ids_supported() -> bool {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
let u = unsafe { &mut attr.__bindgen_anon_3 };
u.prog_type = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32;
let prog: &[u8] = &[
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit
];
let insns = copy_instructions(prog).unwrap();
u.insn_cnt = insns.len() as u32;
u.insns = insns.as_ptr() as u64;
let gpl = b"GPL\0";
u.license = gpl.as_ptr() as u64;
let prog_fd = match bpf_prog_load(&mut attr) {
Ok(fd) => fd,
Err(_) => return false,
};
bpf_obj_get_info_by_fd(prog_fd.as_fd(), |info: &mut bpf_prog_info| {
info.nr_map_ids = 1
})
.is_ok()
}
/// Tests whether `gpl_compatible` field in `bpf_prog_info` is available.
pub(crate) fn is_info_gpl_compatible_supported() -> bool {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
let u = unsafe { &mut attr.__bindgen_anon_3 };
u.prog_type = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32;
let prog: &[u8] = &[
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit
];
let insns = copy_instructions(prog).unwrap();
u.insn_cnt = insns.len() as u32;
u.insns = insns.as_ptr() as u64;
let gpl = b"GPL\0";
u.license = gpl.as_ptr() as u64;
let prog_fd = match bpf_prog_load(&mut attr) {
Ok(fd) => fd,
Err(_) => return false,
};
if let Ok::<bpf_prog_info, _>(info) = bpf_obj_get_info_by_fd(prog_fd.as_fd(), |_| {}) {
return info.gpl_compatible() != 0;
}
false
}
pub(crate) fn is_probe_read_kernel_supported() -> bool {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
let u = unsafe { &mut attr.__bindgen_anon_3 };

@ -1,82 +1,229 @@
//! Tests the Info API.
use std::{fs, time::SystemTime};
use std::{fs, panic, path::Path, time::SystemTime};
use aya::{
maps::{loaded_maps, MapError},
programs::{loaded_programs, ProgramError, SocketFilter},
programs::{loaded_programs, ProgramError, ProgramType, SocketFilter, TracePoint},
sys::enable_stats,
util::KernelVersion,
Ebpf,
};
use aya_obj::generated::{bpf_map_type, bpf_prog_type};
use aya_obj::generated::bpf_map_type;
use libc::EINVAL;
use crate::utils::{kernel_assert, kernel_assert_eq};
const BPF_JIT_ENABLE: &str = "/proc/sys/net/core/bpf_jit_enable";
const BPF_STATS_ENABLED: &str = "/proc/sys/kernel/bpf_stats_enabled";
#[test]
fn list_loaded_programs() {
// Kernels below v4.15 have been observed to have `bpf_jit_enable` disabled by default.
let jit_enabled = enable_jit();
fn test_loaded_programs() {
// Load a program.
// Since we are only testing the programs for their metadata, there is no need to "attach" them.
let mut bpf = Ebpf::load(crate::SIMPLE_PROG).unwrap();
let prog: &mut SocketFilter = bpf.program_mut("simple_prog").unwrap().try_into().unwrap();
prog.load().unwrap();
let test_prog = prog.info().unwrap();
// Ensure the `loaded_programs()` api does not panic and grab the last loaded program in the
// iter, which should be our test program.
let prog = match loaded_programs().last().unwrap() {
Ok(prog) => prog,
Err(err) => {
if let ProgramError::SyscallError(err) = &err {
// Skip entire test since feature not available
if err
.io_error
.raw_os_error()
.is_some_and(|errno| errno == EINVAL)
{
eprintln!("ignoring test completely as `loaded_programs()` is not available on the host");
return;
}
// Ensure loaded program doesn't panic
let mut programs = loaded_programs().peekable();
if let Err(err) = programs.peek().unwrap() {
if let ProgramError::SyscallError(err) = &err {
// Skip entire test since feature not available
if err
.io_error
.raw_os_error()
.is_some_and(|errno| errno == EINVAL)
{
eprintln!(
"ignoring test completely as `loaded_programs()` is not available on the host"
);
return;
}
panic!("{err}");
}
};
panic!("{err}");
}
// Loaded programs should contain our test program
let mut programs = programs.filter_map(|prog| prog.ok());
kernel_assert!(
programs.any(|prog| prog.id() == test_prog.id()),
KernelVersion::new(4, 13, 0)
);
}
#[test]
fn test_program_info() {
// Kernels below v4.15 have been observed to have `bpf_jit_enable` disabled by default.
let previously_enabled = is_sysctl_enabled(BPF_JIT_ENABLE);
// Restore to previous state when panic occurs.
let prev_panic = panic::take_hook();
panic::set_hook(Box::new(move |panic_info| {
if !previously_enabled {
disable_sysctl_param(BPF_JIT_ENABLE);
}
prev_panic(panic_info);
}));
let jit_enabled = previously_enabled || enable_sysctl_param(BPF_JIT_ENABLE);
let mut bpf = Ebpf::load(crate::SIMPLE_PROG).unwrap();
let prog: &mut SocketFilter = bpf.program_mut("simple_prog").unwrap().try_into().unwrap();
prog.load().unwrap();
let test_prog = prog.info().unwrap();
// Test `bpf_prog_info` fields.
kernel_assert_eq!(
bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32,
prog.program_type(),
ProgramType::SocketFilter,
test_prog.program_type().unwrap_or(ProgramType::Unspecified),
KernelVersion::new(4, 13, 0),
);
kernel_assert!(prog.id() > 0, KernelVersion::new(4, 13, 0));
kernel_assert!(prog.tag() > 0, KernelVersion::new(4, 13, 0));
kernel_assert!(test_prog.id().is_some(), KernelVersion::new(4, 13, 0));
kernel_assert!(test_prog.tag().is_some(), KernelVersion::new(4, 13, 0));
if jit_enabled {
kernel_assert!(prog.size_jitted() > 0, KernelVersion::new(4, 13, 0));
kernel_assert!(
test_prog.size_jitted().is_some(),
KernelVersion::new(4, 13, 0),
);
}
kernel_assert!(prog.size_translated() > 0, KernelVersion::new(4, 13, 0));
let uptime = SystemTime::now().duration_since(prog.loaded_at()).unwrap();
kernel_assert!(uptime.as_nanos() > 0, KernelVersion::new(4, 15, 0));
let maps = prog.map_ids().unwrap();
kernel_assert!(maps.is_empty(), KernelVersion::new(4, 15, 0));
let name = prog.name_as_str().unwrap();
kernel_assert_eq!("simple_prog", name, KernelVersion::new(4, 15, 0));
kernel_assert!(prog.gpl_compatible(), KernelVersion::new(4, 18, 0));
kernel_assert!(
prog.verified_instruction_count() > 0,
KernelVersion::new(5, 16, 0)
test_prog.size_translated().is_some(),
KernelVersion::new(4, 13, 0),
);
kernel_assert!(
test_prog.loaded_at().is_some(),
KernelVersion::new(4, 15, 0),
);
kernel_assert!(
test_prog.created_by_uid().is_some_and(|uid| uid == 0),
KernelVersion::new(4, 15, 0),
);
let maps = test_prog.map_ids().unwrap();
kernel_assert!(
maps.is_some_and(|ids| ids.is_empty()),
KernelVersion::new(4, 15, 0),
);
kernel_assert!(
test_prog
.name_as_str()
.is_some_and(|name| name == "simple_prog"),
KernelVersion::new(4, 15, 0),
);
kernel_assert!(
test_prog.gpl_compatible().is_some_and(|gpl| gpl),
KernelVersion::new(4, 18, 0),
);
kernel_assert!(
test_prog.verified_instruction_count().is_some(),
KernelVersion::new(5, 16, 0),
);
// We can't reliably test these fields since `0` can be interpreted as the actual value or
// unavailable.
prog.btf_id();
test_prog.btf_id();
// Ensure rest of the fields do not panic.
prog.memory_locked().unwrap();
prog.fd().unwrap();
test_prog.memory_locked().unwrap();
test_prog.fd().unwrap();
// Restore to previous state
if !previously_enabled {
disable_sysctl_param(BPF_JIT_ENABLE);
}
}
#[test]
fn test_loaded_at() {
let mut bpf: Ebpf = Ebpf::load(crate::SIMPLE_PROG).unwrap();
let prog: &mut SocketFilter = bpf.program_mut("simple_prog").unwrap().try_into().unwrap();
// SystemTime is not monotonic, which can cause this test to flake. We don't expect the clock
// timestamp to continuously jump around, so we add some retries. If the test is ever correct,
// we know that the value returned by loaded_at() was reasonable relative to SystemTime::now().
let mut failures = Vec::new();
for _ in 0..5 {
let t1 = SystemTime::now();
prog.load().unwrap();
let t2 = SystemTime::now();
let loaded_at = match prog.info().unwrap().loaded_at() {
Some(time) => time,
None => {
eprintln!("ignoring test completely as `load_time` field of `bpf_prog_info` is not available on the host");
return;
}
};
prog.unload().unwrap();
let range = t1..t2;
if range.contains(&loaded_at) {
failures.clear();
break;
}
failures.push(LoadedAtRange(loaded_at, range));
}
assert!(
failures.is_empty(),
"loaded_at was not in range: {failures:?}",
);
struct LoadedAtRange(SystemTime, std::ops::Range<SystemTime>);
impl std::fmt::Debug for LoadedAtRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self(loaded_at, range) = self;
write!(f, "{range:?}.contains({loaded_at:?})")
}
}
}
#[test]
fn test_prog_stats() {
// Test depends on whether trace point exists.
if !Path::new("/sys/kernel/debug/tracing/events/syscalls/sys_enter_bpf").exists() {
eprintln!(
"ignoring test completely as `syscalls/sys_enter_bpf` is not available on the host"
);
return;
}
let stats_fd = enable_stats(aya::sys::Stats::RunTime).ok();
// Restore to previous state when panic occurs.
let previously_enabled = is_sysctl_enabled(BPF_STATS_ENABLED);
let prev_panic = panic::take_hook();
panic::set_hook(Box::new(move |panic_info| {
if !previously_enabled {
disable_sysctl_param(BPF_STATS_ENABLED);
}
prev_panic(panic_info);
}));
let stats_enabled =
stats_fd.is_some() || previously_enabled || enable_sysctl_param(BPF_STATS_ENABLED);
if !stats_enabled {
eprintln!("ignoring test completely as bpf stats could not be enabled on the host");
return;
}
let mut bpf = Ebpf::load(crate::TEST).unwrap();
let prog: &mut TracePoint = bpf
.program_mut("test_tracepoint")
.unwrap()
.try_into()
.unwrap();
prog.load().unwrap();
prog.attach("syscalls", "sys_enter_bpf").unwrap();
let test_prog = prog.info().unwrap();
kernel_assert!(
test_prog.run_time().as_nanos() > 0,
KernelVersion::new(5, 1, 0)
);
kernel_assert!(test_prog.run_count() > 0, KernelVersion::new(5, 1, 0));
// Restore to previous state
if !previously_enabled {
disable_sysctl_param(BPF_STATS_ENABLED);
}
}
#[test]
@ -108,19 +255,22 @@ fn list_loaded_maps() {
// There's not a good way to extract our maps of interest with load order being
// non-deterministic. Since we are trying to be more considerate of older kernels, we should
// only rely on v4.13 feats.
// Expected sort order should be: `BAR`, `aya_global` (if ran local), `FOO`
// Expected sort order should be: `BAR`, `aya_global` (if avail), `FOO`
maps.sort_unstable_by_key(|m| (m.map_type(), m.id()));
// Ensure program has the 2 maps.
if let Ok(info) = prog.info() {
let map_ids = info.map_ids().unwrap();
kernel_assert_eq!(2, map_ids.len(), KernelVersion::new(4, 15, 0));
kernel_assert!(map_ids.is_some(), KernelVersion::new(4, 15, 0));
for id in map_ids.iter() {
assert!(
maps.iter().any(|m| m.id() == *id),
"expected `loaded_maps()` to have `map_ids` from program"
);
if let Some(map_ids) = map_ids {
assert_eq!(2, map_ids.len());
for id in map_ids.iter() {
assert!(
maps.iter().any(|m| m.id() == id.get()),
"expected `loaded_maps()` to have `map_ids` from program"
);
}
}
}
@ -164,18 +314,20 @@ fn list_loaded_maps() {
array.fd().unwrap();
}
/// Enable program to be JIT-compiled if not already enabled.
fn enable_jit() -> bool {
match fs::read_to_string(BPF_JIT_ENABLE) {
Ok(contents) => {
if contents.chars().next().is_some_and(|c| c == '0') {
let failed = fs::write(BPF_JIT_ENABLE, b"1").is_err();
if failed {
return false;
}
}
true
}
/// Whether sysctl parameter is enabled in the `/proc` file.
fn is_sysctl_enabled(path: &str) -> bool {
match fs::read_to_string(path) {
Ok(contents) => contents.chars().next().is_some_and(|c| c == '1'),
Err(_) => false,
}
}
/// Enable sysctl parameter through procfs.
fn enable_sysctl_param(path: &str) -> bool {
fs::write(path, b"1").is_ok()
}
/// Disable sysctl parameter through procfs.
fn disable_sysctl_param(path: &str) -> bool {
fs::write(path, b"0").is_ok()
}

@ -1,10 +1,4 @@
use std::{
convert::TryInto as _,
fs::remove_file,
path::Path,
thread,
time::{Duration, SystemTime},
};
use std::{convert::TryInto as _, fs::remove_file, path::Path, thread, time::Duration};
use aya::{
maps::Array,
@ -144,7 +138,7 @@ fn poll_loaded_program_id(name: &str) -> impl Iterator<Item = Option<u32>> + '_
// program in the middle of a `loaded_programs()` call.
loaded_programs()
.filter_map(|prog| prog.ok())
.find_map(|prog| (prog.name() == name.as_bytes()).then(|| prog.id()))
.find_map(|prog| (prog.name() == name.as_bytes()).then(|| prog.id().unwrap().get()))
})
}
@ -221,42 +215,6 @@ fn unload_xdp() {
assert_unloaded("pass");
}
#[test]
fn test_loaded_at() {
let mut bpf = Ebpf::load(crate::TEST).unwrap();
let prog: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap();
// SystemTime is not monotonic, which can cause this test to flake. We don't expect the clock
// timestamp to continuously jump around, so we add some retries. If the test is ever correct,
// we know that the value returned by loaded_at() was reasonable relative to SystemTime::now().
let mut failures = Vec::new();
for _ in 0..5 {
let t1 = SystemTime::now();
prog.load().unwrap();
let t2 = SystemTime::now();
let loaded_at = prog.info().unwrap().loaded_at();
prog.unload().unwrap();
let range = t1..t2;
if range.contains(&loaded_at) {
failures.clear();
break;
}
failures.push(LoadedAtRange(loaded_at, range));
}
assert!(
failures.is_empty(),
"loaded_at was not in range: {failures:?}",
);
struct LoadedAtRange(SystemTime, std::ops::Range<SystemTime>);
impl std::fmt::Debug for LoadedAtRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self(loaded_at, range) = self;
write!(f, "{range:?}.contains({loaded_at:?})")
}
}
}
#[test]
fn unload_kprobe() {
let mut bpf = Ebpf::load(crate::TEST).unwrap();

@ -7109,6 +7109,8 @@ pub fn aya_obj::Features::bpf_probe_read_kernel(&self) -> bool
pub fn aya_obj::Features::btf(&self) -> core::option::Option<&aya_obj::btf::BtfFeatures>
pub fn aya_obj::Features::cpumap_prog_id(&self) -> bool
pub fn aya_obj::Features::devmap_prog_id(&self) -> bool
pub fn aya_obj::Features::prog_info_gpl_compatible(&self) -> bool
pub fn aya_obj::Features::prog_info_map_ids(&self) -> bool
impl core::default::Default for aya_obj::Features
pub fn aya_obj::Features::default() -> aya_obj::Features
impl core::fmt::Debug for aya_obj::Features
@ -7967,6 +7969,8 @@ pub fn aya_obj::Features::bpf_probe_read_kernel(&self) -> bool
pub fn aya_obj::Features::btf(&self) -> core::option::Option<&aya_obj::btf::BtfFeatures>
pub fn aya_obj::Features::cpumap_prog_id(&self) -> bool
pub fn aya_obj::Features::devmap_prog_id(&self) -> bool
pub fn aya_obj::Features::prog_info_gpl_compatible(&self) -> bool
pub fn aya_obj::Features::prog_info_map_ids(&self) -> bool
impl core::default::Default for aya_obj::Features
pub fn aya_obj::Features::default() -> aya_obj::Features
impl core::fmt::Debug for aya_obj::Features

@ -6644,7 +6644,7 @@ impl aya::programs::Program
pub fn aya::programs::Program::fd(&self) -> core::result::Result<&aya::programs::ProgramFd, aya::programs::ProgramError>
pub fn aya::programs::Program::info(&self) -> core::result::Result<aya::programs::ProgramInfo, aya::programs::ProgramError>
pub fn aya::programs::Program::pin<P: core::convert::AsRef<std::path::Path>>(&mut self, path: P) -> core::result::Result<(), aya::pin::PinError>
pub fn aya::programs::Program::prog_type(&self) -> aya_obj::generated::linux_bindings_x86_64::bpf_prog_type
pub fn aya::programs::Program::prog_type(&self) -> aya::programs::ProgramType
pub fn aya::programs::Program::unload(self) -> core::result::Result<(), aya::programs::ProgramError>
impl core::fmt::Debug for aya::programs::Program
pub fn aya::programs::Program::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
@ -6892,6 +6892,79 @@ impl<T> core::borrow::BorrowMut<T> for aya::programs::ProgramError where T: core
pub fn aya::programs::ProgramError::borrow_mut(&mut self) -> &mut T
impl<T> core::convert::From<T> for aya::programs::ProgramError
pub fn aya::programs::ProgramError::from(t: T) -> T
#[non_exhaustive] pub enum aya::programs::ProgramType
pub aya::programs::ProgramType::CgroupDevice = 15
pub aya::programs::ProgramType::CgroupSkb = 8
pub aya::programs::ProgramType::CgroupSock = 9
pub aya::programs::ProgramType::CgroupSockAddr = 18
pub aya::programs::ProgramType::CgroupSockopt = 25
pub aya::programs::ProgramType::CgroupSysctl = 23
pub aya::programs::ProgramType::Extension = 28
pub aya::programs::ProgramType::FlowDissector = 22
pub aya::programs::ProgramType::KProbe = 2
pub aya::programs::ProgramType::LircMode2 = 20
pub aya::programs::ProgramType::Lsm = 29
pub aya::programs::ProgramType::LwtInput = 10
pub aya::programs::ProgramType::LwtOutput = 11
pub aya::programs::ProgramType::LwtSeg6local = 19
pub aya::programs::ProgramType::LwtXmit = 12
pub aya::programs::ProgramType::Netfilter = 32
pub aya::programs::ProgramType::PerfEvent = 7
pub aya::programs::ProgramType::RawTracePoint = 17
pub aya::programs::ProgramType::RawTracePointWritable = 24
pub aya::programs::ProgramType::SchedAction = 4
pub aya::programs::ProgramType::SchedClassifier = 3
pub aya::programs::ProgramType::SkLookup = 30
pub aya::programs::ProgramType::SkMsg = 16
pub aya::programs::ProgramType::SkReuseport = 21
pub aya::programs::ProgramType::SkSkb = 14
pub aya::programs::ProgramType::SockOps = 13
pub aya::programs::ProgramType::SocketFilter = 1
pub aya::programs::ProgramType::StructOps = 27
pub aya::programs::ProgramType::Syscall = 31
pub aya::programs::ProgramType::TracePoint = 5
pub aya::programs::ProgramType::Tracing = 26
pub aya::programs::ProgramType::Unspecified = 0
pub aya::programs::ProgramType::Xdp = 6
impl core::clone::Clone for aya::programs::ProgramType
pub fn aya::programs::ProgramType::clone(&self) -> aya::programs::ProgramType
impl core::cmp::PartialEq for aya::programs::ProgramType
pub fn aya::programs::ProgramType::eq(&self, other: &aya::programs::ProgramType) -> bool
impl core::convert::TryFrom<aya_obj::generated::linux_bindings_x86_64::bpf_prog_type> for aya::programs::ProgramType
pub type aya::programs::ProgramType::Error = aya::programs::ProgramError
pub fn aya::programs::ProgramType::try_from(prog_type: aya_obj::generated::linux_bindings_x86_64::bpf_prog_type) -> core::result::Result<Self, Self::Error>
impl core::fmt::Debug for aya::programs::ProgramType
pub fn aya::programs::ProgramType::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
impl core::marker::Copy for aya::programs::ProgramType
impl core::marker::StructuralPartialEq for aya::programs::ProgramType
impl core::marker::Freeze for aya::programs::ProgramType
impl core::marker::Send for aya::programs::ProgramType
impl core::marker::Sync for aya::programs::ProgramType
impl core::marker::Unpin for aya::programs::ProgramType
impl core::panic::unwind_safe::RefUnwindSafe for aya::programs::ProgramType
impl core::panic::unwind_safe::UnwindSafe for aya::programs::ProgramType
impl<T, U> core::convert::Into<U> for aya::programs::ProgramType where U: core::convert::From<T>
pub fn aya::programs::ProgramType::into(self) -> U
impl<T, U> core::convert::TryFrom<U> for aya::programs::ProgramType where U: core::convert::Into<T>
pub type aya::programs::ProgramType::Error = core::convert::Infallible
pub fn aya::programs::ProgramType::try_from(value: U) -> core::result::Result<T, <T as core::convert::TryFrom<U>>::Error>
impl<T, U> core::convert::TryInto<U> for aya::programs::ProgramType where U: core::convert::TryFrom<T>
pub type aya::programs::ProgramType::Error = <U as core::convert::TryFrom<T>>::Error
pub fn aya::programs::ProgramType::try_into(self) -> core::result::Result<U, <U as core::convert::TryFrom<T>>::Error>
impl<T> alloc::borrow::ToOwned for aya::programs::ProgramType where T: core::clone::Clone
pub type aya::programs::ProgramType::Owned = T
pub fn aya::programs::ProgramType::clone_into(&self, target: &mut T)
pub fn aya::programs::ProgramType::to_owned(&self) -> T
impl<T> core::any::Any for aya::programs::ProgramType where T: 'static + core::marker::Sized
pub fn aya::programs::ProgramType::type_id(&self) -> core::any::TypeId
impl<T> core::borrow::Borrow<T> for aya::programs::ProgramType where T: core::marker::Sized
pub fn aya::programs::ProgramType::borrow(&self) -> &T
impl<T> core::borrow::BorrowMut<T> for aya::programs::ProgramType where T: core::marker::Sized
pub fn aya::programs::ProgramType::borrow_mut(&mut self) -> &mut T
impl<T> core::clone::CloneToUninit for aya::programs::ProgramType where T: core::clone::Clone
pub unsafe fn aya::programs::ProgramType::clone_to_uninit(&self, dst: *mut T)
impl<T> core::convert::From<T> for aya::programs::ProgramType
pub fn aya::programs::ProgramType::from(t: T) -> T
pub enum aya::programs::SamplePolicy
pub aya::programs::SamplePolicy::Frequency(u64)
pub aya::programs::SamplePolicy::Period(u64)
@ -7913,22 +7986,23 @@ pub fn aya::programs::ProgramFd::from(t: T) -> T
pub struct aya::programs::ProgramInfo(_)
impl aya::programs::ProgramInfo
pub fn aya::programs::ProgramInfo::btf_id(&self) -> core::option::Option<core::num::nonzero::NonZeroU32>
pub fn aya::programs::ProgramInfo::created_by_uid(&self) -> core::option::Option<u32>
pub fn aya::programs::ProgramInfo::fd(&self) -> core::result::Result<aya::programs::ProgramFd, aya::programs::ProgramError>
pub fn aya::programs::ProgramInfo::from_pin<P: core::convert::AsRef<std::path::Path>>(path: P) -> core::result::Result<Self, aya::programs::ProgramError>
pub fn aya::programs::ProgramInfo::gpl_compatible(&self) -> bool
pub fn aya::programs::ProgramInfo::id(&self) -> u32
pub fn aya::programs::ProgramInfo::loaded_at(&self) -> std::time::SystemTime
pub fn aya::programs::ProgramInfo::map_ids(&self) -> core::result::Result<alloc::vec::Vec<u32>, aya::programs::ProgramError>
pub fn aya::programs::ProgramInfo::gpl_compatible(&self) -> core::option::Option<bool>
pub fn aya::programs::ProgramInfo::id(&self) -> core::option::Option<core::num::nonzero::NonZeroU32>
pub fn aya::programs::ProgramInfo::loaded_at(&self) -> core::option::Option<std::time::SystemTime>
pub fn aya::programs::ProgramInfo::map_ids(&self) -> core::result::Result<core::option::Option<alloc::vec::Vec<core::num::nonzero::NonZeroU32>>, aya::programs::ProgramError>
pub fn aya::programs::ProgramInfo::memory_locked(&self) -> core::result::Result<u32, aya::programs::ProgramError>
pub fn aya::programs::ProgramInfo::name(&self) -> &[u8]
pub fn aya::programs::ProgramInfo::name_as_str(&self) -> core::option::Option<&str>
pub fn aya::programs::ProgramInfo::program_type(&self) -> u32
pub fn aya::programs::ProgramInfo::program_type(&self) -> core::result::Result<aya::programs::ProgramType, aya::programs::ProgramError>
pub fn aya::programs::ProgramInfo::run_count(&self) -> u64
pub fn aya::programs::ProgramInfo::run_time(&self) -> core::time::Duration
pub fn aya::programs::ProgramInfo::size_jitted(&self) -> u32
pub fn aya::programs::ProgramInfo::size_translated(&self) -> u32
pub fn aya::programs::ProgramInfo::tag(&self) -> u64
pub fn aya::programs::ProgramInfo::verified_instruction_count(&self) -> u32
pub fn aya::programs::ProgramInfo::size_jitted(&self) -> core::option::Option<core::num::nonzero::NonZeroU32>
pub fn aya::programs::ProgramInfo::size_translated(&self) -> core::option::Option<core::num::nonzero::NonZeroU32>
pub fn aya::programs::ProgramInfo::tag(&self) -> core::option::Option<core::num::nonzero::NonZeroU64>
pub fn aya::programs::ProgramInfo::verified_instruction_count(&self) -> core::option::Option<core::num::nonzero::NonZeroU32>
impl core::fmt::Debug for aya::programs::ProgramInfo
pub fn aya::programs::ProgramInfo::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
impl core::marker::Freeze for aya::programs::ProgramInfo
@ -9093,7 +9167,6 @@ impl aya::Pod for u8
impl<K: aya::Pod> aya::Pod for aya::maps::lpm_trie::Key<K>
impl<T: aya::Pod, const N: usize> aya::Pod for [T; N]
pub fn aya::features() -> &'static aya_obj::obj::Features
pub fn aya::loaded_programs() -> impl core::iter::traits::iterator::Iterator<Item = core::result::Result<aya::programs::ProgramInfo, aya::programs::ProgramError>>
pub type aya::Bpf = aya::Ebpf
pub type aya::BpfError = aya::EbpfError
pub type aya::BpfLoader<'a> = aya::EbpfLoader<'a>

Loading…
Cancel
Save