use core::ffi::c_void; use std::ffi::CStr; use std::ffi::CString; use std::ffi::OsStr; use std::ffi::OsString; use std::fmt::Debug; use std::fs::remove_file; use std::io; use std::marker::PhantomData; use std::mem; use std::mem::transmute; use std::ops::Deref; use std::os::unix::ffi::OsStrExt; use std::os::unix::io::AsFd; use std::os::unix::io::AsRawFd; use std::os::unix::io::BorrowedFd; use std::os::unix::io::FromRawFd; use std::os::unix::io::OwnedFd; use std::os::unix::io::RawFd; use std::path::Path; use std::ptr; use std::ptr::NonNull; use std::slice; use std::slice::from_raw_parts; use bitflags::bitflags; use libbpf_sys::bpf_map_info; use libbpf_sys::bpf_obj_get_info_by_fd; use crate::util; use crate::util::parse_ret_i32; use crate::util::validate_bpf_ret; use crate::AsRawLibbpf; use crate::Error; use crate::ErrorExt as _; use crate::Link; use crate::Mut; use crate::Result; /// An immutable parsed but not yet loaded BPF map. pub type OpenMap<'obj> = OpenMapImpl<'obj>; /// A mutable parsed but not yet loaded BPF map. pub type OpenMapMut<'obj> = OpenMapImpl<'obj, Mut>; /// Represents a parsed but not yet loaded BPF map. /// /// This object exposes operations that need to happen before the map is created. /// /// Some methods require working with raw bytes. You may find libraries such as /// [`plain`](https://crates.io/crates/plain) helpful. #[derive(Debug)] #[repr(transparent)] pub struct OpenMapImpl<'obj, T = ()> { ptr: NonNull, _phantom: PhantomData<&'obj T>, } // TODO: Document members. #[allow(missing_docs)] impl<'obj> OpenMap<'obj> { /// Create a new [`OpenMap`] from a ptr to a `libbpf_sys::bpf_map`. pub fn new(object: &'obj libbpf_sys::bpf_map) -> Self { // SAFETY: We inferred the address from a reference, which is always // valid. Self { ptr: unsafe { NonNull::new_unchecked(object as *const _ as *mut _) }, _phantom: PhantomData, } } /// Retrieve the [`OpenMap`]'s name. pub fn name(&self) -> &OsStr { // SAFETY: We ensured `ptr` is valid during construction. let name_ptr = unsafe { libbpf_sys::bpf_map__name(self.ptr.as_ptr()) }; // SAFETY: `bpf_map__name` can return NULL but only if it's passed // NULL. We know `ptr` is not NULL. let name_c_str = unsafe { CStr::from_ptr(name_ptr) }; OsStr::from_bytes(name_c_str.to_bytes()) } /// Retrieve type of the map. pub fn map_type(&self) -> MapType { let ty = unsafe { libbpf_sys::bpf_map__type(self.ptr.as_ptr()) }; MapType::from(ty) } fn initial_value_raw(&self) -> (*mut u8, usize) { let mut size = 0u64; let ptr = unsafe { libbpf_sys::bpf_map__initial_value(self.ptr.as_ptr(), &mut size as *mut _ as _) }; (ptr.cast(), size as _) } /// Retrieve the initial value of the map. pub fn initial_value(&self) -> Option<&[u8]> { let (ptr, size) = self.initial_value_raw(); if ptr.is_null() { None } else { let data = unsafe { slice::from_raw_parts(ptr.cast::(), size) }; Some(data) } } } impl<'obj> OpenMapMut<'obj> { /// Create a new [`OpenMapMut`] from a ptr to a `libbpf_sys::bpf_map`. pub fn new_mut(object: &'obj mut libbpf_sys::bpf_map) -> Self { Self { ptr: unsafe { NonNull::new_unchecked(object as *mut _) }, _phantom: PhantomData, } } /// Retrieve the initial value of the map. pub fn initial_value_mut(&mut self) -> Option<&mut [u8]> { let (ptr, size) = self.initial_value_raw(); if ptr.is_null() { None } else { let data = unsafe { slice::from_raw_parts_mut(ptr.cast::(), size) }; Some(data) } } pub fn set_map_ifindex(&mut self, idx: u32) { unsafe { libbpf_sys::bpf_map__set_ifindex(self.ptr.as_ptr(), idx) }; } pub fn set_initial_value(&mut self, data: &[u8]) -> Result<()> { let ret = unsafe { libbpf_sys::bpf_map__set_initial_value( self.ptr.as_ptr(), data.as_ptr() as *const c_void, data.len() as libbpf_sys::size_t, ) }; util::parse_ret(ret) } pub fn set_type(&mut self, ty: MapType) -> Result<()> { let ret = unsafe { libbpf_sys::bpf_map__set_type(self.ptr.as_ptr(), ty as u32) }; util::parse_ret(ret) } pub fn set_key_size(&mut self, size: u32) -> Result<()> { let ret = unsafe { libbpf_sys::bpf_map__set_key_size(self.ptr.as_ptr(), size) }; util::parse_ret(ret) } pub fn set_value_size(&mut self, size: u32) -> Result<()> { let ret = unsafe { libbpf_sys::bpf_map__set_value_size(self.ptr.as_ptr(), size) }; util::parse_ret(ret) } pub fn set_max_entries(&mut self, count: u32) -> Result<()> { let ret = unsafe { libbpf_sys::bpf_map__set_max_entries(self.ptr.as_ptr(), count) }; util::parse_ret(ret) } pub fn set_map_flags(&mut self, flags: u32) -> Result<()> { let ret = unsafe { libbpf_sys::bpf_map__set_map_flags(self.ptr.as_ptr(), flags) }; util::parse_ret(ret) } pub fn set_numa_node(&mut self, numa_node: u32) -> Result<()> { let ret = unsafe { libbpf_sys::bpf_map__set_numa_node(self.ptr.as_ptr(), numa_node) }; util::parse_ret(ret) } pub fn set_inner_map_fd(&mut self, inner_map_fd: BorrowedFd<'_>) -> Result<()> { let ret = unsafe { libbpf_sys::bpf_map__set_inner_map_fd(self.ptr.as_ptr(), inner_map_fd.as_raw_fd()) }; util::parse_ret(ret) } pub fn set_map_extra(&mut self, map_extra: u64) -> Result<()> { let ret = unsafe { libbpf_sys::bpf_map__set_map_extra(self.ptr.as_ptr(), map_extra) }; util::parse_ret(ret) } pub fn set_autocreate(&mut self, autocreate: bool) -> Result<()> { let ret = unsafe { libbpf_sys::bpf_map__set_autocreate(self.ptr.as_ptr(), autocreate) }; util::parse_ret(ret) } pub fn set_pin_path>(&mut self, path: P) -> Result<()> { let path_c = util::path_to_cstring(path)?; let path_ptr = path_c.as_ptr(); let ret = unsafe { libbpf_sys::bpf_map__set_pin_path(self.ptr.as_ptr(), path_ptr) }; util::parse_ret(ret) } /// Reuse an fd for a BPF map pub fn reuse_fd(&mut self, fd: BorrowedFd<'_>) -> Result<()> { let ret = unsafe { libbpf_sys::bpf_map__reuse_fd(self.ptr.as_ptr(), fd.as_raw_fd()) }; util::parse_ret(ret) } /// Reuse an already-pinned map for `self`. pub fn reuse_pinned_map>(&mut self, path: P) -> Result<()> { let cstring = util::path_to_cstring(path)?; let fd = unsafe { libbpf_sys::bpf_obj_get(cstring.as_ptr()) }; if fd < 0 { return Err(Error::from(io::Error::last_os_error())); } let fd = unsafe { OwnedFd::from_raw_fd(fd) }; let reuse_result = self.reuse_fd(fd.as_fd()); reuse_result } } impl<'obj> Deref for OpenMapMut<'obj> { type Target = OpenMap<'obj>; fn deref(&self) -> &Self::Target { // SAFETY: `OpenMapImpl` is `repr(transparent)` and so in-memory // representation of both types is the same. unsafe { transmute::<&OpenMapMut<'obj>, &OpenMap<'obj>>(self) } } } impl AsRawLibbpf for OpenMapImpl<'_, T> { type LibbpfType = libbpf_sys::bpf_map; /// Retrieve the underlying [`libbpf_sys::bpf_map`]. fn as_libbpf_object(&self) -> NonNull { self.ptr } } pub(crate) fn map_fd(map: NonNull) -> Option { let fd = unsafe { libbpf_sys::bpf_map__fd(map.as_ptr()) }; let fd = util::parse_ret_i32(fd).ok().map(|fd| fd as RawFd); fd } /// Return the size of one value including padding for interacting with per-cpu /// maps. The values are aligned to 8 bytes. fn percpu_aligned_value_size(map: &M) -> usize where M: MapCore + ?Sized, { let val_size = map.value_size() as usize; util::roundup(val_size, 8) } /// Returns the size of the buffer needed for a lookup/update of a per-cpu map. fn percpu_buffer_size(map: &M) -> Result where M: MapCore + ?Sized, { let aligned_val_size = percpu_aligned_value_size(map); let ncpu = crate::num_possible_cpus()?; Ok(ncpu * aligned_val_size) } /// Apply a key check and return a null pointer in case of dealing with queue/stack/bloom-filter /// map, before passing the key to the bpf functions that support the map of type /// queue/stack/bloom-filter. fn map_key(map: &M, key: &[u8]) -> *const c_void where M: MapCore + ?Sized, { // For all they keyless maps we null out the key per documentation of libbpf if map.key_size() == 0 && map.map_type().is_keyless() { return ptr::null(); } key.as_ptr() as *const c_void } /// Internal function to return a value from a map into a buffer of the given size. fn lookup_raw(map: &M, key: &[u8], flags: MapFlags, out_size: usize) -> Result>> where M: MapCore + ?Sized, { if key.len() != map.key_size() as usize { return Err(Error::with_invalid_data(format!( "key_size {} != {}", key.len(), map.key_size() ))); }; let mut out: Vec = Vec::with_capacity(out_size); let ret = unsafe { libbpf_sys::bpf_map_lookup_elem_flags( map.as_fd().as_raw_fd(), map_key(map, key), out.as_mut_ptr() as *mut c_void, flags.bits(), ) }; if ret == 0 { unsafe { out.set_len(out_size); } Ok(Some(out)) } else { let err = io::Error::last_os_error(); if err.kind() == io::ErrorKind::NotFound { Ok(None) } else { Err(Error::from(err)) } } } /// Internal function to update a map. This does not check the length of the /// supplied value. fn update_raw(map: &M, key: &[u8], value: &[u8], flags: MapFlags) -> Result<()> where M: MapCore + ?Sized, { if key.len() != map.key_size() as usize { return Err(Error::with_invalid_data(format!( "key_size {} != {}", key.len(), map.key_size() ))); }; let ret = unsafe { libbpf_sys::bpf_map_update_elem( map.as_fd().as_raw_fd(), map_key(map, key), value.as_ptr() as *const c_void, flags.bits(), ) }; util::parse_ret(ret) } #[allow(clippy::wildcard_imports)] mod private { use super::*; pub trait Sealed {} impl Sealed for MapImpl<'_, T> {} impl Sealed for MapHandle {} } /// A trait representing core functionality common to fully initialized maps. pub trait MapCore: Debug + AsFd + private::Sealed { /// Retrieve the map's name. fn name(&self) -> &OsStr; /// Retrieve type of the map. fn map_type(&self) -> MapType; /// Retrieve the size of the map's keys. fn key_size(&self) -> u32; /// Retrieve the size of the map's values. fn value_size(&self) -> u32; /// Fetch extra map information #[inline] fn info(&self) -> Result { MapInfo::new(self.as_fd()) } /// Returns an iterator over keys in this map /// /// Note that if the map is not stable (stable meaning no updates or deletes) during iteration, /// iteration can skip keys, restart from the beginning, or duplicate keys. In other words, /// iteration becomes unpredictable. fn keys(&self) -> MapKeyIter<'_> { MapKeyIter::new(self.as_fd(), self.key_size()) } /// Returns map value as `Vec` of `u8`. /// /// `key` must have exactly [`Self::key_size()`] elements. /// /// If the map is one of the per-cpu data structures, the function [`Self::lookup_percpu()`] /// must be used. /// If the map is of type bloom_filter the function [`Self::lookup_bloom_filter()`] must be used fn lookup(&self, key: &[u8], flags: MapFlags) -> Result>> { if self.map_type().is_bloom_filter() { return Err(Error::with_invalid_data( "lookup_bloom_filter() must be used for bloom filter maps", )); } if self.map_type().is_percpu() { return Err(Error::with_invalid_data(format!( "lookup_percpu() must be used for per-cpu maps (type of the map is {:?})", self.map_type(), ))); } let out_size = self.value_size() as usize; lookup_raw(self, key, flags, out_size) } /// Returns if the given value is likely present in bloom_filter as `bool`. /// /// `value` must have exactly [`Self::value_size()`] elements. fn lookup_bloom_filter(&self, value: &[u8]) -> Result { let ret = unsafe { libbpf_sys::bpf_map_lookup_elem( self.as_fd().as_raw_fd(), ptr::null(), value.to_vec().as_mut_ptr() as *mut c_void, ) }; if ret == 0 { Ok(true) } else { let err = io::Error::last_os_error(); if err.kind() == io::ErrorKind::NotFound { Ok(false) } else { Err(Error::from(err)) } } } /// Returns one value per cpu as `Vec` of `Vec` of `u8` for per per-cpu maps. /// /// For normal maps, [`Self::lookup()`] must be used. fn lookup_percpu(&self, key: &[u8], flags: MapFlags) -> Result>>> { if !self.map_type().is_percpu() && self.map_type() != MapType::Unknown { return Err(Error::with_invalid_data(format!( "lookup() must be used for maps that are not per-cpu (type of the map is {:?})", self.map_type(), ))); } let val_size = self.value_size() as usize; let aligned_val_size = percpu_aligned_value_size(self); let out_size = percpu_buffer_size(self)?; let raw_res = lookup_raw(self, key, flags, out_size)?; if let Some(raw_vals) = raw_res { let mut out = Vec::new(); for chunk in raw_vals.chunks_exact(aligned_val_size) { out.push(chunk[..val_size].to_vec()); } Ok(Some(out)) } else { Ok(None) } } /// Deletes an element from the map. /// /// `key` must have exactly [`Self::key_size()`] elements. fn delete(&self, key: &[u8]) -> Result<()> { if key.len() != self.key_size() as usize { return Err(Error::with_invalid_data(format!( "key_size {} != {}", key.len(), self.key_size() ))); }; let ret = unsafe { libbpf_sys::bpf_map_delete_elem(self.as_fd().as_raw_fd(), key.as_ptr() as *const c_void) }; util::parse_ret(ret) } /// Deletes many elements in batch mode from the map. /// /// `keys` must have exactly [`Self::key_size()` * count] elements. fn delete_batch( &self, keys: &[u8], count: u32, elem_flags: MapFlags, flags: MapFlags, ) -> Result<()> { if keys.len() as u32 / count != self.key_size() || (keys.len() as u32) % count != 0 { return Err(Error::with_invalid_data(format!( "batch key_size {} != {} * {}", keys.len(), self.key_size(), count ))); }; #[allow(clippy::needless_update)] let opts = libbpf_sys::bpf_map_batch_opts { sz: mem::size_of::() as _, elem_flags: elem_flags.bits(), flags: flags.bits(), // bpf_map_batch_opts might have padding fields on some platform ..Default::default() }; let mut count = count; let ret = unsafe { libbpf_sys::bpf_map_delete_batch( self.as_fd().as_raw_fd(), keys.as_ptr() as *const c_void, (&mut count) as *mut u32, &opts as *const libbpf_sys::bpf_map_batch_opts, ) }; util::parse_ret(ret) } /// Same as [`Self::lookup()`] except this also deletes the key from the map. /// /// Note that this operation is currently only implemented in the kernel for [`MapType::Queue`] /// and [`MapType::Stack`]. /// /// `key` must have exactly [`Self::key_size()`] elements. fn lookup_and_delete(&self, key: &[u8]) -> Result>> { if key.len() != self.key_size() as usize { return Err(Error::with_invalid_data(format!( "key_size {} != {}", key.len(), self.key_size() ))); }; let mut out: Vec = Vec::with_capacity(self.value_size() as usize); let ret = unsafe { libbpf_sys::bpf_map_lookup_and_delete_elem( self.as_fd().as_raw_fd(), map_key(self, key), out.as_mut_ptr() as *mut c_void, ) }; if ret == 0 { unsafe { out.set_len(self.value_size() as usize); } Ok(Some(out)) } else { let err = io::Error::last_os_error(); if err.kind() == io::ErrorKind::NotFound { Ok(None) } else { Err(Error::from(err)) } } } /// Update an element. /// /// `key` must have exactly [`Self::key_size()`] elements. `value` must have exactly /// [`Self::value_size()`] elements. /// /// For per-cpu maps, [`Self::update_percpu()`] must be used. fn update(&self, key: &[u8], value: &[u8], flags: MapFlags) -> Result<()> { if self.map_type().is_percpu() { return Err(Error::with_invalid_data(format!( "update_percpu() must be used for per-cpu maps (type of the map is {:?})", self.map_type(), ))); } if value.len() != self.value_size() as usize { return Err(Error::with_invalid_data(format!( "value_size {} != {}", value.len(), self.value_size() ))); }; update_raw(self, key, value, flags) } /// Updates many elements in batch mode in the map /// /// `keys` must have exactly [`Self::key_size()` * count] elements. `value` must have exactly /// [`Self::key_size()` * count] elements fn update_batch( &self, keys: &[u8], values: &[u8], count: u32, elem_flags: MapFlags, flags: MapFlags, ) -> Result<()> { if keys.len() as u32 / count != self.key_size() || (keys.len() as u32) % count != 0 { return Err(Error::with_invalid_data(format!( "batch key_size {} != {} * {}", keys.len(), self.key_size(), count ))); }; if values.len() as u32 / count != self.value_size() || (values.len() as u32) % count != 0 { return Err(Error::with_invalid_data(format!( "batch value_size {} != {} * {}", values.len(), self.value_size(), count ))); } #[allow(clippy::needless_update)] let opts = libbpf_sys::bpf_map_batch_opts { sz: mem::size_of::() as _, elem_flags: elem_flags.bits(), flags: flags.bits(), // bpf_map_batch_opts might have padding fields on some platform ..Default::default() }; let mut count = count; let ret = unsafe { libbpf_sys::bpf_map_update_batch( self.as_fd().as_raw_fd(), keys.as_ptr() as *const c_void, values.as_ptr() as *const c_void, (&mut count) as *mut u32, &opts as *const libbpf_sys::bpf_map_batch_opts, ) }; util::parse_ret(ret) } /// Update an element in an per-cpu map with one value per cpu. /// /// `key` must have exactly [`Self::key_size()`] elements. `value` must have one /// element per cpu (see [`num_possible_cpus`][crate::num_possible_cpus]) /// with exactly [`Self::value_size()`] elements each. /// /// For per-cpu maps, [`Self::update_percpu()`] must be used. fn update_percpu(&self, key: &[u8], values: &[Vec], flags: MapFlags) -> Result<()> { if !self.map_type().is_percpu() && self.map_type() != MapType::Unknown { return Err(Error::with_invalid_data(format!( "update() must be used for maps that are not per-cpu (type of the map is {:?})", self.map_type(), ))); } if values.len() != crate::num_possible_cpus()? { return Err(Error::with_invalid_data(format!( "number of values {} != number of cpus {}", values.len(), crate::num_possible_cpus()? ))); }; let val_size = self.value_size() as usize; let aligned_val_size = percpu_aligned_value_size(self); let buf_size = percpu_buffer_size(self)?; let mut value_buf = vec![0; buf_size]; for (i, val) in values.iter().enumerate() { if val.len() != val_size { return Err(Error::with_invalid_data(format!( "value size for cpu {} is {} != {}", i, val.len(), val_size ))); } value_buf[(i * aligned_val_size)..(i * aligned_val_size + val_size)] .copy_from_slice(val); } update_raw(self, key, &value_buf, flags) } } /// An immutable loaded BPF map. pub type Map<'obj> = MapImpl<'obj>; /// A mutable loaded BPF map. pub type MapMut<'obj> = MapImpl<'obj, Mut>; /// Represents a libbpf-created map. /// /// Some methods require working with raw bytes. You may find libraries such as /// [`plain`](https://crates.io/crates/plain) helpful. #[derive(Debug)] pub struct MapImpl<'obj, T = ()> { ptr: NonNull, _phantom: PhantomData<&'obj T>, } impl<'obj> Map<'obj> { /// Create a [`Map`] from a [`libbpf_sys::bpf_map`]. pub fn new(map: &'obj libbpf_sys::bpf_map) -> Self { // SAFETY: We inferred the address from a reference, which is always // valid. let ptr = unsafe { NonNull::new_unchecked(map as *const _ as *mut _) }; assert!( map_fd(ptr).is_some(), "provided BPF map does not have file descriptor" ); Self { ptr, _phantom: PhantomData, } } /// Create a [`Map`] from a [`libbpf_sys::bpf_map`] that does not contain a /// file descriptor. /// /// The caller has to ensure that the [`AsFd`] impl is not used, or a panic /// will be the result. /// /// # Safety /// /// The pointer must point to a loaded map. #[doc(hidden)] pub unsafe fn from_map_without_fd(ptr: NonNull) -> Self { Self { ptr, _phantom: PhantomData, } } /// Returns whether map is pinned or not flag pub fn is_pinned(&self) -> bool { unsafe { libbpf_sys::bpf_map__is_pinned(self.ptr.as_ptr()) } } /// Returns the pin_path if the map is pinned, otherwise, None is returned pub fn get_pin_path(&self) -> Option<&OsStr> { let path_ptr = unsafe { libbpf_sys::bpf_map__pin_path(self.ptr.as_ptr()) }; if path_ptr.is_null() { // means map is not pinned return None; } let path_c_str = unsafe { CStr::from_ptr(path_ptr) }; Some(OsStr::from_bytes(path_c_str.to_bytes())) } } impl<'obj> MapMut<'obj> { /// Create a [`MapMut`] from a [`libbpf_sys::bpf_map`]. pub fn new_mut(map: &'obj mut libbpf_sys::bpf_map) -> Self { // SAFETY: We inferred the address from a reference, which is always // valid. let ptr = unsafe { NonNull::new_unchecked(map as *mut _) }; assert!( map_fd(ptr).is_some(), "provided BPF map does not have file descriptor" ); Self { ptr, _phantom: PhantomData, } } /// [Pin](https://facebookmicrosites.github.io/bpf/blog/2018/08/31/object-lifetime.html#bpffs) /// this map to bpffs. pub fn pin>(&mut self, path: P) -> Result<()> { let path_c = util::path_to_cstring(path)?; let path_ptr = path_c.as_ptr(); let ret = unsafe { libbpf_sys::bpf_map__pin(self.ptr.as_ptr(), path_ptr) }; util::parse_ret(ret) } /// [Unpin](https://facebookmicrosites.github.io/bpf/blog/2018/08/31/object-lifetime.html#bpffs) /// this map from bpffs. pub fn unpin>(&mut self, path: P) -> Result<()> { let path_c = util::path_to_cstring(path)?; let path_ptr = path_c.as_ptr(); let ret = unsafe { libbpf_sys::bpf_map__unpin(self.ptr.as_ptr(), path_ptr) }; util::parse_ret(ret) } /// Attach a struct ops map pub fn attach_struct_ops(&mut self) -> Result { if self.map_type() != MapType::StructOps { return Err(Error::with_invalid_data(format!( "Invalid map type ({:?}) for attach_struct_ops()", self.map_type(), ))); } let ptr = unsafe { libbpf_sys::bpf_map__attach_struct_ops(self.ptr.as_ptr()) }; let ptr = validate_bpf_ret(ptr).context("failed to attach struct_ops")?; // SAFETY: the pointer came from libbpf and has been checked for errors. let link = unsafe { Link::new(ptr) }; Ok(link) } } impl<'obj> Deref for MapMut<'obj> { type Target = Map<'obj>; fn deref(&self) -> &Self::Target { unsafe { transmute::<&MapMut<'obj>, &Map<'obj>>(self) } } } impl AsFd for MapImpl<'_, T> { #[inline] fn as_fd(&self) -> BorrowedFd<'_> { // SANITY: Our map must always have a file descriptor associated with // it. let fd = map_fd(self.ptr).unwrap(); // SAFETY: `fd` is guaranteed to be valid for the lifetime of // the created object. let fd = unsafe { BorrowedFd::borrow_raw(fd as _) }; fd } } impl MapCore for MapImpl<'_, T> where T: Debug, { fn name(&self) -> &OsStr { // SAFETY: We ensured `ptr` is valid during construction. let name_ptr = unsafe { libbpf_sys::bpf_map__name(self.ptr.as_ptr()) }; // SAFETY: `bpf_map__name` can return NULL but only if it's passed // NULL. We know `ptr` is not NULL. let name_c_str = unsafe { CStr::from_ptr(name_ptr) }; OsStr::from_bytes(name_c_str.to_bytes()) } #[inline] fn map_type(&self) -> MapType { let ty = unsafe { libbpf_sys::bpf_map__type(self.ptr.as_ptr()) }; MapType::from(ty) } #[inline] fn key_size(&self) -> u32 { unsafe { libbpf_sys::bpf_map__key_size(self.ptr.as_ptr()) } } #[inline] fn value_size(&self) -> u32 { unsafe { libbpf_sys::bpf_map__value_size(self.ptr.as_ptr()) } } } impl AsRawLibbpf for Map<'_> { type LibbpfType = libbpf_sys::bpf_map; /// Retrieve the underlying [`libbpf_sys::bpf_map`]. #[inline] fn as_libbpf_object(&self) -> NonNull { self.ptr } } /// A handle to a map. Handles can be duplicated and dropped. /// /// While possible to [created directly][MapHandle::create], in many cases it is /// useful to create such a handle from an existing [`Map`]: /// ```no_run /// # use libbpf_rs::Map; /// # use libbpf_rs::MapHandle; /// # let get_map = || -> &Map { todo!() }; /// let map: &Map = get_map(); /// let map_handle = MapHandle::try_from(map).unwrap(); /// ``` /// /// Some methods require working with raw bytes. You may find libraries such as /// [`plain`](https://crates.io/crates/plain) helpful. #[derive(Debug)] pub struct MapHandle { fd: OwnedFd, name: OsString, ty: MapType, key_size: u32, value_size: u32, } impl MapHandle { /// Create a bpf map whose data is not managed by libbpf. pub fn create>( map_type: MapType, name: Option, key_size: u32, value_size: u32, max_entries: u32, opts: &libbpf_sys::bpf_map_create_opts, ) -> Result { let name = match name { Some(name) => name.as_ref().to_os_string(), // The old version kernel don't support specifying map name. None => OsString::new(), }; let name_c_str = CString::new(name.as_bytes()).map_err(|_| { Error::with_invalid_data(format!("invalid name `{name:?}`: has NUL bytes")) })?; let name_c_ptr = if name.is_empty() { ptr::null() } else { name_c_str.as_bytes_with_nul().as_ptr() }; let fd = unsafe { libbpf_sys::bpf_map_create( map_type.into(), name_c_ptr.cast(), key_size, value_size, max_entries, opts, ) }; let () = util::parse_ret(fd)?; Ok(Self { // SAFETY: A file descriptor coming from the `bpf_map_create` // function is always suitable for ownership and can be // cleaned up with close. fd: unsafe { OwnedFd::from_raw_fd(fd) }, name, ty: map_type, key_size, value_size, }) } /// Open a previously pinned map from its path. /// /// # Panics /// If the path contains null bytes. pub fn from_pinned_path>(path: P) -> Result { fn inner(path: &Path) -> Result { let p = CString::new(path.as_os_str().as_bytes()).expect("path contained null bytes"); let fd = parse_ret_i32(unsafe { // SAFETY // p is never null since we allocated ourselves. libbpf_sys::bpf_obj_get(p.as_ptr()) })?; MapHandle::from_fd(unsafe { // SAFETY // A file descriptor coming from the bpf_obj_get function is always suitable for // ownership and can be cleaned up with close. OwnedFd::from_raw_fd(fd) }) } inner(path.as_ref()) } /// Open a loaded map from its map id. pub fn from_map_id(id: u32) -> Result { parse_ret_i32(unsafe { // SAFETY // This function is always safe to call. libbpf_sys::bpf_map_get_fd_by_id(id) }) .map(|fd| unsafe { // SAFETY // A file descriptor coming from the bpf_map_get_fd_by_id function is always suitable // for ownership and can be cleaned up with close. OwnedFd::from_raw_fd(fd) }) .and_then(Self::from_fd) } fn from_fd(fd: OwnedFd) -> Result { let info = MapInfo::new(fd.as_fd())?; Ok(Self { fd, name: info.name()?.into(), ty: info.map_type(), key_size: info.info.key_size, value_size: info.info.value_size, }) } /// Freeze the map as read-only from user space. /// /// Entries from a frozen map can no longer be updated or deleted with the /// bpf() system call. This operation is not reversible, and the map remains /// immutable from user space until its destruction. However, read and write /// permissions for BPF programs to the map remain unchanged. pub fn freeze(&self) -> Result<()> { let ret = unsafe { libbpf_sys::bpf_map_freeze(self.fd.as_raw_fd()) }; util::parse_ret(ret) } /// [Pin](https://facebookmicrosites.github.io/bpf/blog/2018/08/31/object-lifetime.html#bpffs) /// this map to bpffs. pub fn pin>(&mut self, path: P) -> Result<()> { let path_c = util::path_to_cstring(path)?; let path_ptr = path_c.as_ptr(); let ret = unsafe { libbpf_sys::bpf_obj_pin(self.fd.as_raw_fd(), path_ptr) }; util::parse_ret(ret) } /// [Unpin](https://facebookmicrosites.github.io/bpf/blog/2018/08/31/object-lifetime.html#bpffs) /// this map from bpffs. pub fn unpin>(&mut self, path: P) -> Result<()> { remove_file(path).context("failed to remove pin map") } } impl MapCore for MapHandle { #[inline] fn name(&self) -> &OsStr { &self.name } #[inline] fn map_type(&self) -> MapType { self.ty } #[inline] fn key_size(&self) -> u32 { self.key_size } #[inline] fn value_size(&self) -> u32 { self.value_size } } impl AsFd for MapHandle { #[inline] fn as_fd(&self) -> BorrowedFd<'_> { self.fd.as_fd() } } impl TryFrom<&MapImpl<'_, T>> for MapHandle where T: Debug, { type Error = Error; fn try_from(other: &MapImpl<'_, T>) -> Result { Ok(Self { fd: other .as_fd() .try_clone_to_owned() .context("failed to duplicate map file descriptor")?, name: other.name().to_os_string(), ty: other.map_type(), key_size: other.key_size(), value_size: other.value_size(), }) } } impl TryFrom<&MapHandle> for MapHandle { type Error = Error; fn try_from(other: &MapHandle) -> Result { Ok(Self { fd: other .as_fd() .try_clone_to_owned() .context("failed to duplicate map file descriptor")?, name: other.name().to_os_string(), ty: other.map_type(), key_size: other.key_size(), value_size: other.value_size(), }) } } bitflags! { /// Flags to configure [`Map`] operations. #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] pub struct MapFlags: u64 { /// See [`libbpf_sys::BPF_ANY`]. const ANY = libbpf_sys::BPF_ANY as _; /// See [`libbpf_sys::BPF_NOEXIST`]. const NO_EXIST = libbpf_sys::BPF_NOEXIST as _; /// See [`libbpf_sys::BPF_EXIST`]. const EXIST = libbpf_sys::BPF_EXIST as _; /// See [`libbpf_sys::BPF_F_LOCK`]. const LOCK = libbpf_sys::BPF_F_LOCK as _; } } /// Type of a [`Map`]. Maps to `enum bpf_map_type` in kernel uapi. // If you add a new per-cpu map, also update `is_percpu`. #[non_exhaustive] #[repr(u32)] #[derive(Copy, Clone, PartialEq, Eq, Debug)] // TODO: Document members. #[allow(missing_docs)] pub enum MapType { Unspec = 0, Hash, Array, ProgArray, PerfEventArray, PercpuHash, PercpuArray, StackTrace, CgroupArray, LruHash, LruPercpuHash, LpmTrie, ArrayOfMaps, HashOfMaps, Devmap, Sockmap, Cpumap, Xskmap, Sockhash, CgroupStorage, ReuseportSockarray, PercpuCgroupStorage, Queue, Stack, SkStorage, DevmapHash, StructOps, RingBuf, InodeStorage, TaskStorage, BloomFilter, UserRingBuf, /// We choose to specify our own "unknown" type here b/c it's really up to the kernel /// to decide if it wants to reject the map. If it accepts it, it just means whoever /// using this library is a bit out of date. Unknown = u32::MAX, } impl MapType { /// Returns if the map is of one of the per-cpu types. pub fn is_percpu(&self) -> bool { matches!( self, MapType::PercpuArray | MapType::PercpuHash | MapType::LruPercpuHash | MapType::PercpuCgroupStorage ) } /// Returns if the map is keyless map type as per documentation of libbpf /// Keyless map types are: Queues, Stacks and Bloom Filters fn is_keyless(&self) -> bool { matches!(self, MapType::Queue | MapType::Stack | MapType::BloomFilter) } /// Returns if the map is of bloom filter type pub fn is_bloom_filter(&self) -> bool { MapType::BloomFilter.eq(self) } /// Detects if host kernel supports this BPF map type. /// /// Make sure the process has required set of CAP_* permissions (or runs as /// root) when performing feature checking. pub fn is_supported(&self) -> Result { let ret = unsafe { libbpf_sys::libbpf_probe_bpf_map_type(*self as u32, ptr::null()) }; match ret { 0 => Ok(false), 1 => Ok(true), _ => Err(Error::from_raw_os_error(-ret)), } } } impl From for MapType { fn from(value: u32) -> Self { use MapType::*; match value { x if x == Unspec as u32 => Unspec, x if x == Hash as u32 => Hash, x if x == Array as u32 => Array, x if x == ProgArray as u32 => ProgArray, x if x == PerfEventArray as u32 => PerfEventArray, x if x == PercpuHash as u32 => PercpuHash, x if x == PercpuArray as u32 => PercpuArray, x if x == StackTrace as u32 => StackTrace, x if x == CgroupArray as u32 => CgroupArray, x if x == LruHash as u32 => LruHash, x if x == LruPercpuHash as u32 => LruPercpuHash, x if x == LpmTrie as u32 => LpmTrie, x if x == ArrayOfMaps as u32 => ArrayOfMaps, x if x == HashOfMaps as u32 => HashOfMaps, x if x == Devmap as u32 => Devmap, x if x == Sockmap as u32 => Sockmap, x if x == Cpumap as u32 => Cpumap, x if x == Xskmap as u32 => Xskmap, x if x == Sockhash as u32 => Sockhash, x if x == CgroupStorage as u32 => CgroupStorage, x if x == ReuseportSockarray as u32 => ReuseportSockarray, x if x == PercpuCgroupStorage as u32 => PercpuCgroupStorage, x if x == Queue as u32 => Queue, x if x == Stack as u32 => Stack, x if x == SkStorage as u32 => SkStorage, x if x == DevmapHash as u32 => DevmapHash, x if x == StructOps as u32 => StructOps, x if x == RingBuf as u32 => RingBuf, x if x == InodeStorage as u32 => InodeStorage, x if x == TaskStorage as u32 => TaskStorage, x if x == BloomFilter as u32 => BloomFilter, x if x == UserRingBuf as u32 => UserRingBuf, _ => Unknown, } } } impl From for u32 { fn from(value: MapType) -> Self { value as u32 } } /// An iterator over the keys of a BPF map. #[derive(Debug)] pub struct MapKeyIter<'map> { map_fd: BorrowedFd<'map>, prev: Option>, next: Vec, } impl<'map> MapKeyIter<'map> { fn new(map_fd: BorrowedFd<'map>, key_size: u32) -> Self { Self { map_fd, prev: None, next: vec![0; key_size as usize], } } } impl Iterator for MapKeyIter<'_> { type Item = Vec; fn next(&mut self) -> Option { let prev = self.prev.as_ref().map_or(ptr::null(), |p| p.as_ptr()); let ret = unsafe { libbpf_sys::bpf_map_get_next_key( self.map_fd.as_raw_fd(), prev as _, self.next.as_mut_ptr() as _, ) }; if ret != 0 { None } else { self.prev = Some(self.next.clone()); Some(self.next.clone()) } } } /// A convenience wrapper for [`bpf_map_info`][libbpf_sys::bpf_map_info]. It /// provides the ability to retrieve the details of a certain map. #[derive(Debug)] pub struct MapInfo { /// The inner [`bpf_map_info`][libbpf_sys::bpf_map_info] object. pub info: bpf_map_info, } impl MapInfo { /// Create a `MapInfo` object from a fd. pub fn new(fd: BorrowedFd<'_>) -> Result { let mut map_info = bpf_map_info::default(); let mut size = mem::size_of_val(&map_info) as u32; // SAFETY: All pointers are derived from references and hence valid. let () = util::parse_ret(unsafe { bpf_obj_get_info_by_fd( fd.as_raw_fd(), &mut map_info as *mut bpf_map_info as *mut c_void, &mut size as *mut u32, ) })?; Ok(Self { info: map_info }) } /// Get the map type #[inline] pub fn map_type(&self) -> MapType { MapType::from(self.info.type_) } /// Get the name of this map. /// /// Returns error if the underlying data in the structure is not a valid /// utf-8 string. pub fn name<'a>(&self) -> Result<&'a str> { // SAFETY: convert &[i8] to &[u8], and then cast that to &str. i8 and u8 has the same size. let char_slice = unsafe { from_raw_parts(self.info.name[..].as_ptr().cast(), self.info.name.len()) }; util::c_char_slice_to_cstr(char_slice) .ok_or_else(|| Error::with_invalid_data("no nul byte found"))? .to_str() .map_err(Error::with_invalid_data) } /// Get the map flags. #[inline] pub fn flags(&self) -> MapFlags { MapFlags::from_bits_truncate(self.info.map_flags as u64) } } #[cfg(test)] mod tests { use super::*; use std::mem::discriminant; #[test] fn map_type() { use MapType::*; for t in [ Unspec, Hash, Array, ProgArray, PerfEventArray, PercpuHash, PercpuArray, StackTrace, CgroupArray, LruHash, LruPercpuHash, LpmTrie, ArrayOfMaps, HashOfMaps, Devmap, Sockmap, Cpumap, Xskmap, Sockhash, CgroupStorage, ReuseportSockarray, PercpuCgroupStorage, Queue, Stack, SkStorage, DevmapHash, StructOps, RingBuf, InodeStorage, TaskStorage, BloomFilter, UserRingBuf, Unknown, ] { // check if discriminants match after a roundtrip conversion assert_eq!(discriminant(&t), discriminant(&MapType::from(t as u32))); } } }