diff --git a/aya/src/maps/mod.rs b/aya/src/maps/mod.rs index ceab7307..fa6d73fc 100644 --- a/aya/src/maps/mod.rs +++ b/aya/src/maps/mod.rs @@ -65,12 +65,13 @@ use obj::maps::InvalidMapTypeError; use thiserror::Error; use crate::{ + generated::bpf_map_info, obj::{self, parse_map_info, BpfSectionKind}, pin::PinError, sys::{ bpf_create_map, bpf_get_object, bpf_map_freeze, bpf_map_get_fd_by_id, bpf_map_get_info_by_fd, bpf_map_get_next_key, bpf_map_update_elem_ptr, bpf_pin_object, - SyscallError, + iter_map_ids, SyscallError, }, util::{nr_cpus, KernelVersion}, PinningType, Pod, @@ -639,21 +640,14 @@ impl MapData { call: "BPF_OBJ_GET", io_error, })?; - let fd = MapFd(fd); - - let info = bpf_map_get_info_by_fd(fd.as_fd())?; - Ok(Self { - obj: parse_map_info(info, PinningType::ByName), - fd, - }) + Self::from_fd(fd) } /// Loads a map from a map id. pub fn from_id(id: u32) -> Result { - bpf_map_get_fd_by_id(id) - .map_err(MapError::from) - .and_then(Self::from_fd) + let fd = bpf_map_get_fd_by_id(id)?; + Self::from_fd(fd) } /// Loads a map from a file descriptor. @@ -662,12 +656,10 @@ impl MapData { /// This API is intended for cases where you have received a valid BPF FD from some other means. /// For example, you received an FD over Unix Domain Socket. pub fn from_fd(fd: OwnedFd) -> Result { - let info = bpf_map_get_info_by_fd(fd.as_fd())?; - - let fd = MapFd(fd); + let MapInfo(info) = MapInfo::new_from_fd(fd.as_fd())?; Ok(Self { obj: parse_map_info(info, PinningType::None), - fd, + fd: MapFd(fd), }) } @@ -723,6 +715,11 @@ impl MapData { let Self { obj, fd: _ } = self; obj } + + /// Returns the kernel's information about the loaded map. + pub fn info(&self) -> Result { + MapInfo::new_from_fd(self.fd.as_fd()) + } } /// An iterable map @@ -911,6 +908,129 @@ impl Deref for PerCpuValues { } } +/// Provides information about a loaded map, like name, id and size. +#[derive(Debug)] +pub struct MapInfo(bpf_map_info); + +impl MapInfo { + fn new_from_fd(fd: BorrowedFd<'_>) -> Result { + let info = bpf_map_get_info_by_fd(fd.as_fd())?; + Ok(Self(info)) + } + + /// Loads map info from a map id. + pub fn from_id(id: u32) -> Result { + bpf_map_get_fd_by_id(id) + .map_err(MapError::from) + .and_then(|fd| Self::new_from_fd(fd.as_fd())) + } + + /// The name of the map, limited to 16 bytes. + pub fn name(&self) -> &[u8] { + let length = self + .0 + .name + .iter() + .rposition(|ch| *ch != 0) + .map(|pos| pos + 1) + .unwrap_or(0); + + // The name field is defined as [std::os::raw::c_char; 16]. c_char may be signed or + // unsigned depending on the platform; that's why we're using from_raw_parts here. + unsafe { std::slice::from_raw_parts(self.0.name.as_ptr() as *const _, length) } + } + + /// The name of the map as a &str. If the name is not valid unicode, None is returned. + pub fn name_as_str(&self) -> Option<&str> { + std::str::from_utf8(self.name()).ok() + } + + /// The id for this map. Each map has a unique id. + pub fn id(&self) -> u32 { + self.0.id + } + + /// The map type as defined by the linux kernel enum + /// [`bpf_map_type`](https://elixir.bootlin.com/linux/v6.4.4/source/include/uapi/linux/bpf.h#L905). + pub fn map_type(&self) -> u32 { + self.0.type_ + } + + /// The key size for this map. + pub fn key_size(&self) -> u32 { + self.0.key_size + } + + /// The value size for this map. + pub fn value_size(&self) -> u32 { + self.0.value_size + } + + /// The maximum number of entries in this map. + pub fn max_entries(&self) -> u32 { + self.0.max_entries + } + + /// The flags for this map. + pub fn map_flags(&self) -> u32 { + self.0.map_flags + } + + /// Returns a file descriptor referencing the map. + /// + /// The returned file descriptor can be closed at any time and doing so does + /// not influence the life cycle of the map. + pub fn fd(&self) -> Result { + let Self(info) = self; + let fd = bpf_map_get_fd_by_id(info.id)?; + Ok(MapFd(fd)) + } + + /// Loads a map from a pinned path in bpffs. + pub fn from_pin>(path: P) -> Result { + use std::os::unix::ffi::OsStrExt as _; + + // TODO: avoid this unwrap by adding a new error variant. + let path_string = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap(); + let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError { + call: "BPF_OBJ_GET", + io_error, + })?; + + Self::new_from_fd(fd.as_fd()) + } +} + +/// Returns an iterator over all loaded bpf maps. +/// +/// This differs from [`crate::Bpf::maps`] since it will return all maps +/// listed on the host system and not only maps for a specific [`crate::Bpf`] instance. +/// +/// # Example +/// ``` +/// # use aya::maps::loaded_maps; +/// +/// for m in loaded_maps() { +/// match m { +/// Ok(map) => println!("{:?}", map.name_as_str()), +/// Err(e) => println!("Error iterating maps: {:?}", e), +/// } +/// } +/// ``` +/// +/// # Errors +/// +/// Returns [`MapError::SyscallError`] if any of the syscalls required to either get +/// next map id, get the map fd, or the [`MapInfo`] fail. In cases where +/// iteration can't be performed, for example the caller does not have the necessary privileges, +/// a single item will be yielded containing the error that occurred. +pub fn loaded_maps() -> impl Iterator> { + iter_map_ids().map(|id| { + let id = id?; + MapInfo::from_id(id) + }) +} + #[cfg(test)] mod tests { use std::os::fd::AsRawFd as _; @@ -994,6 +1114,96 @@ mod tests { ); } + #[test] + // Syscall overrides are performing integer-to-pointer conversions, which + // should be done with `ptr::from_exposed_addr` in Rust nightly, but we have + // to support stable as well. + #[cfg_attr(miri, ignore)] + fn test_name() { + use crate::generated::bpf_map_info; + + const TEST_NAME: &str = "foo"; + + override_syscall(|call| match call { + Syscall::Bpf { + cmd: bpf_cmd::BPF_MAP_CREATE, + .. + } => Ok(42), + Syscall::Bpf { + cmd: bpf_cmd::BPF_OBJ_GET_INFO_BY_FD, + attr, + } => { + assert_eq!( + unsafe { attr.info.info_len }, + mem::size_of::() as u32 + ); + let map_info = unsafe { &mut *(attr.info.info as *mut bpf_map_info) }; + map_info.name[..TEST_NAME.len()] + .copy_from_slice(unsafe { std::mem::transmute(TEST_NAME) }); + Ok(0) + } + _ => Err((-1, io::Error::from_raw_os_error(EFAULT))), + }); + + let map_data = MapData::create(new_obj_map(), TEST_NAME, None).unwrap(); + assert_eq!(TEST_NAME, map_data.info().unwrap().name_as_str().unwrap()); + } + + #[test] + // Syscall overrides are performing integer-to-pointer conversions, which + // should be done with `ptr::from_exposed_addr` in Rust nightly, but we have + // to support stable as well. + #[cfg_attr(miri, ignore)] + fn test_loaded_maps() { + use crate::generated::bpf_map_info; + + override_syscall(|call| match call { + Syscall::Bpf { + cmd: bpf_cmd::BPF_MAP_GET_NEXT_ID, + attr, + } => unsafe { + let id = attr.__bindgen_anon_6.__bindgen_anon_1.start_id; + if id < 5 { + attr.__bindgen_anon_6.next_id = id + 1; + Ok(0) + } else { + Err((-1, io::Error::from_raw_os_error(libc::ENOENT))) + } + }, + Syscall::Bpf { + cmd: bpf_cmd::BPF_MAP_GET_FD_BY_ID, + attr, + } => Ok((1000 + unsafe { attr.__bindgen_anon_6.__bindgen_anon_1.map_id }) as c_long), + Syscall::Bpf { + cmd: bpf_cmd::BPF_OBJ_GET_INFO_BY_FD, + attr, + } => { + let map_info = unsafe { &mut *(attr.info.info as *mut bpf_map_info) }; + map_info.id = unsafe { attr.info.bpf_fd } - 1000; + map_info.key_size = 32; + map_info.value_size = 64; + map_info.map_flags = 1234; + map_info.max_entries = 99; + Ok(0) + } + _ => Err((-1, io::Error::from_raw_os_error(EFAULT))), + }); + + let loaded_maps: Vec<_> = loaded_maps().collect(); + assert_eq!(loaded_maps.len(), 5); + + for (i, map_info) in loaded_maps.into_iter().enumerate() { + let i = i + 1; + let map_info = map_info.unwrap(); + assert_eq!(map_info.id(), i as u32); + assert_eq!(map_info.key_size(), 32); + assert_eq!(map_info.value_size(), 64); + assert_eq!(map_info.map_flags(), 1234); + assert_eq!(map_info.max_entries(), 99); + assert_eq!(map_info.fd().unwrap().as_fd().as_raw_fd(), 1000 + i as i32); + } + } + #[test] fn test_create_failed() { override_syscall(|_| Err((-42, io::Error::from_raw_os_error(EFAULT)))); diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs index 511f0163..a971234f 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -1049,6 +1049,10 @@ pub(crate) fn iter_link_ids() -> impl Iterator> iter_obj_ids(bpf_cmd::BPF_LINK_GET_NEXT_ID, "bpf_link_get_next_id") } +pub(crate) fn iter_map_ids() -> impl Iterator> { + iter_obj_ids(bpf_cmd::BPF_MAP_GET_NEXT_ID, "bpf_map_get_next_id") +} + pub(crate) fn retry_with_verifier_logs( max_retries: usize, f: impl Fn(&mut [u8]) -> SysResult, diff --git a/test/integration-test/src/tests/smoke.rs b/test/integration-test/src/tests/smoke.rs index 2b01938d..13204557 100644 --- a/test/integration-test/src/tests/smoke.rs +++ b/test/integration-test/src/tests/smoke.rs @@ -1,4 +1,5 @@ use aya::{ + maps::loaded_maps, programs::{loaded_programs, Extension, TracePoint, Xdp, XdpFlags}, util::KernelVersion, Bpf, BpfLoader, @@ -98,3 +99,28 @@ fn list_loaded_programs() { prog.loaded_at(); prog.fd().unwrap(); } + +#[test] +fn list_loaded_maps() { + // Load a program with maps. + let mut bpf = Bpf::load(crate::MAP_TEST).unwrap(); + let dispatcher: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); + dispatcher.load().unwrap(); + dispatcher.attach("lo", XdpFlags::default()).unwrap(); + + // Ensure the loaded_maps() api doesn't panic and retrieve a map. + let map = loaded_maps() + .map(|m| m.unwrap()) + .find(|m| m.name_as_str().unwrap() == "FOO") + .unwrap(); + + // Ensure all relevant helper functions don't panic. + map.name(); + map.id(); + map.map_type(); + map.key_size(); + map.value_size(); + map.max_entries(); + map.map_flags(); + map.fd().unwrap(); +} diff --git a/xtask/public-api/aya.txt b/xtask/public-api/aya.txt index 3650d1f3..196e0cec 100644 --- a/xtask/public-api/aya.txt +++ b/xtask/public-api/aya.txt @@ -1688,6 +1688,7 @@ pub fn aya::maps::MapData::fd(&self) -> &aya::maps::MapFd pub fn aya::maps::MapData::from_fd(fd: std::os::fd::owned::OwnedFd) -> core::result::Result pub fn aya::maps::MapData::from_id(id: u32) -> core::result::Result pub fn aya::maps::MapData::from_pin>(path: P) -> core::result::Result +pub fn aya::maps::MapData::info(&self) -> core::result::Result pub fn aya::maps::MapData::pin>(&mut self, path: P) -> core::result::Result<(), aya::pin::PinError> impl core::fmt::Debug for aya::maps::MapData pub fn aya::maps::MapData::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result @@ -1738,6 +1739,42 @@ impl core::borrow::BorrowMut for aya::maps::MapFd where T: core::marker::S pub fn aya::maps::MapFd::borrow_mut(&mut self) -> &mut T impl core::convert::From for aya::maps::MapFd pub fn aya::maps::MapFd::from(t: T) -> T +pub struct aya::maps::MapInfo(_) +impl aya::maps::MapInfo +pub fn aya::maps::MapInfo::fd(&self) -> core::result::Result +pub fn aya::maps::MapInfo::from_id(id: u32) -> core::result::Result +pub fn aya::maps::MapInfo::from_pin>(path: P) -> core::result::Result +pub fn aya::maps::MapInfo::id(&self) -> u32 +pub fn aya::maps::MapInfo::key_size(&self) -> u32 +pub fn aya::maps::MapInfo::map_flags(&self) -> u32 +pub fn aya::maps::MapInfo::map_type(&self) -> u32 +pub fn aya::maps::MapInfo::max_entries(&self) -> u32 +pub fn aya::maps::MapInfo::name(&self) -> &[u8] +pub fn aya::maps::MapInfo::name_as_str(&self) -> core::option::Option<&str> +pub fn aya::maps::MapInfo::value_size(&self) -> u32 +impl core::fmt::Debug for aya::maps::MapInfo +pub fn aya::maps::MapInfo::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::marker::Send for aya::maps::MapInfo +impl core::marker::Sync for aya::maps::MapInfo +impl core::marker::Unpin for aya::maps::MapInfo +impl core::panic::unwind_safe::RefUnwindSafe for aya::maps::MapInfo +impl core::panic::unwind_safe::UnwindSafe for aya::maps::MapInfo +impl core::convert::Into for aya::maps::MapInfo where U: core::convert::From +pub fn aya::maps::MapInfo::into(self) -> U +impl core::convert::TryFrom for aya::maps::MapInfo where U: core::convert::Into +pub type aya::maps::MapInfo::Error = core::convert::Infallible +pub fn aya::maps::MapInfo::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for aya::maps::MapInfo where U: core::convert::TryFrom +pub type aya::maps::MapInfo::Error = >::Error +pub fn aya::maps::MapInfo::try_into(self) -> core::result::Result>::Error> +impl core::any::Any for aya::maps::MapInfo where T: 'static + core::marker::Sized +pub fn aya::maps::MapInfo::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for aya::maps::MapInfo where T: core::marker::Sized +pub fn aya::maps::MapInfo::borrow(&self) -> &T +impl core::borrow::BorrowMut for aya::maps::MapInfo where T: core::marker::Sized +pub fn aya::maps::MapInfo::borrow_mut(&mut self) -> &mut T +impl core::convert::From for aya::maps::MapInfo +pub fn aya::maps::MapInfo::from(t: T) -> T pub struct aya::maps::MapIter<'coll, K: aya::Pod, V, I: aya::maps::IterableMap> impl> core::iter::traits::iterator::Iterator for aya::maps::MapIter<'_, K, V, I> pub type aya::maps::MapIter<'_, K, V, I>::Item = core::result::Result<(K, V), aya::maps::MapError> @@ -2289,6 +2326,7 @@ pub fn aya::maps::PerCpuArray::map(&self) -> &aya::maps::MapData impl> aya::maps::IterableMap for aya::maps::stack_trace::StackTraceMap pub fn aya::maps::stack_trace::StackTraceMap::get(&self, index: &u32) -> core::result::Result pub fn aya::maps::stack_trace::StackTraceMap::map(&self) -> &aya::maps::MapData +pub fn aya::maps::loaded_maps() -> impl core::iter::traits::iterator::Iterator> pub mod aya::pin pub enum aya::pin::PinError pub aya::pin::PinError::InvalidPinPath