Browse Source

aya, bpf: misc fixes following review comments

Tuetuopay 1 year ago
parent
commit
579e3ce

+ 1 - 8
aya-bpf-macros/src/lib.rs

@@ -147,14 +147,7 @@ pub fn sk_msg(attrs: TokenStream, item: TokenStream) -> TokenStream {
 ///
 /// #[xdp(frags)]
 /// pub fn xdp(ctx: XdpContext) -> u32 {
-///     match unsafe { try_xdp(ctx) } {
-///         Ok(ret) => ret,
-///         Err(ret) => ret,
-///     }
-/// }
-///
-/// unsafe fn try_xdp(_ctx: XdpContext) -> Result<u32, u32> {
-///     Ok(XDP_PASS)
+///     XDP_PASS
 /// }
 /// ```
 #[proc_macro_error]

+ 1 - 1
aya-bpf-macros/src/xdp.rs

@@ -28,7 +28,7 @@ impl Xdp {
             Some(name) => {
                 return Err(Error::new_spanned(
                     "map",
-                    format!("invalid value. expected 'cpumap' or 'devmap', found '{name}'"),
+                    format!("Invalid value. Expected 'cpumap' or 'devmap', found '{name}'"),
                 ))
             }
             None => None,

+ 0 - 8
aya-obj/src/obj.rs

@@ -50,7 +50,6 @@ pub struct Features {
     bpf_cookie: bool,
     cpumap_prog_id: bool,
     devmap_prog_id: bool,
-    devmap_hash_prog_id: bool,
     btf: Option<BtfFeatures>,
 }
 
@@ -65,7 +64,6 @@ impl Features {
         bpf_cookie: bool,
         cpumap_prog_id: bool,
         devmap_prog_id: bool,
-        devmap_hash_prog_id: bool,
         btf: Option<BtfFeatures>,
     ) -> Self {
         Self {
@@ -76,7 +74,6 @@ impl Features {
             bpf_cookie,
             cpumap_prog_id,
             devmap_prog_id,
-            devmap_hash_prog_id,
             btf,
         }
     }
@@ -116,11 +113,6 @@ impl Features {
         self.devmap_prog_id
     }
 
-    /// Returns whether XDP Hash Device Maps support chained program IDs.
-    pub fn devmap_hash_prog_id(&self) -> bool {
-        self.devmap_hash_prog_id
-    }
-
     /// If BTF is supported, returns which BTF features are supported.
     pub fn btf(&self) -> Option<&BtfFeatures> {
         self.btf.as_ref()

+ 1 - 5
aya/src/bpf.rs

@@ -96,7 +96,6 @@ fn detect_features() -> Features {
         is_bpf_cookie_supported(),
         is_prog_id_supported(BPF_MAP_TYPE_CPUMAP),
         is_prog_id_supported(BPF_MAP_TYPE_DEVMAP),
-        is_prog_id_supported(BPF_MAP_TYPE_DEVMAP_HASH),
         btf,
     );
     debug!("BPF Feature Detection: {:#?}", f);
@@ -484,12 +483,9 @@ impl<'a> BpfLoader<'a> {
                 Ok(BPF_MAP_TYPE_CPUMAP) => {
                     obj.set_value_size(if FEATURES.cpumap_prog_id() { 8 } else { 4 })
                 }
-                Ok(BPF_MAP_TYPE_DEVMAP) => {
+                Ok(BPF_MAP_TYPE_DEVMAP | BPF_MAP_TYPE_DEVMAP_HASH) => {
                     obj.set_value_size(if FEATURES.devmap_prog_id() { 8 } else { 4 })
                 }
-                Ok(BPF_MAP_TYPE_DEVMAP_HASH) => {
-                    obj.set_value_size(if FEATURES.devmap_hash_prog_id() { 8 } else { 4 })
-                }
                 _ => (),
             }
             let btf_fd = btf_fd.as_deref().map(|fd| fd.as_fd());

+ 1 - 1
aya/src/maps/mod.rs

@@ -279,7 +279,7 @@ pub enum Map {
     DevMapHash(MapData),
     /// A [`XskMap`] map.
     XskMap(MapData),
-    /// An unsupported map type
+    /// An unsupported map type.
     Unsupported(MapData),
 }
 

+ 40 - 30
aya/src/maps/xdp/cpu_map.rs

@@ -6,7 +6,7 @@ use std::{
     os::fd::{AsFd, AsRawFd},
 };
 
-use aya_obj::generated::{bpf_cpumap_val, bpf_cpumap_val__bindgen_ty_1};
+use aya_obj::generated::bpf_cpumap_val;
 
 use crate::{
     maps::{check_bounds, check_kv_size, IterableMap, MapData, MapError},
@@ -15,6 +15,8 @@ use crate::{
     Pod, FEATURES,
 };
 
+use super::XdpMapError;
+
 /// An array of available CPUs.
 ///
 /// XDP programs can use this map to redirect packets to a target
@@ -29,19 +31,24 @@ use crate::{
 /// # let elf_bytes = &[];
 /// use aya::maps::xdp::CpuMap;
 ///
+/// let ncpus = aya::util::nr_cpus().unwrap() as u32;
 /// let mut bpf = aya::BpfLoader::new()
-///     .set_max_entries("CPUS", aya::util::nr_cpus().unwrap() as u32)
+///     .set_max_entries("CPUS", ncpus)
 ///     .load(elf_bytes)
 ///     .unwrap();
 /// let mut cpumap = CpuMap::try_from(bpf.map_mut("CPUS").unwrap())?;
 /// let flags = 0;
 /// let queue_size = 2048;
-/// for i in 0u32..8u32 {
+/// for i in 0..ncpus {
 ///     cpumap.set(i, queue_size, None, flags);
 /// }
 ///
 /// # Ok::<(), aya::BpfError>(())
 /// ```
+///
+/// # See also
+///
+/// Kernel documentation: <https://docs.kernel.org/next/bpf/map_cpumap.html>
 #[doc(alias = "BPF_MAP_TYPE_CPUMAP")]
 pub struct CpuMap<T> {
     inner: T,
@@ -67,7 +74,7 @@ impl<T: Borrow<MapData>> CpuMap<T> {
         self.inner.borrow().obj.max_entries()
     }
 
-    /// Returns the queue size and possible program for a given CPU index.
+    /// Returns the queue size and optional program for a given CPU index.
     ///
     /// # Errors
     ///
@@ -81,7 +88,7 @@ impl<T: Borrow<MapData>> CpuMap<T> {
         let value = if FEATURES.cpumap_prog_id() {
             bpf_map_lookup_elem::<_, bpf_cpumap_val>(fd, &cpu_index, flags).map(|value| {
                 value.map(|value| CpuMapValue {
-                    qsize: value.qsize,
+                    queue_size: value.qsize,
                     // SAFETY: map writes use fd, map reads use id.
                     // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/include/uapi/linux/bpf.h#L6241
                     prog_id: NonZeroU32::new(unsafe { value.bpf_prog.id }),
@@ -90,7 +97,7 @@ impl<T: Borrow<MapData>> CpuMap<T> {
         } else {
             bpf_map_lookup_elem::<_, u32>(fd, &cpu_index, flags).map(|value| {
                 value.map(|qsize| CpuMapValue {
-                    qsize,
+                    queue_size: qsize,
                     prog_id: None,
                 })
             })
@@ -115,52 +122,52 @@ impl<T: BorrowMut<MapData>> CpuMap<T> {
     /// When sending the packet to the CPU at the given index, the kernel will queue up to
     /// `queue_size` packets before dropping them.
     ///
-    /// Another XDP program can be passed in that will be run on the target CPU, instead of the CPU
-    /// that receives the packets. This allows to perform minimal computations on CPUs that
-    /// directly handle packets from a NIC's RX queues, and perform possibly heavier ones in other,
-    /// less busy CPUs.
+    /// Starting from Linux kernel 5.9, another XDP program can be passed in that will be run on the
+    /// target CPU, instead of the CPU that receives the packets. This allows to perform minimal
+    /// computations on CPUs that directly handle packets from a NIC's RX queues, and perform
+    /// possibly heavier ones in other, less busy CPUs.
     ///
-    /// Note that only XDP programs with the `map = "cpumap"` argument can be passed. See the
-    /// kernel-space `aya_bpf::xdp` for more information.
+    /// The chained program must be loaded with the `BPF_XDP_CPUMAP` attach type. When using
+    /// `aya-ebpf`, that means XDP programs that specify the `map = "cpumap"` argument. See the
+    /// kernel-space `aya_ebpf::xdp` for more information.
     ///
     /// # Errors
     ///
     /// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`]
-    /// if `bpf_map_update_elem` fails, [`MapError::ProgIdNotSupported`] if the kernel does not
-    /// support program ids and one is provided.
+    /// if `bpf_map_update_elem` fails, [`XdpMapError::ChainedProgramNotSupported`] if the kernel
+    /// does not support chained programs and one is provided.
     pub fn set(
         &mut self,
         cpu_index: u32,
         queue_size: u32,
         program: Option<&ProgramFd>,
         flags: u64,
-    ) -> Result<(), MapError> {
+    ) -> Result<(), XdpMapError> {
         let data = self.inner.borrow_mut();
         check_bounds(data, cpu_index)?;
         let fd = data.fd().as_fd();
 
         let res = if FEATURES.cpumap_prog_id() {
-            let value = bpf_cpumap_val {
-                qsize: queue_size,
-                bpf_prog: bpf_cpumap_val__bindgen_ty_1 {
-                    // Default is valid as the kernel will only consider fd > 0:
-                    // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/cpumap.c#L466
-                    fd: program
-                        .map(|prog| prog.as_fd().as_raw_fd())
-                        .unwrap_or_default(),
-                },
-            };
+            let mut value = unsafe { std::mem::zeroed::<bpf_cpumap_val>() };
+            value.qsize = queue_size;
+            // Default is valid as the kernel will only consider fd > 0:
+            // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/cpumap.c#L466
+            value.bpf_prog.fd = program
+                .map(|prog| prog.as_fd().as_raw_fd())
+                .unwrap_or_default();
             bpf_map_update_elem(fd, Some(&cpu_index), &value, flags)
         } else {
             if program.is_some() {
-                return Err(MapError::ProgIdNotSupported);
+                return Err(XdpMapError::ChainedProgramNotSupported);
             }
             bpf_map_update_elem(fd, Some(&cpu_index), &queue_size, flags)
         };
 
-        res.map_err(|(_, io_error)| SyscallError {
-            call: "bpf_map_update_elem",
-            io_error,
+        res.map_err(|(_, io_error)| {
+            MapError::from(SyscallError {
+                call: "bpf_map_update_elem",
+                io_error,
+            })
         })?;
         Ok(())
     }
@@ -179,7 +186,10 @@ impl<T: Borrow<MapData>> IterableMap<u32, CpuMapValue> for CpuMap<T> {
 unsafe impl Pod for bpf_cpumap_val {}
 
 #[derive(Clone, Copy, Debug)]
+/// The value of a CPU map.
 pub struct CpuMapValue {
-    pub qsize: u32,
+    /// Size of the for the CPU.
+    pub queue_size: u32,
+    /// Chained XDP program ID.
     pub prog_id: Option<NonZeroU32>,
 }

+ 42 - 34
aya/src/maps/xdp/dev_map.rs

@@ -6,7 +6,7 @@ use std::{
     os::fd::{AsFd, AsRawFd},
 };
 
-use aya_obj::generated::{bpf_devmap_val, bpf_devmap_val__bindgen_ty_1};
+use aya_obj::generated::bpf_devmap_val;
 
 use crate::{
     maps::{check_bounds, check_kv_size, IterableMap, MapData, MapError},
@@ -15,6 +15,8 @@ use crate::{
     Pod, FEATURES,
 };
 
+use super::XdpMapError;
+
 /// An array of network devices.
 ///
 /// XDP programs can use this map to redirect to other network
@@ -30,12 +32,15 @@ use crate::{
 /// use aya::maps::xdp::DevMap;
 ///
 /// let mut devmap = DevMap::try_from(bpf.map_mut("IFACES").unwrap())?;
-/// let source = 32u32;
-/// let dest = 42u32;
-/// devmap.set(source, dest, None, 0);
+/// // Lookups at index 2 will redirect packets to interface with index 3 (e.g. eth1)
+/// devmap.set(2, 3, None, 0);
 ///
 /// # Ok::<(), aya::BpfError>(())
 /// ```
+///
+/// # See also
+///
+/// Kernel documentation: <https://docs.kernel.org/next/bpf/map_devmap.html>
 #[doc(alias = "BPF_MAP_TYPE_DEVMAP")]
 pub struct DevMap<T> {
     inner: T,
@@ -61,7 +66,7 @@ impl<T: Borrow<MapData>> DevMap<T> {
         self.inner.borrow().obj.max_entries()
     }
 
-    /// Returns the target ifindex and possible program at a given index.
+    /// Returns the target interface index and optional program at a given index.
     ///
     /// # Errors
     ///
@@ -75,7 +80,7 @@ impl<T: Borrow<MapData>> DevMap<T> {
         let value = if FEATURES.devmap_prog_id() {
             bpf_map_lookup_elem::<_, bpf_devmap_val>(fd, &index, flags).map(|value| {
                 value.map(|value| DevMapValue {
-                    ifindex: value.ifindex,
+                    if_index: value.ifindex,
                     // SAFETY: map writes use fd, map reads use id.
                     // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/include/uapi/linux/bpf.h#L6228
                     prog_id: NonZeroU32::new(unsafe { value.bpf_prog.id }),
@@ -84,7 +89,7 @@ impl<T: Borrow<MapData>> DevMap<T> {
         } else {
             bpf_map_lookup_elem::<_, u32>(fd, &index, flags).map(|value| {
                 value.map(|ifindex| DevMapValue {
-                    ifindex,
+                    if_index: ifindex,
                     prog_id: None,
                 })
             })
@@ -104,57 +109,57 @@ impl<T: Borrow<MapData>> DevMap<T> {
 }
 
 impl<T: BorrowMut<MapData>> DevMap<T> {
-    /// Sets the target ifindex at index, and optionally a chained program.
+    /// Sets the target interface index at index, and optionally a chained program.
     ///
     /// When redirecting using `index`, packets will be transmitted by the interface with
-    /// `ifindex`.
+    /// `target_if_index`.
     ///
-    /// Another XDP program can be passed in that will be run before actual transmission. It can be
-    /// used to modify the packet before transmission with NIC specific data (MAC address update,
-    /// checksum computations, etc) or other purposes.
+    /// Starting from Linux kernel 5.8, another XDP program can be passed in that will be run before
+    /// actual transmission. It can be used to modify the packet before transmission with NIC
+    /// specific data (MAC address update, checksum computations, etc) or other purposes.
     ///
-    /// Note that only XDP programs with the `map = "devmap"` argument can be passed. See the
-    /// kernel-space `aya_bpf::xdp` for more information.
+    /// The chained program must be loaded with the `BPF_XDP_DEVMAP` attach type. When using
+    /// `aya-ebpf`, that means XDP programs that specify the `map = "devmap"` argument. See the
+    /// kernel-space `aya_ebpf::xdp` for more information.
     ///
     /// # Errors
     ///
     /// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`]
     /// if `bpf_map_update_elem` fails, [`MapError::ProgIdNotSupported`] if the kernel does not
-    /// support program ids and one is provided.
+    /// support chained programs and one is provided.
     pub fn set(
         &mut self,
         index: u32,
-        ifindex: u32,
+        target_if_index: u32,
         program: Option<&ProgramFd>,
         flags: u64,
-    ) -> Result<(), MapError> {
+    ) -> Result<(), XdpMapError> {
         let data = self.inner.borrow_mut();
         check_bounds(data, index)?;
         let fd = data.fd().as_fd();
 
         let res = if FEATURES.devmap_prog_id() {
-            let value = bpf_devmap_val {
-                ifindex,
-                bpf_prog: bpf_devmap_val__bindgen_ty_1 {
-                    // Default is valid as the kernel will only consider fd > 0:
-                    // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L866
-                    // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L918
-                    fd: program
-                        .map(|prog| prog.as_fd().as_raw_fd())
-                        .unwrap_or_default(),
-                },
-            };
+            let mut value = unsafe { std::mem::zeroed::<bpf_devmap_val>() };
+            value.ifindex = target_if_index;
+            // Default is valid as the kernel will only consider fd > 0:
+            // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L866
+            // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L918
+            value.bpf_prog.fd = program
+                .map(|prog| prog.as_fd().as_raw_fd())
+                .unwrap_or_default();
             bpf_map_update_elem(fd, Some(&index), &value, flags)
         } else {
             if program.is_some() {
-                return Err(MapError::ProgIdNotSupported);
+                return Err(XdpMapError::ChainedProgramNotSupported);
             }
-            bpf_map_update_elem(fd, Some(&index), &ifindex, flags)
+            bpf_map_update_elem(fd, Some(&index), &target_if_index, flags)
         };
 
-        res.map_err(|(_, io_error)| SyscallError {
-            call: "bpf_map_update_elem",
-            io_error,
+        res.map_err(|(_, io_error)| {
+            MapError::from(SyscallError {
+                call: "bpf_map_update_elem",
+                io_error,
+            })
         })?;
         Ok(())
     }
@@ -173,7 +178,10 @@ impl<T: Borrow<MapData>> IterableMap<u32, DevMapValue> for DevMap<T> {
 unsafe impl Pod for bpf_devmap_val {}
 
 #[derive(Clone, Copy, Debug)]
+/// The value of a device map.
 pub struct DevMapValue {
-    pub ifindex: u32,
+    /// Target interface index to redirect to.
+    pub if_index: u32,
+    /// Chained XDP program ID.
     pub prog_id: Option<NonZeroU32>,
 }

+ 35 - 33
aya/src/maps/xdp/dev_map_hash.rs

@@ -6,7 +6,7 @@ use std::{
     os::fd::{AsFd, AsRawFd},
 };
 
-use aya_obj::generated::{bpf_devmap_val, bpf_devmap_val__bindgen_ty_1};
+use aya_obj::generated::bpf_devmap_val;
 
 use crate::{
     maps::{check_kv_size, hash_map, IterableMap, MapData, MapError, MapIter, MapKeys},
@@ -15,7 +15,7 @@ use crate::{
     FEATURES,
 };
 
-use super::dev_map::DevMapValue;
+use super::{dev_map::DevMapValue, XdpMapError};
 
 /// An hashmap of network devices.
 ///
@@ -32,12 +32,15 @@ use super::dev_map::DevMapValue;
 /// use aya::maps::xdp::DevMapHash;
 ///
 /// let mut devmap = DevMapHash::try_from(bpf.map_mut("IFACES").unwrap())?;
-/// let flags = 0;
-/// let ifindex = 32u32;
-/// devmap.insert(ifindex, ifindex, None, flags);
+/// // Lookups with key 2 will redirect packets to interface with index 3 (e.g. eth1)
+/// devmap.insert(2, 3, None, 0);
 ///
 /// # Ok::<(), aya::BpfError>(())
 /// ```
+///
+/// # See also
+///
+/// Kernel documentation: <https://docs.kernel.org/next/bpf/map_devmap.html>
 #[doc(alias = "BPF_MAP_TYPE_DEVMAP_HASH")]
 pub struct DevMapHash<T> {
     inner: T,
@@ -47,7 +50,7 @@ impl<T: Borrow<MapData>> DevMapHash<T> {
     pub(crate) fn new(map: T) -> Result<Self, MapError> {
         let data = map.borrow();
 
-        if FEATURES.devmap_hash_prog_id() {
+        if FEATURES.devmap_prog_id() {
             check_kv_size::<u32, bpf_devmap_val>(data)?;
         } else {
             check_kv_size::<u32, u32>(data)?;
@@ -56,7 +59,7 @@ impl<T: Borrow<MapData>> DevMapHash<T> {
         Ok(Self { inner: map })
     }
 
-    /// Returns the target ifindex and possible program for a given key.
+    /// Returns the target interface index and optional program for a given key.
     ///
     /// # Errors
     ///
@@ -64,10 +67,10 @@ impl<T: Borrow<MapData>> DevMapHash<T> {
     pub fn get(&self, key: u32, flags: u64) -> Result<DevMapValue, MapError> {
         let fd = self.inner.borrow().fd().as_fd();
 
-        let value = if FEATURES.devmap_hash_prog_id() {
+        let value = if FEATURES.devmap_prog_id() {
             bpf_map_lookup_elem::<_, bpf_devmap_val>(fd, &key, flags).map(|value| {
                 value.map(|value| DevMapValue {
-                    ifindex: value.ifindex,
+                    if_index: value.ifindex,
                     // SAFETY: map writes use fd, map reads use id.
                     // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/include/uapi/linux/bpf.h#L6228
                     prog_id: NonZeroU32::new(unsafe { value.bpf_prog.id }),
@@ -76,7 +79,7 @@ impl<T: Borrow<MapData>> DevMapHash<T> {
         } else {
             bpf_map_lookup_elem::<_, u32>(fd, &key, flags).map(|value| {
                 value.map(|ifindex| DevMapValue {
-                    ifindex,
+                    if_index: ifindex,
                     prog_id: None,
                 })
             })
@@ -105,44 +108,43 @@ impl<T: BorrowMut<MapData>> DevMapHash<T> {
     ///
     /// When redirecting using `key`, packets will be transmitted by the interface with `ifindex`.
     ///
-    /// Another XDP program can be passed in that will be run before actual transmission. It can be
-    /// used to modify the packet before transmission with NIC specific data (MAC address update,
-    /// checksum computations, etc) or other purposes.
+    /// Starting from Linux kernel 5.8, another XDP program can be passed in that will be run before
+    /// actual transmission. It can be used to modify the packet before transmission with NIC
+    /// specific data (MAC address update, checksum computations, etc) or other purposes.
     ///
-    /// Note that only XDP programs with the `map = "devmap"` argument can be passed. See the
-    /// kernel-space `aya_bpf::xdp` for more information.
+    /// The chained program must be loaded with the `BPF_XDP_DEVMAP` attach type. When using
+    /// `aya-ebpf`, that means XDP programs that specify the `map = "devmap"` argument. See the
+    /// kernel-space `aya_ebpf::xdp` for more information.
     ///
     /// # Errors
     ///
     /// Returns [`MapError::SyscallError`] if `bpf_map_update_elem` fails,
-    /// [`MapError::ProgIdNotSupported`] if the kernel does not support program ids and one is
+    /// [`MapError::ProgIdNotSupported`] if the kernel does not support chained programs and one is
     /// provided.
     pub fn insert(
         &mut self,
         key: u32,
-        ifindex: u32,
+        target_if_index: u32,
         program: Option<&ProgramFd>,
         flags: u64,
-    ) -> Result<(), MapError> {
-        if FEATURES.devmap_hash_prog_id() {
-            let value = bpf_devmap_val {
-                ifindex,
-                bpf_prog: bpf_devmap_val__bindgen_ty_1 {
-                    // Default is valid as the kernel will only consider fd > 0:
-                    // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L866
-                    // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L918
-                    fd: program
-                        .map(|prog| prog.as_fd().as_raw_fd())
-                        .unwrap_or_default(),
-                },
-            };
-            hash_map::insert(self.inner.borrow_mut(), &key, &value, flags)
+    ) -> Result<(), XdpMapError> {
+        if FEATURES.devmap_prog_id() {
+            let mut value = unsafe { std::mem::zeroed::<bpf_devmap_val>() };
+            value.ifindex = target_if_index;
+            // Default is valid as the kernel will only consider fd > 0:
+            // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L866
+            // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L918
+            value.bpf_prog.fd = program
+                .map(|prog| prog.as_fd().as_raw_fd())
+                .unwrap_or_default();
+            hash_map::insert(self.inner.borrow_mut(), &key, &value, flags)?;
         } else {
             if program.is_some() {
-                return Err(MapError::ProgIdNotSupported);
+                return Err(XdpMapError::ChainedProgramNotSupported);
             }
-            hash_map::insert(self.inner.borrow_mut(), &key, &ifindex, flags)
+            hash_map::insert(self.inner.borrow_mut(), &key, &target_if_index, flags)?;
         }
+        Ok(())
     }
 
     /// Removes a value from the map.

+ 15 - 0
aya/src/maps/xdp/mod.rs

@@ -8,3 +8,18 @@ pub use cpu_map::CpuMap;
 pub use dev_map::DevMap;
 pub use dev_map_hash::DevMapHash;
 pub use xsk_map::XskMap;
+
+use super::MapError;
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+/// Errors occuring from working with XDP maps.
+pub enum XdpMapError {
+    /// Chained programs are not supported.
+    #[error("chained programs are not supported by the current kernel")]
+    ChainedProgramNotSupported,
+
+    /// Map operation failed.
+    #[error(transparent)]
+    MapError(#[from] MapError),
+}

+ 4 - 0
aya/src/maps/xdp/xsk_map.rs

@@ -30,6 +30,10 @@ use crate::{
 /// xskmap.set(0, socket_fd, 0);
 /// # Ok::<(), aya::BpfError>(())
 /// ```
+///
+/// # See also
+///
+/// Kernel documentation: <https://docs.kernel.org/next/bpf/map_xskmap.html>
 #[doc(alias = "BPF_MAP_TYPE_XSKMAP")]
 pub struct XskMap<T> {
     inner: T,

+ 10 - 8
bpf/aya-bpf/src/maps/xdp/cpu_map.rs

@@ -1,6 +1,6 @@
 use core::{cell::UnsafeCell, mem};
 
-use aya_bpf_bindings::bindings::bpf_cpumap_val;
+use aya_bpf_bindings::bindings::{bpf_cpumap_val, xdp_action::XDP_REDIRECT};
 
 use crate::{
     bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_CPUMAP},
@@ -40,9 +40,9 @@ unsafe impl Sync for CpuMap {}
 impl CpuMap {
     /// Creates a [`CpuMap`] with a set maximum number of elements.
     ///
-    /// In a CPU Map, an entry represents a CPU core. Thus there should be as many entries as there
-    /// are CPU cores on the system. It can be set to zero here, and updated by userspace at
-    /// runtime. Refer to the userspace documentation for more information.
+    /// In a CPU map, an entry represents a CPU core. Thus there should be as many entries as there
+    /// are CPU cores on the system. `max_entries` can be set to zero here, and updated by userspace
+    /// at runtime. Refer to the userspace documentation for more information.
     ///
     /// # Examples
     ///
@@ -109,16 +109,18 @@ impl CpuMap {
     /// #[xdp]
     /// fn xdp(_ctx: XdpContext) -> u32 {
     ///     // Redirect to CPU 7 or drop packet if no entry found.
-    ///     MAP.redirect(7, xdp_action::XDP_DROP as u64)
+    ///     MAP.redirect(7, 0).unwrap_or(xdp_action::XDP_DROP)
     /// }
     /// ```
     #[inline(always)]
-    pub fn redirect(&self, index: u32, flags: u64) -> u32 {
-        unsafe {
+    pub fn redirect(&self, index: u32, flags: u64) -> Result<u32, u32> {
+        let ret = unsafe { bpf_redirect_map(self.def.get() as *mut _, index.into(), flags) };
+        match ret.unsigned_abs() as u32 {
+            XDP_REDIRECT => Ok(XDP_REDIRECT),
             // Return XDP_REDIRECT on success, or the value of the two lower bits of the flags
             // argument on error. Thus I have no idea why it returns a long (i64) instead of
             // something saner, hence the unsigned_abs.
-            bpf_redirect_map(self.def.get() as *mut _, index.into(), flags).unsigned_abs() as u32
+            ret => Err(ret),
         }
     }
 }

+ 21 - 16
bpf/aya-bpf/src/maps/xdp/dev_map.rs

@@ -1,6 +1,6 @@
-use core::{cell::UnsafeCell, mem, ptr::NonNull};
+use core::{cell::UnsafeCell, mem, num::NonZeroU32, ptr::NonNull};
 
-use aya_bpf_bindings::bindings::bpf_devmap_val;
+use aya_bpf_bindings::bindings::{bpf_devmap_val, xdp_action::XDP_REDIRECT};
 use aya_bpf_cty::c_void;
 
 use crate::{
@@ -99,9 +99,9 @@ impl DevMap {
     /// #[map]
     /// static MAP: DevMap = DevMap::with_max_entries(1, 0);
     ///
-    /// let ifindex = MAP.get(0);
+    /// let target_if_index = MAP.get(0).target_if_index;
     ///
-    /// // redirect to ifindex
+    /// // redirect to if_index
     /// ```
     #[inline(always)]
     pub fn get(&self, index: u32) -> Option<DevMapValue> {
@@ -111,10 +111,10 @@ impl DevMap {
                 &index as *const _ as *const c_void,
             );
             NonNull::new(value as *mut bpf_devmap_val).map(|p| DevMapValue {
-                ifindex: p.as_ref().ifindex,
+                if_index: p.as_ref().ifindex,
                 // SAFETY: map writes use fd, map reads use id.
                 // https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/bpf.h#L6136
-                prog_id: p.as_ref().bpf_prog.id,
+                prog_id: NonZeroU32::new(p.as_ref().bpf_prog.id),
             })
         }
     }
@@ -133,23 +133,28 @@ impl DevMap {
     /// static MAP: DevMap = DevMap::with_max_entries(8, 0);
     ///
     /// #[xdp]
-    /// fn xdp(_ctx: XdpContext) -> i32 {
-    ///     MAP.redirect(7, xdp_action::XDP_PASS as u64)
+    /// fn xdp(_ctx: XdpContext) -> u32 {
+    ///     MAP.redirect(7, 0).unwrap_or(xdp_action::XDP_DROP)
     /// }
     /// ```
     #[inline(always)]
-    pub fn redirect(&self, index: u32, flags: u64) -> u32 {
-        // Return XDP_REDIRECT on success, or the value of the two lower bits of the flags
-        // argument on error. Thus I have no idea why it returns a long (i64) instead of
-        // something saner, hence the unsigned_abs.
-        unsafe {
-            bpf_redirect_map(self.def.get() as *mut _, index.into(), flags).unsigned_abs() as u32
+    pub fn redirect(&self, index: u32, flags: u64) -> Result<u32, u32> {
+        let ret = unsafe { bpf_redirect_map(self.def.get() as *mut _, index.into(), flags) };
+        match ret.unsigned_abs() as u32 {
+            XDP_REDIRECT => Ok(XDP_REDIRECT),
+            // Return XDP_REDIRECT on success, or the value of the two lower bits of the flags
+            // argument on error. Thus I have no idea why it returns a long (i64) instead of
+            // something saner, hence the unsigned_abs.
+            ret => Err(ret),
         }
     }
 }
 
 #[derive(Clone, Copy)]
+/// The value of a device map.
 pub struct DevMapValue {
-    pub ifindex: u32,
-    pub prog_id: u32,
+    /// Target interface index to redirect to.
+    pub if_index: u32,
+    /// Chained XDP program ID.
+    pub prog_id: Option<NonZeroU32>,
 }

+ 12 - 10
bpf/aya-bpf/src/maps/xdp/dev_map_hash.rs

@@ -1,6 +1,6 @@
-use core::{cell::UnsafeCell, mem, ptr::NonNull};
+use core::{cell::UnsafeCell, mem, num::NonZeroU32, ptr::NonNull};
 
-use aya_bpf_bindings::bindings::bpf_devmap_val;
+use aya_bpf_bindings::bindings::{bpf_devmap_val, xdp_action::XDP_REDIRECT};
 use aya_bpf_cty::c_void;
 
 use crate::{
@@ -103,7 +103,7 @@ impl DevMapHash {
     /// #[map]
     /// static MAP: DevMapHash = DevMapHash::with_max_entries(1, 0);
     ///
-    /// let ifindex = MAP.get(42);
+    /// let target_if_index = MAP.get(42).target_if_index;
     ///
     /// // redirect to ifindex
     /// ```
@@ -113,10 +113,10 @@ impl DevMapHash {
             let value =
                 bpf_map_lookup_elem(self.def.get() as *mut _, &key as *const _ as *const c_void);
             NonNull::new(value as *mut bpf_devmap_val).map(|p| DevMapValue {
-                ifindex: p.as_ref().ifindex,
+                if_index: p.as_ref().ifindex,
                 // SAFETY: map writes use fd, map reads use id.
                 // https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/bpf.h#L6136
-                prog_id: p.as_ref().bpf_prog.id,
+                prog_id: NonZeroU32::new(p.as_ref().bpf_prog.id),
             })
         }
     }
@@ -135,17 +135,19 @@ impl DevMapHash {
     /// static MAP: DevMapHash = DevMapHash::with_max_entries(8, 0);
     ///
     /// #[xdp]
-    /// fn xdp(_ctx: XdpContext) -> i32 {
-    ///     MAP.redirect(7, xdp_action::XDP_PASS as u64)
+    /// fn xdp(_ctx: XdpContext) -> u32 {
+    ///     MAP.redirect(7, 0).unwrap_or(xdp_action::XDP_DROP)
     /// }
     /// ```
     #[inline(always)]
-    pub fn redirect(&self, key: u32, flags: u64) -> u32 {
-        unsafe {
+    pub fn redirect(&self, index: u32, flags: u64) -> Result<u32, u32> {
+        let ret = unsafe { bpf_redirect_map(self.def.get() as *mut _, index.into(), flags) };
+        match ret.unsigned_abs() as u32 {
+            XDP_REDIRECT => Ok(XDP_REDIRECT),
             // Return XDP_REDIRECT on success, or the value of the two lower bits of the flags
             // argument on error. Thus I have no idea why it returns a long (i64) instead of
             // something saner, hence the unsigned_abs.
-            bpf_redirect_map(self.def.get() as *mut _, key.into(), flags).unsigned_abs() as u32
+            ret => Err(ret),
         }
     }
 }

+ 8 - 6
bpf/aya-bpf/src/maps/xdp/xsk_map.rs

@@ -1,6 +1,6 @@
 use core::{cell::UnsafeCell, mem, ptr::NonNull};
 
-use aya_bpf_bindings::bindings::bpf_xdp_sock;
+use aya_bpf_bindings::bindings::{bpf_xdp_sock, xdp_action::XDP_REDIRECT};
 use aya_bpf_cty::c_void;
 
 use crate::{
@@ -150,18 +150,20 @@ impl XskMap {
     /// static SOCKS: XskMap = XskMap::with_max_entries(8, 0);
     ///
     /// #[xdp]
-    /// fn xdp(ctx, XdpContext) -> i32 {
+    /// fn xdp(ctx, XdpContext) -> u32 {
     ///     let queue_id = unsafe { (*ctx.ctx).rx_queue_index };
-    ///     MAP.redirect(queue_id, xdp_action::XDP_DROP as u64)
+    ///     MAP.redirect(queue_id, 0).unwrap_or(xdp_action::XDP_DROP)
     /// }
     /// ```
     #[inline(always)]
-    pub fn redirect(&self, index: u32, flags: u64) -> u32 {
-        unsafe {
+    pub fn redirect(&self, index: u32, flags: u64) -> Result<u32, u32> {
+        let ret = unsafe { bpf_redirect_map(self.def.get() as *mut _, index.into(), flags) };
+        match ret.unsigned_abs() as u32 {
+            XDP_REDIRECT => Ok(XDP_REDIRECT),
             // Return XDP_REDIRECT on success, or the value of the two lower bits of the flags
             // argument on error. Thus I have no idea why it returns a long (i64) instead of
             // something saner, hence the unsigned_abs.
-            bpf_redirect_map(self.def.get() as *mut _, index.into(), flags).unsigned_abs() as u32
+            ret => Err(ret),
         }
     }
 }

+ 4 - 4
test/integration-ebpf/src/redirect.rs

@@ -26,25 +26,25 @@ static mut HITS: Array<u32> = Array::with_max_entries(2, 0);
 
 #[xdp]
 pub fn redirect_sock(_ctx: XdpContext) -> u32 {
-    SOCKS.redirect(0, xdp_action::XDP_ABORTED as u64)
+    SOCKS.redirect(0, 0).unwrap_or(xdp_action::XDP_ABORTED)
 }
 
 #[xdp]
 pub fn redirect_dev(_ctx: XdpContext) -> u32 {
     inc_hit(0);
-    DEVS.redirect(0, xdp_action::XDP_ABORTED as u64)
+    DEVS.redirect(0, 0).unwrap_or(xdp_action::XDP_ABORTED)
 }
 
 #[xdp]
 pub fn redirect_dev_hash(_ctx: XdpContext) -> u32 {
     inc_hit(0);
-    DEVS_HASH.redirect(10, xdp_action::XDP_ABORTED as u64)
+    DEVS_HASH.redirect(10, 0).unwrap_or(xdp_action::XDP_ABORTED)
 }
 
 #[xdp]
 pub fn redirect_cpu(_ctx: XdpContext) -> u32 {
     inc_hit(0);
-    CPUS.redirect(0, xdp_action::XDP_ABORTED as u64)
+    CPUS.redirect(0, 0).unwrap_or(xdp_action::XDP_ABORTED)
 }
 
 #[xdp(map = "cpumap")]

+ 8 - 8
xtask/public-api/aya-bpf.txt

@@ -567,7 +567,7 @@ pub mod aya_bpf::maps::xdp
 #[repr(transparent)] pub struct aya_bpf::maps::xdp::CpuMap
 impl aya_bpf::maps::CpuMap
 pub const fn aya_bpf::maps::CpuMap::pinned(max_entries: u32, flags: u32) -> aya_bpf::maps::CpuMap
-pub fn aya_bpf::maps::CpuMap::redirect(&self, index: u32, flags: u64) -> u32
+pub fn aya_bpf::maps::CpuMap::redirect(&self, index: u32, flags: u64) -> core::result::Result<u32, u32>
 pub const fn aya_bpf::maps::CpuMap::with_max_entries(max_entries: u32, flags: u32) -> aya_bpf::maps::CpuMap
 impl core::marker::Sync for aya_bpf::maps::CpuMap
 impl core::marker::Send for aya_bpf::maps::CpuMap
@@ -594,7 +594,7 @@ pub fn aya_bpf::maps::CpuMap::from(t: T) -> T
 impl aya_bpf::maps::DevMap
 pub fn aya_bpf::maps::DevMap::get(&self, index: u32) -> core::option::Option<DevMapValue>
 pub const fn aya_bpf::maps::DevMap::pinned(max_entries: u32, flags: u32) -> aya_bpf::maps::DevMap
-pub fn aya_bpf::maps::DevMap::redirect(&self, index: u32, flags: u64) -> u32
+pub fn aya_bpf::maps::DevMap::redirect(&self, index: u32, flags: u64) -> core::result::Result<u32, u32>
 pub const fn aya_bpf::maps::DevMap::with_max_entries(max_entries: u32, flags: u32) -> aya_bpf::maps::DevMap
 impl core::marker::Sync for aya_bpf::maps::DevMap
 impl core::marker::Send for aya_bpf::maps::DevMap
@@ -621,7 +621,7 @@ pub fn aya_bpf::maps::DevMap::from(t: T) -> T
 impl aya_bpf::maps::DevMapHash
 pub fn aya_bpf::maps::DevMapHash::get(&self, key: u32) -> core::option::Option<DevMapValue>
 pub const fn aya_bpf::maps::DevMapHash::pinned(max_entries: u32, flags: u32) -> aya_bpf::maps::DevMapHash
-pub fn aya_bpf::maps::DevMapHash::redirect(&self, key: u32, flags: u64) -> u32
+pub fn aya_bpf::maps::DevMapHash::redirect(&self, index: u32, flags: u64) -> core::result::Result<u32, u32>
 pub const fn aya_bpf::maps::DevMapHash::with_max_entries(max_entries: u32, flags: u32) -> aya_bpf::maps::DevMapHash
 impl core::marker::Sync for aya_bpf::maps::DevMapHash
 impl core::marker::Send for aya_bpf::maps::DevMapHash
@@ -648,7 +648,7 @@ pub fn aya_bpf::maps::DevMapHash::from(t: T) -> T
 impl aya_bpf::maps::XskMap
 pub fn aya_bpf::maps::XskMap::get(&self, index: u32) -> core::option::Option<u32>
 pub const fn aya_bpf::maps::XskMap::pinned(max_entries: u32, flags: u32) -> aya_bpf::maps::XskMap
-pub fn aya_bpf::maps::XskMap::redirect(&self, index: u32, flags: u64) -> u32
+pub fn aya_bpf::maps::XskMap::redirect(&self, index: u32, flags: u64) -> core::result::Result<u32, u32>
 pub const fn aya_bpf::maps::XskMap::with_max_entries(max_entries: u32, flags: u32) -> aya_bpf::maps::XskMap
 impl core::marker::Sync for aya_bpf::maps::XskMap
 impl core::marker::Send for aya_bpf::maps::XskMap
@@ -729,7 +729,7 @@ pub fn aya_bpf::maps::bloom_filter::BloomFilter<T>::from(t: T) -> T
 #[repr(transparent)] pub struct aya_bpf::maps::CpuMap
 impl aya_bpf::maps::CpuMap
 pub const fn aya_bpf::maps::CpuMap::pinned(max_entries: u32, flags: u32) -> aya_bpf::maps::CpuMap
-pub fn aya_bpf::maps::CpuMap::redirect(&self, index: u32, flags: u64) -> u32
+pub fn aya_bpf::maps::CpuMap::redirect(&self, index: u32, flags: u64) -> core::result::Result<u32, u32>
 pub const fn aya_bpf::maps::CpuMap::with_max_entries(max_entries: u32, flags: u32) -> aya_bpf::maps::CpuMap
 impl core::marker::Sync for aya_bpf::maps::CpuMap
 impl core::marker::Send for aya_bpf::maps::CpuMap
@@ -756,7 +756,7 @@ pub fn aya_bpf::maps::CpuMap::from(t: T) -> T
 impl aya_bpf::maps::DevMap
 pub fn aya_bpf::maps::DevMap::get(&self, index: u32) -> core::option::Option<DevMapValue>
 pub const fn aya_bpf::maps::DevMap::pinned(max_entries: u32, flags: u32) -> aya_bpf::maps::DevMap
-pub fn aya_bpf::maps::DevMap::redirect(&self, index: u32, flags: u64) -> u32
+pub fn aya_bpf::maps::DevMap::redirect(&self, index: u32, flags: u64) -> core::result::Result<u32, u32>
 pub const fn aya_bpf::maps::DevMap::with_max_entries(max_entries: u32, flags: u32) -> aya_bpf::maps::DevMap
 impl core::marker::Sync for aya_bpf::maps::DevMap
 impl core::marker::Send for aya_bpf::maps::DevMap
@@ -783,7 +783,7 @@ pub fn aya_bpf::maps::DevMap::from(t: T) -> T
 impl aya_bpf::maps::DevMapHash
 pub fn aya_bpf::maps::DevMapHash::get(&self, key: u32) -> core::option::Option<DevMapValue>
 pub const fn aya_bpf::maps::DevMapHash::pinned(max_entries: u32, flags: u32) -> aya_bpf::maps::DevMapHash
-pub fn aya_bpf::maps::DevMapHash::redirect(&self, key: u32, flags: u64) -> u32
+pub fn aya_bpf::maps::DevMapHash::redirect(&self, index: u32, flags: u64) -> core::result::Result<u32, u32>
 pub const fn aya_bpf::maps::DevMapHash::with_max_entries(max_entries: u32, flags: u32) -> aya_bpf::maps::DevMapHash
 impl core::marker::Sync for aya_bpf::maps::DevMapHash
 impl core::marker::Send for aya_bpf::maps::DevMapHash
@@ -1206,7 +1206,7 @@ pub fn aya_bpf::maps::stack_trace::StackTrace::from(t: T) -> T
 impl aya_bpf::maps::XskMap
 pub fn aya_bpf::maps::XskMap::get(&self, index: u32) -> core::option::Option<u32>
 pub const fn aya_bpf::maps::XskMap::pinned(max_entries: u32, flags: u32) -> aya_bpf::maps::XskMap
-pub fn aya_bpf::maps::XskMap::redirect(&self, index: u32, flags: u64) -> u32
+pub fn aya_bpf::maps::XskMap::redirect(&self, index: u32, flags: u64) -> core::result::Result<u32, u32>
 pub const fn aya_bpf::maps::XskMap::with_max_entries(max_entries: u32, flags: u32) -> aya_bpf::maps::XskMap
 impl core::marker::Sync for aya_bpf::maps::XskMap
 impl core::marker::Send for aya_bpf::maps::XskMap