diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index 1d244af9..2c88a986 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -22,9 +22,10 @@ use crate::{ MapKind, Object, ParseError, ProgramSection, }, programs::{ - BtfTracePoint, CgroupSkb, CgroupSkbAttachType, Extension, FEntry, FExit, KProbe, LircMode2, - Lsm, PerfEvent, ProbeKind, Program, ProgramData, ProgramError, RawTracePoint, - SchedClassifier, SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint, UProbe, Xdp, + BtfTracePoint, CgroupSkb, CgroupSkbAttachType, CgroupSysctl, Extension, FEntry, FExit, + KProbe, LircMode2, Lsm, PerfEvent, ProbeKind, Program, ProgramData, ProgramError, + RawTracePoint, SchedClassifier, SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint, + UProbe, Xdp, }, sys::{ bpf_load_btf, bpf_map_freeze, bpf_map_update_elem_ptr, is_btf_datasec_supported, @@ -443,6 +444,11 @@ impl<'a> BpfLoader<'a> { ProgramSection::SkMsg { .. } => Program::SkMsg(SkMsg { data: ProgramData::new(prog_name, obj, btf_fd), }), + ProgramSection::CgroupSysctl { .. } => { + Program::CgroupSysctl(CgroupSysctl { + data: ProgramData::new(prog_name, obj, btf_fd), + }) + } ProgramSection::SkSkbStreamParser { .. } => Program::SkSkb(SkSkb { data: ProgramData::new(prog_name, obj, btf_fd), kind: SkSkbKind::StreamParser, diff --git a/aya/src/obj/mod.rs b/aya/src/obj/mod.rs index 7888b906..5bd59007 100644 --- a/aya/src/obj/mod.rs +++ b/aya/src/obj/mod.rs @@ -119,6 +119,7 @@ pub enum ProgramSection { CgroupSkb { name: String }, CgroupSkbIngress { name: String }, CgroupSkbEgress { name: String }, + CgroupSysctl { name: String }, LircMode2 { name: String }, PerfEvent { name: String }, RawTracePoint { name: String }, @@ -147,6 +148,7 @@ impl ProgramSection { ProgramSection::CgroupSkb { name } => name, ProgramSection::CgroupSkbIngress { name } => name, ProgramSection::CgroupSkbEgress { name } => name, + ProgramSection::CgroupSysctl { name } => name, ProgramSection::LircMode2 { name } => name, ProgramSection::PerfEvent { name } => name, ProgramSection::RawTracePoint { name } => name, @@ -214,8 +216,10 @@ impl FromStr for ProgramSection { "cgroup_skb/ingress" => CgroupSkbIngress { name }, "cgroup_skb/egress" => CgroupSkbEgress { name }, "cgroup/skb" => CgroupSkb { name }, + "cgroup/sysctl" => CgroupSysctl { name }, "cgroup" => match &*name { "skb" => CgroupSkb { name }, + "sysctl" => CgroupSysctl { name }, _ => { return Err(ParseError::InvalidProgramSection { section: section.to_owned(), diff --git a/aya/src/programs/cgroup_sysctl.rs b/aya/src/programs/cgroup_sysctl.rs new file mode 100644 index 00000000..ce070f0e --- /dev/null +++ b/aya/src/programs/cgroup_sysctl.rs @@ -0,0 +1,153 @@ +use std::{ + hash::Hash, + os::unix::prelude::{AsRawFd, RawFd}, +}; + +use crate::{ + generated::{bpf_attach_type::BPF_CGROUP_SYSCTL, bpf_prog_type::BPF_PROG_TYPE_CGROUP_SYSCTL}, + programs::{ + define_link_wrapper, load_program, FdLink, Link, OwnedLink, ProgAttachLink, ProgramData, + ProgramError, + }, + sys::{bpf_link_create, bpf_prog_attach, kernel_version}, +}; + +/// A program used to watch for sysctl changes. +/// +/// [`CgroupSysctl`] programs can be attached to a cgroup and will be called every +/// time a process inside that cgroup tries to read from or write to a sysctl knob in proc. +/// +/// # Minimum kernel version +/// +/// The minimum kernel version required to use this feature is 5.2. +/// +/// # Examples +/// +/// ```no_run +/// # #[derive(Debug, thiserror::Error)] +/// # enum Error { +/// # #[error(transparent)] +/// # IO(#[from] std::io::Error), +/// # #[error(transparent)] +/// # Map(#[from] aya::maps::MapError), +/// # #[error(transparent)] +/// # Program(#[from] aya::programs::ProgramError), +/// # #[error(transparent)] +/// # Bpf(#[from] aya::BpfError) +/// # } +/// # let mut bpf = aya::Bpf::load(&[])?; +/// use std::fs::File; +/// use std::convert::TryInto; +/// use aya::programs::CgroupSysctl; +/// +/// let file = File::open("/sys/fs/cgroup/unified")?; +/// let program: &mut CgroupSysctl = bpf.program_mut("cgroup_sysctl").unwrap().try_into()?; +/// program.load()?; +/// program.attach(file)?; +/// # Ok::<(), Error>(()) +/// ``` +#[derive(Debug)] +#[doc(alias = "BPF_PROG_TYPE_CGROUP_SYSCTL")] +pub struct CgroupSysctl { + pub(crate) data: ProgramData, +} + +impl CgroupSysctl { + /// Loads the program inside the kernel. + pub fn load(&mut self) -> Result<(), ProgramError> { + load_program(BPF_PROG_TYPE_CGROUP_SYSCTL, &mut self.data) + } + + /// Attaches the program to the given cgroup. + /// + /// The returned value can be used to detach, see [CgroupSysctl::detach]. + pub fn attach(&mut self, cgroup: T) -> Result { + let prog_fd = self.data.fd_or_err()?; + let cgroup_fd = cgroup.as_raw_fd(); + + let k_ver = kernel_version().unwrap(); + if k_ver >= (5, 7, 0) { + let link_fd = bpf_link_create(prog_fd, cgroup_fd, BPF_CGROUP_SYSCTL, None, 0).map_err( + |(_, io_error)| ProgramError::SyscallError { + call: "bpf_link_create".to_owned(), + io_error, + }, + )? as RawFd; + self.data + .links + .insert(CgroupSysctlLink(CgroupSysctlLinkInner::Fd(FdLink::new( + link_fd, + )))) + } else { + bpf_prog_attach(prog_fd, cgroup_fd, BPF_CGROUP_SYSCTL).map_err(|(_, io_error)| { + ProgramError::SyscallError { + call: "bpf_prog_attach".to_owned(), + io_error, + } + })?; + + self.data + .links + .insert(CgroupSysctlLink(CgroupSysctlLinkInner::ProgAttach( + ProgAttachLink::new(prog_fd, cgroup_fd, BPF_CGROUP_SYSCTL), + ))) + } + } + + /// 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 forget_link( + &mut self, + link_id: CgroupSysctlLinkId, + ) -> Result, ProgramError> { + Ok(OwnedLink::new(self.data.forget_link(link_id)?)) + } + + /// Detaches the program. + /// + /// See [CgroupSysctl::attach]. + pub fn detach(&mut self, link_id: CgroupSysctlLinkId) -> Result<(), ProgramError> { + self.data.links.remove(link_id) + } +} + +#[derive(Debug, Hash, Eq, PartialEq)] +enum CgroupSysctlLinkIdInner { + Fd(::Id), + ProgAttach(::Id), +} + +#[derive(Debug)] +enum CgroupSysctlLinkInner { + Fd(FdLink), + ProgAttach(ProgAttachLink), +} + +impl Link for CgroupSysctlLinkInner { + type Id = CgroupSysctlLinkIdInner; + + fn id(&self) -> Self::Id { + match self { + CgroupSysctlLinkInner::Fd(fd) => CgroupSysctlLinkIdInner::Fd(fd.id()), + CgroupSysctlLinkInner::ProgAttach(p) => CgroupSysctlLinkIdInner::ProgAttach(p.id()), + } + } + + fn detach(self) -> Result<(), ProgramError> { + match self { + CgroupSysctlLinkInner::Fd(fd) => fd.detach(), + CgroupSysctlLinkInner::ProgAttach(p) => p.detach(), + } + } +} + +define_link_wrapper!( + /// The link used by [CgroupSysctl] programs. + CgroupSysctlLink, + /// The type returned by [CgroupSysctl::attach]. Can be passed to [CgroupSysctl::detach]. + CgroupSysctlLinkId, + CgroupSysctlLinkInner, + CgroupSysctlLinkIdInner +); diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index 559c6e4f..59f9fd71 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -37,6 +37,7 @@ //! [`Bpf::program_mut`]: crate::Bpf::program_mut //! [`maps`]: crate::maps mod cgroup_skb; +mod cgroup_sysctl; mod extension; mod fentry; mod fexit; @@ -70,6 +71,7 @@ use std::{ use thiserror::Error; pub use cgroup_skb::{CgroupSkb, CgroupSkbAttachType}; +pub use cgroup_sysctl::CgroupSysctl; pub use extension::{Extension, ExtensionError}; pub use fentry::FEntry; pub use fexit::FExit; @@ -233,6 +235,8 @@ pub enum Program { SchedClassifier(SchedClassifier), /// A [`CgroupSkb`] program CgroupSkb(CgroupSkb), + /// A [`CgroupSysctl`] program + CgroupSysctl(CgroupSysctl), /// A [`LircMode2`] program LircMode2(LircMode2), /// A [`PerfEvent`] program @@ -266,6 +270,7 @@ impl Program { 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::LircMode2(_) => BPF_PROG_TYPE_LIRC_MODE2, Program::PerfEvent(_) => BPF_PROG_TYPE_PERF_EVENT, Program::RawTracePoint(_) => BPF_PROG_TYPE_RAW_TRACEPOINT, @@ -290,6 +295,7 @@ impl Program { Program::SockOps(p) => p.data.pin(path), Program::SchedClassifier(p) => p.data.pin(path), Program::CgroupSkb(p) => p.data.pin(path), + Program::CgroupSysctl(p) => p.data.pin(path), Program::LircMode2(p) => p.data.pin(path), Program::PerfEvent(p) => p.data.pin(path), Program::RawTracePoint(p) => p.data.pin(path), @@ -494,6 +500,7 @@ impl ProgramFd for Program { Program::SockOps(p) => p.data.fd, Program::SchedClassifier(p) => p.data.fd, Program::CgroupSkb(p) => p.data.fd, + Program::CgroupSysctl(p) => p.data.fd, Program::LircMode2(p) => p.data.fd, Program::PerfEvent(p) => p.data.fd, Program::RawTracePoint(p) => p.data.fd, @@ -540,6 +547,7 @@ impl_program_fd!( SkSkb, SchedClassifier, CgroupSkb, + CgroupSysctl, LircMode2, PerfEvent, Lsm, @@ -589,6 +597,7 @@ impl_try_from_program!( SockOps, SchedClassifier, CgroupSkb, + CgroupSysctl, LircMode2, PerfEvent, Lsm, diff --git a/bpf/aya-bpf-macros/src/expand.rs b/bpf/aya-bpf-macros/src/expand.rs index 4285de81..7e3947ac 100644 --- a/bpf/aya-bpf-macros/src/expand.rs +++ b/bpf/aya-bpf-macros/src/expand.rs @@ -215,6 +215,38 @@ impl SchedClassifier { } } +pub struct CgroupSysctl { + item: ItemFn, + name: Option, +} + +impl CgroupSysctl { + pub fn from_syn(mut args: Args, item: ItemFn) -> Result { + let name = name_arg(&mut args)?; + + Ok(CgroupSysctl { item, name }) + } + + pub fn expand(&self) -> Result { + let section_name = if let Some(name) = &self.name { + format!("cgroup/sysctl/{}", name) + } else { + ("cgroup/sysctl").to_owned() + }; + let fn_name = &self.item.sig.ident; + let item = &self.item; + Ok(quote! { + #[no_mangle] + #[link_section = #section_name] + fn #fn_name(ctx: *mut ::aya_bpf::bindings::bpf_sysctl) -> i32 { + return #fn_name(::aya_bpf::programs::SysctlContext::new(ctx)); + + #item + } + }) + } +} + pub struct CgroupSkb { item: ItemFn, expected_attach_type: Option, diff --git a/bpf/aya-bpf-macros/src/lib.rs b/bpf/aya-bpf-macros/src/lib.rs index 96b0267e..1512842d 100644 --- a/bpf/aya-bpf-macros/src/lib.rs +++ b/bpf/aya-bpf-macros/src/lib.rs @@ -1,9 +1,9 @@ mod expand; use expand::{ - Args, BtfTracePoint, CgroupSkb, FEntry, FExit, Lsm, Map, PerfEvent, Probe, ProbeKind, - RawTracePoint, SchedClassifier, SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint, - Xdp, + Args, BtfTracePoint, CgroupSkb, CgroupSysctl, FEntry, FExit, Lsm, Map, PerfEvent, Probe, + ProbeKind, RawTracePoint, SchedClassifier, SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, + TracePoint, Xdp, }; use proc_macro::TokenStream; use syn::{parse_macro_input, ItemFn, ItemStatic}; @@ -83,6 +83,17 @@ pub fn classifier(attrs: TokenStream, item: TokenStream) -> TokenStream { .into() } +#[proc_macro_attribute] +pub fn cgroup_sysctl(attrs: TokenStream, item: TokenStream) -> TokenStream { + let args = parse_macro_input!(attrs as Args); + let item = parse_macro_input!(item as ItemFn); + + CgroupSysctl::from_syn(args, item) + .and_then(|u| u.expand()) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} + #[proc_macro_attribute] pub fn cgroup_skb(attrs: TokenStream, item: TokenStream) -> TokenStream { let args = parse_macro_input!(attrs as Args); diff --git a/bpf/aya-bpf/src/programs/mod.rs b/bpf/aya-bpf/src/programs/mod.rs index 8a022c18..3c4348b6 100644 --- a/bpf/aya-bpf/src/programs/mod.rs +++ b/bpf/aya-bpf/src/programs/mod.rs @@ -7,6 +7,7 @@ pub mod raw_tracepoint; pub mod sk_buff; pub mod sk_msg; pub mod sock_ops; +pub mod sysctl; pub mod tp_btf; pub mod tracepoint; pub mod xdp; @@ -20,6 +21,7 @@ pub use raw_tracepoint::RawTracePointContext; pub use sk_buff::SkBuffContext; pub use sk_msg::SkMsgContext; pub use sock_ops::SockOpsContext; +pub use sysctl::SysctlContext; pub use tp_btf::BtfTracePointContext; pub use tracepoint::TracePointContext; pub use xdp::XdpContext; diff --git a/bpf/aya-bpf/src/programs/sysctl.rs b/bpf/aya-bpf/src/programs/sysctl.rs new file mode 100644 index 00000000..fedbd5d7 --- /dev/null +++ b/bpf/aya-bpf/src/programs/sysctl.rs @@ -0,0 +1,19 @@ +use core::ffi::c_void; + +use crate::{bindings::bpf_sysctl, BpfContext}; + +pub struct SysctlContext { + pub sysctl: *mut bpf_sysctl, +} + +impl SysctlContext { + pub fn new(sysctl: *mut bpf_sysctl) -> SysctlContext { + SysctlContext { sysctl } + } +} + +impl BpfContext for SysctlContext { + fn as_ptr(&self) -> *mut c_void { + self.sysctl as *mut _ + } +}