From 6fbde9502d3ca221325fda6c3a79666592aeafc4 Mon Sep 17 00:00:00 2001
From: Tuetuopay <tuetuopay@me.com>
Date: Sat, 5 Aug 2023 00:15:49 +0200
Subject: [PATCH] aya: add support for map-bound XDP programs

Such programs are to be bound to cpumap or devmap instead of the usual
network interfaces.
---
 aya-obj/src/obj.rs                      | 23 +++++++++---
 aya-obj/src/programs/mod.rs             |  2 +
 aya-obj/src/programs/xdp.rs             | 24 ++++++++++++
 aya/src/bpf.rs                          | 14 +++++--
 aya/src/programs/mod.rs                 |  1 -
 aya/src/programs/xdp.rs                 | 49 ++++++++++++++++++-------
 test/integration-test/src/tests/load.rs |  6 ++-
 test/integration-test/src/tests/rbpf.rs |  7 +++-
 test/integration-test/src/tests/xdp.rs  | 13 +++++++
 9 files changed, 111 insertions(+), 28 deletions(-)
 create mode 100644 aya-obj/src/programs/xdp.rs

diff --git a/aya-obj/src/obj.rs b/aya-obj/src/obj.rs
index deadeeaf..0d792d55 100644
--- a/aya-obj/src/obj.rs
+++ b/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() {
+                    Some("cpumap") => XdpAttachType::CpuMap,
+                    Some("devmap") => XdpAttachType::DevMap,
+                    None => XdpAttachType::Interface,
+                    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
             )),
diff --git a/aya-obj/src/programs/mod.rs b/aya-obj/src/programs/mod.rs
index 4f76211a..6b66b005 100644
--- a/aya-obj/src/programs/mod.rs
+++ b/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;
diff --git a/aya-obj/src/programs/xdp.rs b/aya-obj/src/programs/xdp.rs
new file mode 100644
index 00000000..17fab6ab
--- /dev/null
+++ b/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,
+        }
+    }
+}
diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs
index 33ac878c..76604500 100644
--- a/aya/src/bpf.rs
+++ b/aya/src/bpf.rs
@@ -413,7 +413,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
@@ -573,13 +576,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),
diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs
index 07755307..84d6d6dd 100644
--- a/aya/src/programs/mod.rs
+++ b/aya/src/programs/mod.rs
@@ -847,7 +847,6 @@ macro_rules! impl_from_pin {
 impl_from_pin!(
     TracePoint,
     SocketFilter,
-    Xdp,
     SkMsg,
     CgroupSysctl,
     LircMode2,
diff --git a/aya/src/programs/xdp.rs b/aya/src/programs/xdp.rs
index 22eb4f70..26b162b1 100644
--- a/aya/src/programs/xdp.rs
+++ b/aya/src/programs/xdp.rs
@@ -9,18 +9,21 @@ use std::{
     hash::Hash,
     io,
     os::fd::{AsFd as _, AsRawFd as _, 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`.
@@ -77,12 +80,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)
     }
 
@@ -132,17 +136,19 @@ impl Xdp {
         let if_index = if_index as RawFd;
 
         if KernelVersion::current().unwrap() >= KernelVersion::new(5, 9, 0) {
-            let link_fd = bpf_link_create(
-                prog_fd,
-                if_index,
-                bpf_attach_type::BPF_XDP,
-                None,
-                flags.bits(),
-            )
-            .map_err(|(_, io_error)| SyscallError {
-                call: "bpf_link_create",
-                io_error,
-            })?;
+            // 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, if_index, attach_type, None, flags.bits())
+                .map_err(|(_, io_error)| SyscallError {
+                    call: "bpf_link_create",
+                    io_error,
+                })?;
             self.data
                 .links
                 .insert(XdpLink::new(XdpLinkInner::FdLink(FdLink::new(link_fd))))
@@ -160,6 +166,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].
diff --git a/test/integration-test/src/tests/load.rs b/test/integration-test/src/tests/load.rs
index 0b0d1532..9e8f44b1 100644
--- a/test/integration-test/src/tests/load.rs
+++ b/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);
@@ -276,7 +277,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
@@ -284,7 +285,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();
diff --git a/test/integration-test/src/tests/rbpf.rs b/test/integration-test/src/tests/rbpf.rs
index 0a85adbf..1de874ff 100644
--- a/test/integration-test/src/tests/rbpf.rs
+++ b/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
diff --git a/test/integration-test/src/tests/xdp.rs b/test/integration-test/src/tests/xdp.rs
index 0c2f56da..566ca5e5 100644
--- a/test/integration-test/src/tests/xdp.rs
+++ b/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();
+}