You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
aya/aya/src/programs/mod.rs

1131 lines
34 KiB
Rust

//! eBPF program types.
//!
//! eBPF programs are loaded inside the kernel and attached to one or more hook
//! points. Whenever the hook points are reached, the programs are executed.
//!
//! # Loading and attaching programs
//!
//! When you call [`Bpf::load_file`] or [`Bpf::load`], all the programs included
//! in the object code are parsed and relocated. Programs are not loaded
//! automatically though, since often you will need to do some application
//! specific setup before you can actually load them.
//!
//! In order to load and attach a program, you need to retrieve it using [`Bpf::program_mut`],
//! then call the `load()` and `attach()` methods, for example:
//!
//! ```no_run
//! use aya::{Bpf, programs::KProbe};
//!
//! let mut bpf = Bpf::load_file("ebpf_programs.o")?;
//! // intercept_wakeups is the name of the program we want to load
//! let program: &mut KProbe = bpf.program_mut("intercept_wakeups").unwrap().try_into()?;
//! program.load()?;
//! // intercept_wakeups will be called every time try_to_wake_up() is called
//! // inside the kernel
//! program.attach("try_to_wake_up", 0)?;
//! # Ok::<(), aya::BpfError>(())
//! ```
//!
//! The signature of the `attach()` method varies depending on what kind of
//! program you're trying to attach.
//!
//! [`Bpf::load_file`]: crate::Bpf::load_file
//! [`Bpf::load`]: crate::Bpf::load
//! [`Bpf::programs`]: crate::Bpf::programs
//! [`Bpf::program`]: crate::Bpf::program
//! [`Bpf::program_mut`]: crate::Bpf::program_mut
//! [`maps`]: crate::maps
pub mod cgroup_device;
pub mod cgroup_skb;
pub mod cgroup_sock;
pub mod cgroup_sock_addr;
pub mod cgroup_sockopt;
pub mod cgroup_sysctl;
pub mod extension;
pub mod fentry;
pub mod fexit;
pub mod kprobe;
pub mod links;
pub mod lirc_mode2;
pub mod lsm;
pub mod perf_attach;
pub mod perf_event;
mod probe;
mod raw_trace_point;
mod sk_lookup;
mod sk_msg;
mod sk_skb;
mod sock_ops;
mod socket_filter;
pub mod tc;
pub mod tp_btf;
pub mod trace_point;
pub mod uprobe;
mod utils;
pub mod xdp;
use libc::ENOSPC;
use std::{
ffi::CString,
io,
num::NonZeroU32,
os::fd::{AsFd, AsRawFd, IntoRawFd as _, OwnedFd, RawFd},
path::{Path, PathBuf},
sync::Arc,
time::{Duration, SystemTime},
};
use thiserror::Error;
pub use cgroup_device::CgroupDevice;
pub use cgroup_skb::{CgroupSkb, CgroupSkbAttachType};
pub use cgroup_sock::{CgroupSock, CgroupSockAttachType};
pub use cgroup_sock_addr::{CgroupSockAddr, CgroupSockAddrAttachType};
pub use cgroup_sockopt::{CgroupSockopt, CgroupSockoptAttachType};
pub use cgroup_sysctl::CgroupSysctl;
pub use extension::{Extension, ExtensionError};
pub use fentry::FEntry;
pub use fexit::FExit;
pub use kprobe::{KProbe, KProbeError};
pub use links::Link;
use links::*;
pub use lirc_mode2::LircMode2;
pub use lsm::Lsm;
use perf_attach::*;
pub use perf_event::{PerfEvent, PerfEventScope, PerfTypeId, SamplePolicy};
pub use probe::ProbeKind;
pub use raw_trace_point::RawTracePoint;
pub use sk_lookup::SkLookup;
pub use sk_msg::SkMsg;
pub use sk_skb::{SkSkb, SkSkbKind};
pub use sock_ops::SockOps;
pub use socket_filter::{SocketFilter, SocketFilterError};
pub use tc::{SchedClassifier, TcAttachType, TcError};
pub use tp_btf::BtfTracePoint;
pub use trace_point::{TracePoint, TracePointError};
pub use uprobe::{UProbe, UProbeError};
pub use xdp::{Xdp, XdpError, XdpFlags};
use crate::{
generated::{bpf_attach_type, bpf_link_info, bpf_prog_info, bpf_prog_type},
maps::MapError,
obj::{self, btf::BtfError, Function, VerifierLog},
pin::PinError,
programs::utils::{boot_time, get_fdinfo},
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,
BpfLoadProgramAttrs, SyscallError,
},
util::KernelVersion,
VerifierLogLevel,
};
/// Error type returned when working with programs.
#[derive(Debug, Error)]
pub enum ProgramError {
/// The program is already loaded.
#[error("the program is already loaded")]
AlreadyLoaded,
/// The program is not loaded.
#[error("the program is not loaded")]
NotLoaded,
/// The program is already attached.
#[error("the program was already attached")]
AlreadyAttached,
/// The program is not attached.
#[error("the program is not attached")]
NotAttached,
/// Loading the program failed.
#[error("the BPF_PROG_LOAD syscall failed. Verifier output: {verifier_log}")]
LoadError {
/// The [`io::Error`] returned by the `BPF_PROG_LOAD` syscall.
#[source]
io_error: io::Error,
/// The error log produced by the kernel verifier.
verifier_log: VerifierLog,
},
/// A syscall failed.
#[error(transparent)]
SyscallError(#[from] SyscallError),
/// The network interface does not exist.
#[error("unknown network interface {name}")]
UnknownInterface {
/// interface name
name: String,
},
/// The program is not of the expected type.
#[error("unexpected program type")]
UnexpectedProgramType,
/// A map error occurred while loading or attaching a program.
#[error(transparent)]
MapError(#[from] MapError),
/// An error occurred while working with a [`KProbe`].
#[error(transparent)]
KProbeError(#[from] KProbeError),
/// An error occurred while working with an [`UProbe`].
#[error(transparent)]
UProbeError(#[from] UProbeError),
/// An error occurred while working with a [`TracePoint`].
#[error(transparent)]
TracePointError(#[from] TracePointError),
/// An error occurred while working with a [`SocketFilter`].
#[error(transparent)]
SocketFilterError(#[from] SocketFilterError),
/// An error occurred while working with an [`Xdp`] program.
#[error(transparent)]
XdpError(#[from] XdpError),
/// An error occurred while working with a TC program.
#[error(transparent)]
TcError(#[from] TcError),
/// An error occurred while working with an [`Extension`] program.
#[error(transparent)]
ExtensionError(#[from] ExtensionError),
/// An error occurred while working with BTF.
#[error(transparent)]
Btf(#[from] BtfError),
/// The program is not attached.
#[error("the program name `{name}` is invalid")]
InvalidName {
/// program name
name: String,
},
/// An error occurred while working with IO.
#[error(transparent)]
IOError(#[from] io::Error),
}
/// A [`Program`] file descriptor.
#[derive(Copy, Clone)]
pub struct ProgramFd(RawFd);
impl AsRawFd for ProgramFd {
fn as_raw_fd(&self) -> RawFd {
self.0
}
}
/// eBPF program type.
#[derive(Debug)]
pub enum Program {
/// A [`KProbe`] program
KProbe(KProbe),
/// A [`UProbe`] program
UProbe(UProbe),
/// A [`TracePoint`] program
TracePoint(TracePoint),
/// A [`SocketFilter`] program
SocketFilter(SocketFilter),
/// A [`Xdp`] program
Xdp(Xdp),
/// A [`SkMsg`] program
SkMsg(SkMsg),
/// A [`SkSkb`] program
SkSkb(SkSkb),
/// A [`CgroupSockAddr`] program
CgroupSockAddr(CgroupSockAddr),
/// A [`SockOps`] program
SockOps(SockOps),
/// A [`SchedClassifier`] program
SchedClassifier(SchedClassifier),
/// A [`CgroupSkb`] program
CgroupSkb(CgroupSkb),
/// A [`CgroupSysctl`] program
CgroupSysctl(CgroupSysctl),
/// A [`CgroupSockopt`] program
CgroupSockopt(CgroupSockopt),
/// A [`LircMode2`] program
LircMode2(LircMode2),
/// A [`PerfEvent`] program
PerfEvent(PerfEvent),
/// A [`RawTracePoint`] program
RawTracePoint(RawTracePoint),
/// A [`Lsm`] program
Lsm(Lsm),
/// A [`BtfTracePoint`] program
BtfTracePoint(BtfTracePoint),
/// A [`FEntry`] program
FEntry(FEntry),
/// A [`FExit`] program
FExit(FExit),
/// A [`Extension`] program
Extension(Extension),
/// A [`SkLookup`] program
SkLookup(SkLookup),
/// A [`CgroupSock`] program
CgroupSock(CgroupSock),
/// A [`CgroupDevice`] program
CgroupDevice(CgroupDevice),
}
impl Program {
/// Returns the low level program type.
pub fn prog_type(&self) -> bpf_prog_type {
use crate::generated::bpf_prog_type::*;
match self {
Program::KProbe(_) => BPF_PROG_TYPE_KPROBE,
Program::UProbe(_) => BPF_PROG_TYPE_KPROBE,
Program::TracePoint(_) => BPF_PROG_TYPE_TRACEPOINT,
Program::SocketFilter(_) => BPF_PROG_TYPE_SOCKET_FILTER,
Program::Xdp(_) => BPF_PROG_TYPE_XDP,
Program::SkMsg(_) => BPF_PROG_TYPE_SK_MSG,
Program::SkSkb(_) => BPF_PROG_TYPE_SK_SKB,
Program::SockOps(_) => BPF_PROG_TYPE_SOCK_OPS,
Program::SchedClassifier(_) => BPF_PROG_TYPE_SCHED_CLS,
Program::CgroupSkb(_) => BPF_PROG_TYPE_CGROUP_SKB,
Program::CgroupSysctl(_) => BPF_PROG_TYPE_CGROUP_SYSCTL,
Program::CgroupSockopt(_) => BPF_PROG_TYPE_CGROUP_SOCKOPT,
Program::LircMode2(_) => BPF_PROG_TYPE_LIRC_MODE2,
Program::PerfEvent(_) => BPF_PROG_TYPE_PERF_EVENT,
Program::RawTracePoint(_) => BPF_PROG_TYPE_RAW_TRACEPOINT,
Program::Lsm(_) => BPF_PROG_TYPE_LSM,
Program::BtfTracePoint(_) => BPF_PROG_TYPE_TRACING,
Program::FEntry(_) => BPF_PROG_TYPE_TRACING,
Program::FExit(_) => BPF_PROG_TYPE_TRACING,
Program::Extension(_) => BPF_PROG_TYPE_EXT,
Program::CgroupSockAddr(_) => BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
Program::SkLookup(_) => BPF_PROG_TYPE_SK_LOOKUP,
Program::CgroupSock(_) => BPF_PROG_TYPE_CGROUP_SOCK,
Program::CgroupDevice(_) => BPF_PROG_TYPE_CGROUP_DEVICE,
}
}
/// Pin the program to the provided path
pub fn pin<P: AsRef<Path>>(&mut self, path: P) -> Result<(), PinError> {
match self {
Program::KProbe(p) => p.pin(path),
Program::UProbe(p) => p.pin(path),
Program::TracePoint(p) => p.pin(path),
Program::SocketFilter(p) => p.pin(path),
Program::Xdp(p) => p.pin(path),
Program::SkMsg(p) => p.pin(path),
Program::SkSkb(p) => p.pin(path),
Program::SockOps(p) => p.pin(path),
Program::SchedClassifier(p) => p.pin(path),
Program::CgroupSkb(p) => p.pin(path),
Program::CgroupSysctl(p) => p.pin(path),
Program::CgroupSockopt(p) => p.pin(path),
Program::LircMode2(p) => p.pin(path),
Program::PerfEvent(p) => p.pin(path),
Program::RawTracePoint(p) => p.pin(path),
Program::Lsm(p) => p.pin(path),
Program::BtfTracePoint(p) => p.pin(path),
Program::FEntry(p) => p.pin(path),
Program::FExit(p) => p.pin(path),
Program::Extension(p) => p.pin(path),
Program::CgroupSockAddr(p) => p.pin(path),
Program::SkLookup(p) => p.pin(path),
Program::CgroupSock(p) => p.pin(path),
Program::CgroupDevice(p) => p.pin(path),
}
}
/// Unloads the program from the kernel.
pub fn unload(self) -> Result<(), ProgramError> {
match self {
Program::KProbe(mut p) => p.unload(),
Program::UProbe(mut p) => p.unload(),
Program::TracePoint(mut p) => p.unload(),
Program::SocketFilter(mut p) => p.unload(),
Program::Xdp(mut p) => p.unload(),
Program::SkMsg(mut p) => p.unload(),
Program::SkSkb(mut p) => p.unload(),
Program::SockOps(mut p) => p.unload(),
Program::SchedClassifier(mut p) => p.unload(),
Program::CgroupSkb(mut p) => p.unload(),
Program::CgroupSysctl(mut p) => p.unload(),
Program::CgroupSockopt(mut p) => p.unload(),
Program::LircMode2(mut p) => p.unload(),
Program::PerfEvent(mut p) => p.unload(),
Program::RawTracePoint(mut p) => p.unload(),
Program::Lsm(mut p) => p.unload(),
Program::BtfTracePoint(mut p) => p.unload(),
Program::FEntry(mut p) => p.unload(),
Program::FExit(mut p) => p.unload(),
Program::Extension(mut p) => p.unload(),
Program::CgroupSockAddr(mut p) => p.unload(),
Program::SkLookup(mut p) => p.unload(),
Program::CgroupSock(mut p) => p.unload(),
Program::CgroupDevice(mut p) => p.unload(),
}
}
/// Returns the file descriptor of a program.
///
/// Can be used to add a program to a [`crate::maps::ProgramArray`] or attach an [`Extension`] program.
/// Can be converted to [`RawFd`] using [`AsRawFd`].
pub fn fd(&self) -> Option<ProgramFd> {
match self {
Program::KProbe(p) => p.fd(),
Program::UProbe(p) => p.fd(),
Program::TracePoint(p) => p.fd(),
Program::SocketFilter(p) => p.fd(),
Program::Xdp(p) => p.fd(),
Program::SkMsg(p) => p.fd(),
Program::SkSkb(p) => p.fd(),
Program::SockOps(p) => p.fd(),
Program::SchedClassifier(p) => p.fd(),
Program::CgroupSkb(p) => p.fd(),
Program::CgroupSysctl(p) => p.fd(),
Program::CgroupSockopt(p) => p.fd(),
Program::LircMode2(p) => p.fd(),
Program::PerfEvent(p) => p.fd(),
Program::RawTracePoint(p) => p.fd(),
Program::Lsm(p) => p.fd(),
Program::BtfTracePoint(p) => p.fd(),
Program::FEntry(p) => p.fd(),
Program::FExit(p) => p.fd(),
Program::Extension(p) => p.fd(),
Program::CgroupSockAddr(p) => p.fd(),
Program::SkLookup(p) => p.fd(),
Program::CgroupSock(p) => p.fd(),
Program::CgroupDevice(p) => p.fd(),
}
}
}
#[derive(Debug)]
pub(crate) struct ProgramData<T: Link> {
pub(crate) name: Option<String>,
pub(crate) obj: Option<(obj::Program, obj::Function)>,
pub(crate) fd: Option<RawFd>,
pub(crate) links: LinkMap<T>,
pub(crate) expected_attach_type: Option<bpf_attach_type>,
pub(crate) attach_btf_obj_fd: Option<OwnedFd>,
pub(crate) attach_btf_id: Option<u32>,
pub(crate) attach_prog_fd: Option<RawFd>,
pub(crate) btf_fd: Option<Arc<OwnedFd>>,
pub(crate) verifier_log_level: VerifierLogLevel,
pub(crate) path: Option<PathBuf>,
pub(crate) flags: u32,
}
impl<T: Link> ProgramData<T> {
pub(crate) fn new(
name: Option<String>,
obj: (obj::Program, obj::Function),
btf_fd: Option<Arc<OwnedFd>>,
verifier_log_level: VerifierLogLevel,
) -> ProgramData<T> {
ProgramData {
name,
obj: Some(obj),
fd: None,
links: LinkMap::new(),
expected_attach_type: None,
attach_btf_obj_fd: None,
attach_btf_id: None,
attach_prog_fd: None,
btf_fd,
verifier_log_level,
path: None,
flags: 0,
}
}
pub(crate) fn from_bpf_prog_info(
name: Option<String>,
fd: OwnedFd,
path: &Path,
info: bpf_prog_info,
verifier_log_level: VerifierLogLevel,
) -> Result<ProgramData<T>, ProgramError> {
let attach_btf_id = if info.attach_btf_id > 0 {
Some(info.attach_btf_id)
} else {
None
};
let attach_btf_obj_fd = (info.attach_btf_obj_id != 0)
.then(|| bpf_btf_get_fd_by_id(info.attach_btf_obj_id))
.transpose()?;
Ok(ProgramData {
name,
obj: None,
fd: Some(fd.into_raw_fd()),
links: LinkMap::new(),
expected_attach_type: None,
attach_btf_obj_fd,
attach_btf_id,
attach_prog_fd: None,
btf_fd: None,
verifier_log_level,
path: Some(path.to_path_buf()),
flags: 0,
})
}
pub(crate) fn from_pinned_path<P: AsRef<Path>>(
path: P,
verifier_log_level: VerifierLogLevel,
) -> Result<ProgramData<T>, ProgramError> {
let path_string =
CString::new(path.as_ref().as_os_str().to_string_lossy().as_bytes()).unwrap();
let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError {
call: "bpf_obj_get",
io_error,
})?;
let info = ProgramInfo::new_from_fd(fd.as_raw_fd())?;
let name = info.name_as_str().map(|s| s.to_string());
ProgramData::from_bpf_prog_info(name, fd, path.as_ref(), info.0, verifier_log_level)
}
}
impl<T: Link> ProgramData<T> {
fn fd_or_err(&self) -> Result<RawFd, ProgramError> {
self.fd.ok_or(ProgramError::NotLoaded)
}
pub(crate) fn take_link(&mut self, link_id: T::Id) -> Result<T, ProgramError> {
self.links.forget(link_id)
}
}
fn unload_program<T: Link>(data: &mut ProgramData<T>) -> Result<(), ProgramError> {
data.links.remove_all()?;
let fd = data.fd.take().ok_or(ProgramError::NotLoaded)?;
unsafe {
libc::close(fd);
}
Ok(())
}
fn pin_program<T: Link, P: AsRef<Path>>(data: &ProgramData<T>, path: P) -> Result<(), PinError> {
let fd = data.fd.ok_or(PinError::NoFd {
name: data
.name
.as_deref()
.unwrap_or("<unknown program>")
.to_string(),
})?;
let path_string = CString::new(path.as_ref().to_string_lossy().into_owned()).map_err(|e| {
PinError::InvalidPinPath {
error: e.to_string(),
}
})?;
bpf_pin_object(fd, &path_string).map_err(|(_, io_error)| SyscallError {
call: "BPF_OBJ_PIN",
io_error,
})?;
Ok(())
}
fn load_program<T: Link>(
prog_type: bpf_prog_type,
data: &mut ProgramData<T>,
) -> Result<(), ProgramError> {
let ProgramData {
name,
obj,
fd,
links: _,
expected_attach_type,
attach_btf_obj_fd,
attach_btf_id,
attach_prog_fd,
btf_fd,
verifier_log_level,
path: _,
flags,
} = data;
if fd.is_some() {
return Err(ProgramError::AlreadyLoaded);
}
if obj.is_none() {
// This program was loaded from a pin in bpffs
return Err(ProgramError::AlreadyLoaded);
}
let obj = obj.as_ref().unwrap();
let (
crate::obj::Program {
license,
kernel_version,
..
},
Function {
instructions,
func_info,
line_info,
func_info_rec_size,
line_info_rec_size,
..
},
) = obj;
let target_kernel_version = kernel_version.unwrap_or_else(|| {
let KernelVersion {
major,
minor,
patch,
} = KernelVersion::current().unwrap();
(u32::from(major) << 16) + (u32::from(minor) << 8) + u32::from(patch)
});
let prog_name = if let Some(name) = name {
let mut name = name.clone();
if name.len() > 15 {
name.truncate(15);
}
let prog_name = CString::new(name.clone())
.map_err(|_| ProgramError::InvalidName { name: name.clone() })?;
Some(prog_name)
} else {
None
};
let attr = BpfLoadProgramAttrs {
name: prog_name,
ty: prog_type,
insns: instructions,
license,
kernel_version: target_kernel_version,
expected_attach_type: *expected_attach_type,
prog_btf_fd: btf_fd.as_ref().map(|f| f.as_fd()),
attach_btf_obj_fd: attach_btf_obj_fd.as_ref().map(|fd| fd.as_fd()),
attach_btf_id: *attach_btf_id,
attach_prog_fd: *attach_prog_fd,
func_info_rec_size: *func_info_rec_size,
func_info: func_info.clone(),
line_info_rec_size: *line_info_rec_size,
line_info: line_info.clone(),
flags: *flags,
};
let (ret, verifier_log) = retry_with_verifier_logs(10, |logger| {
bpf_load_program(&attr, logger, *verifier_log_level)
});
match ret {
Ok(prog_fd) => {
*fd = Some(prog_fd as RawFd);
Ok(())
}
Err((_, io_error)) => Err(ProgramError::LoadError {
io_error,
verifier_log,
}),
}
}
pub(crate) fn query<T: AsRawFd>(
target_fd: T,
attach_type: bpf_attach_type,
query_flags: u32,
attach_flags: &mut Option<u32>,
) -> Result<Vec<u32>, ProgramError> {
let mut prog_ids = vec![0u32; 64];
let mut prog_cnt = prog_ids.len() as u32;
let mut retries = 0;
loop {
match bpf_prog_query(
target_fd.as_raw_fd(),
attach_type,
query_flags,
attach_flags.as_mut(),
&mut prog_ids,
&mut prog_cnt,
) {
Ok(_) => {
prog_ids.resize(prog_cnt as usize, 0);
return Ok(prog_ids);
}
Err((_, io_error)) => {
if retries == 0 && io_error.raw_os_error() == Some(ENOSPC) {
prog_ids.resize(prog_cnt as usize, 0);
retries += 1;
} else {
return Err(SyscallError {
call: "bpf_prog_query",
io_error,
}
.into());
}
}
}
}
}
macro_rules! impl_program_unload {
($($struct_name:ident),+ $(,)?) => {
$(
impl $struct_name {
/// Unloads the program from the kernel.
///
/// Links will be detached before unloading the program. Note
/// that owned links obtained using `take_link()` will not be
/// detached.
pub fn unload(&mut self) -> Result<(), ProgramError> {
unload_program(&mut self.data)
}
}
impl Drop for $struct_name {
fn drop(&mut self) {
let _ = self.unload();
}
}
)+
}
}
impl_program_unload!(
KProbe,
UProbe,
TracePoint,
SocketFilter,
Xdp,
SkMsg,
SkSkb,
SchedClassifier,
CgroupSkb,
CgroupSysctl,
CgroupSockopt,
LircMode2,
PerfEvent,
Lsm,
RawTracePoint,
BtfTracePoint,
FEntry,
FExit,
Extension,
CgroupSockAddr,
SkLookup,
SockOps,
CgroupSock,
CgroupDevice,
);
macro_rules! impl_fd {
($($struct_name:ident),+ $(,)?) => {
$(
impl $struct_name {
/// Returns the file descriptor of this Program.
pub fn fd(&self) -> Option<ProgramFd> {
self.data.fd.map(|fd| ProgramFd(fd))
}
}
)+
}
}
impl_fd!(
KProbe,
UProbe,
TracePoint,
SocketFilter,
Xdp,
SkMsg,
SkSkb,
SchedClassifier,
CgroupSkb,
CgroupSysctl,
CgroupSockopt,
LircMode2,
PerfEvent,
Lsm,
RawTracePoint,
BtfTracePoint,
FEntry,
FExit,
Extension,
CgroupSockAddr,
SkLookup,
SockOps,
CgroupSock,
CgroupDevice,
);
macro_rules! impl_program_pin{
($($struct_name:ident),+ $(,)?) => {
$(
impl $struct_name {
/// Pins the program to a BPF filesystem.
///
/// When a BPF object is pinned to a BPF filesystem it will remain loaded after
/// Aya has unloaded the program.
/// To remove the program, the file on the BPF filesystem must be removed.
/// Any directories in the the path provided should have been created by the caller.
pub fn pin<P: AsRef<Path>>(&mut self, path: P) -> Result<(), PinError> {
self.data.path = Some(path.as_ref().to_path_buf());
pin_program(&self.data, path)
}
/// Removes the pinned link from the filesystem.
pub fn unpin(mut self) -> Result<(), io::Error> {
if let Some(path) = self.data.path.take() {
std::fs::remove_file(path)?;
}
Ok(())
}
}
)+
}
}
impl_program_pin!(
KProbe,
UProbe,
TracePoint,
SocketFilter,
Xdp,
SkMsg,
SkSkb,
SchedClassifier,
CgroupSkb,
CgroupSysctl,
CgroupSockopt,
LircMode2,
PerfEvent,
Lsm,
RawTracePoint,
BtfTracePoint,
FEntry,
FExit,
Extension,
CgroupSockAddr,
SkLookup,
SockOps,
CgroupSock,
CgroupDevice,
);
macro_rules! impl_from_pin {
($($struct_name:ident),+ $(,)?) => {
$(
impl $struct_name {
/// Creates a program from a pinned entry on a bpffs.
///
/// Existing links will not be populated. To work with existing links you should use [`crate::programs::links::PinnedLink`].
///
/// On drop, any managed links are detached and the program is unloaded. This will not result in
/// the program being unloaded from the kernel if it is still pinned.
pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, ProgramError> {
let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?;
Ok(Self { data })
}
}
)+
}
}
// Use impl_from_pin if the program doesn't require additional data
impl_from_pin!(
TracePoint,
SocketFilter,
Xdp,
SkMsg,
CgroupSysctl,
LircMode2,
PerfEvent,
Lsm,
RawTracePoint,
BtfTracePoint,
FEntry,
FExit,
Extension,
SkLookup,
SockOps,
CgroupDevice,
);
macro_rules! impl_try_from_program {
($($ty:ident),+ $(,)?) => {
$(
impl<'a> TryFrom<&'a Program> for &'a $ty {
type Error = ProgramError;
fn try_from(program: &'a Program) -> Result<&'a $ty, ProgramError> {
match program {
Program::$ty(p) => Ok(p),
_ => Err(ProgramError::UnexpectedProgramType),
}
}
}
impl<'a> TryFrom<&'a mut Program> for &'a mut $ty {
type Error = ProgramError;
fn try_from(program: &'a mut Program) -> Result<&'a mut $ty, ProgramError> {
match program {
Program::$ty(p) => Ok(p),
_ => Err(ProgramError::UnexpectedProgramType),
}
}
}
)+
}
}
impl_try_from_program!(
KProbe,
UProbe,
TracePoint,
SocketFilter,
Xdp,
SkMsg,
SkSkb,
SockOps,
SchedClassifier,
CgroupSkb,
CgroupSysctl,
CgroupSockopt,
LircMode2,
PerfEvent,
Lsm,
RawTracePoint,
BtfTracePoint,
FEntry,
FExit,
Extension,
CgroupSockAddr,
SkLookup,
CgroupSock,
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_program_info {
($($struct_name:ident),+ $(,)?) => {
$(
impl $struct_name {
/// Returns the file descriptor of this Program.
pub fn program_info(&self) -> Result<ProgramInfo, ProgramError> {
let fd = self.data.fd_or_err()?;
ProgramInfo::new_from_fd(fd.as_raw_fd())
}
}
)+
}
}
impl_program_info!(
KProbe,
UProbe,
TracePoint,
SocketFilter,
Xdp,
SkMsg,
SkSkb,
SchedClassifier,
CgroupSkb,
CgroupSysctl,
CgroupSockopt,
LircMode2,
PerfEvent,
Lsm,
RawTracePoint,
BtfTracePoint,
FEntry,
FExit,
Extension,
CgroupSockAddr,
SkLookup,
SockOps,
CgroupSock,
CgroupDevice,
);
/// Provides information about a loaded program, like name, id and statistics
#[derive(Debug)]
pub struct ProgramInfo(bpf_prog_info);
impl ProgramInfo {
fn new_from_fd(fd: RawFd) -> Result<Self, ProgramError> {
Ok(ProgramInfo(bpf_prog_get_info_by_fd(
fd.as_raw_fd(),
&mut [],
)?))
}
/// The name of the program as was provided when it was load. This is limited to 16 bytes
pub fn name(&self) -> &[u8] {
let length = self
.0
.name
.iter()
.rposition(|ch| *ch != 0)
.map(|pos| pos + 1)
.unwrap_or(0);
// The name field is defined as [std::os::raw::c_char; 16]. c_char may be signed or
// unsigned depending on the platform; that's why we're using from_raw_parts here
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.
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 fd = self.fd()?;
let mut map_ids = vec![0u32; self.0.nr_map_ids as usize];
bpf_prog_get_info_by_fd(fd.as_raw_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<OwnedFd, ProgramError> {
let Self(info) = self;
let fd = bpf_prog_get_fd_by_id(info.id)?;
Ok(fd)
}
/// Loads a program from a pinned path in bpffs.
pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<ProgramInfo, ProgramError> {
let path_string = CString::new(path.as_ref().to_str().unwrap()).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_raw_fd(), &mut [])?;
Ok(ProgramInfo(info))
}
}
/// 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() -> 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_raw_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)]
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_fd())
})
.map(|result| result.map_err(Into::into))
}