From 581ba9a435ed355978c4acc2696ba814839f7371 Mon Sep 17 00:00:00 2001 From: Michal Rostecki Date: Mon, 6 Mar 2023 14:10:54 +0100 Subject: [PATCH] ebpf: Add documentation for `HashMap` Signed-off-by: Michal Rostecki --- bpf/aya-bpf/src/maps/hash_map.rs | 189 +++++++++++++++++++++++++++++-- bpf/aya-bpf/src/maps/mod.rs | 29 +++++ 2 files changed, 209 insertions(+), 9 deletions(-) diff --git a/bpf/aya-bpf/src/maps/hash_map.rs b/bpf/aya-bpf/src/maps/hash_map.rs index 5877be90..c899d31e 100644 --- a/bpf/aya-bpf/src/maps/hash_map.rs +++ b/bpf/aya-bpf/src/maps/hash_map.rs @@ -11,6 +11,35 @@ use crate::{ maps::PinningType, }; +/// A hash map that can be shared between eBPF programs and user-space. +/// +/// # Minimum kernel version +/// +/// The minimum kernel version required to use this feature is 3.19. +/// +/// # Examples +/// +/// XDP program using a `HashMap` to block traffic from IP addresses +/// defined in it: +/// +/// ```no_run +/// # use core::ffi::c_long; +/// use aya_bpf::{bindings::xdp_action, macros::map, maps::HashMap}; +/// # use aya_bpf::programs::XdpContext; +/// +/// /// A map which stores IP addresses to block. +/// #[map] +/// static BLOCKLIST: HashMap = HashMap::with_max_entries(1024, 0); +/// +/// # fn parse_src_addr(ctx: &XdpContext) -> u32 { 0 } +/// # fn try_test(ctx: &XdpContext) -> Result { +/// let src_addr: u32 = parse_src_addr(&ctx); +/// if BLOCKLIST.get(&src_addr).is_some() { +/// return Ok(xdp_action::XDP_DROP); +/// } +/// Ok(xdp_action::XDP_PASS) +/// # } +/// ``` #[repr(transparent)] pub struct HashMap { def: UnsafeCell, @@ -21,6 +50,17 @@ pub struct HashMap { unsafe impl Sync for HashMap {} impl HashMap { + /// Creates an empty `HashMap` with the specified maximum number of + /// elements. + /// + /// # Examples + /// + /// ```no_run + /// use aya_bpf::{macros::map, maps::HashMap}; + /// + /// #[map] + /// static mut REDIRECT_PORTS: HashMap = HashMap::with_max_entries(1024, 0); + /// ``` pub const fn with_max_entries(max_entries: u32, flags: u32) -> HashMap { HashMap { def: UnsafeCell::new(build_def::( @@ -34,6 +74,17 @@ impl HashMap { } } + /// Creates an empty `HashMap` with the specified maximum number of + /// elements, and pins it to the BPF file system (BPFFS). + /// + /// # Examples + /// + /// ```no_run + /// use aya_bpf::{macros::map, maps::HashMap}; + /// + /// #[map] + /// static mut REDIRECT_PORTS: HashMap = HashMap::pinned(1024, 0); + /// ``` pub const fn pinned(max_entries: u32, flags: u32) -> HashMap { HashMap { def: UnsafeCell::new(build_def::( @@ -47,38 +98,158 @@ impl HashMap { } } - /// Retrieve the value associate with `key` from the map. - /// This function is unsafe. Unless the map flag `BPF_F_NO_PREALLOC` is used, the kernel does not - /// make guarantee on the atomicity of `insert` or `remove`, and any element removed from the - /// map might get aliased by another element in the map, causing garbage to be read, or + /// Retrieves the value associate with `key` from the map. + /// + /// # Safety + /// + /// Unless the map flag `BPF_F_NO_PREALLOC` is used, the kernel does not + /// make guarantee on the atomicity of [`HashMap::insert`] or + /// [`HashMap::remove`], and any element removed from the map might get + /// aliased by another element in the map, causing garbage to be read, or /// corruption in case of writes. + /// + /// There is no guarantee that the reference returned by this method is + /// aligned. + /// + /// The value contained in the map must be of type `T`. Calling this method + /// with the incorrect type is *undefined behavior*. + /// + /// # Examples + /// + /// XDP program using a `HashMap` to store port redirection rules: + /// + /// ```no_run + /// # use core::ffi::c_long; + /// use aya_bpf::{macros::map, maps::HashMap}; + /// # use aya_bpf::programs::XdpContext; + /// + /// #[map] + /// static REDIRECT_PORTS: HashMap = HashMap::with_max_entries(1024, 0); + /// + /// # fn parse_source(ctx: &XdpContext) -> u16 { 0 } + /// # fn try_test(ctx: &XdpContext) -> Result { + /// // Source port of the packet. + /// let source: u16 = parse_source(&ctx); + /// // Port to which we want redirect the packet to. + /// let redirect = REDIRECT_PORTS.get(&source); + /// + /// // Redirect the packet. + /// # Ok(0) + /// # } + /// ``` #[inline] pub unsafe fn get(&self, key: &K) -> Option<&V> { get(self.def.get(), key) } /// Retrieve the value associate with `key` from the map. - /// The same caveat as `get` applies, but this returns a raw pointer and it's up to the caller - /// to decide whether it's safe to dereference the pointer or not. + /// + /// # Safety + /// + /// Unless the map flag `BPF_F_NO_PREALLOC` is used, the kernel does not + /// make guarantee on the atomicity of [`HashMap::insert`] or + /// [`HashMap::remove`], and any element removed from the map might get + /// aliased by another element in the map, causing garbage to be read, or + /// corruption in case of writes. + /// + /// This method is safe, but it returns a raw pointer and deferefencing it + /// is an unsafe operation. #[inline] pub fn get_ptr(&self, key: &K) -> Option<*const V> { get_ptr(self.def.get(), key) } /// Retrieve the value associate with `key` from the map. - /// The same caveat as `get` applies, and additionally cares should be taken to avoid - /// concurrent writes, but it's up to the caller to decide whether it's safe to dereference the - /// pointer or not. + /// + /// # Safety + /// + /// Unless the map flag `BPF_F_NO_PREALLOC` is used, the kernel does not + /// make guarantee on the atomicity of [`HashMap::insert`] or + /// [`HashMap::remove`], and any element removed from the map might get + /// aliased by another element in the map, causing garbage to be read, or + /// corruption in case of writes. + /// + /// This method is safe, but it returns a raw pointer and deferefencing it + /// is an unsafe operation. + /// + /// # Examples + /// + /// Tracepoint program (attached to `syscall/sys_enter_read`) which counts + /// the number of reads performed by each process in a `HashMap`: + /// + /// ```no_run + /// # use core::ffi::c_long; + /// use aya_bpf::{macros::map, maps::HashMap}; + /// # use aya_bpf::programs::TracePointContext; + /// + /// /// A maps which maps processes to the number of reads they performed. + /// #[map] + /// static PROCESS_READS: HashMap = HashMap::with_max_entries(1024, 0); + /// + /// # fn try_test(ctx: &TracePointContext) -> Result<(), c_long> { + /// let pid = ctx.pid(); + /// match PROCESS_READS.get_ptr_mut(&pid) { + /// Some(count) => unsafe { *count += 1 }, + /// None => PROCESS_READS.insert(&pid, &1, 0)?, + /// } + /// # Ok(()) + /// # } + /// ``` #[inline] pub fn get_ptr_mut(&self, key: &K) -> Option<*mut V> { get_ptr_mut(self.def.get(), key) } + /// Insert the given key and value into the map. + /// + /// # Examples + /// + /// Tracepoint program (attached to `syscall/sys_enter_bind`) which maps + /// processes to the port they are listening on in a `HashMap`: + /// + /// ```no_run + /// # use core::ffi::c_long; + /// use aya_bpf::{macros::map, maps::HashMap}; + /// # use aya_bpf::programs::TracePointContext; + /// + /// /// A maps which maps processes to the port they are listening on. + /// #[map] + /// static PROCESS_PORT: HashMap = HashMap::with_max_entries(1024, 0); + /// + /// # fn parse_local_port(ctx: &TracePointContext) -> u16 { 0 } + /// # fn try_test(ctx: &TracePointContext) -> Result<(), c_long> { + /// let pid = ctx.pid(); + /// let port = parse_local_port(&ctx); + /// PROCESS_PORT.insert(&pid, &port, 0)?; + /// # Ok(()) + /// # } + /// ``` #[inline] pub fn insert(&self, key: &K, value: &V, flags: u64) -> Result<(), c_long> { insert(self.def.get(), key, value, flags) } + /// Remove the given key from the map. + /// + /// # Examples + /// + /// Tracepoint program (attached to `sched/sched_process_exit`) which + /// removes the process from a `HashMap` once it exits: + /// + /// ```no_run + /// # use core::ffi::c_long; + /// use aya_bpf::{macros::map, maps::HashMap}; + /// # use aya_bpf::programs::TracePointContext; + /// + /// /// A maps which maps PIDs to the number of reads they performed. + /// #[map] + /// static PROCESS_READS: HashMap = HashMap::with_max_entries(1024, 0); + /// + /// # fn try_test(ctx: &TracePointContext) -> Result<(), c_long> { + /// let pid = ctx.pid(); + /// PROCESS_READS.remove(&pid)?; + /// # Ok(()) + /// # } #[inline] pub fn remove(&self, key: &K) -> Result<(), c_long> { remove(self.def.get(), key) diff --git a/bpf/aya-bpf/src/maps/mod.rs b/bpf/aya-bpf/src/maps/mod.rs index 8fa375dd..558fb6fa 100644 --- a/bpf/aya-bpf/src/maps/mod.rs +++ b/bpf/aya-bpf/src/maps/mod.rs @@ -1,3 +1,32 @@ +//! Data structures used to setup and share data with eBPF programs. +//! +//! The eBPF platform provides data structures - maps in eBPF speak - that are +//! used to store data and share them with user-space. +//! +//! # Examples +//! +//! XDP program using a [`HashMap`](HashMap) to block traffic from +//! IP addresses defined in it: +//! +//! ```no_run +//! # use core::ffi::c_long; +//! use aya_bpf::{bindings::xdp_action, macros::map, maps::HashMap}; +//! # use aya_bpf::programs::XdpContext; +//! +//! /// A map which stores IP addresses to block. +//! #[map] +//! static BLOCKLIST: HashMap = HashMap::with_max_entries(1024, 0); +//! +//! # fn parse_src_addr(ctx: &XdpContext) -> u32 { 0 } +//! # fn try_test(ctx: &XdpContext) -> Result<(), c_long> { +//! let src_addr = parse_src_addr(ctx); +//! if BLOCKLIST.get(&src_addr).is_some() { +//! return Ok(xdp_action::XDP_DROP); +//! } +//! Ok(xdp_action::XDP_PASS) +//! # } +//! ``` + #[repr(u32)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(crate) enum PinningType {