From 7474b6b6a0d0530cf394411d35445765fc423946 Mon Sep 17 00:00:00 2001 From: Tuetuopay Date: Sat, 5 Aug 2023 00:16:22 +0200 Subject: [PATCH] bpf: add documentation for XDP maps --- bpf/aya-bpf/src/maps/xdp/cpu_map.rs | 68 ++++++++++++++++ bpf/aya-bpf/src/maps/xdp/dev_map.rs | 76 ++++++++++++++++++ bpf/aya-bpf/src/maps/xdp/dev_map_hash.rs | 78 +++++++++++++++++++ bpf/aya-bpf/src/maps/xdp/xsk_map.rs | 99 ++++++++++++++++++++++++ 4 files changed, 321 insertions(+) diff --git a/bpf/aya-bpf/src/maps/xdp/cpu_map.rs b/bpf/aya-bpf/src/maps/xdp/cpu_map.rs index f1d224b3..0d8a1a4a 100644 --- a/bpf/aya-bpf/src/maps/xdp/cpu_map.rs +++ b/bpf/aya-bpf/src/maps/xdp/cpu_map.rs @@ -8,6 +8,28 @@ use crate::{ maps::PinningType, }; +/// An array of available CPUs. +/// +/// XDP programs can use this map to redirect packets to a target CPU for processing. +/// +/// # Minimum kernel version +/// +/// The minimum kernel version required to use this feature is 4.15. +/// +/// # Examples +/// +/// ```rust,no_run +/// use aya_bpf::{bindings::xdp_action, macros::{map, xdp}, maps::CpuMap, programs::XdpContext}; +/// +/// #[map] +/// static MAP: CpuMap = CpuMap::with_max_entries(8, 0); +/// +/// #[xdp] +/// fn xdp(_ctx: XdpContext) -> i32 { +/// // Redirect to CPU 7 or drop packet if no entry found. +/// MAP.redirect(7, xdp_action::XDP_DROP as u64) +/// } +/// ``` #[repr(transparent)] pub struct CpuMap { def: UnsafeCell, @@ -16,6 +38,20 @@ pub struct CpuMap { 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. To dynamically set the entry count at runtime, refer to the + /// userspace documentation. + /// + /// # Examples + /// + /// ```rust,no_run + /// use aya_bpf::{macros::map, maps::CpuMap}; + /// + /// #[map] + /// static MAP: CpuMap = CpuMap::with_max_entries(8, 0); + /// ``` pub const fn with_max_entries(max_entries: u32, flags: u32) -> CpuMap { CpuMap { def: UnsafeCell::new(bpf_map_def { @@ -30,6 +66,19 @@ impl CpuMap { } } + /// Creates a [`CpuMap`] with a set maximum number of elements that can be pinned to the BPF + /// File System (bpffs). + /// + /// See [`CpuMap::with_max_entries`] for more information. + /// + /// # Examples + /// + /// ```rust,no_run + /// use aya_bpf::{macros::map, maps::CpuMap}; + /// + /// #[map] + /// static MAP: CpuMap = CpuMap::pinned(8, 0); + /// ``` pub const fn pinned(max_entries: u32, flags: u32) -> CpuMap { CpuMap { def: UnsafeCell::new(bpf_map_def { @@ -44,6 +93,25 @@ impl CpuMap { } } + /// Redirects the current packet on the CPU at `index`. + /// + /// The lower two bits of `flags` are used for the return code if the map lookup fails, which + /// can be used as the XDP program's return code if a CPU cannot be found. + /// + /// # Examples + /// + /// ```rust,no_run + /// use aya_bpf::{bindings::xdp_action, macros::{map, xdp}, maps::CpuMap, programs::XdpContext}; + /// + /// #[map] + /// static MAP: CpuMap = CpuMap::with_max_entries(8, 0); + /// + /// #[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) + /// } + /// ``` #[inline(always)] pub fn redirect(&self, index: u32, flags: u64) -> u32 { unsafe { diff --git a/bpf/aya-bpf/src/maps/xdp/dev_map.rs b/bpf/aya-bpf/src/maps/xdp/dev_map.rs index ca54da42..66103758 100644 --- a/bpf/aya-bpf/src/maps/xdp/dev_map.rs +++ b/bpf/aya-bpf/src/maps/xdp/dev_map.rs @@ -9,6 +9,27 @@ use crate::{ maps::PinningType, }; +/// An array of network devices. +/// +/// XDP programs can use this map to redirect packets to other network deviecs. +/// +/// # Minimum kernel version +/// +/// The minimum kernel version required to use this feature is 4.14. +/// +/// # Examples +/// +/// ```rust,no_run +/// use aya_bpf::{bindings::xdp_action, macros::{map, xdp}, maps::DevMap, programs::XdpContext}; +/// +/// #[map] +/// static MAP: DevMap = DevMap::with_max_entries(1, 0); +/// +/// #[xdp] +/// fn xdp(_ctx: XdpContext) -> i32 { +/// MAP.redirect(0, xdp_action::XDP_PASS as u64) +/// } +/// ``` #[repr(transparent)] pub struct DevMap { def: UnsafeCell, @@ -17,6 +38,16 @@ pub struct DevMap { unsafe impl Sync for DevMap {} impl DevMap { + /// Creates a [`DevMap`] with a set maximum number of elements. + /// + /// # Examples + /// + /// ```rust,no_run + /// use aya_bpf::{macros::map, maps::DevMap}; + /// + /// #[map] + /// static MAP: DevMap = DevMap::with_max_entries(8, 0); + /// ``` pub const fn with_max_entries(max_entries: u32, flags: u32) -> DevMap { DevMap { def: UnsafeCell::new(bpf_map_def { @@ -31,6 +62,17 @@ impl DevMap { } } + /// Creates a [`DevMap`] with a set maximum number of elements that can be pinned to the BPF + /// File System (bpffs). + /// + /// # Examples + /// + /// ```rust,no_run + /// use aya_bpf::{macros::map, maps::DevMap}; + /// + /// #[map] + /// static MAP: DevMap = DevMap::pinned(8, 0); + /// ``` pub const fn pinned(max_entries: u32, flags: u32) -> DevMap { DevMap { def: UnsafeCell::new(bpf_map_def { @@ -45,6 +87,22 @@ impl DevMap { } } + /// Retrieves the interface index at `index` in the array. + /// + /// To actually redirect a packet, see [`DevMap::redirect`]. + /// + /// # Examples + /// + /// ```rust,no_run + /// use aya_bpf::{macros::map, maps::DevMap}; + /// + /// #[map] + /// static MAP: DevMap = DevMap::with_max_entries(1, 0); + /// + /// let ifindex = MAP.get(0); + /// + /// // redirect to ifindex + /// ``` #[inline(always)] pub fn get(&self, index: u32) -> Option { unsafe { @@ -56,6 +114,24 @@ impl DevMap { } } + /// Redirects the current packet on the interface at `index`. + /// + /// The lower two bits of `flags` are used for the return code if the map lookup fails, which + /// can be used as the XDP program's return code if a CPU cannot be found. + /// + /// # Examples + /// + /// ```rust,no_run + /// use aya_bpf::{bindings::xdp_action, macros::{map, xdp}, maps::DevMap, programs::XdpContext}; + /// + /// #[map] + /// static MAP: DevMap = DevMap::with_max_entries(8, 0); + /// + /// #[xdp] + /// fn xdp(_ctx: XdpContext) -> i32 { + /// MAP.redirect(7, xdp_action::XDP_PASS as u64) + /// } + /// ``` #[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 diff --git a/bpf/aya-bpf/src/maps/xdp/dev_map_hash.rs b/bpf/aya-bpf/src/maps/xdp/dev_map_hash.rs index 87a61299..600aa9df 100644 --- a/bpf/aya-bpf/src/maps/xdp/dev_map_hash.rs +++ b/bpf/aya-bpf/src/maps/xdp/dev_map_hash.rs @@ -9,6 +9,29 @@ use crate::{ maps::PinningType, }; +/// A map of network devices. +/// +/// XDP programs can use this map to redirect packets to other network devices. It is similar to +/// [`DevMap`](super::DevMap), but is an hash map rather than an array. Keys do not need to be +/// contiguous nor start at zero, but there is a hashing cost to every lookup. +/// +/// # Minimum kernel version +/// +/// The minimum kernel version required to use this feature is 5.4. +/// +/// # Examples +/// +/// ```rust,no_run +/// use aya_bpf::{bindings::xdp_action, macros::{map, xdp}, maps::DevMapHash, programs::XdpContext}; +/// +/// #[map] +/// static MAP: DevMapHash = DevMapHash::with_max_entries(1, 0); +/// +/// #[xdp] +/// fn xdp(_ctx: XdpContext) -> i32 { +/// MAP.redirect(42, xdp_action::XDP_PASS as u64) +/// } +/// ``` #[repr(transparent)] pub struct DevMapHash { def: UnsafeCell, @@ -17,6 +40,16 @@ pub struct DevMapHash { unsafe impl Sync for DevMapHash {} impl DevMapHash { + /// Creates a [`DevMapHash`] with a set maximum number of elements. + /// + /// # Examples + /// + /// ```rust,no_run + /// use aya_bpf::{macros::map, maps::DevMapHash}; + /// + /// #[map] + /// static MAP: DevMapHash = DevMapHash::with_max_entries(8, 0); + /// ``` pub const fn with_max_entries(max_entries: u32, flags: u32) -> DevMapHash { DevMapHash { def: UnsafeCell::new(bpf_map_def { @@ -31,6 +64,17 @@ impl DevMapHash { } } + /// Creates a [`DevMapHash`] with a set maximum number of elements that can be pinned to the BPF + /// File System (bpffs). + /// + /// # Examples + /// + /// ```rust,no_run + /// use aya_bpf::{macros::map, maps::DevMapHash}; + /// + /// #[map] + /// static MAP: DevMapHash = DevMapHash::pinned(8, 0); + /// ``` pub const fn pinned(max_entries: u32, flags: u32) -> DevMapHash { DevMapHash { def: UnsafeCell::new(bpf_map_def { @@ -45,6 +89,22 @@ impl DevMapHash { } } + /// Retrieves the interface index with `key` in the map. + /// + /// To actually redirect a packet, see [`DevMapHash::redirect`]. + /// + /// # Examples + /// + /// ```rust,no_run + /// use aya_bpf::{macros::map, maps::DevMapHash}; + /// + /// #[map] + /// static MAP: DevMapHash = DevMapHash::with_max_entries(1, 0); + /// + /// let ifindex = MAP.get(42); + /// + /// // redirect to ifindex + /// ``` #[inline(always)] pub fn get(&self, key: u32) -> Option { unsafe { @@ -54,6 +114,24 @@ impl DevMapHash { } } + /// Redirects the current packet on the interface at `key`. + /// + /// The lower two bits of `flags` are used for the return code if the map lookup fails, which + /// can be used as the XDP program's return code if a CPU cannot be found. + /// + /// # Examples + /// + /// ```rust,no_run + /// use aya_bpf::{bindings::xdp_action, macros::{map, xdp}, maps::DevMapHash, programs::XdpContext}; + /// + /// #[map] + /// static MAP: DevMapHash = DevMapHash::with_max_entries(8, 0); + /// + /// #[xdp] + /// fn xdp(_ctx: XdpContext) -> i32 { + /// MAP.redirect(7, xdp_action::XDP_PASS as u64) + /// } + /// ``` #[inline(always)] pub fn redirect(&self, key: u32, flags: u64) -> u32 { unsafe { diff --git a/bpf/aya-bpf/src/maps/xdp/xsk_map.rs b/bpf/aya-bpf/src/maps/xdp/xsk_map.rs index a5e12767..ee766fa5 100644 --- a/bpf/aya-bpf/src/maps/xdp/xsk_map.rs +++ b/bpf/aya-bpf/src/maps/xdp/xsk_map.rs @@ -9,6 +9,48 @@ use crate::{ maps::PinningType, }; +/// An array of AF_XDP sockets. +/// +/// XDP programs can use this map to redirect packets to a target AF_XDP socket using the +/// `XDP_REDIRECT` action. +/// +/// # Minimum kernel version +/// +/// The minimum kernel version required to use this feature is 4.18. +/// +/// # Examples +/// +/// ```rust,no_run +/// use aya_bpf::{bindings::xdp_action, macros::{map, xdp}, maps::XskMap, programs::XdpContext}; +/// +/// #[map] +/// static SOCKS: XskMap = XskMap::with_max_entries(8, 0); +/// +/// #[xdp] +/// fn xdp(ctx, XdpContext) -> i32 { +/// let queue_id = unsafe { (*ctx.ctx).rx_queue_index }; +/// MAP.redirect(queue_id, xdp_action::XDP_DROP as u64) +/// } +/// ``` +/// +/// # Queue management +/// +/// Packets received on a RX queue can only be redirected to sockets bound on the same queue. Most +/// hardware NICs have multiple RX queue to spread the load across multiple CPU cores using RSS. +/// +/// Three strategies are possible: +/// +/// - Reduce the RX queue count to a single one. This option is great for development, but is +/// detrimental for performance as the single CPU core recieving packets will get overwhelmed. +/// Setting the queue count for a NIC can be achieved using `ethtool -L combined 1`. +/// - Create a socket for every RX queue. Most modern NICs will have an RX queue per CPU thread, so +/// a socket per CPU thread is best for performance. To dynamically size the map depending on the +/// recieve queue count, see the userspace documentation of `CpuMap`. +/// - Create a single socket and use a [`CpuMap`](super::CpuMap) to redirect the packet to the +/// correct CPU core. This way, the packet is sent to another CPU, and a chained XDP program can +/// the redirect to the AF_XDP socket. Using a single socket simplifies the userspace code but +/// will not perform great unless not a lot of traffic is redirected to the socket. Regular +/// traffic however will not be impacted, contrary to reducing the queue count. #[repr(transparent)] pub struct XskMap { def: UnsafeCell, @@ -17,6 +59,16 @@ pub struct XskMap { unsafe impl Sync for XskMap {} impl XskMap { + /// Creates a [`XskMap`] with a set maximum number of elements. + /// + /// # Examples + /// + /// ```rust,no_run + /// use aya_bpf::{macros::map, maps::XskMap}; + /// + /// #[map] + /// static SOCKS: XskMap::with_max_entries(8, 0); + /// ``` pub const fn with_max_entries(max_entries: u32, flags: u32) -> XskMap { XskMap { def: UnsafeCell::new(bpf_map_def { @@ -31,6 +83,17 @@ impl XskMap { } } + /// Creates a [`XskMap`] with a set maximum number of elements that can be pinned to the BPF + /// filesystem (bpffs). + /// + /// # Examples + /// + /// ```rust,no_run + /// use aya_bpf::{macros::map, maps::XskMap}; + /// + /// #[map] + /// static SOCKS: XskMap::pinned(8, 0); + /// ``` pub const fn pinned(max_entries: u32, flags: u32) -> XskMap { XskMap { def: UnsafeCell::new(bpf_map_def { @@ -45,6 +108,20 @@ impl XskMap { } } + /// Retrieves the queue to which the socket is bound at `index` in the array. + /// + /// To actually redirect a packet, see [`XskMap::redirect`]. + /// + /// # Examples + /// + /// ```rust,no_run + /// use aya_bpf::{macros::map, maps::XskMap}; + /// + /// #[map] + /// static SOCKS: XskMap = XskMap::with_max_entries(8, 0); + /// + /// let queue_id = SOCKS.get(0); + /// ``` #[inline(always)] pub fn get(&self, index: u32) -> Option { unsafe { @@ -56,6 +133,28 @@ impl XskMap { } } + /// Redirects the current packet to the AF_XDP socket at `index`. + /// + /// The lower two bits of `flags` are used for the return code if the map lookup fails, which + /// can be used as the XDP program's return code if a matching socket cannot be found. + /// + /// However, if the socket at `index` is bound to a RX queue which is not the current RX queue, + /// the packet will be dropped. + /// + /// # Examples + /// + /// ```rust,no_run + /// use aya_bpf::{bindings::xdp_action, macros::{map, xdp}, maps::XskMap, programs::XdpContext}; + /// + /// #[map] + /// static SOCKS: XskMap = XskMap::with_max_entries(8, 0); + /// + /// #[xdp] + /// fn xdp(ctx, XdpContext) -> i32 { + /// let queue_id = unsafe { (*ctx.ctx).rx_queue_index }; + /// MAP.redirect(queue_id, xdp_action::XDP_DROP as u64) + /// } + /// ``` #[inline(always)] pub fn redirect(&self, index: u32, flags: u64) -> u32 { unsafe {