mod bpf;
pub(crate) mod feature_probe;
mod netlink;
mod perf_event;
#[cfg(test)]
mod fake;
use std::{
ffi::{c_int, c_void},
io,
os::fd::{BorrowedFd, OwnedFd},
};
use aya_obj::generated::{bpf_attr, bpf_cmd, bpf_stats_type, perf_event_attr};
pub(crate) use bpf::*;
#[cfg(test)]
pub(crate) use fake::*;
pub use feature_probe::{is_map_supported, is_program_supported};
#[doc(hidden)]
pub use netlink::netlink_set_link_up;
pub(crate) use netlink::*;
pub(crate) use perf_event::*;
use thiserror::Error;
pub(crate) type SysResult = Result<i64, (i64, io::Error)>;
#[cfg_attr(test, expect(dead_code, reason = "test stubs cut above this"))]
#[derive(Debug)]
pub(crate) enum PerfEventIoctlRequest<'a> {
Enable,
Disable,
SetBpf(BorrowedFd<'a>),
}
#[cfg_attr(test, expect(dead_code, reason = "test stubs cut above this"))]
pub(crate) enum Syscall<'a> {
Ebpf {
cmd: bpf_cmd,
attr: &'a mut bpf_attr,
},
PerfEventOpen {
attr: perf_event_attr,
pid: libc::pid_t,
cpu: i32,
group: i32,
flags: u32,
},
PerfEventIoctl {
fd: BorrowedFd<'a>,
request: PerfEventIoctlRequest<'a>,
},
}
#[derive(Debug, Error)]
#[error("`{call}` failed")]
pub struct SyscallError {
pub call: &'static str,
#[source]
pub io_error: io::Error,
}
impl std::fmt::Debug for Syscall<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Ebpf { cmd, attr: _ } => f
.debug_struct("Syscall::Ebpf")
.field("cmd", cmd)
.field("attr", &format_args!("_"))
.finish(),
Self::PerfEventOpen {
attr: _,
pid,
cpu,
group,
flags,
} => f
.debug_struct("Syscall::PerfEventOpen")
.field("attr", &format_args!("_"))
.field("pid", pid)
.field("cpu", cpu)
.field("group", group)
.field("flags", flags)
.finish(),
Self::PerfEventIoctl { fd, request } => f
.debug_struct("Syscall::PerfEventIoctl")
.field("fd", fd)
.field("request", request)
.finish(),
}
}
}
fn syscall(call: Syscall<'_>) -> SysResult {
#[cfg(test)]
{
TEST_SYSCALL.with(|test_impl| unsafe { test_impl.borrow()(call) })
}
#[cfg(not(test))]
{
let ret = unsafe {
match call {
Syscall::Ebpf { cmd, attr } => {
libc::syscall(libc::SYS_bpf, cmd, attr, size_of::<bpf_attr>())
}
Syscall::PerfEventOpen {
attr,
pid,
cpu,
group,
flags,
} => libc::syscall(libc::SYS_perf_event_open, &attr, pid, cpu, group, flags),
Syscall::PerfEventIoctl { fd, request } => {
use std::os::fd::AsRawFd as _;
let fd = fd.as_raw_fd();
match request {
PerfEventIoctlRequest::Enable => libc::syscall(
libc::SYS_ioctl,
fd,
aya_obj::generated::PERF_EVENT_IOC_ENABLE,
),
PerfEventIoctlRequest::Disable => libc::syscall(
libc::SYS_ioctl,
fd,
aya_obj::generated::PERF_EVENT_IOC_DISABLE,
),
PerfEventIoctlRequest::SetBpf(bpf_fd) => libc::syscall(
libc::SYS_ioctl,
fd,
aya_obj::generated::PERF_EVENT_IOC_SET_BPF,
bpf_fd.as_raw_fd(),
),
}
}
}
};
#[expect(clippy::allow_attributes, reason = "architecture specific")]
#[allow(clippy::useless_conversion, reason = "architecture specific")]
let ret: i64 = ret.into();
match ret {
0.. => Ok(ret),
ret => Err((ret, io::Error::last_os_error())),
}
}
}
#[cfg_attr(
test,
expect(unused_variables, reason = "TODO: we should validate all arguments")
)]
pub(crate) unsafe fn mmap(
addr: *mut c_void,
len: usize,
prot: c_int,
flags: c_int,
fd: BorrowedFd<'_>,
offset: libc::off_t,
) -> *mut c_void {
#[cfg(test)]
{
TEST_MMAP_RET.with(|ret| *ret.borrow())
}
#[cfg(not(test))]
{
use std::os::fd::AsRawFd as _;
unsafe { libc::mmap(addr, len, prot, flags, fd.as_raw_fd(), offset) }
}
}
#[cfg_attr(
test,
expect(clippy::missing_const_for_fn, reason = "only const in cfg(test)"),
expect(unused_variables, reason = "TODO: we should validate all arguments")
)]
pub(crate) unsafe fn munmap(addr: *mut c_void, len: usize) -> c_int {
#[cfg(test)]
{
0
}
#[cfg(not(test))]
{
unsafe { libc::munmap(addr, len) }
}
}
#[non_exhaustive]
#[doc(alias = "bpf_stats_type")]
#[derive(Copy, Clone, Debug)]
pub enum Stats {
#[doc(alias = "BPF_STATS_RUN_TIME")]
RunTime,
}
impl From<Stats> for bpf_stats_type {
fn from(value: Stats) -> Self {
match value {
Stats::RunTime => Self::BPF_STATS_RUN_TIME,
}
}
}
#[doc(alias = "BPF_ENABLE_STATS")]
pub fn enable_stats(stats_type: Stats) -> Result<OwnedFd, SyscallError> {
bpf_enable_stats(stats_type.into()).map(super::MockableFd::into_inner)
}