use std::{
ffi::CString,
io,
os::fd::{AsFd as _, AsRawFd as _, BorrowedFd, RawFd},
path::{Path, PathBuf},
};
use aya_obj::{
InvalidTypeBinding,
generated::{
BPF_F_AFTER, BPF_F_ALLOW_MULTI, BPF_F_ALLOW_OVERRIDE, BPF_F_BEFORE, BPF_F_ID, BPF_F_LINK,
BPF_F_REPLACE, bpf_attach_type, bpf_link_info, bpf_link_type,
},
};
use hashbrown::hash_set::{Entry, HashSet};
use thiserror::Error;
use crate::{
pin::PinError,
programs::{MultiProgLink, MultiProgram, ProgramError, ProgramFd, ProgramId},
sys::{
SyscallError, bpf_get_object, bpf_link_get_info_by_fd, bpf_pin_object, bpf_prog_attach,
bpf_prog_detach,
},
};
pub trait Link: std::fmt::Debug + Eq + std::hash::Hash + 'static {
type Id: std::fmt::Debug + Eq + std::hash::Hash + hashbrown::Equivalent<Self>;
fn id(&self) -> Self::Id;
fn detach(self) -> Result<(), ProgramError>;
}
#[derive(Clone, Copy, Debug, Default)]
pub enum CgroupAttachMode {
#[default]
Single,
AllowOverride,
AllowMultiple,
}
impl From<CgroupAttachMode> for u32 {
fn from(mode: CgroupAttachMode) -> Self {
match mode {
CgroupAttachMode::Single => 0,
CgroupAttachMode::AllowOverride => BPF_F_ALLOW_OVERRIDE,
CgroupAttachMode::AllowMultiple => BPF_F_ALLOW_MULTI,
}
}
}
#[derive(Debug)]
pub(crate) struct Links<T: Link> {
links: HashSet<T>,
}
impl<T> Links<T>
where
T: Eq + std::hash::Hash + Link,
T::Id: hashbrown::Equivalent<T> + Eq + std::hash::Hash,
{
pub(crate) fn new() -> Self {
Self {
links: Default::default(),
}
}
pub(crate) fn insert(&mut self, link: T) -> Result<T::Id, ProgramError> {
match self.links.entry(link) {
Entry::Occupied(_entry) => Err(ProgramError::AlreadyAttached),
Entry::Vacant(entry) => Ok(entry.insert().get().id()),
}
}
pub(crate) fn remove(&mut self, link_id: T::Id) -> Result<(), ProgramError> {
self.links
.take(&link_id)
.ok_or(ProgramError::NotAttached)?
.detach()
}
pub(crate) fn forget(&mut self, link_id: T::Id) -> Result<T, ProgramError> {
self.links.take(&link_id).ok_or(ProgramError::NotAttached)
}
}
impl<T: Link> Links<T> {
pub(crate) fn remove_all(&mut self) -> Result<(), ProgramError> {
for link in self.links.drain() {
link.detach()?;
}
Ok(())
}
}
impl<T: Link> Drop for Links<T> {
fn drop(&mut self) {
let _unused: Result<(), ProgramError> = self.remove_all();
}
}
#[doc(alias = "bpf_link_info")]
pub struct LinkInfo(bpf_link_info);
impl LinkInfo {
pub(crate) fn new_from_fd(fd: BorrowedFd<'_>) -> Result<Self, LinkError> {
let info = bpf_link_get_info_by_fd(fd)?;
Ok(Self(info))
}
pub const fn id(&self) -> u32 {
self.0.id
}
pub const fn program_id(&self) -> u32 {
self.0.prog_id
}
pub fn link_type(&self) -> Result<LinkType, LinkError> {
bpf_link_type::try_from(self.0.type_)
.map_err(|InvalidTypeBinding { value }| LinkError::UnknownLinkType(value))
.and_then(LinkType::try_from)
}
}
#[non_exhaustive]
#[doc(alias = "bpf_link_type")]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum LinkType {
#[doc(alias = "BPF_LINK_TYPE_UNSPEC")]
Unspecified = bpf_link_type::BPF_LINK_TYPE_UNSPEC as isize,
#[doc(alias = "BPF_LINK_TYPE_RAW_TRACEPOINT")]
RawTracePoint = bpf_link_type::BPF_LINK_TYPE_RAW_TRACEPOINT as isize,
#[doc(alias = "BPF_LINK_TYPE_TRACING")]
Tracing = bpf_link_type::BPF_LINK_TYPE_TRACING as isize,
#[doc(alias = "BPF_LINK_TYPE_CGROUP")]
Cgroup = bpf_link_type::BPF_LINK_TYPE_CGROUP as isize,
#[doc(alias = "BPF_LINK_TYPE_ITER")]
Iter = bpf_link_type::BPF_LINK_TYPE_ITER as isize,
#[doc(alias = "BPF_LINK_TYPE_NETNS")]
Netns = bpf_link_type::BPF_LINK_TYPE_NETNS as isize,
#[doc(alias = "BPF_LINK_TYPE_XDP")]
Xdp = bpf_link_type::BPF_LINK_TYPE_XDP as isize,
#[doc(alias = "BPF_LINK_TYPE_PERF_EVENT")]
PerfEvent = bpf_link_type::BPF_LINK_TYPE_PERF_EVENT as isize,
#[doc(alias = "BPF_LINK_TYPE_KPROBE_MULTI")]
KProbeMulti = bpf_link_type::BPF_LINK_TYPE_KPROBE_MULTI as isize,
#[doc(alias = "BPF_LINK_TYPE_STRUCT_OPS")]
StructOps = bpf_link_type::BPF_LINK_TYPE_STRUCT_OPS as isize,
#[doc(alias = "BPF_LINK_TYPE_NETFILTER")]
Netfilter = bpf_link_type::BPF_LINK_TYPE_NETFILTER as isize,
#[doc(alias = "BPF_LINK_TYPE_TCX")]
Tcx = bpf_link_type::BPF_LINK_TYPE_TCX as isize,
#[doc(alias = "BPF_LINK_TYPE_UPROBE_MULTI")]
UProbeMulti = bpf_link_type::BPF_LINK_TYPE_UPROBE_MULTI as isize,
#[doc(alias = "BPF_LINK_TYPE_NETKIT")]
Netkit = bpf_link_type::BPF_LINK_TYPE_NETKIT as isize,
}
impl TryFrom<bpf_link_type> for LinkType {
type Error = LinkError;
fn try_from(link_type: bpf_link_type) -> Result<Self, Self::Error> {
match link_type {
bpf_link_type::BPF_LINK_TYPE_UNSPEC => Ok(Self::Unspecified),
bpf_link_type::BPF_LINK_TYPE_RAW_TRACEPOINT => Ok(Self::RawTracePoint),
bpf_link_type::BPF_LINK_TYPE_TRACING => Ok(Self::Tracing),
bpf_link_type::BPF_LINK_TYPE_CGROUP => Ok(Self::Cgroup),
bpf_link_type::BPF_LINK_TYPE_ITER => Ok(Self::Iter),
bpf_link_type::BPF_LINK_TYPE_NETNS => Ok(Self::Netns),
bpf_link_type::BPF_LINK_TYPE_XDP => Ok(Self::Xdp),
bpf_link_type::BPF_LINK_TYPE_PERF_EVENT => Ok(Self::PerfEvent),
bpf_link_type::BPF_LINK_TYPE_KPROBE_MULTI => Ok(Self::KProbeMulti),
bpf_link_type::BPF_LINK_TYPE_STRUCT_OPS => Ok(Self::StructOps),
bpf_link_type::BPF_LINK_TYPE_NETFILTER => Ok(Self::Netfilter),
bpf_link_type::BPF_LINK_TYPE_TCX => Ok(Self::Tcx),
bpf_link_type::BPF_LINK_TYPE_UPROBE_MULTI => Ok(Self::UProbeMulti),
bpf_link_type::BPF_LINK_TYPE_NETKIT => Ok(Self::Netkit),
bpf_link_type::__MAX_BPF_LINK_TYPE => Err(LinkError::UnknownLinkType(link_type as u32)),
}
}
}
#[derive(Debug, Hash, Eq, PartialEq)]
pub struct FdLinkId(pub(crate) RawFd);
#[derive(Debug)]
pub struct FdLink {
pub(crate) fd: crate::MockableFd,
}
impl FdLink {
pub(crate) const fn new(fd: crate::MockableFd) -> Self {
Self { fd }
}
pub fn pin<P: AsRef<Path>>(self, path: P) -> Result<PinnedLink, PinError> {
use std::os::unix::ffi::OsStrExt as _;
let path = path.as_ref();
let path_string = CString::new(path.as_os_str().as_bytes()).map_err(|error| {
PinError::InvalidPinPath {
path: path.into(),
error,
}
})?;
bpf_pin_object(self.fd.as_fd(), &path_string).map_err(|io_error| SyscallError {
call: "BPF_OBJ_PIN",
io_error,
})?;
Ok(PinnedLink::new(path.into(), self))
}
pub fn info(&self) -> Result<LinkInfo, LinkError> {
LinkInfo::new_from_fd(self.fd.as_fd())
}
}
impl Link for FdLink {
type Id = FdLinkId;
fn id(&self) -> Self::Id {
FdLinkId(self.fd.as_raw_fd())
}
fn detach(self) -> Result<(), ProgramError> {
Ok(())
}
}
id_as_key!(FdLink, FdLinkId);
impl From<PinnedLink> for FdLink {
fn from(p: PinnedLink) -> Self {
p.inner
}
}
#[derive(Debug)]
pub struct PinnedLink {
inner: FdLink,
path: PathBuf,
}
impl PinnedLink {
const fn new(path: PathBuf, link: FdLink) -> Self {
Self { inner: link, path }
}
pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, LinkError> {
use std::os::unix::ffi::OsStrExt as _;
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| {
LinkError::SyscallError(SyscallError {
call: "BPF_OBJ_GET",
io_error,
})
})?;
Ok(Self::new(path.as_ref().to_path_buf(), FdLink::new(fd)))
}
pub fn unpin(self) -> Result<FdLink, io::Error> {
std::fs::remove_file(self.path)?;
Ok(self.inner)
}
}
#[derive(Debug, Hash, Eq, PartialEq)]
pub struct ProgAttachLinkId(RawFd, RawFd, bpf_attach_type);
#[derive(Debug)]
pub struct ProgAttachLink {
prog_fd: ProgramFd,
target_fd: crate::MockableFd,
attach_type: bpf_attach_type,
}
impl ProgAttachLink {
pub(crate) const fn new(
prog_fd: ProgramFd,
target_fd: crate::MockableFd,
attach_type: bpf_attach_type,
) -> Self {
Self {
prog_fd,
target_fd,
attach_type,
}
}
pub(crate) fn attach(
prog_fd: BorrowedFd<'_>,
target_fd: BorrowedFd<'_>,
attach_type: bpf_attach_type,
mode: CgroupAttachMode,
) -> Result<Self, ProgramError> {
let prog_fd = prog_fd.try_clone_to_owned()?;
let prog_fd = crate::MockableFd::from_fd(prog_fd);
let target_fd = target_fd.try_clone_to_owned()?;
let target_fd = crate::MockableFd::from_fd(target_fd);
bpf_prog_attach(prog_fd.as_fd(), target_fd.as_fd(), attach_type, mode.into())?;
let prog_fd = ProgramFd(prog_fd);
Ok(Self {
prog_fd,
target_fd,
attach_type,
})
}
}
impl Link for ProgAttachLink {
type Id = ProgAttachLinkId;
fn id(&self) -> Self::Id {
ProgAttachLinkId(
self.prog_fd.as_fd().as_raw_fd(),
self.target_fd.as_raw_fd(),
self.attach_type,
)
}
fn detach(self) -> Result<(), ProgramError> {
bpf_prog_detach(
self.prog_fd.as_fd(),
self.target_fd.as_fd(),
self.attach_type,
)
.map_err(Into::into)
}
}
id_as_key!(ProgAttachLink, ProgAttachLinkId);
macro_rules! id_as_key {
($wrapper:ident, $wrapper_id:ident) => {
impl PartialEq for $wrapper {
fn eq(&self, other: &Self) -> bool {
use $crate::programs::links::Link as _;
self.id() == other.id()
}
}
impl Eq for $wrapper {}
impl std::hash::Hash for $wrapper {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
use $crate::programs::links::Link as _;
self.id().hash(state)
}
}
impl hashbrown::Equivalent<$wrapper> for $wrapper_id {
fn equivalent(&self, key: &$wrapper) -> bool {
use $crate::programs::links::Link as _;
*self == key.id()
}
}
};
}
pub(crate) use id_as_key;
macro_rules! define_link_wrapper {
($wrapper:ident, $wrapper_id:ident, $base:ident, $base_id:ident, $program:ident $(,)?) => {
#[doc = concat!("[`", stringify!($program), "::attach`]")]
#[doc = concat!("[`", stringify!($program), "::detach`]")]
#[derive(Debug, Hash, Eq, PartialEq)]
pub struct $wrapper_id($base_id);
/// The link used by
#[doc = concat!("[`", stringify!($program), "`]")]
#[derive(Debug)]
pub struct $wrapper(Option<$base>);
#[expect(clippy::allow_attributes, reason = "macro")]
#[allow(dead_code, reason = "macro")]
impl $wrapper {
const fn new(base: $base) -> $wrapper {
$wrapper(Some(base))
}
const fn inner(&self) -> &$base {
self.0.as_ref().unwrap()
}
fn into_inner(mut self) -> $base {
self.0.take().unwrap()
}
}
impl Drop for $wrapper {
fn drop(&mut self) {
use $crate::programs::links::Link as _;
if let Some(base) = self.0.take() {
let _unused: Result<(), ProgramError> = base.detach();
}
}
}
impl $crate::programs::Link for $wrapper {
type Id = $wrapper_id;
fn id(&self) -> Self::Id {
$wrapper_id(self.0.as_ref().unwrap().id())
}
fn detach(mut self) -> Result<(), ProgramError> {
self.0.take().unwrap().detach()
}
}
$crate::programs::links::id_as_key!($wrapper, $wrapper_id);
impl From<$base> for $wrapper {
fn from(b: $base) -> $wrapper {
$wrapper(Some(b))
}
}
impl From<$wrapper> for $base {
fn from(mut w: $wrapper) -> $base {
w.0.take().unwrap()
}
}
impl $program {
pub fn detach(&mut self, link_id: $wrapper_id) -> Result<(), ProgramError> {
self.data.links.remove(link_id)
}
#[doc = concat!("[`", stringify!($wrapper), "`]")]
pub fn take_link(&mut self, link_id: $wrapper_id) -> Result<$wrapper, ProgramError> {
self.data.links.forget(link_id)
}
}
};
}
pub(crate) use define_link_wrapper;
macro_rules! impl_try_into_fdlink {
($wrapper:ident, $inner:ident) => {
impl TryFrom<$wrapper> for $crate::programs::FdLink {
type Error = $crate::programs::LinkError;
fn try_from(value: $wrapper) -> Result<Self, Self::Error> {
if let $inner::Fd(fd) = value.into_inner() {
Ok(fd)
} else {
Err($crate::programs::LinkError::InvalidLink)
}
}
}
};
}
pub(crate) use impl_try_into_fdlink;
#[derive(Error, Debug)]
pub enum LinkError {
#[error("Invalid link")]
InvalidLink,
#[error("unknown link type {0}")]
UnknownLinkType(u32),
#[error(transparent)]
SyscallError(#[from] SyscallError),
}
#[derive(Debug)]
pub(crate) enum LinkRef {
Id(u32),
Fd(RawFd),
}
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct MprogFlags: u32 {
const REPLACE = BPF_F_REPLACE;
const BEFORE = BPF_F_BEFORE;
const AFTER = BPF_F_AFTER;
const ID = BPF_F_ID;
const LINK = BPF_F_LINK;
}
}
#[derive(Debug)]
pub struct LinkOrder {
pub(crate) link_ref: LinkRef,
pub(crate) flags: MprogFlags,
}
impl Default for LinkOrder {
fn default() -> Self {
Self {
link_ref: LinkRef::Fd(0),
flags: MprogFlags::AFTER,
}
}
}
impl LinkOrder {
pub const fn first() -> Self {
Self {
link_ref: LinkRef::Id(0),
flags: MprogFlags::BEFORE,
}
}
pub const fn last() -> Self {
Self {
link_ref: LinkRef::Id(0),
flags: MprogFlags::AFTER,
}
}
pub fn before_link<L: MultiProgLink>(link: &L) -> Result<Self, LinkError> {
Ok(Self {
link_ref: LinkRef::Fd(link.fd()?.as_raw_fd()),
flags: MprogFlags::BEFORE | MprogFlags::LINK,
})
}
pub fn after_link<L: MultiProgLink>(link: &L) -> Result<Self, LinkError> {
Ok(Self {
link_ref: LinkRef::Fd(link.fd()?.as_raw_fd()),
flags: MprogFlags::AFTER | MprogFlags::LINK,
})
}
pub fn before_program<P: MultiProgram>(program: &P) -> Result<Self, ProgramError> {
Ok(Self {
link_ref: LinkRef::Fd(program.fd()?.as_raw_fd()),
flags: MprogFlags::BEFORE,
})
}
pub fn after_program<P: MultiProgram>(program: &P) -> Result<Self, ProgramError> {
Ok(Self {
link_ref: LinkRef::Fd(program.fd()?.as_raw_fd()),
flags: MprogFlags::AFTER,
})
}
pub fn before_program_id(id: ProgramId) -> Self {
Self {
link_ref: LinkRef::Id(id.0),
flags: MprogFlags::BEFORE | MprogFlags::ID,
}
}
pub fn after_program_id(id: ProgramId) -> Self {
Self {
link_ref: LinkRef::Id(id.0),
flags: MprogFlags::AFTER | MprogFlags::ID,
}
}
}
#[cfg(test)]
mod tests {
use std::{cell::RefCell, fs::File, rc::Rc};
use assert_matches::assert_matches;
use aya_obj::generated::{BPF_F_ALLOW_MULTI, BPF_F_ALLOW_OVERRIDE};
use tempfile::tempdir;
use super::{FdLink, Link, Links};
use crate::{
programs::{CgroupAttachMode, ProgramError},
sys::override_syscall,
};
#[derive(Debug, Hash, Eq, PartialEq)]
struct TestLinkId(u8, u8);
#[derive(Debug)]
struct TestLink {
id: (u8, u8),
detached: Rc<RefCell<u8>>,
}
impl TestLink {
fn new(a: u8, b: u8) -> Self {
Self {
id: (a, b),
detached: Rc::new(RefCell::new(0)),
}
}
}
impl Link for TestLink {
type Id = TestLinkId;
fn id(&self) -> Self::Id {
TestLinkId(self.id.0, self.id.1)
}
fn detach(self) -> Result<(), ProgramError> {
*self.detached.borrow_mut() += 1;
Ok(())
}
}
id_as_key!(TestLink, TestLinkId);
#[test]
fn test_link_map() {
let mut links = Links::new();
let l1 = TestLink::new(1, 2);
let l1_detached = Rc::clone(&l1.detached);
let l2 = TestLink::new(1, 3);
let l2_detached = Rc::clone(&l2.detached);
let id1 = links.insert(l1).unwrap();
let id2 = links.insert(l2).unwrap();
assert_eq!(*l1_detached.borrow(), 0);
assert_eq!(*l2_detached.borrow(), 0);
links.remove(id1).unwrap();
assert_eq!(*l1_detached.borrow(), 1);
assert_eq!(*l2_detached.borrow(), 0);
links.remove(id2).unwrap();
assert_eq!(*l1_detached.borrow(), 1);
assert_eq!(*l2_detached.borrow(), 1);
}
#[test]
fn test_already_attached() {
let mut links = Links::new();
links.insert(TestLink::new(1, 2)).unwrap();
assert_matches!(
links.insert(TestLink::new(1, 2)),
Err(ProgramError::AlreadyAttached)
);
}
#[test]
fn test_not_attached() {
let mut links = Links::new();
let l1 = TestLink::new(1, 2);
let l1_id1 = l1.id();
let l1_id2 = l1.id();
links.insert(TestLink::new(1, 2)).unwrap();
links.remove(l1_id1).unwrap();
assert_matches!(links.remove(l1_id2), Err(ProgramError::NotAttached));
}
#[test]
fn test_drop_detach() {
let l1 = TestLink::new(1, 2);
let l1_detached = Rc::clone(&l1.detached);
let l2 = TestLink::new(1, 3);
let l2_detached = Rc::clone(&l2.detached);
{
let mut links = Links::new();
let id1 = links.insert(l1).unwrap();
links.insert(l2).unwrap();
links.remove(id1).unwrap();
assert_eq!(*l1_detached.borrow(), 1);
assert_eq!(*l2_detached.borrow(), 0);
}
assert_eq!(*l1_detached.borrow(), 1);
assert_eq!(*l2_detached.borrow(), 1);
}
#[test]
fn test_owned_detach() {
let l1 = TestLink::new(1, 2);
let l1_detached = Rc::clone(&l1.detached);
let l2 = TestLink::new(1, 3);
let l2_detached = Rc::clone(&l2.detached);
let owned_l1 = {
let mut links = Links::new();
let id1 = links.insert(l1).unwrap();
links.insert(l2).unwrap();
let owned_l1 = links.forget(id1);
assert_eq!(*l1_detached.borrow(), 0);
assert_eq!(*l2_detached.borrow(), 0);
owned_l1.unwrap()
};
assert_eq!(*l1_detached.borrow(), 0);
assert_eq!(*l2_detached.borrow(), 1);
owned_l1.detach().unwrap();
assert_eq!(*l1_detached.borrow(), 1);
assert_eq!(*l2_detached.borrow(), 1);
}
#[test]
#[cfg_attr(miri, ignore = "`mkdir` not available when isolation is enabled")]
fn test_pin() {
let dir = tempdir().unwrap();
let f1 = File::create(dir.path().join("f1")).expect("unable to create file in tmpdir");
let fd_link = FdLink::new(f1.into());
override_syscall(|_| Ok(0));
let pin = dir.path().join("f1-pin");
File::create(&pin).expect("unable to create file in tmpdir");
assert!(pin.exists());
let pinned_link = fd_link.pin(&pin).expect("pin failed");
pinned_link.unpin().expect("unpin failed");
assert!(!pin.exists());
}
#[test]
fn test_cgroup_attach_flag() {
assert_eq!(u32::from(CgroupAttachMode::Single), 0);
assert_eq!(
u32::from(CgroupAttachMode::AllowOverride),
BPF_F_ALLOW_OVERRIDE
);
assert_eq!(
u32::from(CgroupAttachMode::AllowMultiple),
BPF_F_ALLOW_MULTI
);
}
}