Selaa lähdekoodia

Add support for BPF_PROG_TYPE_CGROUP_SOCKOPT (#268)

Kenjiro Nakayama 2 vuotta sitten
vanhempi
commit
e68d734c68

+ 10 - 4
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,

+ 71 - 1
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();

+ 189 - 0
aya/src/programs/cgroup_sockopt.rs

@@ -0,0 +1,189 @@
+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.
+///
+/// [`CgroupSockopt`] programs can be attached to a cgroup and will be called every
+/// time a process executes getsockopt or setsockopt 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<CgroupSockoptLink>,
+    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<T: AsRawFd>(&mut self, cgroup: T) -> Result<CgroupSockoptLinkId, ProgramError> {
+        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<OwnedLink<CgroupSockoptLink>, 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(<FdLink as Link>::Id),
+    ProgAttach(<ProgAttachLink as Link>::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<CgroupSockoptAttachType> 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<CgroupSockoptAttachType, InvalidAttachType> {
+        match value {
+            "getsockopt" => Ok(CgroupSockoptAttachType::Get),
+            "setsockopt" => Ok(CgroupSockoptAttachType::Set),
+            _ => Err(InvalidAttachType(value.to_owned())),
+        }
+    }
+}

+ 9 - 0
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,

+ 58 - 0
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<SockoptArgs> {
+        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::<Token![,]>().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<String>,
+}
+
+impl CgroupSockopt {
+    pub fn from_syn(mut args: Args, item: ItemFn, attach_type: String) -> Result<CgroupSockopt> {
+        let name = pop_arg(&mut args, "name");
+        err_on_unknown_args(&args)?;
+
+        Ok(CgroupSockopt {
+            item,
+            attach_type,
+            name,
+        })
+    }
+    pub fn expand(&self) -> Result<TokenStream> {
+        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<String>,

+ 15 - 3
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);

+ 2 - 0
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;

+ 19 - 0
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 _
+    }
+}