Browse Source

aya: add support for map-bound XDP programs

Such programs are to be bound to cpumap or devmap instead of the usual
network interfaces.
Tuetuopay 1 year ago
parent
commit
139f382638

+ 17 - 6
aya-obj/src/obj.rs

@@ -19,6 +19,7 @@ use crate::{
     btf::BtfFeatures,
     generated::{BPF_CALL, BPF_JMP, BPF_K},
     maps::{BtfMap, LegacyMap, Map, MINIMUM_MAP_SIZE},
+    programs::XdpAttachType,
     relocation::*,
     util::HashMap,
 };
@@ -204,8 +205,6 @@ pub struct Function {
 /// - `struct_ops+`
 /// - `fmod_ret+`, `fmod_ret.s+`
 /// - `iter+`, `iter.s+`
-/// - `xdp.frags/cpumap`, `xdp/cpumap`
-/// - `xdp.frags/devmap`, `xdp/devmap`
 #[derive(Debug, Clone)]
 #[allow(missing_docs)]
 pub enum ProgramSection {
@@ -221,6 +220,7 @@ pub enum ProgramSection {
     SocketFilter,
     Xdp {
         frags: bool,
+        attach_type: XdpAttachType,
     },
     SkMsg,
     SkSkbStreamParser,
@@ -283,8 +283,19 @@ impl FromStr for ProgramSection {
             "uprobe.s" => UProbe { sleepable: true },
             "uretprobe" => URetProbe { sleepable: false },
             "uretprobe.s" => URetProbe { sleepable: true },
-            "xdp" => Xdp { frags: false },
-            "xdp.frags" => Xdp { frags: true },
+            "xdp" | "xdp.frags" => Xdp {
+                frags: kind == "xdp.frags",
+                attach_type: match pieces.next() {
+                    None => XdpAttachType::Interface,
+                    Some("cpumap") => XdpAttachType::CpuMap,
+                    Some("devmap") => XdpAttachType::DevMap,
+                    Some(_) => {
+                        return Err(ParseError::InvalidProgramSection {
+                            section: section.to_owned(),
+                        })
+                    }
+                },
+            },
             "tp_btf" => BtfTracePoint,
             "tracepoint" | "tp" => TracePoint,
             "socket" => SocketFilter,
@@ -2012,7 +2023,7 @@ mod tests {
         assert_matches!(
             obj.parse_section(fake_section(
                 BpfSectionKind::Program,
-                "xdp/foo",
+                "xdp",
                 bytes_of(&fake_ins()),
                 None
             )),
@@ -2035,7 +2046,7 @@ mod tests {
         assert_matches!(
             obj.parse_section(fake_section(
                 BpfSectionKind::Program,
-                "xdp.frags/foo",
+                "xdp.frags",
                 bytes_of(&fake_ins()),
                 None
             )),

+ 2 - 0
aya-obj/src/programs/mod.rs

@@ -3,7 +3,9 @@
 pub mod cgroup_sock;
 pub mod cgroup_sock_addr;
 pub mod cgroup_sockopt;
+pub mod xdp;
 
 pub use cgroup_sock::CgroupSockAttachType;
 pub use cgroup_sock_addr::CgroupSockAddrAttachType;
 pub use cgroup_sockopt::CgroupSockoptAttachType;
+pub use xdp::XdpAttachType;

+ 24 - 0
aya-obj/src/programs/xdp.rs

@@ -0,0 +1,24 @@
+//! XDP programs.
+
+use crate::generated::bpf_attach_type;
+
+/// Defines where to attach an `XDP` program.
+#[derive(Copy, Clone, Debug)]
+pub enum XdpAttachType {
+    /// Attach to a network interface.
+    Interface,
+    /// Attach to a cpumap. Requires kernel 5.9 or later.
+    CpuMap,
+    /// Attach to a devmap. Requires kernel 5.8 or later.
+    DevMap,
+}
+
+impl From<XdpAttachType> for bpf_attach_type {
+    fn from(value: XdpAttachType) -> Self {
+        match value {
+            XdpAttachType::Interface => bpf_attach_type::BPF_XDP,
+            XdpAttachType::CpuMap => bpf_attach_type::BPF_XDP_CPUMAP,
+            XdpAttachType::DevMap => bpf_attach_type::BPF_XDP_DEVMAP,
+        }
+    }
+}

+ 11 - 3
aya/src/bpf.rs

@@ -412,7 +412,10 @@ impl<'a> BpfLoader<'a> {
                                 | ProgramSection::URetProbe { sleepable: _ }
                                 | ProgramSection::TracePoint
                                 | ProgramSection::SocketFilter
-                                | ProgramSection::Xdp { frags: _ }
+                                | ProgramSection::Xdp {
+                                    frags: _,
+                                    attach_type: _,
+                                }
                                 | ProgramSection::SkMsg
                                 | ProgramSection::SkSkbStreamParser
                                 | ProgramSection::SkSkbStreamVerdict
@@ -556,13 +559,18 @@ impl<'a> BpfLoader<'a> {
                         ProgramSection::SocketFilter => Program::SocketFilter(SocketFilter {
                             data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                         }),
-                        ProgramSection::Xdp { frags, .. } => {
+                        ProgramSection::Xdp {
+                            frags, attach_type, ..
+                        } => {
                             let mut data =
                                 ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level);
                             if *frags {
                                 data.flags = BPF_F_XDP_HAS_FRAGS;
                             }
-                            Program::Xdp(Xdp { data })
+                            Program::Xdp(Xdp {
+                                data,
+                                attach_type: *attach_type,
+                            })
                         }
                         ProgramSection::SkMsg => Program::SkMsg(SkMsg {
                             data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),

+ 0 - 1
aya/src/programs/mod.rs

@@ -879,7 +879,6 @@ macro_rules! impl_from_pin {
 impl_from_pin!(
     TracePoint,
     SocketFilter,
-    Xdp,
     SkMsg,
     CgroupSysctl,
     LircMode2,

+ 31 - 4
aya/src/programs/xdp.rs

@@ -12,18 +12,21 @@ use std::{
     hash::Hash,
     io,
     os::fd::{AsFd as _, AsRawFd as _, BorrowedFd, RawFd},
+    path::Path,
 };
 use thiserror::Error;
 
 use crate::{
     generated::{
-        bpf_attach_type, bpf_link_type, bpf_prog_type, XDP_FLAGS_DRV_MODE, XDP_FLAGS_HW_MODE,
-        XDP_FLAGS_REPLACE, XDP_FLAGS_SKB_MODE, XDP_FLAGS_UPDATE_IF_NOEXIST,
+        bpf_link_type, bpf_prog_type, XDP_FLAGS_DRV_MODE, XDP_FLAGS_HW_MODE, XDP_FLAGS_REPLACE,
+        XDP_FLAGS_SKB_MODE, XDP_FLAGS_UPDATE_IF_NOEXIST,
     },
+    obj::programs::XdpAttachType,
     programs::{
         define_link_wrapper, load_program, FdLink, Link, LinkError, ProgramData, ProgramError,
     },
     sys::{bpf_link_create, bpf_link_get_info_by_fd, bpf_link_update, netlink_set_xdp_fd},
+    VerifierLogLevel,
 };
 
 /// The type returned when attaching an [`Xdp`] program fails on kernels `< 5.9`.
@@ -80,12 +83,13 @@ bitflags::bitflags! {
 #[doc(alias = "BPF_PROG_TYPE_XDP")]
 pub struct Xdp {
     pub(crate) data: ProgramData<XdpLink>,
+    pub(crate) attach_type: XdpAttachType,
 }
 
 impl Xdp {
     /// Loads the program inside the kernel.
     pub fn load(&mut self) -> Result<(), ProgramError> {
-        self.data.expected_attach_type = Some(bpf_attach_type::BPF_XDP);
+        self.data.expected_attach_type = Some(self.attach_type.into());
         load_program(bpf_prog_type::BPF_PROG_TYPE_XDP, &mut self.data)
     }
 
@@ -133,10 +137,18 @@ impl Xdp {
         let prog_fd = prog_fd.as_fd();
 
         if KernelVersion::current().unwrap() >= KernelVersion::new(5, 9, 0) {
+            // Unwrap safety: the function starts with `self.fd()?` that will succeed if and only
+            // if the program has been loaded, i.e. there is an fd. We get one by:
+            // - Using `Xdp::from_pin` that sets `expected_attach_type`
+            // - Calling `Xdp::attach` that sets `expected_attach_type`, as geting an `Xdp`
+            //   instance trhough `Xdp:try_from(Program)` does not set any fd.
+            // So, in all cases where we have an fd, we have an expected_attach_type. Thus, if we
+            // reach this point, expected_attach_type is guaranteed to be Some(_).
+            let attach_type = self.data.expected_attach_type.unwrap();
             let link_fd = bpf_link_create(
                 prog_fd,
                 LinkTarget::IfIndex(if_index),
-                bpf_attach_type::BPF_XDP,
+                attach_type,
                 None,
                 flags.bits(),
             )
@@ -163,6 +175,21 @@ impl Xdp {
         }
     }
 
+    /// Creates a program from a pinned entry on a bpffs.
+    ///
+    /// Existing links will not be populated. To work with existing links you should use [`crate::programs::links::PinnedLink`].
+    ///
+    /// On drop, any managed links are detached and the program is unloaded. This will not result in
+    /// the program being unloaded from the kernel if it is still pinned.
+    pub fn from_pin<P: AsRef<Path>>(
+        path: P,
+        attach_type: XdpAttachType,
+    ) -> Result<Self, ProgramError> {
+        let mut data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?;
+        data.expected_attach_type = Some(attach_type.into());
+        Ok(Self { data, attach_type })
+    }
+
     /// Detaches the program.
     ///
     /// See [Xdp::attach].

+ 4 - 2
test/integration-test/src/tests/load.rs

@@ -9,6 +9,7 @@ use aya::{
     util::KernelVersion,
     Bpf,
 };
+use aya_obj::programs::XdpAttachType;
 
 const MAX_RETRIES: usize = 100;
 const RETRY_DURATION: time::Duration = time::Duration::from_millis(10);
@@ -283,7 +284,7 @@ fn pin_lifecycle() {
 
     // 2. Load program from bpffs but don't attach it
     {
-        let _ = Xdp::from_pin("/sys/fs/bpf/aya-xdp-test-prog").unwrap();
+        let _ = Xdp::from_pin("/sys/fs/bpf/aya-xdp-test-prog", XdpAttachType::Interface).unwrap();
     }
 
     // should still be loaded since prog was pinned
@@ -291,7 +292,8 @@ fn pin_lifecycle() {
 
     // 3. Load program from bpffs and attach
     {
-        let mut prog = Xdp::from_pin("/sys/fs/bpf/aya-xdp-test-prog").unwrap();
+        let mut prog =
+            Xdp::from_pin("/sys/fs/bpf/aya-xdp-test-prog", XdpAttachType::Interface).unwrap();
         let link_id = prog.attach("lo", XdpFlags::default()).unwrap();
         let link = prog.take_link(link_id).unwrap();
         let fd_link: FdLink = link.try_into().unwrap();

+ 5 - 2
test/integration-test/src/tests/rbpf.rs

@@ -2,7 +2,7 @@ use core::{mem::size_of, ptr::null_mut, slice::from_raw_parts};
 use std::collections::HashMap;
 
 use assert_matches::assert_matches;
-use aya_obj::{generated::bpf_insn, Object, ProgramSection};
+use aya_obj::{generated::bpf_insn, programs::XdpAttachType, Object, ProgramSection};
 
 #[test]
 fn run_with_rbpf() {
@@ -11,7 +11,10 @@ fn run_with_rbpf() {
     assert_eq!(object.programs.len(), 1);
     assert_matches!(
         object.programs["pass"].section,
-        ProgramSection::Xdp { frags: true }
+        ProgramSection::Xdp {
+            frags: true,
+            attach_type: XdpAttachType::Interface
+        }
     );
 
     let instructions = &object

+ 13 - 0
test/integration-test/src/tests/xdp.rs

@@ -1,3 +1,4 @@
+use aya::Bpf;
 use object::{Object, ObjectSection, ObjectSymbol, SymbolSection};
 
 #[test]
@@ -33,3 +34,15 @@ fn ensure_symbol(obj_file: &object::File, sec_name: &str, sym_name: &str) {
         "symbol not found. available symbols in section: {syms:?}"
     );
 }
+
+#[test]
+fn map_load() {
+    let bpf = Bpf::load(crate::XDP_SEC).unwrap();
+
+    bpf.program("xdp_plain").unwrap();
+    bpf.program("xdp_frags").unwrap();
+    bpf.program("xdp_cpumap").unwrap();
+    bpf.program("xdp_devmap").unwrap();
+    bpf.program("xdp_frags_cpumap").unwrap();
+    bpf.program("xdp_frags_devmap").unwrap();
+}