mirror of https://github.com/aya-rs/aya
feat(aya): Add iterator program type
BPF iterators[0] are a way to dump kernel data into user-space and an alternative to `/proc` filesystem. This change adds support for BPF iterators on the user-space side. It provides a possibility to retrieve the outputs of BPF iterator programs both from sync and async Rust code. [0] https://docs.kernel.org/bpf/bpf_iterators.htmlreviewable/pr1088/r1
parent
e423fce58f
commit
88d20a0c99
@ -0,0 +1,225 @@
|
|||||||
|
//! Iterators.
|
||||||
|
use std::{
|
||||||
|
mem::ManuallyDrop,
|
||||||
|
os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
generated::{bpf_attach_type::BPF_TRACE_ITER, bpf_prog_type},
|
||||||
|
obj::{
|
||||||
|
btf::{Btf, BtfKind},
|
||||||
|
generated::bpf_link_type,
|
||||||
|
},
|
||||||
|
programs::{
|
||||||
|
define_link_wrapper, load_program, FdLink, LinkError, PerfLinkIdInner, PerfLinkInner,
|
||||||
|
ProgramData, ProgramError,
|
||||||
|
},
|
||||||
|
sys::{bpf_create_iter, bpf_link_create, bpf_link_get_info_by_fd, LinkTarget, SyscallError},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An eBPF iterator which allows to dump into user space.
|
||||||
|
///
|
||||||
|
/// It can be seen as an alternative to `/proc` filesystem, which offers more
|
||||||
|
/// flexibility about what information should be retrieved and how it should be
|
||||||
|
/// formatted.
|
||||||
|
///
|
||||||
|
/// # Minimum kernel version
|
||||||
|
///
|
||||||
|
/// The minimum kernel version required to use this feature is 5.8.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ## Non-async Rust
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use std::io::{BufRead, BufReader};
|
||||||
|
/// use aya::{programs::{Iter, ProgramError}, BtfError, Btf, Ebpf};
|
||||||
|
/// # let mut ebpf = Ebpf::load_file("ebpf_programs.o")?;
|
||||||
|
///
|
||||||
|
/// let btf = Btf::from_sys_fs()?;
|
||||||
|
/// let program: &mut Iter = ebpf.program_mut("iter_prog").unwrap().try_into()?;
|
||||||
|
/// program.load("task", &btf)?;
|
||||||
|
///
|
||||||
|
/// let link_id = program.attach()?;
|
||||||
|
/// let link = program.take_link(link_id)?;
|
||||||
|
/// let file = link.into_file()?;
|
||||||
|
/// let reader = BufReader::new(file);
|
||||||
|
///
|
||||||
|
/// let mut lines = reader.lines();
|
||||||
|
/// for line in lines {
|
||||||
|
/// let line = line?;
|
||||||
|
/// println!("{line}");
|
||||||
|
/// }
|
||||||
|
/// # Ok::<(), Box<dyn std::error::Error>>(())
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Iter {
|
||||||
|
pub(crate) data: ProgramData<IterLink>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iter {
|
||||||
|
/// Loads the program inside the kernel.
|
||||||
|
pub fn load(&mut self, iter_type: &str, btf: &Btf) -> Result<(), ProgramError> {
|
||||||
|
self.data.expected_attach_type = Some(BPF_TRACE_ITER);
|
||||||
|
let type_name = format!("bpf_iter_{iter_type}");
|
||||||
|
self.data.attach_btf_id =
|
||||||
|
Some(btf.id_by_type_name_kind(type_name.as_str(), BtfKind::Func)?);
|
||||||
|
load_program(bpf_prog_type::BPF_PROG_TYPE_TRACING, &mut self.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attaches the program.
|
||||||
|
///
|
||||||
|
/// The returned value can be used to detach, see [Iter::detach].
|
||||||
|
pub fn attach(&mut self) -> Result<IterLinkId, ProgramError> {
|
||||||
|
let prog_fd = self.fd()?;
|
||||||
|
let prog_fd = prog_fd.as_fd();
|
||||||
|
let link_fd = bpf_link_create(prog_fd, LinkTarget::Iter, BPF_TRACE_ITER, None, 0, None)
|
||||||
|
.map_err(|(_, io_error)| SyscallError {
|
||||||
|
call: "bpf_link_create",
|
||||||
|
io_error,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
self.data
|
||||||
|
.links
|
||||||
|
.insert(IterLink::new(PerfLinkInner::FdLink(FdLink::new(link_fd))))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Detaches the program.
|
||||||
|
///
|
||||||
|
/// See [Iter::attach].
|
||||||
|
pub fn detach(&mut self, link_id: IterLinkId) -> Result<(), ProgramError> {
|
||||||
|
self.data.links.remove(link_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes ownership of the link referenced by the provided link_id.
|
||||||
|
///
|
||||||
|
/// The link will be detached on `Drop` and the caller is now responsible
|
||||||
|
/// for managing its lifetime.
|
||||||
|
pub fn take_link(&mut self, link_id: IterLinkId) -> Result<IterLink, ProgramError> {
|
||||||
|
self.data.take_link(link_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator descriptor.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct IterFd {
|
||||||
|
fd: crate::MockableFd,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsFd for IterFd {
|
||||||
|
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||||
|
let Self { fd } = self;
|
||||||
|
fd.as_fd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<IterLink> for FdLink {
|
||||||
|
type Error = LinkError;
|
||||||
|
|
||||||
|
fn try_from(value: IterLink) -> Result<Self, Self::Error> {
|
||||||
|
if let PerfLinkInner::FdLink(fd) = value.into_inner() {
|
||||||
|
Ok(fd)
|
||||||
|
} else {
|
||||||
|
Err(LinkError::InvalidLink)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<FdLink> for IterLink {
|
||||||
|
type Error = LinkError;
|
||||||
|
|
||||||
|
fn try_from(fd_link: FdLink) -> Result<Self, Self::Error> {
|
||||||
|
let info = bpf_link_get_info_by_fd(fd_link.fd.as_fd())?;
|
||||||
|
if info.type_ == (bpf_link_type::BPF_LINK_TYPE_ITER as u32) {
|
||||||
|
return Ok(Self::new(PerfLinkInner::FdLink(fd_link)));
|
||||||
|
}
|
||||||
|
Err(LinkError::InvalidLink)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
define_link_wrapper!(
|
||||||
|
/// The link used by [Iter] programs.
|
||||||
|
IterLink,
|
||||||
|
/// The type returned by [Iter::attach]. Can be passed to [Iter::detach].
|
||||||
|
IterLinkId,
|
||||||
|
PerfLinkInner,
|
||||||
|
PerfLinkIdInner
|
||||||
|
);
|
||||||
|
|
||||||
|
impl IterLink {
|
||||||
|
/// Converts [IterLink] into a [std::fs::File]. That file can be used to
|
||||||
|
/// retrieve the outputs of the iterator program.
|
||||||
|
pub fn into_file(self) -> Result<std::fs::File, LinkError> {
|
||||||
|
if let PerfLinkInner::FdLink(fd) = self.into_inner() {
|
||||||
|
let fd = bpf_create_iter(fd.fd.as_fd()).map_err(|(_, error)| {
|
||||||
|
LinkError::SyscallError(SyscallError {
|
||||||
|
call: "bpf_iter_create",
|
||||||
|
io_error: error,
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
// We don't want to drop the descriptor when going out of the scope
|
||||||
|
// of this method. The lifecycle of the descriptor is going to be
|
||||||
|
// managed by the `File` created below.
|
||||||
|
let fd = ManuallyDrop::new(fd);
|
||||||
|
// SAFETY: We are sure that the file descriptor is valid. This
|
||||||
|
// `File` takes responsibility of closing it.
|
||||||
|
let file = unsafe { std::fs::File::from_raw_fd(fd.as_raw_fd()) };
|
||||||
|
Ok(file)
|
||||||
|
} else {
|
||||||
|
Err(LinkError::InvalidLink)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts [IterLink] into an [async_fs::File]. That file can be used to
|
||||||
|
/// retrieve the outputs of the iterator program.
|
||||||
|
#[cfg(feature = "async_std")]
|
||||||
|
pub fn into_async_io_file(self) -> Result<async_fs::File, LinkError> {
|
||||||
|
if let PerfLinkInner::FdLink(fd) = self.into_inner() {
|
||||||
|
let fd = bpf_create_iter(fd.fd.as_fd()).map_err(|(_, error)| {
|
||||||
|
LinkError::SyscallError(SyscallError {
|
||||||
|
call: "bpf_iter_create",
|
||||||
|
io_error: error,
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
// We don't want to drop the descriptor when going out of the scope
|
||||||
|
// of this method. The lifecycle of the descriptor is going to be
|
||||||
|
// managed by the `File` created below.
|
||||||
|
let fd = ManuallyDrop::new(fd);
|
||||||
|
// SAFETY: We are sure that the file descriptor is valid. This
|
||||||
|
// `File` takes responsibility of closing it.
|
||||||
|
//
|
||||||
|
// NOTE: Unfortunately, there is no `async_fs::File::from_raw_fd`
|
||||||
|
// method, so we have to work around that with creating
|
||||||
|
// `std::fs::File` and then converting it into `async_fs::File`.
|
||||||
|
let file = unsafe { std::fs::File::from_raw_fd(fd.as_raw_fd()) };
|
||||||
|
let file: async_fs::File = file.into();
|
||||||
|
Ok(file)
|
||||||
|
} else {
|
||||||
|
Err(LinkError::InvalidLink)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts [IterLink] into a [tokio::fs::File]. That file can be used to
|
||||||
|
/// retrieve the outputs of the iterator program.
|
||||||
|
#[cfg(feature = "async_tokio")]
|
||||||
|
pub fn into_tokio_file(self) -> Result<tokio::fs::File, LinkError> {
|
||||||
|
if let PerfLinkInner::FdLink(fd) = self.into_inner() {
|
||||||
|
let fd = bpf_create_iter(fd.fd.as_fd()).map_err(|(_, error)| {
|
||||||
|
LinkError::SyscallError(SyscallError {
|
||||||
|
call: "bpf_iter_create",
|
||||||
|
io_error: error,
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
// We don't want to drop the descriptor when going out of the scope
|
||||||
|
// of this method. The lifecycle of the descriptor is going to be
|
||||||
|
// managed by the `File` created below.
|
||||||
|
let fd = ManuallyDrop::new(fd);
|
||||||
|
// SAFETY: We are sure that the file descriptor is valid. This
|
||||||
|
// `File` takes responsibility of closing it.
|
||||||
|
let file = unsafe { tokio::fs::File::from_raw_fd(fd.as_raw_fd()) };
|
||||||
|
Ok(file)
|
||||||
|
} else {
|
||||||
|
Err(LinkError::InvalidLink)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
// clang-format off
|
||||||
|
#include <vmlinux.h>
|
||||||
|
#include <bpf/bpf_helpers.h>
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
char _license[] SEC("license") = "GPL";
|
||||||
|
|
||||||
|
SEC("iter/task")
|
||||||
|
int iter_task(struct bpf_iter__task *ctx) {
|
||||||
|
struct seq_file *seq = ctx->meta->seq;
|
||||||
|
struct task_struct *task = ctx->task;
|
||||||
|
if (task == NULL) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->meta->seq_num == 0) {
|
||||||
|
BPF_SEQ_PRINTF(seq, "tgid pid name\n");
|
||||||
|
}
|
||||||
|
BPF_SEQ_PRINTF(seq, "%-8d %-8d %s\n", task->tgid, task->pid, task->comm);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
use std::io::BufRead;
|
||||||
|
|
||||||
|
use aya::{programs::Iter, Btf, Ebpf};
|
||||||
|
use test_log::test;
|
||||||
|
use tokio::io::AsyncBufReadExt;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn iter_task() {
|
||||||
|
let mut ebpf = Ebpf::load(crate::ITER_TASK).unwrap();
|
||||||
|
let btf = Btf::from_sys_fs().unwrap();
|
||||||
|
let prog: &mut Iter = ebpf.program_mut("iter_task").unwrap().try_into().unwrap();
|
||||||
|
prog.load("task", &btf).unwrap();
|
||||||
|
|
||||||
|
let link_id = prog.attach().unwrap();
|
||||||
|
let link = prog.take_link(link_id).unwrap();
|
||||||
|
let file = link.into_file().unwrap();
|
||||||
|
let reader = std::io::BufReader::new(file);
|
||||||
|
|
||||||
|
let mut lines = reader.lines();
|
||||||
|
let line_title = lines.next().unwrap().unwrap();
|
||||||
|
let line_init = lines.next().unwrap().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(line_title, "tgid pid name");
|
||||||
|
assert!(line_init == "1 1 init" || line_init == "1 1 systemd");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test(tokio::test)]
|
||||||
|
async fn iter_async_task() {
|
||||||
|
let mut ebpf = Ebpf::load(crate::ITER_TASK).unwrap();
|
||||||
|
let btf = Btf::from_sys_fs().unwrap();
|
||||||
|
let prog: &mut Iter = ebpf.program_mut("iter_task").unwrap().try_into().unwrap();
|
||||||
|
prog.load("task", &btf).unwrap();
|
||||||
|
|
||||||
|
let link_id = prog.attach().unwrap();
|
||||||
|
let link = prog.take_link(link_id).unwrap();
|
||||||
|
let file = link.into_tokio_file().unwrap();
|
||||||
|
let reader = tokio::io::BufReader::new(file);
|
||||||
|
|
||||||
|
let mut lines = reader.lines();
|
||||||
|
let line_title = lines.next_line().await.unwrap().unwrap();
|
||||||
|
let line_init = lines.next_line().await.unwrap().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(line_title, "tgid pid name");
|
||||||
|
assert!(line_init == "1 1 init" || line_init == "1 1 systemd");
|
||||||
|
}
|
Loading…
Reference in New Issue