-
Notifications
You must be signed in to change notification settings - Fork 291
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.html
- Loading branch information
1 parent
2791bad
commit a06e293
Showing
12 changed files
with
332 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
//! 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. | ||
/// | ||
/// ```no_run | ||
/// use aya::{programs::Iter, BtfError, Btf, Ebpf}; | ||
/// | ||
/// let program: &mut Iter = bpf.program_mut("iter_prog").unwrap().try_into()?; | ||
/// program.load()?; | ||
/// program.attach()?; | ||
/// # Ok::<(), LsmError>(()) | ||
/// ``` | ||
#[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::BtfId, 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_fd::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) | ||
} | ||
} | ||
} |
Oops, something went wrong.