diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index f0506df5..8b3dee97 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -22,10 +22,10 @@ use crate::{ MapKind, Object, ParseError, ProgramSection, }, programs::{ - BtfTracePoint, CgroupSkb, CgroupSkbAttachType, CgroupSockAddr, CgroupSysctl, Extension, - FEntry, FExit, KProbe, LircMode2, Lsm, PerfEvent, ProbeKind, Program, ProgramData, - ProgramError, RawTracePoint, SchedClassifier, SkMsg, SkSkb, SkSkbKind, SockOps, - SocketFilter, TracePoint, UProbe, Xdp, + BtfTracePoint, CgroupSkb, CgroupSkbAttachType, CgroupSockAddr, CgroupSockopt, 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, @@ -449,6 +449,12 @@ impl<'a> BpfLoader<'a> { data: ProgramData::new(prog_name, obj, btf_fd), }) } + ProgramSection::CgroupSockopt { attach_type, .. } => { + Program::CgroupSockopt(CgroupSockopt { + data: ProgramData::new(prog_name, obj, btf_fd), + attach_type: *attach_type, + }) + } 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 ad2afaf1..a8624c50 100644 --- a/aya/src/obj/mod.rs +++ b/aya/src/obj/mod.rs @@ -21,7 +21,7 @@ use crate::{ bpf_map_def, generated::{bpf_insn, bpf_map_type::BPF_MAP_TYPE_ARRAY, BPF_F_RDONLY_PROG}, obj::btf::{Btf, BtfError, BtfExt}, - programs::CgroupSockAddrAttachType, + programs::{CgroupSockAddrAttachType, CgroupSockoptAttachType}, BpfError, }; use std::slice::from_raw_parts_mut; @@ -157,6 +157,10 @@ pub enum ProgramSection { CgroupSysctl { name: String, }, + CgroupSockopt { + name: String, + attach_type: CgroupSockoptAttachType, + }, LircMode2 { name: String, }, @@ -203,6 +207,7 @@ impl ProgramSection { ProgramSection::CgroupSkbEgress { name, .. } => name, ProgramSection::CgroupSockAddr { name, .. } => name, ProgramSection::CgroupSysctl { name } => name, + ProgramSection::CgroupSockopt { name, .. } => name, ProgramSection::LircMode2 { name } => name, ProgramSection::PerfEvent { name } => name, ProgramSection::RawTracePoint { name } => name, @@ -271,9 +276,26 @@ impl FromStr for ProgramSection { "cgroup_skb/egress" => CgroupSkbEgress { name }, "cgroup/skb" => CgroupSkb { name }, "cgroup/sysctl" => CgroupSysctl { name }, + "cgroup/getsockopt" => CgroupSockopt { + name, + attach_type: CgroupSockoptAttachType::Get, + }, + "cgroup/setsockopt" => CgroupSockopt { + name, + attach_type: CgroupSockoptAttachType::Set, + }, "cgroup" => match &*name { "skb" => CgroupSkb { name }, "sysctl" => CgroupSysctl { name }, + "getsockopt" | "setsockopt" => { + if let Ok(attach_type) = CgroupSockoptAttachType::try_from(name.as_str()) { + CgroupSockopt { name, attach_type } + } else { + return Err(ParseError::InvalidProgramSection { + section: section.to_owned(), + }); + } + } _ => { if let Ok(attach_type) = CgroupSockAddrAttachType::try_from(name.as_str()) { CgroupSockAddr { name, attach_type } @@ -1795,6 +1817,54 @@ mod tests { ); } + #[test] + fn test_parse_section_sockopt_named() { + let mut obj = fake_obj(); + + assert_matches!( + obj.parse_section(fake_section( + BpfSectionKind::Program, + "cgroup/getsockopt/foo", + bytes_of(&fake_ins()) + )), + Ok(()) + ); + assert_matches!( + obj.programs.get("foo"), + Some(Program { + section: ProgramSection::CgroupSockopt { + attach_type: CgroupSockoptAttachType::Get, + .. + }, + .. + }) + ); + } + + #[test] + fn test_parse_section_sockopt_unnamed() { + let mut obj = fake_obj(); + + assert_matches!( + obj.parse_section(fake_section( + BpfSectionKind::Program, + "cgroup/getsockopt", + bytes_of(&fake_ins()) + )), + Ok(()) + ); + assert_matches!( + obj.programs.get("getsockopt"), + Some(Program { + section: ProgramSection::CgroupSockopt { + attach_type: CgroupSockoptAttachType::Get, + .. + }, + .. + }) + ); + } + #[test] fn test_patch_map_data() { let mut obj = fake_obj(); diff --git a/aya/src/programs/cgroup_sockopt.rs b/aya/src/programs/cgroup_sockopt.rs new file mode 100644 index 00000000..cacd9497 --- /dev/null +++ b/aya/src/programs/cgroup_sockopt.rs @@ -0,0 +1,193 @@ +use thiserror::Error; + +use std::{ + hash::Hash, + os::unix::prelude::{AsRawFd, RawFd}, +}; + +use crate::{ + generated::bpf_prog_type::BPF_PROG_TYPE_CGROUP_SOCKOPT, + programs::{ + bpf_attach_type, define_link_wrapper, load_program, FdLink, Link, OwnedLink, + ProgAttachLink, ProgramData, ProgramError, + }, + sys::{bpf_link_create, bpf_prog_attach, kernel_version}, +}; + +/// A program that can be used to get or set options on sockets. +/// +/// [`SockAddr`] programs can be used to inspect or modify socket addresses passed to +/// various syscalls within a [cgroup]. They can be attached to a number of different +/// places as described in [`SockAddrAttachType`]. +/// +/// [`CgroupSockopt`] programs can be attached to a cgroup and will be called every +/// time a process executes getsockopt or getsockopt system call. +/// +/// # Minimum kernel version +/// +/// The minimum kernel version required to use this feature is 5.3. +/// +/// # 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::CgroupSockopt; +/// +/// let file = File::open("/sys/fs/cgroup/unified")?; +/// let program: &mut CgroupSockopt = bpf.program_mut("cgroup_sockopt").unwrap().try_into()?; +/// program.load()?; +/// program.attach(file)?; +/// # Ok::<(), Error>(()) +/// ``` +#[derive(Debug)] +#[doc(alias = "BPF_PROG_TYPE_CGROUP_SOCKOPT")] +pub struct CgroupSockopt { + pub(crate) data: ProgramData, + pub(crate) attach_type: CgroupSockoptAttachType, +} + +impl CgroupSockopt { + /// Loads the program inside the kernel. + pub fn load(&mut self) -> Result<(), ProgramError> { + self.data.expected_attach_type = Some(self.attach_type.into()); + load_program(BPF_PROG_TYPE_CGROUP_SOCKOPT, &mut self.data) + } + + /// Attaches the program to the given cgroup. + /// + /// The returned value can be used to detach, see [CgroupSockopt::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 attach_type = self.data.expected_attach_type.unwrap(); + let k_ver = kernel_version().unwrap(); + if k_ver >= (5, 7, 0) { + let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err( + |(_, io_error)| ProgramError::SyscallError { + call: "bpf_link_create".to_owned(), + io_error, + }, + )? as RawFd; + self.data + .links + .insert(CgroupSockoptLink(CgroupSockoptLinkInner::Fd(FdLink::new( + link_fd, + )))) + } else { + bpf_prog_attach(prog_fd, cgroup_fd, attach_type).map_err(|(_, io_error)| { + ProgramError::SyscallError { + call: "bpf_prog_attach".to_owned(), + io_error, + } + })?; + + self.data + .links + .insert(CgroupSockoptLink(CgroupSockoptLinkInner::ProgAttach( + ProgAttachLink::new(prog_fd, cgroup_fd, attach_type), + ))) + } + } + + /// 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: CgroupSockoptLinkId, + ) -> Result, ProgramError> { + Ok(OwnedLink::new(self.data.forget_link(link_id)?)) + } + + /// Detaches the program. + /// + /// See [CgroupSockopt::attach]. + pub fn detach(&mut self, link_id: CgroupSockoptLinkId) -> Result<(), ProgramError> { + self.data.links.remove(link_id) + } +} + +#[derive(Debug, Hash, Eq, PartialEq)] +enum CgroupSockoptLinkIdInner { + Fd(::Id), + ProgAttach(::Id), +} + +#[derive(Debug)] +enum CgroupSockoptLinkInner { + Fd(FdLink), + ProgAttach(ProgAttachLink), +} + +impl Link for CgroupSockoptLinkInner { + type Id = CgroupSockoptLinkIdInner; + + fn id(&self) -> Self::Id { + match self { + CgroupSockoptLinkInner::Fd(fd) => CgroupSockoptLinkIdInner::Fd(fd.id()), + CgroupSockoptLinkInner::ProgAttach(p) => CgroupSockoptLinkIdInner::ProgAttach(p.id()), + } + } + + fn detach(self) -> Result<(), ProgramError> { + match self { + CgroupSockoptLinkInner::Fd(fd) => fd.detach(), + CgroupSockoptLinkInner::ProgAttach(p) => p.detach(), + } + } +} + +define_link_wrapper!( + /// The link used by [CgroupSockopt] programs. + CgroupSockoptLink, + /// The type returned by [CgroupSockopt::attach]. Can be passed to [CgroupSockopt::detach]. + CgroupSockoptLinkId, + CgroupSockoptLinkInner, + CgroupSockoptLinkIdInner +); + +/// Defines where to attach a [`CgroupSockopt`] program. +#[derive(Copy, Clone, Debug)] +pub enum CgroupSockoptAttachType { + /// Attach to GetSockopt. + Get, + /// Attach to SetSockopt. + Set, +} + +impl From for bpf_attach_type { + fn from(s: CgroupSockoptAttachType) -> bpf_attach_type { + match s { + CgroupSockoptAttachType::Get => bpf_attach_type::BPF_CGROUP_GETSOCKOPT, + CgroupSockoptAttachType::Set => bpf_attach_type::BPF_CGROUP_SETSOCKOPT, + } + } +} + +#[derive(Debug, Error)] +#[error("{0} is not a valid attach type for a CGROUP_SOCKOPT program")] +pub(crate) struct InvalidAttachType(String); + +impl CgroupSockoptAttachType { + pub(crate) fn try_from(value: &str) -> Result { + match value { + "getsockopt" => Ok(CgroupSockoptAttachType::Get), + "setsockopt" => Ok(CgroupSockoptAttachType::Set), + _ => Err(InvalidAttachType(value.to_owned())), + } + } +} diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index 22597493..81fc6b4f 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -38,6 +38,7 @@ //! [`maps`]: crate::maps mod cgroup_skb; mod cgroup_sock_addr; +mod cgroup_sockopt; mod cgroup_sysctl; mod extension; mod fentry; @@ -73,6 +74,7 @@ use thiserror::Error; pub use cgroup_skb::{CgroupSkb, CgroupSkbAttachType}; 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; @@ -241,6 +243,8 @@ pub enum Program { CgroupSkb(CgroupSkb), /// A [`CgroupSysctl`] program CgroupSysctl(CgroupSysctl), + /// A [`CgroupSockopt`] program + CgroupSockopt(CgroupSockopt), /// A [`LircMode2`] program LircMode2(LircMode2), /// A [`PerfEvent`] program @@ -275,6 +279,7 @@ impl Program { 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, @@ -301,6 +306,7 @@ impl Program { Program::SchedClassifier(p) => p.data.pin(path), Program::CgroupSkb(p) => p.data.pin(path), Program::CgroupSysctl(p) => p.data.pin(path), + Program::CgroupSockopt(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), @@ -507,6 +513,7 @@ impl ProgramFd for Program { Program::SchedClassifier(p) => p.data.fd, Program::CgroupSkb(p) => p.data.fd, Program::CgroupSysctl(p) => p.data.fd, + Program::CgroupSockopt(p) => p.data.fd, Program::LircMode2(p) => p.data.fd, Program::PerfEvent(p) => p.data.fd, Program::RawTracePoint(p) => p.data.fd, @@ -555,6 +562,7 @@ impl_program_fd!( SchedClassifier, CgroupSkb, CgroupSysctl, + CgroupSockopt, LircMode2, PerfEvent, Lsm, @@ -606,6 +614,7 @@ impl_try_from_program!( SchedClassifier, CgroupSkb, CgroupSysctl, + CgroupSockopt, LircMode2, PerfEvent, Lsm, diff --git a/bpf/aya-bpf-macros/src/expand.rs b/bpf/aya-bpf-macros/src/expand.rs index 3b7a0630..df628711 100644 --- a/bpf/aya-bpf-macros/src/expand.rs +++ b/bpf/aya-bpf-macros/src/expand.rs @@ -60,6 +60,27 @@ impl Parse for SockAddrArgs { } } +pub struct SockoptArgs { + pub(crate) attach_type: Ident, + pub(crate) args: Args, +} + +impl Parse for SockoptArgs { + fn parse(input: ParseStream) -> Result { + let attach_type: Ident = input.parse()?; + match attach_type.to_string().as_str() { + "getsockopt" | "setsockopt" => (), + _ => return Err(input.error("invalid attach type")), + } + let args = if input.parse::().is_ok() { + Args::parse(input)? + } else { + Args { args: vec![] } + }; + Ok(SockoptArgs { attach_type, args }) + } +} + pub struct Map { item: ItemStatic, name: String, @@ -270,6 +291,43 @@ impl CgroupSysctl { } } +pub struct CgroupSockopt { + item: ItemFn, + attach_type: String, + name: Option, +} + +impl CgroupSockopt { + pub fn from_syn(mut args: Args, item: ItemFn, attach_type: String) -> Result { + let name = pop_arg(&mut args, "name"); + err_on_unknown_args(&args)?; + + Ok(CgroupSockopt { + item, + attach_type, + name, + }) + } + pub fn expand(&self) -> Result { + let section_name = if let Some(name) = &self.name { + format!("cgroup/{}/{}", self.attach_type, name) + } else { + format!("cgroup/{}", self.attach_type) + }; + 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_sockopt) -> i32 { + return #fn_name(::aya_bpf::programs::SockoptContext::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 45e54130..853b50a8 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, CgroupSockAddr, CgroupSysctl, FEntry, FExit, Lsm, Map, - PerfEvent, Probe, ProbeKind, RawTracePoint, SchedClassifier, SkMsg, SkSkb, SkSkbKind, - SockAddrArgs, SockOps, SocketFilter, TracePoint, Xdp, + Args, BtfTracePoint, CgroupSkb, CgroupSockAddr, CgroupSockopt, CgroupSysctl, FEntry, FExit, + Lsm, Map, PerfEvent, Probe, ProbeKind, RawTracePoint, SchedClassifier, SkMsg, SkSkb, SkSkbKind, + SockAddrArgs, SockOps, SocketFilter, SockoptArgs, TracePoint, Xdp, }; use proc_macro::TokenStream; use syn::{parse_macro_input, ItemFn, ItemStatic}; @@ -94,6 +94,18 @@ pub fn cgroup_sysctl(attrs: TokenStream, item: TokenStream) -> TokenStream { .into() } +#[proc_macro_attribute] +pub fn cgroup_sockopt(attrs: TokenStream, item: TokenStream) -> TokenStream { + let args = parse_macro_input!(attrs as SockoptArgs); + let attach_type = args.attach_type.to_string(); + let item = parse_macro_input!(item as ItemFn); + + CgroupSockopt::from_syn(args.args, item, attach_type) + .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 ab238c51..ceaed5f8 100644 --- a/bpf/aya-bpf/src/programs/mod.rs +++ b/bpf/aya-bpf/src/programs/mod.rs @@ -8,6 +8,7 @@ pub mod sk_buff; pub mod sk_msg; pub mod sock_addr; pub mod sock_ops; +pub mod sockopt; pub mod sysctl; pub mod tp_btf; pub mod tracepoint; @@ -23,6 +24,7 @@ pub use sk_buff::SkBuffContext; pub use sk_msg::SkMsgContext; pub use sock_addr::SockAddrContext; pub use sock_ops::SockOpsContext; +pub use sockopt::SockoptContext; pub use sysctl::SysctlContext; pub use tp_btf::BtfTracePointContext; pub use tracepoint::TracePointContext; diff --git a/bpf/aya-bpf/src/programs/sockopt.rs b/bpf/aya-bpf/src/programs/sockopt.rs new file mode 100644 index 00000000..a1440895 --- /dev/null +++ b/bpf/aya-bpf/src/programs/sockopt.rs @@ -0,0 +1,19 @@ +use core::ffi::c_void; + +use crate::{bindings::bpf_sockopt, BpfContext}; + +pub struct SockoptContext { + pub sockopt: *mut bpf_sockopt, +} + +impl SockoptContext { + pub fn new(sockopt: *mut bpf_sockopt) -> SockoptContext { + SockoptContext { sockopt } + } +} + +impl BpfContext for SockoptContext { + fn as_ptr(&self) -> *mut c_void { + self.sockopt as *mut _ + } +}