Procházet zdrojové kódy

aya: add MapInfo struct following the same pattern as ProgramInfo

This makes the APIs for loading maps and programs more similar.
Adam Preuss před 1 rokem
rodič
revize
4d24d1cfe8

+ 225 - 15
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<Self, MapError> {
-        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<Self, MapError> {
-        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, MapError> {
+        MapInfo::new_from_fd(self.fd.as_fd())
+    }
 }
 
 /// An iterable map
@@ -911,6 +908,129 @@ impl<T: Pod> Deref for PerCpuValues<T> {
     }
 }
 
+/// 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<Self, MapError> {
+        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<Self, MapError> {
+        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<MapFd, MapError> {
+        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<P: AsRef<Path>>(path: P) -> Result<Self, MapError> {
+        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<Item = Result<MapInfo, MapError>> {
+    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::<bpf_map_info>() 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))));

+ 4 - 0
aya/src/sys/bpf.rs

@@ -1049,6 +1049,10 @@ pub(crate) fn iter_link_ids() -> impl Iterator<Item = Result<u32, SyscallError>>
     iter_obj_ids(bpf_cmd::BPF_LINK_GET_NEXT_ID, "bpf_link_get_next_id")
 }
 
+pub(crate) fn iter_map_ids() -> impl Iterator<Item = Result<u32, SyscallError>> {
+    iter_obj_ids(bpf_cmd::BPF_MAP_GET_NEXT_ID, "bpf_map_get_next_id")
+}
+
 pub(crate) fn retry_with_verifier_logs<T>(
     max_retries: usize,
     f: impl Fn(&mut [u8]) -> SysResult<T>,

+ 26 - 0
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();
+}

+ 38 - 0
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<Self, aya::maps::MapError>
 pub fn aya::maps::MapData::from_id(id: u32) -> core::result::Result<Self, aya::maps::MapError>
 pub fn aya::maps::MapData::from_pin<P: core::convert::AsRef<std::path::Path>>(path: P) -> core::result::Result<Self, aya::maps::MapError>
+pub fn aya::maps::MapData::info(&self) -> core::result::Result<aya::maps::MapInfo, aya::maps::MapError>
 pub fn aya::maps::MapData::pin<P: core::convert::AsRef<std::path::Path>>(&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<T> core::borrow::BorrowMut<T> for aya::maps::MapFd where T: core::marker::S
 pub fn aya::maps::MapFd::borrow_mut(&mut self) -> &mut T
 impl<T> core::convert::From<T> 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<aya::maps::MapFd, aya::maps::MapError>
+pub fn aya::maps::MapInfo::from_id(id: u32) -> core::result::Result<Self, aya::maps::MapError>
+pub fn aya::maps::MapInfo::from_pin<P: core::convert::AsRef<std::path::Path>>(path: P) -> core::result::Result<Self, aya::maps::MapError>
+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<T, U> core::convert::Into<U> for aya::maps::MapInfo where U: core::convert::From<T>
+pub fn aya::maps::MapInfo::into(self) -> U
+impl<T, U> core::convert::TryFrom<U> for aya::maps::MapInfo where U: core::convert::Into<T>
+pub type aya::maps::MapInfo::Error = core::convert::Infallible
+pub fn aya::maps::MapInfo::try_from(value: U) -> core::result::Result<T, <T as core::convert::TryFrom<U>>::Error>
+impl<T, U> core::convert::TryInto<U> for aya::maps::MapInfo where U: core::convert::TryFrom<T>
+pub type aya::maps::MapInfo::Error = <U as core::convert::TryFrom<T>>::Error
+pub fn aya::maps::MapInfo::try_into(self) -> core::result::Result<U, <U as core::convert::TryFrom<T>>::Error>
+impl<T> 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<T> core::borrow::Borrow<T> for aya::maps::MapInfo where T: core::marker::Sized
+pub fn aya::maps::MapInfo::borrow(&self) -> &T
+impl<T> core::borrow::BorrowMut<T> for aya::maps::MapInfo where T: core::marker::Sized
+pub fn aya::maps::MapInfo::borrow_mut(&mut self) -> &mut T
+impl<T> core::convert::From<T> 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<K, V>>
 impl<K: aya::Pod, V, I: aya::maps::IterableMap<K, V>> 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<T, V>::map(&self) -> &aya::maps::MapData
 impl<T: core::borrow::Borrow<aya::maps::MapData>> aya::maps::IterableMap<u32, aya::maps::stack_trace::StackTrace> for aya::maps::stack_trace::StackTraceMap<T>
 pub fn aya::maps::stack_trace::StackTraceMap<T>::get(&self, index: &u32) -> core::result::Result<aya::maps::stack_trace::StackTrace, aya::maps::MapError>
 pub fn aya::maps::stack_trace::StackTraceMap<T>::map(&self) -> &aya::maps::MapData
+pub fn aya::maps::loaded_maps() -> impl core::iter::traits::iterator::Iterator<Item = core::result::Result<aya::maps::MapInfo, aya::maps::MapError>>
 pub mod aya::pin
 pub enum aya::pin::PinError
 pub aya::pin::PinError::InvalidPinPath