Browse Source

Merge pull request #1007 from tyrone-wu/aya/info-api

aya,aya-obj,integration-test: add better support in `ProgramInfo` & `MapInfo` for old kernels
Alessandro Decina 7 months ago
parent
commit
15eb935bce

+ 6 - 5
aya-log/src/lib.rs

@@ -60,12 +60,11 @@ use std::{
 const MAP_NAME: &str = "AYA_LOGS";
 
 use aya::{
-    loaded_programs,
     maps::{
         perf::{AsyncPerfEventArray, Events, PerfBufferError},
         Map, MapData, MapError, MapInfo,
     },
-    programs::ProgramError,
+    programs::{loaded_programs, ProgramError},
     util::online_cpus,
     Ebpf, Pod,
 };
@@ -137,19 +136,21 @@ impl EbpfLogger {
     ) -> Result<EbpfLogger, Error> {
         let program_info = loaded_programs()
             .filter_map(|info| info.ok())
-            .find(|info| info.id() == program_id)
+            .find(|info| info.id().is_some_and(|id| id.get() == program_id))
             .ok_or(Error::ProgramNotFound)?;
+
         let map = program_info
             .map_ids()
             .map_err(Error::ProgramError)?
+            .expect("`map_ids` field in `bpf_prog_info` not available")
             .iter()
-            .filter_map(|id| MapInfo::from_id(*id).ok())
+            .filter_map(|id| MapInfo::from_id(id.get()).ok())
             .find(|map_info| match map_info.name_as_str() {
                 Some(name) => name == MAP_NAME,
                 None => false,
             })
             .ok_or(Error::MapNotFound)?;
-        let map = MapData::from_id(map.id()).map_err(Error::MapError)?;
+        let map = MapData::from_id(map.id().unwrap().get()).map_err(Error::MapError)?;
 
         Self::read_logs_async(Map::PerfEventArray(map), logger)?;
 

+ 1 - 0
aya-obj/src/lib.rs

@@ -87,6 +87,7 @@ mod std {
 
 pub mod btf;
 pub mod generated;
+pub mod links;
 pub mod maps;
 pub mod obj;
 pub mod programs;

+ 100 - 0
aya-obj/src/links.rs

@@ -0,0 +1,100 @@
+//! Link type bindings.
+
+use crate::{
+    generated::{bpf_attach_type, bpf_link_type},
+    InvalidTypeBinding,
+};
+
+impl TryFrom<u32> for bpf_link_type {
+    type Error = InvalidTypeBinding<u32>;
+
+    fn try_from(link_type: u32) -> Result<Self, Self::Error> {
+        use bpf_link_type::*;
+        Ok(match link_type {
+            x if x == BPF_LINK_TYPE_UNSPEC as u32 => BPF_LINK_TYPE_UNSPEC,
+            x if x == BPF_LINK_TYPE_RAW_TRACEPOINT as u32 => BPF_LINK_TYPE_RAW_TRACEPOINT,
+            x if x == BPF_LINK_TYPE_TRACING as u32 => BPF_LINK_TYPE_TRACING,
+            x if x == BPF_LINK_TYPE_CGROUP as u32 => BPF_LINK_TYPE_CGROUP,
+            x if x == BPF_LINK_TYPE_ITER as u32 => BPF_LINK_TYPE_ITER,
+            x if x == BPF_LINK_TYPE_NETNS as u32 => BPF_LINK_TYPE_NETNS,
+            x if x == BPF_LINK_TYPE_XDP as u32 => BPF_LINK_TYPE_XDP,
+            x if x == BPF_LINK_TYPE_PERF_EVENT as u32 => BPF_LINK_TYPE_PERF_EVENT,
+            x if x == BPF_LINK_TYPE_KPROBE_MULTI as u32 => BPF_LINK_TYPE_KPROBE_MULTI,
+            x if x == BPF_LINK_TYPE_STRUCT_OPS as u32 => BPF_LINK_TYPE_STRUCT_OPS,
+            x if x == BPF_LINK_TYPE_NETFILTER as u32 => BPF_LINK_TYPE_NETFILTER,
+            x if x == BPF_LINK_TYPE_TCX as u32 => BPF_LINK_TYPE_TCX,
+            x if x == BPF_LINK_TYPE_UPROBE_MULTI as u32 => BPF_LINK_TYPE_UPROBE_MULTI,
+            x if x == BPF_LINK_TYPE_NETKIT as u32 => BPF_LINK_TYPE_NETKIT,
+            _ => return Err(InvalidTypeBinding { value: link_type }),
+        })
+    }
+}
+
+impl TryFrom<u32> for bpf_attach_type {
+    type Error = InvalidTypeBinding<u32>;
+
+    fn try_from(attach_type: u32) -> Result<Self, Self::Error> {
+        use bpf_attach_type::*;
+        Ok(match attach_type {
+            x if x == BPF_CGROUP_INET_INGRESS as u32 => BPF_CGROUP_INET_INGRESS,
+            x if x == BPF_CGROUP_INET_EGRESS as u32 => BPF_CGROUP_INET_EGRESS,
+            x if x == BPF_CGROUP_INET_SOCK_CREATE as u32 => BPF_CGROUP_INET_SOCK_CREATE,
+            x if x == BPF_CGROUP_SOCK_OPS as u32 => BPF_CGROUP_SOCK_OPS,
+            x if x == BPF_SK_SKB_STREAM_PARSER as u32 => BPF_SK_SKB_STREAM_PARSER,
+            x if x == BPF_SK_SKB_STREAM_VERDICT as u32 => BPF_SK_SKB_STREAM_VERDICT,
+            x if x == BPF_CGROUP_DEVICE as u32 => BPF_CGROUP_DEVICE,
+            x if x == BPF_SK_MSG_VERDICT as u32 => BPF_SK_MSG_VERDICT,
+            x if x == BPF_CGROUP_INET4_BIND as u32 => BPF_CGROUP_INET4_BIND,
+            x if x == BPF_CGROUP_INET6_BIND as u32 => BPF_CGROUP_INET6_BIND,
+            x if x == BPF_CGROUP_INET4_CONNECT as u32 => BPF_CGROUP_INET4_CONNECT,
+            x if x == BPF_CGROUP_INET6_CONNECT as u32 => BPF_CGROUP_INET6_CONNECT,
+            x if x == BPF_CGROUP_INET4_POST_BIND as u32 => BPF_CGROUP_INET4_POST_BIND,
+            x if x == BPF_CGROUP_INET6_POST_BIND as u32 => BPF_CGROUP_INET6_POST_BIND,
+            x if x == BPF_CGROUP_UDP4_SENDMSG as u32 => BPF_CGROUP_UDP4_SENDMSG,
+            x if x == BPF_CGROUP_UDP6_SENDMSG as u32 => BPF_CGROUP_UDP6_SENDMSG,
+            x if x == BPF_LIRC_MODE2 as u32 => BPF_LIRC_MODE2,
+            x if x == BPF_FLOW_DISSECTOR as u32 => BPF_FLOW_DISSECTOR,
+            x if x == BPF_CGROUP_SYSCTL as u32 => BPF_CGROUP_SYSCTL,
+            x if x == BPF_CGROUP_UDP4_RECVMSG as u32 => BPF_CGROUP_UDP4_RECVMSG,
+            x if x == BPF_CGROUP_UDP6_RECVMSG as u32 => BPF_CGROUP_UDP6_RECVMSG,
+            x if x == BPF_CGROUP_GETSOCKOPT as u32 => BPF_CGROUP_GETSOCKOPT,
+            x if x == BPF_CGROUP_SETSOCKOPT as u32 => BPF_CGROUP_SETSOCKOPT,
+            x if x == BPF_TRACE_RAW_TP as u32 => BPF_TRACE_RAW_TP,
+            x if x == BPF_TRACE_FENTRY as u32 => BPF_TRACE_FENTRY,
+            x if x == BPF_TRACE_FEXIT as u32 => BPF_TRACE_FEXIT,
+            x if x == BPF_MODIFY_RETURN as u32 => BPF_MODIFY_RETURN,
+            x if x == BPF_LSM_MAC as u32 => BPF_LSM_MAC,
+            x if x == BPF_TRACE_ITER as u32 => BPF_TRACE_ITER,
+            x if x == BPF_CGROUP_INET4_GETPEERNAME as u32 => BPF_CGROUP_INET4_GETPEERNAME,
+            x if x == BPF_CGROUP_INET6_GETPEERNAME as u32 => BPF_CGROUP_INET6_GETPEERNAME,
+            x if x == BPF_CGROUP_INET4_GETSOCKNAME as u32 => BPF_CGROUP_INET4_GETSOCKNAME,
+            x if x == BPF_CGROUP_INET6_GETSOCKNAME as u32 => BPF_CGROUP_INET6_GETSOCKNAME,
+            x if x == BPF_XDP_DEVMAP as u32 => BPF_XDP_DEVMAP,
+            x if x == BPF_CGROUP_INET_SOCK_RELEASE as u32 => BPF_CGROUP_INET_SOCK_RELEASE,
+            x if x == BPF_XDP_CPUMAP as u32 => BPF_XDP_CPUMAP,
+            x if x == BPF_SK_LOOKUP as u32 => BPF_SK_LOOKUP,
+            x if x == BPF_XDP as u32 => BPF_XDP,
+            x if x == BPF_SK_SKB_VERDICT as u32 => BPF_SK_SKB_VERDICT,
+            x if x == BPF_SK_REUSEPORT_SELECT as u32 => BPF_SK_REUSEPORT_SELECT,
+            x if x == BPF_SK_REUSEPORT_SELECT_OR_MIGRATE as u32 => {
+                BPF_SK_REUSEPORT_SELECT_OR_MIGRATE
+            }
+            x if x == BPF_PERF_EVENT as u32 => BPF_PERF_EVENT,
+            x if x == BPF_TRACE_KPROBE_MULTI as u32 => BPF_TRACE_KPROBE_MULTI,
+            x if x == BPF_LSM_CGROUP as u32 => BPF_LSM_CGROUP,
+            x if x == BPF_STRUCT_OPS as u32 => BPF_STRUCT_OPS,
+            x if x == BPF_NETFILTER as u32 => BPF_NETFILTER,
+            x if x == BPF_TCX_INGRESS as u32 => BPF_TCX_INGRESS,
+            x if x == BPF_TCX_EGRESS as u32 => BPF_TCX_EGRESS,
+            x if x == BPF_TRACE_UPROBE_MULTI as u32 => BPF_TRACE_UPROBE_MULTI,
+            x if x == BPF_CGROUP_UNIX_CONNECT as u32 => BPF_CGROUP_UNIX_CONNECT,
+            x if x == BPF_CGROUP_UNIX_SENDMSG as u32 => BPF_CGROUP_UNIX_SENDMSG,
+            x if x == BPF_CGROUP_UNIX_RECVMSG as u32 => BPF_CGROUP_UNIX_RECVMSG,
+            x if x == BPF_CGROUP_UNIX_GETPEERNAME as u32 => BPF_CGROUP_UNIX_GETPEERNAME,
+            x if x == BPF_CGROUP_UNIX_GETSOCKNAME as u32 => BPF_CGROUP_UNIX_GETSOCKNAME,
+            x if x == BPF_NETKIT_PRIMARY as u32 => BPF_NETKIT_PRIMARY,
+            x if x == BPF_NETKIT_PEER as u32 => BPF_NETKIT_PEER,
+            _ => return Err(InvalidTypeBinding { value: attach_type }),
+        })
+    }
+}

+ 4 - 11
aya-obj/src/maps.rs

@@ -5,16 +5,10 @@ use core::mem;
 
 #[cfg(not(feature = "std"))]
 use crate::std;
-use crate::EbpfSectionKind;
-
-/// Invalid map type encontered
-pub struct InvalidMapTypeError {
-    /// The map type
-    pub map_type: u32,
-}
+use crate::{EbpfSectionKind, InvalidTypeBinding};
 
 impl TryFrom<u32> for crate::generated::bpf_map_type {
-    type Error = InvalidMapTypeError;
+    type Error = InvalidTypeBinding<u32>;
 
     fn try_from(map_type: u32) -> Result<Self, Self::Error> {
         use crate::generated::bpf_map_type::*;
@@ -31,7 +25,6 @@ impl TryFrom<u32> for crate::generated::bpf_map_type {
             x if x == BPF_MAP_TYPE_LRU_HASH as u32 => BPF_MAP_TYPE_LRU_HASH,
             x if x == BPF_MAP_TYPE_LRU_PERCPU_HASH as u32 => BPF_MAP_TYPE_LRU_PERCPU_HASH,
             x if x == BPF_MAP_TYPE_LPM_TRIE as u32 => BPF_MAP_TYPE_LPM_TRIE,
-            x if x == BPF_MAP_TYPE_BLOOM_FILTER as u32 => BPF_MAP_TYPE_BLOOM_FILTER,
             x if x == BPF_MAP_TYPE_ARRAY_OF_MAPS as u32 => BPF_MAP_TYPE_ARRAY_OF_MAPS,
             x if x == BPF_MAP_TYPE_HASH_OF_MAPS as u32 => BPF_MAP_TYPE_HASH_OF_MAPS,
             x if x == BPF_MAP_TYPE_DEVMAP as u32 => BPF_MAP_TYPE_DEVMAP,
@@ -42,7 +35,6 @@ impl TryFrom<u32> for crate::generated::bpf_map_type {
             x if x == BPF_MAP_TYPE_CGROUP_STORAGE_DEPRECATED as u32 => {
                 BPF_MAP_TYPE_CGROUP_STORAGE_DEPRECATED
             }
-            x if x == BPF_MAP_TYPE_CGRP_STORAGE as u32 => BPF_MAP_TYPE_CGRP_STORAGE,
             x if x == BPF_MAP_TYPE_REUSEPORT_SOCKARRAY as u32 => BPF_MAP_TYPE_REUSEPORT_SOCKARRAY,
             x if x == BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE_DEPRECATED as u32 => {
                 BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE_DEPRECATED
@@ -58,7 +50,8 @@ impl TryFrom<u32> for crate::generated::bpf_map_type {
             x if x == BPF_MAP_TYPE_BLOOM_FILTER as u32 => BPF_MAP_TYPE_BLOOM_FILTER,
             x if x == BPF_MAP_TYPE_USER_RINGBUF as u32 => BPF_MAP_TYPE_USER_RINGBUF,
             x if x == BPF_MAP_TYPE_CGRP_STORAGE as u32 => BPF_MAP_TYPE_CGRP_STORAGE,
-            _ => return Err(InvalidMapTypeError { map_type }),
+            x if x == BPF_MAP_TYPE_ARENA as u32 => BPF_MAP_TYPE_ARENA,
+            _ => return Err(InvalidTypeBinding { value: map_type }),
         })
     }
 }

+ 26 - 1
aya-obj/src/obj.rs

@@ -47,6 +47,8 @@ pub struct Features {
     bpf_cookie: bool,
     cpumap_prog_id: bool,
     devmap_prog_id: bool,
+    prog_info_map_ids: bool,
+    prog_info_gpl_compatible: bool,
     btf: Option<BtfFeatures>,
 }
 
@@ -61,6 +63,8 @@ impl Features {
         bpf_cookie: bool,
         cpumap_prog_id: bool,
         devmap_prog_id: bool,
+        prog_info_map_ids: bool,
+        prog_info_gpl_compatible: bool,
         btf: Option<BtfFeatures>,
     ) -> Self {
         Self {
@@ -71,11 +75,16 @@ impl Features {
             bpf_cookie,
             cpumap_prog_id,
             devmap_prog_id,
+            prog_info_map_ids,
+            prog_info_gpl_compatible,
             btf,
         }
     }
 
-    /// Returns whether BPF program names are supported.
+    /// Returns whether BPF program names and map names are supported.
+    ///
+    /// Although the feature probe performs the check for program name, we can use this to also
+    /// detect if map name is supported since they were both introduced in the same commit.
     pub fn bpf_name(&self) -> bool {
         self.bpf_name
     }
@@ -110,6 +119,16 @@ impl Features {
         self.devmap_prog_id
     }
 
+    /// Returns whether `bpf_prog_info` supports `nr_map_ids` & `map_ids` fields.
+    pub fn prog_info_map_ids(&self) -> bool {
+        self.prog_info_map_ids
+    }
+
+    /// Returns whether `bpf_prog_info` supports `gpl_compatible` field.
+    pub fn prog_info_gpl_compatible(&self) -> bool {
+        self.prog_info_gpl_compatible
+    }
+
     /// If BTF is supported, returns which BTF features are supported.
     pub fn btf(&self) -> Option<&BtfFeatures> {
         self.btf.as_ref()
@@ -987,6 +1006,12 @@ pub enum ParseError {
     NoBTF,
 }
 
+/// Invalid bindings to the bpf type from the parsed/received value.
+pub struct InvalidTypeBinding<T> {
+    /// The value parsed/received.
+    pub value: T,
+}
+
 /// The kind of an ELF section.
 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
 pub enum EbpfSectionKind {

+ 1 - 0
aya-obj/src/programs/mod.rs

@@ -3,6 +3,7 @@
 pub mod cgroup_sock;
 pub mod cgroup_sock_addr;
 pub mod cgroup_sockopt;
+mod types;
 pub mod xdp;
 
 pub use cgroup_sock::CgroupSockAttachType;

+ 51 - 0
aya-obj/src/programs/types.rs

@@ -0,0 +1,51 @@
+//! Program type bindings.
+
+use crate::{
+    generated::bpf_prog_type::{self, *},
+    InvalidTypeBinding,
+};
+
+impl TryFrom<u32> for bpf_prog_type {
+    type Error = InvalidTypeBinding<u32>;
+
+    fn try_from(prog_type: u32) -> Result<Self, Self::Error> {
+        Ok(match prog_type {
+            x if x == BPF_PROG_TYPE_UNSPEC as u32 => BPF_PROG_TYPE_UNSPEC,
+            x if x == BPF_PROG_TYPE_SOCKET_FILTER as u32 => BPF_PROG_TYPE_SOCKET_FILTER,
+            x if x == BPF_PROG_TYPE_KPROBE as u32 => BPF_PROG_TYPE_KPROBE,
+            x if x == BPF_PROG_TYPE_SCHED_CLS as u32 => BPF_PROG_TYPE_SCHED_CLS,
+            x if x == BPF_PROG_TYPE_SCHED_ACT as u32 => BPF_PROG_TYPE_SCHED_ACT,
+            x if x == BPF_PROG_TYPE_TRACEPOINT as u32 => BPF_PROG_TYPE_TRACEPOINT,
+            x if x == BPF_PROG_TYPE_XDP as u32 => BPF_PROG_TYPE_XDP,
+            x if x == BPF_PROG_TYPE_PERF_EVENT as u32 => BPF_PROG_TYPE_PERF_EVENT,
+            x if x == BPF_PROG_TYPE_CGROUP_SKB as u32 => BPF_PROG_TYPE_CGROUP_SKB,
+            x if x == BPF_PROG_TYPE_CGROUP_SOCK as u32 => BPF_PROG_TYPE_CGROUP_SOCK,
+            x if x == BPF_PROG_TYPE_LWT_IN as u32 => BPF_PROG_TYPE_LWT_IN,
+            x if x == BPF_PROG_TYPE_LWT_OUT as u32 => BPF_PROG_TYPE_LWT_OUT,
+            x if x == BPF_PROG_TYPE_LWT_XMIT as u32 => BPF_PROG_TYPE_LWT_XMIT,
+            x if x == BPF_PROG_TYPE_SOCK_OPS as u32 => BPF_PROG_TYPE_SOCK_OPS,
+            x if x == BPF_PROG_TYPE_SK_SKB as u32 => BPF_PROG_TYPE_SK_SKB,
+            x if x == BPF_PROG_TYPE_CGROUP_DEVICE as u32 => BPF_PROG_TYPE_CGROUP_DEVICE,
+            x if x == BPF_PROG_TYPE_SK_MSG as u32 => BPF_PROG_TYPE_SK_MSG,
+            x if x == BPF_PROG_TYPE_RAW_TRACEPOINT as u32 => BPF_PROG_TYPE_RAW_TRACEPOINT,
+            x if x == BPF_PROG_TYPE_CGROUP_SOCK_ADDR as u32 => BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
+            x if x == BPF_PROG_TYPE_LWT_SEG6LOCAL as u32 => BPF_PROG_TYPE_LWT_SEG6LOCAL,
+            x if x == BPF_PROG_TYPE_LIRC_MODE2 as u32 => BPF_PROG_TYPE_LIRC_MODE2,
+            x if x == BPF_PROG_TYPE_SK_REUSEPORT as u32 => BPF_PROG_TYPE_SK_REUSEPORT,
+            x if x == BPF_PROG_TYPE_FLOW_DISSECTOR as u32 => BPF_PROG_TYPE_FLOW_DISSECTOR,
+            x if x == BPF_PROG_TYPE_CGROUP_SYSCTL as u32 => BPF_PROG_TYPE_CGROUP_SYSCTL,
+            x if x == BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE as u32 => {
+                BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE
+            }
+            x if x == BPF_PROG_TYPE_CGROUP_SOCKOPT as u32 => BPF_PROG_TYPE_CGROUP_SOCKOPT,
+            x if x == BPF_PROG_TYPE_TRACING as u32 => BPF_PROG_TYPE_TRACING,
+            x if x == BPF_PROG_TYPE_STRUCT_OPS as u32 => BPF_PROG_TYPE_STRUCT_OPS,
+            x if x == BPF_PROG_TYPE_EXT as u32 => BPF_PROG_TYPE_EXT,
+            x if x == BPF_PROG_TYPE_LSM as u32 => BPF_PROG_TYPE_LSM,
+            x if x == BPF_PROG_TYPE_SK_LOOKUP as u32 => BPF_PROG_TYPE_SK_LOOKUP,
+            x if x == BPF_PROG_TYPE_SYSCALL as u32 => BPF_PROG_TYPE_SYSCALL,
+            x if x == BPF_PROG_TYPE_NETFILTER as u32 => BPF_PROG_TYPE_NETFILTER,
+            _ => return Err(InvalidTypeBinding { value: prog_type }),
+        })
+    }
+}

+ 7 - 5
aya/src/bpf.rs

@@ -21,8 +21,8 @@ use thiserror::Error;
 
 use crate::{
     generated::{
-        bpf_map_type, bpf_map_type::*, AYA_PERF_EVENT_IOC_DISABLE, AYA_PERF_EVENT_IOC_ENABLE,
-        AYA_PERF_EVENT_IOC_SET_BPF,
+        bpf_map_type::{self, *},
+        AYA_PERF_EVENT_IOC_DISABLE, AYA_PERF_EVENT_IOC_ENABLE, AYA_PERF_EVENT_IOC_SET_BPF,
     },
     maps::{Map, MapData, MapError},
     obj::{
@@ -39,9 +39,9 @@ use crate::{
         bpf_load_btf, is_bpf_cookie_supported, is_bpf_global_data_supported,
         is_btf_datasec_supported, is_btf_decl_tag_supported, is_btf_enum64_supported,
         is_btf_float_supported, is_btf_func_global_supported, is_btf_func_supported,
-        is_btf_supported, is_btf_type_tag_supported, is_perf_link_supported,
-        is_probe_read_kernel_supported, is_prog_id_supported, is_prog_name_supported,
-        retry_with_verifier_logs,
+        is_btf_supported, is_btf_type_tag_supported, is_info_gpl_compatible_supported,
+        is_info_map_ids_supported, is_perf_link_supported, is_probe_read_kernel_supported,
+        is_prog_id_supported, is_prog_name_supported, retry_with_verifier_logs,
     },
     util::{bytes_of, bytes_of_slice, page_size, possible_cpus, POSSIBLE_CPUS},
 };
@@ -96,6 +96,8 @@ fn detect_features() -> Features {
         is_bpf_cookie_supported(),
         is_prog_id_supported(BPF_MAP_TYPE_CPUMAP),
         is_prog_id_supported(BPF_MAP_TYPE_DEVMAP),
+        is_info_map_ids_supported(),
+        is_info_gpl_compatible_supported(),
         btf,
     );
     debug!("BPF Feature Detection: {:#?}", f);

+ 0 - 1
aya/src/lib.rs

@@ -91,7 +91,6 @@ use aya_obj::generated;
 pub use bpf::*;
 pub use obj::btf::{Btf, BtfError};
 pub use object::Endianness;
-pub use programs::loaded_programs;
 #[doc(hidden)]
 pub use sys::netlink_set_link_up;
 

+ 413 - 0
aya/src/maps/info.rs

@@ -0,0 +1,413 @@
+//! Metadata information about an eBPF map.
+
+use std::{
+    ffi::CString,
+    num::NonZeroU32,
+    os::fd::{AsFd as _, BorrowedFd},
+    path::Path,
+};
+
+use aya_obj::generated::{bpf_map_info, bpf_map_type};
+
+use super::{MapError, MapFd};
+use crate::{
+    sys::{
+        bpf_get_object, bpf_map_get_fd_by_id, bpf_map_get_info_by_fd, iter_map_ids, SyscallError,
+    },
+    util::bytes_of_bpf_name,
+    FEATURES,
+};
+
+/// Provides Provides metadata information about a loaded eBPF map.
+#[doc(alias = "bpf_map_info")]
+#[derive(Debug)]
+pub struct MapInfo(pub(crate) bpf_map_info);
+
+impl MapInfo {
+    pub(crate) 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.
+    ///
+    /// Uses kernel v4.13 features.
+    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 type of map.
+    ///
+    /// Introduced in kernel v4.13.
+    pub fn map_type(&self) -> Result<MapType, MapError> {
+        bpf_map_type::try_from(self.0.type_)
+            .unwrap_or(bpf_map_type::__MAX_BPF_MAP_TYPE)
+            .try_into()
+    }
+
+    /// The unique ID for this map.
+    ///
+    /// `None` is returned if the field is not available.
+    ///
+    /// Introduced in kernel v4.13.
+    pub fn id(&self) -> Option<NonZeroU32> {
+        NonZeroU32::new(self.0.id)
+    }
+
+    /// The key size for this map in bytes.
+    ///
+    /// `None` is returned if the field is not available.
+    ///
+    /// Introduced in kernel v4.13.
+    pub fn key_size(&self) -> Option<NonZeroU32> {
+        NonZeroU32::new(self.0.key_size)
+    }
+
+    /// The value size for this map in bytes.
+    ///
+    /// `None` is returned if the field is not available.
+    ///
+    /// Introduced in kernel v4.13.
+    pub fn value_size(&self) -> Option<NonZeroU32> {
+        NonZeroU32::new(self.0.value_size)
+    }
+
+    /// The maximum number of entries in this map.
+    ///
+    /// `None` is returned if the field is not available.
+    ///
+    /// Introduced in kernel v4.13.
+    pub fn max_entries(&self) -> Option<NonZeroU32> {
+        NonZeroU32::new(self.0.max_entries)
+    }
+
+    /// The flags used in loading this map.
+    ///
+    /// Introduced in kernel v4.13.
+    pub fn map_flags(&self) -> u32 {
+        self.0.map_flags
+    }
+
+    /// The name of the map, limited to 16 bytes.
+    ///
+    /// Introduced in kernel v4.15.
+    pub fn name(&self) -> &[u8] {
+        bytes_of_bpf_name(&self.0.name)
+    }
+
+    /// The name of the map as a &str.
+    ///
+    /// `None` is returned if the name was not valid unicode or if field is not available.
+    ///
+    /// Introduced in kernel v4.15.
+    pub fn name_as_str(&self) -> Option<&str> {
+        let name = std::str::from_utf8(self.name()).ok();
+        if let Some(name_str) = name {
+            // Char in program name was introduced in the same commit as map name
+            if FEATURES.bpf_name() || !name_str.is_empty() {
+                return name;
+            }
+        }
+        None
+    }
+
+    /// 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.
+    ///
+    /// Uses kernel v4.13 features.
+    pub fn fd(&self) -> Result<MapFd, MapError> {
+        let Self(info) = self;
+        let fd = bpf_map_get_fd_by_id(info.id)?;
+        Ok(MapFd::from_fd(fd))
+    }
+
+    /// Loads a map from a pinned path in bpffs.
+    ///
+    /// Uses kernel v4.4 and v4.13 features.
+    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 of [`MapInfo`] over all eBPF maps on the host.
+///
+/// Unlike [`Ebpf::maps`](crate::Ebpf::maps), this includes all maps on the host system, not
+/// just those tied to a specific [`crate::Ebpf`] instance.
+///
+/// Uses kernel v4.13 features.
+///
+/// # 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)
+    })
+}
+
+/// The type of eBPF map.
+#[non_exhaustive]
+#[doc(alias = "bpf_map_type")]
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum MapType {
+    /// An unspecified program type.
+    Unspecified = bpf_map_type::BPF_MAP_TYPE_UNSPEC as isize,
+    /// A Hash map type. See [`HashMap`](super::hash_map::HashMap) for the map implementation.
+    ///
+    /// Introduced in kernel v3.19.
+    #[doc(alias = "BPF_MAP_TYPE_HASH")]
+    Hash = bpf_map_type::BPF_MAP_TYPE_HASH as isize,
+    /// An Array map type. See [`Array`](super::array::Array) for the map implementation.
+    ///
+    /// Introduced in kernel v3.19.
+    #[doc(alias = "BPF_MAP_TYPE_ARRAY")]
+    Array = bpf_map_type::BPF_MAP_TYPE_ARRAY as isize,
+    /// A Program Array map type. See [`ProgramArray`](super::array::ProgramArray) for the map
+    /// implementation.
+    ///
+    /// Introduced in kernel v4.2.
+    #[doc(alias = "BPF_MAP_TYPE_PROG_ARRAY")]
+    ProgramArray = bpf_map_type::BPF_MAP_TYPE_PROG_ARRAY as isize,
+    /// A Perf Event Array map type. See [`PerfEventArray`](super::perf::PerfEventArray) and
+    /// [`AsyncPerfEventArray`](super::perf::AsyncPerfEventArray) for the map implementations.
+    ///
+    /// Introduced in kernel v4.3.
+    #[doc(alias = "BPF_MAP_TYPE_PERF_EVENT_ARRAY")]
+    PerfEventArray = bpf_map_type::BPF_MAP_TYPE_PERF_EVENT_ARRAY as isize,
+    /// A per-CPU Hash map type. See [`PerCpuHashMap`](super::hash_map::PerCpuHashMap) for the map
+    /// implementation.
+    ///
+    /// Introduced in kernel v4.6.
+    #[doc(alias = "BPF_MAP_TYPE_PERCPU_HASH")]
+    PerCpuHash = bpf_map_type::BPF_MAP_TYPE_PERCPU_HASH as isize,
+    /// A per-CPU Array map type. See [`PerCpuArray`](super::array::PerCpuArray) for the map
+    /// implementation.
+    ///
+    /// Introduced in kernel v4.6.
+    #[doc(alias = "BPF_MAP_TYPE_PERCPU_ARRAY")]
+    PerCpuArray = bpf_map_type::BPF_MAP_TYPE_PERCPU_ARRAY as isize,
+    /// A Stack Trace map type. See [`StackTraceMap`](super::stack_trace::StackTraceMap) for the map
+    /// implementation.
+    ///
+    /// Introduced in kernel v4.6.
+    #[doc(alias = "BPF_MAP_TYPE_STACK_TRACE")]
+    StackTrace = bpf_map_type::BPF_MAP_TYPE_STACK_TRACE as isize,
+    /// A cGroup Array map type.
+    ///
+    /// Introduced in kernel v4.8.
+    #[doc(alias = "BPF_MAP_TYPE_CGROUP_ARRAY")]
+    CgroupArray = bpf_map_type::BPF_MAP_TYPE_CGROUP_ARRAY as isize,
+    /// A Least Recently Used (LRU) Hash map type. See [`HashMap`](super::hash_map::HashMap) for
+    /// the map implementation.
+    ///
+    /// Introduced in kernel v4.10.
+    #[doc(alias = "BPF_MAP_TYPE_LRU_HASH")]
+    LruHash = bpf_map_type::BPF_MAP_TYPE_LRU_HASH as isize,
+    /// A Least Recently Used (LRU) per-CPU Hash map type. See
+    /// [`PerCpuHashMap`](super::hash_map::PerCpuHashMap) for the map implementation.
+    ///
+    /// Introduced in kernel v4.10.
+    #[doc(alias = "BPF_MAP_TYPE_LRU_PERCPU_HASH")]
+    LruPerCpuHash = bpf_map_type::BPF_MAP_TYPE_LRU_PERCPU_HASH as isize,
+    /// A Longest Prefix Match (LPM) Trie map type. See [`LpmTrie`](super::lpm_trie::LpmTrie) for
+    /// the map implementation.
+    ///
+    /// Introduced in kernel v4.11.
+    #[doc(alias = "BPF_MAP_TYPE_LPM_TRIE")]
+    LpmTrie = bpf_map_type::BPF_MAP_TYPE_LPM_TRIE as isize,
+    /// An Array of Maps map type.
+    ///
+    /// Introduced in kernel v4.12.
+    #[doc(alias = "BPF_MAP_TYPE_ARRAY_OF_MAPS")]
+    ArrayOfMaps = bpf_map_type::BPF_MAP_TYPE_ARRAY_OF_MAPS as isize,
+    /// A Hash of Maps map type.
+    ///
+    /// Introduced in kernel v4.12.
+    #[doc(alias = "BPF_MAP_TYPE_HASH_OF_MAPS")]
+    HashOfMaps = bpf_map_type::BPF_MAP_TYPE_HASH_OF_MAPS as isize,
+    /// A Device Map type. See [`DevMap`](super::xdp::DevMap) for the map implementation.
+    ///
+    /// Introduced in kernel v4.14.
+    #[doc(alias = "BPF_MAP_TYPE_DEVMAP")]
+    DevMap = bpf_map_type::BPF_MAP_TYPE_DEVMAP as isize,
+    /// A Socket Map type. See [`SockMap`](super::sock::SockMap) for the map implementation.
+    ///
+    /// Introduced in kernel v4.14.
+    #[doc(alias = "BPF_MAP_TYPE_SOCKMAP")]
+    SockMap = bpf_map_type::BPF_MAP_TYPE_SOCKMAP as isize,
+    /// A CPU Map type. See [`CpuMap`](super::xdp::CpuMap) for the map implementation.
+    ///
+    /// Introduced in kernel v4.15.
+    #[doc(alias = "BPF_MAP_TYPE_CPUMAP")]
+    CpuMap = bpf_map_type::BPF_MAP_TYPE_CPUMAP as isize,
+    /// An XDP Socket Map type. See [`XskMap`](super::xdp::XskMap) for the map implementation.
+    ///
+    /// Introduced in kernel v4.18.
+    #[doc(alias = "BPF_MAP_TYPE_XSKMAP")]
+    XskMap = bpf_map_type::BPF_MAP_TYPE_XSKMAP as isize,
+    /// A Socket Hash map type. See [`SockHash`](super::sock::SockHash) for the map implementation.
+    ///
+    /// Introduced in kernel v4.18.
+    #[doc(alias = "BPF_MAP_TYPE_SOCKHASH")]
+    SockHash = bpf_map_type::BPF_MAP_TYPE_SOCKHASH as isize,
+    /// A cGroup Storage map type.
+    ///
+    /// Introduced in kernel v4.19.
+    // #[deprecated]
+    #[doc(alias = "BPF_MAP_TYPE_CGROUP_STORAGE")]
+    #[doc(alias = "BPF_MAP_TYPE_CGROUP_STORAGE_DEPRECATED")]
+    CgroupStorage = bpf_map_type::BPF_MAP_TYPE_CGROUP_STORAGE as isize,
+    /// A Reuseport Socket Array map type.
+    ///
+    /// Introduced in kernel v4.19.
+    #[doc(alias = "BPF_MAP_TYPE_REUSEPORT_SOCKARRAY")]
+    ReuseportSockArray = bpf_map_type::BPF_MAP_TYPE_REUSEPORT_SOCKARRAY as isize,
+    /// A per-CPU cGroup Storage map type.
+    ///
+    /// Introduced in kernel v4.20.
+    #[doc(alias = "BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE")]
+    #[doc(alias = "BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE_DEPRECATED")]
+    PerCpuCgroupStorage = bpf_map_type::BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE as isize,
+    /// A Queue map type. See [`Queue`](super::queue::Queue) for the map implementation.
+    ///
+    /// Introduced in kernel v4.20.
+    #[doc(alias = "BPF_MAP_TYPE_QUEUE")]
+    Queue = bpf_map_type::BPF_MAP_TYPE_QUEUE as isize,
+    /// A Stack map type. See [`Stack`](super::stack::Stack) for the map implementation.
+    ///
+    /// Introduced in kernel v4.20.
+    #[doc(alias = "BPF_MAP_TYPE_STACK")]
+    Stack = bpf_map_type::BPF_MAP_TYPE_STACK as isize,
+    /// A Socket-local Storage map type.
+    ///
+    /// Introduced in kernel v5.2.
+    #[doc(alias = "BPF_MAP_TYPE_SK_STORAGE")]
+    SkStorage = bpf_map_type::BPF_MAP_TYPE_SK_STORAGE as isize,
+    /// A Device Hash Map type. See [`DevMapHash`](super::xdp::DevMapHash) for the map
+    /// implementation.
+    ///
+    /// Introduced in kernel v5.4.
+    #[doc(alias = "BPF_MAP_TYPE_DEVMAP_HASH")]
+    DevMapHash = bpf_map_type::BPF_MAP_TYPE_DEVMAP_HASH as isize,
+    /// A Struct Ops map type.
+    ///
+    /// Introduced in kernel v5.6.
+    #[doc(alias = "BPF_MAP_TYPE_STRUCT_OPS")]
+    StructOps = bpf_map_type::BPF_MAP_TYPE_STRUCT_OPS as isize,
+    /// A Ring Buffer map type. See [`RingBuf`](super::ring_buf::RingBuf) for the map
+    /// implementation.
+    ///
+    /// Introduced in kernel v5.8.
+    #[doc(alias = "BPF_MAP_TYPE_RINGBUF")]
+    RingBuf = bpf_map_type::BPF_MAP_TYPE_RINGBUF as isize,
+    /// An Inode Storage map type.
+    ///
+    /// Introduced in kernel v5.10.
+    #[doc(alias = "BPF_MAP_TYPE_INODE_STORAGE")]
+    InodeStorage = bpf_map_type::BPF_MAP_TYPE_INODE_STORAGE as isize,
+    /// A Task Storage map type.
+    ///
+    /// Introduced in kernel v5.11.
+    #[doc(alias = "BPF_MAP_TYPE_TASK_STORAGE")]
+    TaskStorage = bpf_map_type::BPF_MAP_TYPE_TASK_STORAGE as isize,
+    /// A Bloom Filter map type. See [`BloomFilter`](super::bloom_filter::BloomFilter) for the map
+    /// implementation.
+    ///
+    /// Introduced in kernel v5.16.
+    #[doc(alias = "BPF_MAP_TYPE_BLOOM_FILTER")]
+    BloomFilter = bpf_map_type::BPF_MAP_TYPE_BLOOM_FILTER as isize,
+    /// A User Ring Buffer map type.
+    ///
+    /// Introduced in kernel v6.1.
+    #[doc(alias = "BPF_MAP_TYPE_USER_RINGBUF")]
+    UserRingBuf = bpf_map_type::BPF_MAP_TYPE_USER_RINGBUF as isize,
+    /// A cGroup Storage map type.
+    ///
+    /// Introduced in kernel v6.2.
+    #[doc(alias = "BPF_MAP_TYPE_CGRP_STORAGE")]
+    CgrpStorage = bpf_map_type::BPF_MAP_TYPE_CGRP_STORAGE as isize,
+    /// An Arena map type.
+    ///
+    /// Introduced in kernel v6.9.
+    #[doc(alias = "BPF_MAP_TYPE_ARENA")]
+    Arena = bpf_map_type::BPF_MAP_TYPE_ARENA as isize,
+}
+
+impl TryFrom<bpf_map_type> for MapType {
+    type Error = MapError;
+
+    fn try_from(map_type: bpf_map_type) -> Result<Self, Self::Error> {
+        use bpf_map_type::*;
+        Ok(match map_type {
+            BPF_MAP_TYPE_UNSPEC => Self::Unspecified,
+            BPF_MAP_TYPE_HASH => Self::Hash,
+            BPF_MAP_TYPE_ARRAY => Self::Array,
+            BPF_MAP_TYPE_PROG_ARRAY => Self::ProgramArray,
+            BPF_MAP_TYPE_PERF_EVENT_ARRAY => Self::PerfEventArray,
+            BPF_MAP_TYPE_PERCPU_HASH => Self::PerCpuHash,
+            BPF_MAP_TYPE_PERCPU_ARRAY => Self::PerCpuArray,
+            BPF_MAP_TYPE_STACK_TRACE => Self::StackTrace,
+            BPF_MAP_TYPE_CGROUP_ARRAY => Self::CgroupArray,
+            BPF_MAP_TYPE_LRU_HASH => Self::LruHash,
+            BPF_MAP_TYPE_LRU_PERCPU_HASH => Self::LruPerCpuHash,
+            BPF_MAP_TYPE_LPM_TRIE => Self::LpmTrie,
+            BPF_MAP_TYPE_ARRAY_OF_MAPS => Self::ArrayOfMaps,
+            BPF_MAP_TYPE_HASH_OF_MAPS => Self::HashOfMaps,
+            BPF_MAP_TYPE_DEVMAP => Self::DevMap,
+            BPF_MAP_TYPE_SOCKMAP => Self::SockMap,
+            BPF_MAP_TYPE_CPUMAP => Self::CpuMap,
+            BPF_MAP_TYPE_XSKMAP => Self::XskMap,
+            BPF_MAP_TYPE_SOCKHASH => Self::SockHash,
+            BPF_MAP_TYPE_CGROUP_STORAGE_DEPRECATED => Self::CgroupStorage,
+            BPF_MAP_TYPE_REUSEPORT_SOCKARRAY => Self::ReuseportSockArray,
+            BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE_DEPRECATED => Self::PerCpuCgroupStorage,
+            BPF_MAP_TYPE_QUEUE => Self::Queue,
+            BPF_MAP_TYPE_STACK => Self::Stack,
+            BPF_MAP_TYPE_SK_STORAGE => Self::SkStorage,
+            BPF_MAP_TYPE_DEVMAP_HASH => Self::DevMapHash,
+            BPF_MAP_TYPE_STRUCT_OPS => Self::StructOps,
+            BPF_MAP_TYPE_RINGBUF => Self::RingBuf,
+            BPF_MAP_TYPE_INODE_STORAGE => Self::InodeStorage,
+            BPF_MAP_TYPE_TASK_STORAGE => Self::TaskStorage,
+            BPF_MAP_TYPE_BLOOM_FILTER => Self::BloomFilter,
+            BPF_MAP_TYPE_USER_RINGBUF => Self::UserRingBuf,
+            BPF_MAP_TYPE_CGRP_STORAGE => Self::CgrpStorage,
+            BPF_MAP_TYPE_ARENA => Self::Arena,
+            __MAX_BPF_MAP_TYPE => {
+                return Err(MapError::InvalidMapType {
+                    map_type: map_type as u32,
+                })
+            }
+        })
+    }
+}

+ 14 - 131
aya/src/maps/mod.rs

@@ -59,28 +59,26 @@ use std::{
     ptr,
 };
 
-use aya_obj::generated::bpf_map_type;
+use aya_obj::{generated::bpf_map_type, InvalidTypeBinding};
 use libc::{getrlimit, rlim_t, rlimit, RLIMIT_MEMLOCK, RLIM_INFINITY};
 use log::warn;
-use obj::maps::InvalidMapTypeError;
 use thiserror::Error;
 
 use crate::{
-    generated::bpf_map_info,
     obj::{self, parse_map_info, EbpfSectionKind},
     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,
-        iter_map_ids, SyscallError,
+        bpf_create_map, bpf_get_object, bpf_map_freeze, bpf_map_get_fd_by_id, bpf_map_get_next_key,
+        bpf_map_update_elem_ptr, bpf_pin_object, SyscallError,
     },
-    util::{bytes_of_bpf_name, nr_cpus, KernelVersion},
+    util::{nr_cpus, KernelVersion},
     PinningType, Pod,
 };
 
 pub mod array;
 pub mod bloom_filter;
 pub mod hash_map;
+mod info;
 pub mod lpm_trie;
 pub mod perf;
 pub mod queue;
@@ -93,6 +91,7 @@ pub mod xdp;
 pub use array::{Array, PerCpuArray, ProgramArray};
 pub use bloom_filter::BloomFilter;
 pub use hash_map::{HashMap, PerCpuHashMap};
+pub use info::{loaded_maps, MapInfo, MapType};
 pub use lpm_trie::LpmTrie;
 #[cfg(any(feature = "async_tokio", feature = "async_std"))]
 #[cfg_attr(docsrs, doc(cfg(any(feature = "async_tokio", feature = "async_std"))))]
@@ -203,13 +202,10 @@ pub enum MapError {
     },
 }
 
-// Note that this is not just derived using #[from] because InvalidMapTypeError cannot implement
-// Error due the the fact that aya-obj is no_std and error_in_core is not stabilized
-// (https://github.com/rust-lang/rust/issues/103765).
-impl From<InvalidMapTypeError> for MapError {
-    fn from(e: InvalidMapTypeError) -> Self {
-        let InvalidMapTypeError { map_type } = e;
-        Self::InvalidMapType { map_type }
+impl From<InvalidTypeBinding<u32>> for MapError {
+    fn from(e: InvalidTypeBinding<u32>) -> Self {
+        let InvalidTypeBinding { value } = e;
+        Self::InvalidMapType { map_type: value }
     }
 }
 
@@ -951,119 +947,6 @@ 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] {
-        bytes_of_bpf_name(&self.0.name)
-    }
-
-    /// 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::from_fd(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::Ebpf::maps`] since it will return all maps
-/// listed on the host system and not only maps for a specific [`crate::Ebpf`] 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 test_utils {
     use crate::{
@@ -1334,11 +1217,11 @@ mod tests {
                 .map(|map_info| {
                     let map_info = map_info.unwrap();
                     (
-                        map_info.id(),
-                        map_info.key_size(),
-                        map_info.value_size(),
+                        map_info.id().unwrap().get(),
+                        map_info.key_size().unwrap().get(),
+                        map_info.value_size().unwrap().get(),
                         map_info.map_flags(),
-                        map_info.max_entries(),
+                        map_info.max_entries().unwrap().get(),
                         map_info.fd().unwrap().as_fd().as_raw_fd(),
                     )
                 })

+ 548 - 0
aya/src/programs/info.rs

@@ -0,0 +1,548 @@
+//! Metadata information about an eBPF program.
+
+use std::{
+    ffi::CString,
+    num::{NonZeroU32, NonZeroU64},
+    os::fd::{AsFd as _, BorrowedFd},
+    path::Path,
+    time::{Duration, SystemTime},
+};
+
+use aya_obj::generated::{bpf_prog_info, bpf_prog_type};
+
+use super::{
+    utils::{boot_time, get_fdinfo},
+    ProgramError, ProgramFd,
+};
+use crate::{
+    sys::{
+        bpf_get_object, bpf_prog_get_fd_by_id, bpf_prog_get_info_by_fd, iter_prog_ids, SyscallError,
+    },
+    util::bytes_of_bpf_name,
+    FEATURES,
+};
+
+/// Provides metadata information about a loaded eBPF program.
+///
+/// Introduced in kernel v4.13.
+#[doc(alias = "bpf_prog_info")]
+#[derive(Debug)]
+pub struct ProgramInfo(pub(crate) bpf_prog_info);
+
+impl ProgramInfo {
+    pub(crate) fn new_from_fd(fd: BorrowedFd<'_>) -> Result<Self, ProgramError> {
+        let info = bpf_prog_get_info_by_fd(fd, &mut [])?;
+        Ok(Self(info))
+    }
+
+    /// The type of program.
+    ///
+    /// Introduced in kernel v4.13.
+    pub fn program_type(&self) -> Result<ProgramType, ProgramError> {
+        bpf_prog_type::try_from(self.0.type_)
+            .unwrap_or(bpf_prog_type::__MAX_BPF_PROG_TYPE)
+            .try_into()
+    }
+
+    /// The unique ID for this program.
+    ///
+    /// `None` is returned if the field is not available.
+    ///
+    /// Introduced in kernel v4.13.
+    pub fn id(&self) -> Option<NonZeroU32> {
+        NonZeroU32::new(self.0.id)
+    }
+
+    /// The program tag.
+    ///
+    /// The tag is a SHA sum of the program's instructions which be used as an alternative to
+    /// [`Self::id()`]. A program's ID can vary every time it's loaded or unloaded, but the tag
+    /// will remain the same.
+    ///
+    /// `None` is returned if the field is not available.
+    ///
+    /// Introduced in kernel v4.13.
+    pub fn tag(&self) -> Option<NonZeroU64> {
+        NonZeroU64::new(u64::from_be_bytes(self.0.tag))
+    }
+
+    /// The size in bytes of the program's JIT-compiled machine code.
+    ///
+    /// Note that this field is only updated when BPF JIT compiler is enabled. Kernels v4.15 and
+    /// above may already have it enabled by default.
+    ///
+    /// `None` is returned if the field is not available, or if the JIT compiler is not enabled.
+    ///
+    /// Introduced in kernel v4.13.
+    pub fn size_jitted(&self) -> Option<NonZeroU32> {
+        NonZeroU32::new(self.0.jited_prog_len)
+    }
+
+    /// The size in bytes of the program's translated eBPF bytecode.
+    ///
+    /// The translated bytecode is after it has been passed though the verifier where it was
+    /// possibly modified by the kernel.
+    ///
+    /// `None` is returned if the field is not available.
+    ///
+    /// Introduced in kernel v4.15.
+    pub fn size_translated(&self) -> Option<NonZeroU32> {
+        NonZeroU32::new(self.0.xlated_prog_len)
+    }
+
+    /// The time when the program was loaded.
+    ///
+    /// `None` is returned if the field is not available.
+    ///
+    /// Introduced in kernel v4.15.
+    pub fn loaded_at(&self) -> Option<SystemTime> {
+        if self.0.load_time > 0 {
+            Some(boot_time() + Duration::from_nanos(self.0.load_time))
+        } else {
+            None
+        }
+    }
+
+    /// The user ID of the process who loaded the program.
+    ///
+    /// `None` is returned if the field is not available.
+    ///
+    /// Introduced in kernel v4.15.
+    pub fn created_by_uid(&self) -> Option<u32> {
+        // This field was introduced in the same commit as `load_time`.
+        if self.0.load_time > 0 {
+            Some(self.0.created_by_uid)
+        } else {
+            None
+        }
+    }
+
+    /// The IDs of the maps used by the program.
+    ///
+    /// `None` is returned if the field is not available.
+    ///
+    /// Introduced in kernel v4.15.
+    pub fn map_ids(&self) -> Result<Option<Vec<NonZeroU32>>, ProgramError> {
+        if FEATURES.prog_info_map_ids() {
+            let mut map_ids = vec![0u32; self.0.nr_map_ids as usize];
+            bpf_prog_get_info_by_fd(self.fd()?.as_fd(), &mut map_ids)?;
+
+            Ok(Some(
+                map_ids
+                    .into_iter()
+                    .map(|id| NonZeroU32::new(id).unwrap())
+                    .collect(),
+            ))
+        } else {
+            Ok(None)
+        }
+    }
+
+    /// The name of the program as was provided when it was load. This is limited to 16 bytes.
+    ///
+    /// Introduced in kernel v4.15.
+    pub fn name(&self) -> &[u8] {
+        bytes_of_bpf_name(&self.0.name)
+    }
+
+    /// The name of the program as a &str.
+    ///
+    /// `None` is returned if the name was not valid unicode or if field is not available.
+    ///
+    /// Introduced in kernel v4.15.
+    pub fn name_as_str(&self) -> Option<&str> {
+        let name = std::str::from_utf8(self.name()).ok();
+        if let Some(name_str) = name {
+            if FEATURES.bpf_name() || !name_str.is_empty() {
+                return name;
+            }
+        }
+        None
+    }
+
+    /// Returns true if the program is defined with a GPL-compatible license.
+    ///
+    /// `None` is returned if the field is not available.
+    ///
+    /// Introduced in kernel v4.18.
+    pub fn gpl_compatible(&self) -> Option<bool> {
+        if FEATURES.prog_info_gpl_compatible() {
+            Some(self.0.gpl_compatible() != 0)
+        } else {
+            None
+        }
+    }
+
+    /// The BTF ID for the program.
+    ///
+    /// Introduced in kernel v5.0.
+    pub fn btf_id(&self) -> Option<NonZeroU32> {
+        NonZeroU32::new(self.0.btf_id)
+    }
+
+    /// The accumulated time that the program has been actively running.
+    ///
+    /// This is not to be confused with the duration since the program was
+    /// first loaded on the host.
+    ///
+    /// Note this field is only updated for as long as
+    /// [`enable_stats`](crate::sys::enable_stats) is enabled
+    /// with [`Stats::RunTime`](crate::sys::Stats::RunTime).
+    ///
+    /// Introduced in kernel v5.1.
+    pub fn run_time(&self) -> Duration {
+        Duration::from_nanos(self.0.run_time_ns)
+    }
+
+    /// The accumulated execution count of the program.
+    ///
+    /// Note this field is only updated for as long as
+    /// [`enable_stats`](crate::sys::enable_stats) is enabled
+    /// with [`Stats::RunTime`](crate::sys::Stats::RunTime).
+    ///
+    /// Introduced in kernel v5.1.
+    pub fn run_count(&self) -> u64 {
+        self.0.run_cnt
+    }
+
+    /// The number of verified instructions in the program.
+    ///
+    /// This may be less than the total number of instructions in the compiled program due to dead
+    /// code elimination in the verifier.
+    ///
+    /// `None` is returned if the field is not available.
+    ///
+    /// Introduced in kernel v5.16.
+    pub fn verified_instruction_count(&self) -> Option<NonZeroU32> {
+        NonZeroU32::new(self.0.verified_insns)
+    }
+
+    /// How much memory in bytes has been allocated and locked for the program.
+    pub fn memory_locked(&self) -> Result<u32, ProgramError> {
+        get_fdinfo(self.fd()?.as_fd(), "memlock")
+    }
+
+    /// Returns a file descriptor referencing the program.
+    ///
+    /// The returned file descriptor can be closed at any time and doing so does
+    /// not influence the life cycle of the program.
+    ///
+    /// Uses kernel v4.13 features.
+    pub fn fd(&self) -> Result<ProgramFd, ProgramError> {
+        let Self(info) = self;
+        let fd = bpf_prog_get_fd_by_id(info.id)?;
+        Ok(ProgramFd(fd))
+    }
+
+    /// Loads a program from a pinned path in bpffs.
+    ///
+    /// Uses kernel v4.4 and v4.13 features.
+    pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, ProgramError> {
+        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 information about a loaded program with the [`ProgramInfo`] structure.
+///
+/// This information is populated at load time by the kernel and can be used
+/// to correlate a given [`crate::programs::Program`] to it's corresponding [`ProgramInfo`]
+/// metadata.
+macro_rules! impl_info {
+    ($($struct_name:ident),+ $(,)?) => {
+        $(
+            impl $struct_name {
+                /// Returns metadata information of this program.
+                ///
+                /// Uses kernel v4.13 features.
+                pub fn info(&self) -> Result<ProgramInfo, ProgramError> {
+                    let ProgramFd(fd) = self.fd()?;
+                    ProgramInfo::new_from_fd(fd.as_fd())
+                }
+            }
+        )+
+    }
+}
+
+pub(crate) use impl_info;
+
+/// Returns an iterator of [`ProgramInfo`] over all eBPF programs loaded on the host.
+///
+/// Unlike [`Ebpf::programs`](crate::Ebpf::programs), this includes **all** programs on the host
+/// system, not just those tied to a specific [`crate::Ebpf`] instance.
+///
+/// Uses kernel v4.13 features.
+///
+/// # Example
+/// ```
+/// # use aya::programs::loaded_programs;
+/// #
+/// for p in loaded_programs() {
+///     match p {
+///         Ok(program) => println!("{}", String::from_utf8_lossy(program.name())),
+///         Err(e) => println!("Error iterating programs: {:?}", e),
+///     }
+/// }
+/// ```
+///
+/// # Errors
+///
+/// Returns [`ProgramError::SyscallError`] if any of the syscalls required to either get
+/// next program id, get the program fd, or the [`ProgramInfo`] 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_programs() -> impl Iterator<Item = Result<ProgramInfo, ProgramError>> {
+    iter_prog_ids()
+        .map(|id| {
+            let id = id?;
+            bpf_prog_get_fd_by_id(id)
+        })
+        .map(|fd| {
+            let fd = fd?;
+            bpf_prog_get_info_by_fd(fd.as_fd(), &mut [])
+        })
+        .map(|result| result.map(ProgramInfo).map_err(Into::into))
+}
+
+/// The type of eBPF program.
+#[non_exhaustive]
+#[doc(alias = "bpf_prog_type")]
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum ProgramType {
+    /// An unspecified program type.
+    Unspecified = bpf_prog_type::BPF_PROG_TYPE_UNSPEC as isize,
+    /// A Socket Filter program type. See [`SocketFilter`](super::socket_filter::SocketFilter)
+    /// for the program implementation.
+    ///
+    /// Introduced in kernel v3.19.
+    #[doc(alias = "BPF_PROG_TYPE_SOCKET_FILTER")]
+    SocketFilter = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as isize,
+    /// A Kernel Probe program type. See [`KProbe`](super::kprobe::KProbe) and
+    /// [`UProbe`](super::uprobe::UProbe) for the program implementations.
+    ///
+    /// Introduced in kernel v4.1.
+    #[doc(alias = "BPF_PROG_TYPE_KPROBE")]
+    KProbe = bpf_prog_type::BPF_PROG_TYPE_KPROBE as isize,
+    /// A Traffic Control (TC) Classifier program type. See
+    /// [`SchedClassifier`](super::tc::SchedClassifier) for the program implementation.
+    ///
+    /// Introduced in kernel v4.1.
+    #[doc(alias = "BPF_PROG_TYPE_SCHED_CLS")]
+    SchedClassifier = bpf_prog_type::BPF_PROG_TYPE_SCHED_CLS as isize,
+    /// A Traffic Control (TC) Action program type.
+    ///
+    /// Introduced in kernel v4.1.
+    #[doc(alias = "BPF_PROG_TYPE_SCHED_ACT")]
+    SchedAction = bpf_prog_type::BPF_PROG_TYPE_SCHED_ACT as isize,
+    /// A Tracepoint program type. See [`TracePoint`](super::trace_point::TracePoint) for the
+    /// program implementation.
+    ///
+    /// Introduced in kernel v4.7.
+    #[doc(alias = "BPF_PROG_TYPE_TRACEPOINT")]
+    TracePoint = bpf_prog_type::BPF_PROG_TYPE_TRACEPOINT as isize,
+    /// An Express Data Path (XDP) program type. See [`Xdp`](super::xdp::Xdp) for the program
+    /// implementation.
+    ///
+    /// Introduced in kernel v4.8.
+    #[doc(alias = "BPF_PROG_TYPE_XDP")]
+    Xdp = bpf_prog_type::BPF_PROG_TYPE_XDP as isize,
+    /// A Perf Event program type. See [`PerfEvent`](super::perf_event::PerfEvent) for the program
+    /// implementation.
+    ///
+    /// Introduced in kernel v4.9.
+    #[doc(alias = "BPF_PROG_TYPE_PERF_EVENT")]
+    PerfEvent = bpf_prog_type::BPF_PROG_TYPE_PERF_EVENT as isize,
+    /// A cGroup Socket Buffer program type. See [`CgroupSkb`](super::cgroup_skb::CgroupSkb) for
+    /// the program implementation.
+    ///
+    /// Introduced in kernel v4.10.
+    #[doc(alias = "BPF_PROG_TYPE_CGROUP_SKB")]
+    CgroupSkb = bpf_prog_type::BPF_PROG_TYPE_CGROUP_SKB as isize,
+    /// A cGroup Socket program type. See [`CgroupSock`](super::cgroup_sock::CgroupSock) for the
+    /// program implementation.
+    ///
+    /// Introduced in kernel v4.10.
+    #[doc(alias = "BPF_PROG_TYPE_CGROUP_SOCK")]
+    CgroupSock = bpf_prog_type::BPF_PROG_TYPE_CGROUP_SOCK as isize,
+    /// A Lightweight Tunnel (LWT) Input program type.
+    ///
+    /// Introduced in kernel v4.10.
+    #[doc(alias = "BPF_PROG_TYPE_LWT_IN")]
+    LwtInput = bpf_prog_type::BPF_PROG_TYPE_LWT_IN as isize,
+    /// A Lightweight Tunnel (LWT) Output program type.
+    ///
+    /// Introduced in kernel v4.10.
+    #[doc(alias = "BPF_PROG_TYPE_LWT_OUT")]
+    LwtOutput = bpf_prog_type::BPF_PROG_TYPE_LWT_OUT as isize,
+    /// A Lightweight Tunnel (LWT) Transmit program type.
+    ///
+    /// Introduced in kernel v4.10.
+    #[doc(alias = "BPF_PROG_TYPE_LWT_XMIT")]
+    LwtXmit = bpf_prog_type::BPF_PROG_TYPE_LWT_XMIT as isize,
+    /// A Socket Operation program type. See [`SockOps`](super::sock_ops::SockOps) for the program
+    /// implementation.
+    ///
+    /// Introduced in kernel v4.13.
+    #[doc(alias = "BPF_PROG_TYPE_SOCK_OPS")]
+    SockOps = bpf_prog_type::BPF_PROG_TYPE_SOCK_OPS as isize,
+    /// A Socket-to-Socket Buffer program type. See [`SkSkb`](super::sk_skb::SkSkb) for the program
+    /// implementation.
+    ///
+    /// Introduced in kernel v4.14.
+    #[doc(alias = "BPF_PROG_TYPE_SK_SKB")]
+    SkSkb = bpf_prog_type::BPF_PROG_TYPE_SK_SKB as isize,
+    /// A cGroup Device program type. See [`CgroupDevice`](super::cgroup_device::CgroupDevice)
+    /// for the program implementation.
+    ///
+    /// Introduced in kernel v4.15.
+    #[doc(alias = "BPF_PROG_TYPE_CGROUP_DEVICE")]
+    CgroupDevice = bpf_prog_type::BPF_PROG_TYPE_CGROUP_DEVICE as isize,
+    /// A Socket Message program type. See [`SkMsg`](super::sk_msg::SkMsg) for the program
+    /// implementation.
+    ///
+    /// Introduced in kernel v4.17.
+    #[doc(alias = "BPF_PROG_TYPE_SK_MSG")]
+    SkMsg = bpf_prog_type::BPF_PROG_TYPE_SK_MSG as isize,
+    /// A Raw Tracepoint program type. See [`RawTracePoint`](super::raw_trace_point::RawTracePoint)
+    /// for the program implementation.
+    ///
+    /// Introduced in kernel v4.17.
+    #[doc(alias = "BPF_PROG_TYPE_RAW_TRACEPOINT")]
+    RawTracePoint = bpf_prog_type::BPF_PROG_TYPE_RAW_TRACEPOINT as isize,
+    /// A cGroup Socket Address program type. See
+    /// [`CgroupSockAddr`](super::cgroup_sock_addr::CgroupSockAddr) for the program implementation.
+    ///
+    /// Introduced in kernel v4.17.
+    #[doc(alias = "BPF_PROG_TYPE_CGROUP_SOCK_ADDR")]
+    CgroupSockAddr = bpf_prog_type::BPF_PROG_TYPE_CGROUP_SOCK_ADDR as isize,
+    /// A Lightweight Tunnel (LWT) Seg6local program type.
+    ///
+    /// Introduced in kernel v4.18.
+    #[doc(alias = "BPF_PROG_TYPE_LWT_SEG6LOCAL")]
+    LwtSeg6local = bpf_prog_type::BPF_PROG_TYPE_LWT_SEG6LOCAL as isize,
+    /// A Linux Infrared Remote Control (LIRC) Mode2 program type. See
+    /// [`LircMode2`](super::lirc_mode2::LircMode2) for the program implementation.
+    ///
+    /// Introduced in kernel v4.18.
+    #[doc(alias = "BPF_PROG_TYPE_LIRC_MODE2")]
+    LircMode2 = bpf_prog_type::BPF_PROG_TYPE_LIRC_MODE2 as isize,
+    /// A Socket Reuseport program type.
+    ///
+    /// Introduced in kernel v4.19.
+    #[doc(alias = "BPF_PROG_TYPE_SK_REUSEPORT")]
+    SkReuseport = bpf_prog_type::BPF_PROG_TYPE_SK_REUSEPORT as isize,
+    /// A Flow Dissector program type.
+    ///
+    /// Introduced in kernel v4.20.
+    #[doc(alias = "BPF_PROG_TYPE_FLOW_DISSECTOR")]
+    FlowDissector = bpf_prog_type::BPF_PROG_TYPE_FLOW_DISSECTOR as isize,
+    /// A cGroup Sysctl program type. See [`CgroupSysctl`](super::cgroup_sysctl::CgroupSysctl) for
+    /// the program implementation.
+    ///
+    /// Introduced in kernel v5.2.
+    #[doc(alias = "BPF_PROG_TYPE_CGROUP_SYSCTL")]
+    CgroupSysctl = bpf_prog_type::BPF_PROG_TYPE_CGROUP_SYSCTL as isize,
+    /// A Writable Raw Tracepoint program type.
+    ///
+    /// Introduced in kernel v5.2.
+    #[doc(alias = "BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE")]
+    RawTracePointWritable = bpf_prog_type::BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE as isize,
+    /// A cGroup Socket Option program type. See [`CgroupSockopt`](super::cgroup_sockopt::CgroupSockopt)
+    /// for the program implementation.
+    ///
+    /// Introduced in kernel v5.3.
+    #[doc(alias = "BPF_PROG_TYPE_CGROUP_SOCKOPT")]
+    CgroupSockopt = bpf_prog_type::BPF_PROG_TYPE_CGROUP_SOCKOPT as isize,
+    /// A Tracing program type. See [`FEntry`](super::fentry::FEntry), [`FExit`](super::fexit::FExit),
+    /// and [`BtfTracePoint`](super::tp_btf::BtfTracePoint) for the program implementations.
+    ///
+    /// Introduced in kernel v5.5.
+    #[doc(alias = "BPF_PROG_TYPE_TRACING")]
+    Tracing = bpf_prog_type::BPF_PROG_TYPE_TRACING as isize,
+    /// A Struct Ops program type.
+    ///
+    /// Introduced in kernel v5.6.
+    #[doc(alias = "BPF_PROG_TYPE_STRUCT_OPS")]
+    StructOps = bpf_prog_type::BPF_PROG_TYPE_STRUCT_OPS as isize,
+    /// A Extension program type. See [`Extension`](super::extension::Extension) for the program
+    /// implementation.
+    ///
+    /// Introduced in kernel v5.6.
+    #[doc(alias = "BPF_PROG_TYPE_EXT")]
+    Extension = bpf_prog_type::BPF_PROG_TYPE_EXT as isize,
+    /// A Linux Security Module (LSM) program type. See [`Lsm`](super::lsm::Lsm) for the program
+    /// implementation.
+    ///
+    /// Introduced in kernel v5.7.
+    #[doc(alias = "BPF_PROG_TYPE_LSM")]
+    Lsm = bpf_prog_type::BPF_PROG_TYPE_LSM as isize,
+    /// A Socket Lookup program type. See [`SkLookup`](super::sk_lookup::SkLookup) for the program
+    /// implementation.
+    ///
+    /// Introduced in kernel v5.9.
+    #[doc(alias = "BPF_PROG_TYPE_SK_LOOKUP")]
+    SkLookup = bpf_prog_type::BPF_PROG_TYPE_SK_LOOKUP as isize,
+    /// A Syscall program type.
+    ///
+    /// Introduced in kernel v5.14.
+    #[doc(alias = "BPF_PROG_TYPE_SYSCALL")]
+    Syscall = bpf_prog_type::BPF_PROG_TYPE_SYSCALL as isize,
+    /// A Netfilter program type.
+    ///
+    /// Introduced in kernel v6.4.
+    #[doc(alias = "BPF_PROG_TYPE_NETFILTER")]
+    Netfilter = bpf_prog_type::BPF_PROG_TYPE_NETFILTER as isize,
+}
+
+impl TryFrom<bpf_prog_type> for ProgramType {
+    type Error = ProgramError;
+
+    fn try_from(prog_type: bpf_prog_type) -> Result<Self, Self::Error> {
+        use bpf_prog_type::*;
+        Ok(match prog_type {
+            BPF_PROG_TYPE_UNSPEC => Self::Unspecified,
+            BPF_PROG_TYPE_SOCKET_FILTER => Self::SocketFilter,
+            BPF_PROG_TYPE_KPROBE => Self::KProbe,
+            BPF_PROG_TYPE_SCHED_CLS => Self::SchedClassifier,
+            BPF_PROG_TYPE_SCHED_ACT => Self::SchedAction,
+            BPF_PROG_TYPE_TRACEPOINT => Self::TracePoint,
+            BPF_PROG_TYPE_XDP => Self::Xdp,
+            BPF_PROG_TYPE_PERF_EVENT => Self::PerfEvent,
+            BPF_PROG_TYPE_CGROUP_SKB => Self::CgroupSkb,
+            BPF_PROG_TYPE_CGROUP_SOCK => Self::CgroupSock,
+            BPF_PROG_TYPE_LWT_IN => Self::LwtInput,
+            BPF_PROG_TYPE_LWT_OUT => Self::LwtOutput,
+            BPF_PROG_TYPE_LWT_XMIT => Self::LwtXmit,
+            BPF_PROG_TYPE_SOCK_OPS => Self::SockOps,
+            BPF_PROG_TYPE_SK_SKB => Self::SkSkb,
+            BPF_PROG_TYPE_CGROUP_DEVICE => Self::CgroupDevice,
+            BPF_PROG_TYPE_SK_MSG => Self::SkMsg,
+            BPF_PROG_TYPE_RAW_TRACEPOINT => Self::RawTracePoint,
+            BPF_PROG_TYPE_CGROUP_SOCK_ADDR => Self::CgroupSockAddr,
+            BPF_PROG_TYPE_LWT_SEG6LOCAL => Self::LwtSeg6local,
+            BPF_PROG_TYPE_LIRC_MODE2 => Self::LircMode2,
+            BPF_PROG_TYPE_SK_REUSEPORT => Self::SkReuseport,
+            BPF_PROG_TYPE_FLOW_DISSECTOR => Self::FlowDissector,
+            BPF_PROG_TYPE_CGROUP_SYSCTL => Self::CgroupSysctl,
+            BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE => Self::RawTracePointWritable,
+            BPF_PROG_TYPE_CGROUP_SOCKOPT => Self::CgroupSockopt,
+            BPF_PROG_TYPE_TRACING => Self::Tracing,
+            BPF_PROG_TYPE_STRUCT_OPS => Self::StructOps,
+            BPF_PROG_TYPE_EXT => Self::Extension,
+            BPF_PROG_TYPE_LSM => Self::Lsm,
+            BPF_PROG_TYPE_SK_LOOKUP => Self::SkLookup,
+            BPF_PROG_TYPE_SYSCALL => Self::Syscall,
+            BPF_PROG_TYPE_NETFILTER => Self::Netfilter,
+            __MAX_BPF_PROG_TYPE => return Err(ProgramError::UnexpectedProgramType),
+        })
+    }
+}

+ 31 - 233
aya/src/programs/mod.rs

@@ -37,6 +37,7 @@
 //! [`maps`]: crate::maps
 
 // modules we don't export
+mod info;
 mod probe;
 mod utils;
 
@@ -71,13 +72,13 @@ pub mod xdp;
 use std::{
     ffi::CString,
     io,
-    num::NonZeroU32,
     os::fd::{AsFd, AsRawFd, BorrowedFd},
     path::{Path, PathBuf},
     sync::Arc,
-    time::{Duration, SystemTime},
 };
 
+use info::impl_info;
+pub use info::{loaded_programs, ProgramInfo, ProgramType};
 use libc::ENOSPC;
 use thiserror::Error;
 
@@ -115,18 +116,13 @@ use crate::{
     maps::MapError,
     obj::{self, btf::BtfError, VerifierLog},
     pin::PinError,
-    programs::{
-        links::*,
-        perf_attach::*,
-        utils::{boot_time, get_fdinfo},
-    },
+    programs::{links::*, perf_attach::*},
     sys::{
         bpf_btf_get_fd_by_id, bpf_get_object, bpf_link_get_fd_by_id, bpf_link_get_info_by_fd,
-        bpf_load_program, bpf_pin_object, bpf_prog_get_fd_by_id, bpf_prog_get_info_by_fd,
-        bpf_prog_query, iter_link_ids, iter_prog_ids, retry_with_verifier_logs,
-        EbpfLoadProgramAttrs, SyscallError,
+        bpf_load_program, bpf_pin_object, bpf_prog_get_fd_by_id, bpf_prog_query, iter_link_ids,
+        retry_with_verifier_logs, EbpfLoadProgramAttrs, SyscallError,
     },
-    util::{bytes_of_bpf_name, KernelVersion},
+    util::KernelVersion,
     VerifierLogLevel,
 };
 
@@ -242,7 +238,7 @@ impl AsFd for ProgramFd {
     }
 }
 
-/// eBPF program type.
+/// The various eBPF programs.
 #[derive(Debug)]
 pub enum Program {
     /// A [`KProbe`] program
@@ -296,34 +292,30 @@ pub enum Program {
 }
 
 impl Program {
-    /// Returns the low level program type.
-    pub fn prog_type(&self) -> bpf_prog_type {
-        use crate::generated::bpf_prog_type::*;
+    /// Returns the program type.
+    pub fn prog_type(&self) -> ProgramType {
         match self {
-            Self::KProbe(_) => BPF_PROG_TYPE_KPROBE,
-            Self::UProbe(_) => BPF_PROG_TYPE_KPROBE,
-            Self::TracePoint(_) => BPF_PROG_TYPE_TRACEPOINT,
-            Self::SocketFilter(_) => BPF_PROG_TYPE_SOCKET_FILTER,
-            Self::Xdp(_) => BPF_PROG_TYPE_XDP,
-            Self::SkMsg(_) => BPF_PROG_TYPE_SK_MSG,
-            Self::SkSkb(_) => BPF_PROG_TYPE_SK_SKB,
-            Self::SockOps(_) => BPF_PROG_TYPE_SOCK_OPS,
-            Self::SchedClassifier(_) => BPF_PROG_TYPE_SCHED_CLS,
-            Self::CgroupSkb(_) => BPF_PROG_TYPE_CGROUP_SKB,
-            Self::CgroupSysctl(_) => BPF_PROG_TYPE_CGROUP_SYSCTL,
-            Self::CgroupSockopt(_) => BPF_PROG_TYPE_CGROUP_SOCKOPT,
-            Self::LircMode2(_) => BPF_PROG_TYPE_LIRC_MODE2,
-            Self::PerfEvent(_) => BPF_PROG_TYPE_PERF_EVENT,
-            Self::RawTracePoint(_) => BPF_PROG_TYPE_RAW_TRACEPOINT,
-            Self::Lsm(_) => BPF_PROG_TYPE_LSM,
-            Self::BtfTracePoint(_) => BPF_PROG_TYPE_TRACING,
-            Self::FEntry(_) => BPF_PROG_TYPE_TRACING,
-            Self::FExit(_) => BPF_PROG_TYPE_TRACING,
-            Self::Extension(_) => BPF_PROG_TYPE_EXT,
-            Self::CgroupSockAddr(_) => BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
-            Self::SkLookup(_) => BPF_PROG_TYPE_SK_LOOKUP,
-            Self::CgroupSock(_) => BPF_PROG_TYPE_CGROUP_SOCK,
-            Self::CgroupDevice(_) => BPF_PROG_TYPE_CGROUP_DEVICE,
+            Self::KProbe(_) | Self::UProbe(_) => ProgramType::KProbe,
+            Self::TracePoint(_) => ProgramType::TracePoint,
+            Self::SocketFilter(_) => ProgramType::SocketFilter,
+            Self::Xdp(_) => ProgramType::Xdp,
+            Self::SkMsg(_) => ProgramType::SkMsg,
+            Self::SkSkb(_) => ProgramType::SkSkb,
+            Self::SockOps(_) => ProgramType::SockOps,
+            Self::SchedClassifier(_) => ProgramType::SchedClassifier,
+            Self::CgroupSkb(_) => ProgramType::CgroupSkb,
+            Self::CgroupSysctl(_) => ProgramType::CgroupSysctl,
+            Self::CgroupSockopt(_) => ProgramType::CgroupSockopt,
+            Self::LircMode2(_) => ProgramType::LircMode2,
+            Self::PerfEvent(_) => ProgramType::PerfEvent,
+            Self::RawTracePoint(_) => ProgramType::RawTracePoint,
+            Self::Lsm(_) => ProgramType::Lsm,
+            Self::BtfTracePoint(_) | Self::FEntry(_) | Self::FExit(_) => ProgramType::Tracing,
+            Self::Extension(_) => ProgramType::Extension,
+            Self::CgroupSockAddr(_) => ProgramType::CgroupSockAddr,
+            Self::SkLookup(_) => ProgramType::SkLookup,
+            Self::CgroupSock(_) => ProgramType::CgroupSock,
+            Self::CgroupDevice(_) => ProgramType::CgroupDevice,
         }
     }
 
@@ -952,26 +944,6 @@ impl_try_from_program!(
     CgroupDevice,
 );
 
-/// Returns information about a loaded program with the [`ProgramInfo`] structure.
-///
-/// This information is populated at load time by the kernel and can be used
-/// to correlate a given [`Program`] to it's corresponding [`ProgramInfo`]
-/// metadata.
-macro_rules! impl_info {
-    ($($struct_name:ident),+ $(,)?) => {
-        $(
-            impl $struct_name {
-                /// Returns the file descriptor of this Program.
-                pub fn info(&self) -> Result<ProgramInfo, ProgramError> {
-                    let ProgramFd(fd) = self.fd()?;
-
-                    ProgramInfo::new_from_fd(fd.as_fd())
-                }
-            }
-        )+
-    }
-}
-
 impl_info!(
     KProbe,
     UProbe,
@@ -999,180 +971,6 @@ impl_info!(
     CgroupDevice,
 );
 
-/// Provides information about a loaded program, like name, id and statistics
-#[doc(alias = "bpf_prog_info")]
-#[derive(Debug)]
-pub struct ProgramInfo(bpf_prog_info);
-
-impl ProgramInfo {
-    fn new_from_fd(fd: BorrowedFd<'_>) -> Result<Self, ProgramError> {
-        let info = bpf_prog_get_info_by_fd(fd, &mut [])?;
-        Ok(Self(info))
-    }
-
-    /// The name of the program as was provided when it was load. This is limited to 16 bytes
-    pub fn name(&self) -> &[u8] {
-        bytes_of_bpf_name(&self.0.name)
-    }
-
-    /// The name of the program as a &str. If the name was 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 program. Each program has a unique id.
-    pub fn id(&self) -> u32 {
-        self.0.id
-    }
-
-    /// The program tag.
-    ///
-    /// The program tag is a SHA sum of the program's instructions which be used as an alternative to
-    /// [`Self::id()`]". A program's id can vary every time it's loaded or unloaded, but the tag
-    /// will remain the same.
-    pub fn tag(&self) -> u64 {
-        u64::from_be_bytes(self.0.tag)
-    }
-
-    /// The program type as defined by the linux kernel enum
-    /// [`bpf_prog_type`](https://elixir.bootlin.com/linux/v6.4.4/source/include/uapi/linux/bpf.h#L948).
-    pub fn program_type(&self) -> u32 {
-        self.0.type_
-    }
-
-    /// Returns true if the program is defined with a GPL-compatible license.
-    pub fn gpl_compatible(&self) -> bool {
-        self.0.gpl_compatible() != 0
-    }
-
-    /// The ids of the maps used by the program.
-    pub fn map_ids(&self) -> Result<Vec<u32>, ProgramError> {
-        let ProgramFd(fd) = self.fd()?;
-        let mut map_ids = vec![0u32; self.0.nr_map_ids as usize];
-
-        bpf_prog_get_info_by_fd(fd.as_fd(), &mut map_ids)?;
-
-        Ok(map_ids)
-    }
-
-    /// The btf id for the program.
-    pub fn btf_id(&self) -> Option<NonZeroU32> {
-        NonZeroU32::new(self.0.btf_id)
-    }
-
-    /// The size in bytes of the program's translated eBPF bytecode, which is
-    /// the bytecode after it has been passed though the verifier where it was
-    /// possibly modified by the kernel.
-    pub fn size_translated(&self) -> u32 {
-        self.0.xlated_prog_len
-    }
-
-    /// The size in bytes of the program's JIT-compiled machine code.
-    pub fn size_jitted(&self) -> u32 {
-        self.0.jited_prog_len
-    }
-
-    /// How much memory in bytes has been allocated and locked for the program.
-    pub fn memory_locked(&self) -> Result<u32, ProgramError> {
-        get_fdinfo(self.fd()?.as_fd(), "memlock")
-    }
-
-    /// The number of verified instructions in the program.
-    ///
-    /// This may be less than the total number of instructions in the compiled
-    /// program due to dead code elimination in the verifier.
-    pub fn verified_instruction_count(&self) -> u32 {
-        self.0.verified_insns
-    }
-
-    /// The time the program was loaded.
-    pub fn loaded_at(&self) -> SystemTime {
-        boot_time() + Duration::from_nanos(self.0.load_time)
-    }
-
-    /// Returns a file descriptor referencing the program.
-    ///
-    /// The returned file descriptor can be closed at any time and doing so does
-    /// not influence the life cycle of the program.
-    pub fn fd(&self) -> Result<ProgramFd, ProgramError> {
-        let Self(info) = self;
-        let fd = bpf_prog_get_fd_by_id(info.id)?;
-        Ok(ProgramFd(fd))
-    }
-
-    /// The accumulated time that the program has been actively running.
-    ///
-    /// This is not to be confused with the duration since the program was
-    /// first loaded on the host.
-    ///
-    /// Note this field is only updated for as long as
-    /// [`enable_stats`](crate::sys::enable_stats) is enabled
-    /// with [`Stats::RunTime`](crate::sys::Stats::RunTime).
-    pub fn run_time(&self) -> Duration {
-        Duration::from_nanos(self.0.run_time_ns)
-    }
-
-    /// The accumulated execution count of the program.
-    ///
-    /// Note this field is only updated for as long as
-    /// [`enable_stats`](crate::sys::enable_stats) is enabled
-    /// with [`Stats::RunTime`](crate::sys::Stats::RunTime).
-    pub fn run_count(&self) -> u64 {
-        self.0.run_cnt
-    }
-
-    /// Loads a program from a pinned path in bpffs.
-    pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, ProgramError> {
-        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,
-        })?;
-
-        let info = bpf_prog_get_info_by_fd(fd.as_fd(), &mut [])?;
-        Ok(Self(info))
-    }
-}
-
-/// Returns an iterator over all loaded bpf programs.
-///
-/// This differs from [`crate::Ebpf::programs`] since it will return all programs
-/// listed on the host system and not only programs a specific [`crate::Ebpf`] instance.
-///
-/// # Example
-/// ```
-/// # use aya::programs::loaded_programs;
-///
-/// for p in loaded_programs() {
-///     match p {
-///         Ok(program) => println!("{}", String::from_utf8_lossy(program.name())),
-///         Err(e) => println!("Error iterating programs: {:?}", e),
-///     }
-/// }
-/// ```
-///
-/// # Errors
-///
-/// Returns [`ProgramError::SyscallError`] if any of the syscalls required to either get
-/// next program id, get the program fd, or the [`ProgramInfo`] 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_programs() -> impl Iterator<Item = Result<ProgramInfo, ProgramError>> {
-    iter_prog_ids()
-        .map(|id| {
-            let id = id?;
-            bpf_prog_get_fd_by_id(id)
-        })
-        .map(|fd| {
-            let fd = fd?;
-            bpf_prog_get_info_by_fd(fd.as_fd(), &mut [])
-        })
-        .map(|result| result.map(ProgramInfo).map_err(Into::into))
-}
-
 // TODO(https://github.com/aya-rs/aya/issues/645): this API is currently used in tests. Stabilize
 // and remove doc(hidden).
 #[doc(hidden)]

+ 70 - 3
aya/src/sys/bpf.rs

@@ -32,7 +32,7 @@ use crate::{
     },
     sys::{syscall, SysResult, Syscall, SyscallError},
     util::KernelVersion,
-    Btf, Pod, VerifierLogLevel, BPF_OBJ_NAME_LEN,
+    Btf, Pod, VerifierLogLevel, BPF_OBJ_NAME_LEN, FEATURES,
 };
 
 pub(crate) fn bpf_create_map(
@@ -105,6 +105,7 @@ pub(crate) fn bpf_pin_object(fd: BorrowedFd<'_>, path: &CStr) -> SysResult<i64>
     sys_bpf(bpf_cmd::BPF_OBJ_PIN, &mut attr)
 }
 
+/// Introduced in kernel v4.4.
 pub(crate) fn bpf_get_object(path: &CStr) -> SysResult<crate::MockableFd> {
     let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
     let u = unsafe { &mut attr.__bindgen_anon_4 };
@@ -499,6 +500,7 @@ pub(crate) fn bpf_prog_query(
     ret
 }
 
+/// Introduced in kernel v4.13.
 pub(crate) fn bpf_prog_get_fd_by_id(prog_id: u32) -> Result<crate::MockableFd, SyscallError> {
     let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
 
@@ -513,6 +515,7 @@ pub(crate) fn bpf_prog_get_fd_by_id(prog_id: u32) -> Result<crate::MockableFd, S
     })
 }
 
+/// Introduced in kernel v4.13.
 fn bpf_obj_get_info_by_fd<T, F: FnOnce(&mut T)>(
     fd: BorrowedFd<'_>,
     init: F,
@@ -541,16 +544,22 @@ fn bpf_obj_get_info_by_fd<T, F: FnOnce(&mut T)>(
     }
 }
 
+/// Introduced in kernel v4.13.
 pub(crate) fn bpf_prog_get_info_by_fd(
     fd: BorrowedFd<'_>,
     map_ids: &mut [u32],
 ) -> Result<bpf_prog_info, SyscallError> {
+    // An `E2BIG` error can occur on kernels below v4.15 when handing over a large struct where the
+    // extra space is not all-zero bytes.
     bpf_obj_get_info_by_fd(fd, |info: &mut bpf_prog_info| {
-        info.nr_map_ids = map_ids.len() as _;
-        info.map_ids = map_ids.as_mut_ptr() as _;
+        if FEATURES.prog_info_map_ids() {
+            info.nr_map_ids = map_ids.len() as _;
+            info.map_ids = map_ids.as_mut_ptr() as _;
+        }
     })
 }
 
+/// Introduced in kernel v4.13.
 pub(crate) fn bpf_map_get_fd_by_id(map_id: u32) -> Result<crate::MockableFd, SyscallError> {
     let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
 
@@ -694,6 +703,62 @@ pub(crate) fn is_prog_name_supported() -> bool {
     bpf_prog_load(&mut attr).is_ok()
 }
 
+/// Tests whether `nr_map_ids` & `map_ids` fields in `bpf_prog_info` is available.
+pub(crate) fn is_info_map_ids_supported() -> bool {
+    let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
+    let u = unsafe { &mut attr.__bindgen_anon_3 };
+
+    u.prog_type = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32;
+
+    let prog: &[u8] = &[
+        0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0
+        0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit
+    ];
+    let insns = copy_instructions(prog).unwrap();
+    u.insn_cnt = insns.len() as u32;
+    u.insns = insns.as_ptr() as u64;
+
+    let gpl = b"GPL\0";
+    u.license = gpl.as_ptr() as u64;
+
+    let prog_fd = match bpf_prog_load(&mut attr) {
+        Ok(fd) => fd,
+        Err(_) => return false,
+    };
+    bpf_obj_get_info_by_fd(prog_fd.as_fd(), |info: &mut bpf_prog_info| {
+        info.nr_map_ids = 1
+    })
+    .is_ok()
+}
+
+/// Tests whether `gpl_compatible` field in `bpf_prog_info` is available.
+pub(crate) fn is_info_gpl_compatible_supported() -> bool {
+    let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
+    let u = unsafe { &mut attr.__bindgen_anon_3 };
+
+    u.prog_type = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32;
+
+    let prog: &[u8] = &[
+        0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0
+        0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit
+    ];
+    let insns = copy_instructions(prog).unwrap();
+    u.insn_cnt = insns.len() as u32;
+    u.insns = insns.as_ptr() as u64;
+
+    let gpl = b"GPL\0";
+    u.license = gpl.as_ptr() as u64;
+
+    let prog_fd = match bpf_prog_load(&mut attr) {
+        Ok(fd) => fd,
+        Err(_) => return false,
+    };
+    if let Ok::<bpf_prog_info, _>(info) = bpf_obj_get_info_by_fd(prog_fd.as_fd(), |_| {}) {
+        return info.gpl_compatible() != 0;
+    }
+    false
+}
+
 pub(crate) fn is_probe_read_kernel_supported() -> bool {
     let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
     let u = unsafe { &mut attr.__bindgen_anon_3 };
@@ -1093,6 +1158,7 @@ fn iter_obj_ids(
     })
 }
 
+/// Introduced in kernel v4.13.
 pub(crate) fn iter_prog_ids() -> impl Iterator<Item = Result<u32, SyscallError>> {
     iter_obj_ids(bpf_cmd::BPF_PROG_GET_NEXT_ID, "bpf_prog_get_next_id")
 }
@@ -1101,6 +1167,7 @@ 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")
 }
 
+/// Introduced in kernel v4.13.
 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")
 }

+ 7 - 0
aya/src/util.rs

@@ -3,6 +3,7 @@ use std::{
     collections::BTreeMap,
     error::Error,
     ffi::{CStr, CString},
+    fmt::Display,
     fs::{self, File},
     io::{self, BufRead, BufReader},
     mem,
@@ -177,6 +178,12 @@ impl KernelVersion {
     }
 }
 
+impl Display for KernelVersion {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
+    }
+}
+
 const ONLINE_CPUS: &str = "/sys/devices/system/cpu/online";
 pub(crate) const POSSIBLE_CPUS: &str = "/sys/devices/system/cpu/possible";
 

+ 4 - 0
test/integration-ebpf/Cargo.toml

@@ -64,3 +64,7 @@ path = "src/ring_buf.rs"
 [[bin]]
 name = "memmove_test"
 path = "src/memmove_test.rs"
+
+[[bin]]
+name = "simple_prog"
+path = "src/simple_prog.rs"

+ 19 - 14
test/integration-ebpf/src/map_test.rs

@@ -1,29 +1,34 @@
+// Socket Filter program for testing with an arbitrary program with maps.
+// This is mainly used in tests with consideration for old kernels.
+
 #![no_std]
 #![no_main]
 
 use aya_ebpf::{
-    bindings::xdp_action,
-    macros::{map, xdp},
-    maps::Array,
-    programs::XdpContext,
+    macros::{map, socket_filter},
+    maps::{Array, HashMap},
+    programs::SkBuffContext,
 };
 
+// Introduced in kernel v3.19.
 #[map]
 static FOO: Array<u32> = Array::<u32>::with_max_entries(10, 0);
 
+// Introduced in kernel v3.19.
 #[map(name = "BAR")]
-static BAZ: Array<u32> = Array::<u32>::with_max_entries(10, 0);
+static BAZ: HashMap<u32, u8> = HashMap::<u32, u8>::with_max_entries(8, 0);
 
-#[xdp(frags)]
-pub fn pass(ctx: XdpContext) -> u32 {
-    match unsafe { try_pass(ctx) } {
-        Ok(ret) => ret,
-        Err(_) => xdp_action::XDP_ABORTED,
-    }
-}
+// Introduced in kernel v3.19.
+#[socket_filter]
+pub fn simple_prog(_ctx: SkBuffContext) -> i64 {
+    // So that these maps show up under the `map_ids` field.
+    FOO.get(0);
+    // If we use the literal value `0` instead of the local variable `i`, then an additional
+    // `.rodata` map will be associated with the program.
+    let i = 0;
+    BAZ.get_ptr(&i);
 
-unsafe fn try_pass(_ctx: XdpContext) -> Result<u32, u32> {
-    Ok(xdp_action::XDP_PASS)
+    0
 }
 
 #[cfg(not(test))]

+ 19 - 0
test/integration-ebpf/src/simple_prog.rs

@@ -0,0 +1,19 @@
+// Socket Filter program for testing with an arbitrary program.
+// This is mainly used in tests with consideration for old kernels.
+
+#![no_std]
+#![no_main]
+
+use aya_ebpf::{macros::socket_filter, programs::SkBuffContext};
+
+// Introduced in kernel v3.19.
+#[socket_filter]
+pub fn simple_prog(_ctx: SkBuffContext) -> i64 {
+    0
+}
+
+#[cfg(not(test))]
+#[panic_handler]
+fn panic(_info: &core::panic::PanicInfo) -> ! {
+    loop {}
+}

+ 1 - 0
test/integration-test/src/lib.rs

@@ -23,6 +23,7 @@ pub const REDIRECT: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/re
 pub const XDP_SEC: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/xdp_sec"));
 pub const RING_BUF: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/ring_buf"));
 pub const MEMMOVE_TEST: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/memmove_test"));
+pub const SIMPLE_PROG: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/simple_prog"));
 
 #[cfg(test)]
 mod tests;

+ 1 - 0
test/integration-test/src/tests.rs

@@ -1,6 +1,7 @@
 mod bpf_probe_read;
 mod btf_relocations;
 mod elf;
+mod info;
 mod load;
 mod log;
 mod rbpf;

+ 362 - 0
test/integration-test/src/tests/info.rs

@@ -0,0 +1,362 @@
+//! Tests the Info API.
+
+use std::{fs, panic, path::Path, time::SystemTime};
+
+use aya::{
+    maps::{loaded_maps, Array, HashMap, IterableMap as _, MapError, MapType},
+    programs::{loaded_programs, ProgramError, ProgramType, SocketFilter, TracePoint},
+    sys::enable_stats,
+    util::KernelVersion,
+    Ebpf,
+};
+use libc::EINVAL;
+
+use crate::utils::{kernel_assert, kernel_assert_eq};
+
+const BPF_JIT_ENABLE: &str = "/proc/sys/net/core/bpf_jit_enable";
+const BPF_STATS_ENABLED: &str = "/proc/sys/kernel/bpf_stats_enabled";
+
+#[test]
+fn test_loaded_programs() {
+    // Load a program.
+    // Since we are only testing the programs for their metadata, there is no need to "attach" them.
+    let mut bpf = Ebpf::load(crate::SIMPLE_PROG).unwrap();
+    let prog: &mut SocketFilter = bpf.program_mut("simple_prog").unwrap().try_into().unwrap();
+    prog.load().unwrap();
+    let test_prog = prog.info().unwrap();
+
+    // Ensure loaded program doesn't panic
+    let mut programs = loaded_programs().peekable();
+    if let Err(err) = programs.peek().unwrap() {
+        if let ProgramError::SyscallError(err) = &err {
+            // Skip entire test since feature not available
+            if err
+                .io_error
+                .raw_os_error()
+                .is_some_and(|errno| errno == EINVAL)
+            {
+                eprintln!(
+                    "ignoring test completely as `loaded_programs()` is not available on the host"
+                );
+                return;
+            }
+        }
+        panic!("{err}");
+    }
+
+    // Loaded programs should contain our test program
+    let mut programs = programs.filter_map(|prog| prog.ok());
+    kernel_assert!(
+        programs.any(|prog| prog.id() == test_prog.id()),
+        KernelVersion::new(4, 13, 0)
+    );
+}
+
+#[test]
+fn test_program_info() {
+    // Kernels below v4.15 have been observed to have `bpf_jit_enable` disabled by default.
+    let previously_enabled = is_sysctl_enabled(BPF_JIT_ENABLE);
+    // Restore to previous state when panic occurs.
+    let prev_panic = panic::take_hook();
+    panic::set_hook(Box::new(move |panic_info| {
+        if !previously_enabled {
+            disable_sysctl_param(BPF_JIT_ENABLE);
+        }
+        prev_panic(panic_info);
+    }));
+    let jit_enabled = previously_enabled || enable_sysctl_param(BPF_JIT_ENABLE);
+
+    let mut bpf = Ebpf::load(crate::SIMPLE_PROG).unwrap();
+    let prog: &mut SocketFilter = bpf.program_mut("simple_prog").unwrap().try_into().unwrap();
+    prog.load().unwrap();
+    let test_prog = prog.info().unwrap();
+
+    // Test `bpf_prog_info` fields.
+    kernel_assert_eq!(
+        ProgramType::SocketFilter,
+        test_prog.program_type().unwrap_or(ProgramType::Unspecified),
+        KernelVersion::new(4, 13, 0),
+    );
+    kernel_assert!(test_prog.id().is_some(), KernelVersion::new(4, 13, 0));
+    kernel_assert!(test_prog.tag().is_some(), KernelVersion::new(4, 13, 0));
+    if jit_enabled {
+        kernel_assert!(
+            test_prog.size_jitted().is_some(),
+            KernelVersion::new(4, 13, 0),
+        );
+    }
+    kernel_assert!(
+        test_prog.size_translated().is_some(),
+        KernelVersion::new(4, 13, 0),
+    );
+    kernel_assert!(
+        test_prog.loaded_at().is_some(),
+        KernelVersion::new(4, 15, 0),
+    );
+    kernel_assert!(
+        test_prog.created_by_uid().is_some_and(|uid| uid == 0),
+        KernelVersion::new(4, 15, 0),
+    );
+    let maps = test_prog.map_ids().unwrap();
+    kernel_assert!(
+        maps.is_some_and(|ids| ids.is_empty()),
+        KernelVersion::new(4, 15, 0),
+    );
+    kernel_assert!(
+        test_prog
+            .name_as_str()
+            .is_some_and(|name| name == "simple_prog"),
+        KernelVersion::new(4, 15, 0),
+    );
+    kernel_assert!(
+        test_prog.gpl_compatible().is_some_and(|gpl| gpl),
+        KernelVersion::new(4, 18, 0),
+    );
+    kernel_assert!(
+        test_prog.verified_instruction_count().is_some(),
+        KernelVersion::new(5, 16, 0),
+    );
+
+    // We can't reliably test these fields since `0` can be interpreted as the actual value or
+    // unavailable.
+    test_prog.btf_id();
+
+    // Ensure rest of the fields do not panic.
+    test_prog.memory_locked().unwrap();
+    test_prog.fd().unwrap();
+
+    // Restore to previous state
+    if !previously_enabled {
+        disable_sysctl_param(BPF_JIT_ENABLE);
+    }
+}
+
+#[test]
+fn test_loaded_at() {
+    let mut bpf: Ebpf = Ebpf::load(crate::SIMPLE_PROG).unwrap();
+    let prog: &mut SocketFilter = bpf.program_mut("simple_prog").unwrap().try_into().unwrap();
+
+    // SystemTime is not monotonic, which can cause this test to flake. We don't expect the clock
+    // timestamp to continuously jump around, so we add some retries. If the test is ever correct,
+    // we know that the value returned by loaded_at() was reasonable relative to SystemTime::now().
+    let mut failures = Vec::new();
+    for _ in 0..5 {
+        let t1 = SystemTime::now();
+        prog.load().unwrap();
+
+        let t2 = SystemTime::now();
+        let loaded_at = match prog.info().unwrap().loaded_at() {
+            Some(time) => time,
+            None => {
+                eprintln!("ignoring test completely as `load_time` field of `bpf_prog_info` is not available on the host");
+                return;
+            }
+        };
+        prog.unload().unwrap();
+
+        let range = t1..t2;
+        if range.contains(&loaded_at) {
+            failures.clear();
+            break;
+        }
+        failures.push(LoadedAtRange(loaded_at, range));
+    }
+    assert!(
+        failures.is_empty(),
+        "loaded_at was not in range: {failures:?}",
+    );
+
+    struct LoadedAtRange(SystemTime, std::ops::Range<SystemTime>);
+    impl std::fmt::Debug for LoadedAtRange {
+        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+            let Self(loaded_at, range) = self;
+            write!(f, "{range:?}.contains({loaded_at:?})")
+        }
+    }
+}
+
+#[test]
+fn test_prog_stats() {
+    // Test depends on whether trace point exists.
+    if !Path::new("/sys/kernel/debug/tracing/events/syscalls/sys_enter_bpf").exists() {
+        eprintln!(
+            "ignoring test completely as `syscalls/sys_enter_bpf` is not available on the host"
+        );
+        return;
+    }
+
+    let stats_fd = enable_stats(aya::sys::Stats::RunTime).ok();
+    // Restore to previous state when panic occurs.
+    let previously_enabled = is_sysctl_enabled(BPF_STATS_ENABLED);
+    let prev_panic = panic::take_hook();
+    panic::set_hook(Box::new(move |panic_info| {
+        if !previously_enabled {
+            disable_sysctl_param(BPF_STATS_ENABLED);
+        }
+        prev_panic(panic_info);
+    }));
+
+    let stats_enabled =
+        stats_fd.is_some() || previously_enabled || enable_sysctl_param(BPF_STATS_ENABLED);
+    if !stats_enabled {
+        eprintln!("ignoring test completely as bpf stats could not be enabled on the host");
+        return;
+    }
+
+    let mut bpf = Ebpf::load(crate::TEST).unwrap();
+    let prog: &mut TracePoint = bpf
+        .program_mut("test_tracepoint")
+        .unwrap()
+        .try_into()
+        .unwrap();
+    prog.load().unwrap();
+    prog.attach("syscalls", "sys_enter_bpf").unwrap();
+    let test_prog = prog.info().unwrap();
+
+    kernel_assert!(
+        test_prog.run_time().as_nanos() > 0,
+        KernelVersion::new(5, 1, 0)
+    );
+    kernel_assert!(test_prog.run_count() > 0, KernelVersion::new(5, 1, 0));
+
+    // Restore to previous state
+    if !previously_enabled {
+        disable_sysctl_param(BPF_STATS_ENABLED);
+    }
+}
+
+#[test]
+fn list_loaded_maps() {
+    // Load a program with maps.
+    let mut bpf: Ebpf = Ebpf::load(crate::MAP_TEST).unwrap();
+    let prog: &mut SocketFilter = bpf.program_mut("simple_prog").unwrap().try_into().unwrap();
+    prog.load().unwrap();
+
+    // Ensure the loaded_maps() api doesn't panic
+    let mut maps = loaded_maps().peekable();
+    if let Err(err) = maps.peek().unwrap() {
+        if let MapError::SyscallError(err) = &err {
+            if err
+                .io_error
+                .raw_os_error()
+                .is_some_and(|errno| errno == EINVAL)
+            {
+                eprintln!(
+                    "ignoring test completely as `loaded_maps()` is not available on the host"
+                );
+                return;
+            }
+        }
+        panic!("{err}");
+    }
+
+    // Loaded maps should contain our test maps
+    let maps: Vec<_> = maps.filter_map(|m| m.ok()).collect();
+    if let Ok(info) = &prog.info() {
+        if let Some(map_ids) = info.map_ids().unwrap() {
+            assert_eq!(2, map_ids.len());
+            for id in map_ids.iter() {
+                assert!(
+                    maps.iter().any(|m| &m.id().unwrap() == id),
+                    "expected `loaded_maps()` to have `map_ids` from program",
+                );
+            }
+        }
+    }
+
+    let hash: HashMap<_, u32, u8> = HashMap::try_from(bpf.map("BAR").unwrap()).unwrap();
+    let hash_id = hash.map().info().unwrap().id();
+    kernel_assert!(
+        maps.iter().any(|map| map.id() == hash_id),
+        KernelVersion::new(4, 13, 0),
+    );
+
+    let array: Array<_, u32> = Array::try_from(bpf.map("FOO").unwrap()).unwrap();
+    let array_id = array.map().info().unwrap().id();
+    kernel_assert!(
+        maps.iter().any(|map| map.id() == array_id),
+        KernelVersion::new(4, 13, 0),
+    );
+}
+
+#[test]
+fn test_map_info() {
+    let mut bpf: Ebpf = Ebpf::load(crate::MAP_TEST).unwrap();
+    let prog: &mut SocketFilter = bpf.program_mut("simple_prog").unwrap().try_into().unwrap();
+    prog.load().unwrap();
+
+    // Test `bpf_map_info` fields.
+    let hash: HashMap<_, u32, u8> = HashMap::try_from(bpf.map("BAR").unwrap()).unwrap();
+    let hash = hash.map().info().unwrap();
+    kernel_assert_eq!(
+        MapType::Hash,
+        hash.map_type().unwrap_or(MapType::Unspecified),
+        KernelVersion::new(4, 13, 0),
+    );
+    kernel_assert!(hash.id().is_some(), KernelVersion::new(4, 13, 0));
+    kernel_assert!(
+        hash.key_size().is_some_and(|size| size.get() == 4),
+        KernelVersion::new(4, 13, 0),
+    );
+    kernel_assert!(
+        hash.value_size().is_some_and(|size| size.get() == 1),
+        KernelVersion::new(4, 13, 0),
+    );
+    kernel_assert!(
+        hash.max_entries().is_some_and(|size| size.get() == 8),
+        KernelVersion::new(4, 13, 0),
+    );
+    kernel_assert!(
+        hash.name_as_str().is_some_and(|name| name == "BAR"),
+        KernelVersion::new(4, 15, 0),
+    );
+
+    hash.map_flags();
+    hash.fd().unwrap();
+
+    let array: Array<_, u32> = Array::try_from(bpf.map("FOO").unwrap()).unwrap();
+    let array = array.map().info().unwrap();
+    kernel_assert_eq!(
+        MapType::Array,
+        array.map_type().unwrap_or(MapType::Unspecified),
+        KernelVersion::new(4, 13, 0),
+    );
+    kernel_assert!(array.id().is_some(), KernelVersion::new(4, 13, 0));
+    kernel_assert!(
+        array.key_size().is_some_and(|size| size.get() == 4),
+        KernelVersion::new(4, 13, 0),
+    );
+    kernel_assert!(
+        array.value_size().is_some_and(|size| size.get() == 4),
+        KernelVersion::new(4, 13, 0),
+    );
+    kernel_assert!(
+        array.max_entries().is_some_and(|size| size.get() == 10),
+        KernelVersion::new(4, 13, 0),
+    );
+    kernel_assert!(
+        array.name_as_str().is_some_and(|name| name == "FOO"),
+        KernelVersion::new(4, 15, 0),
+    );
+
+    array.map_flags();
+    array.fd().unwrap();
+}
+
+/// Whether sysctl parameter is enabled in the `/proc` file.
+fn is_sysctl_enabled(path: &str) -> bool {
+    match fs::read_to_string(path) {
+        Ok(contents) => contents.chars().next().is_some_and(|c| c == '1'),
+        Err(_) => false,
+    }
+}
+
+/// Enable sysctl parameter through procfs.
+fn enable_sysctl_param(path: &str) -> bool {
+    fs::write(path, b"1").is_ok()
+}
+
+/// Disable sysctl parameter through procfs.
+fn disable_sysctl_param(path: &str) -> bool {
+    fs::write(path, b"0").is_ok()
+}

+ 2 - 44
test/integration-test/src/tests/load.rs

@@ -1,10 +1,4 @@
-use std::{
-    convert::TryInto as _,
-    fs::remove_file,
-    path::Path,
-    thread,
-    time::{Duration, SystemTime},
-};
+use std::{convert::TryInto as _, fs::remove_file, path::Path, thread, time::Duration};
 
 use aya::{
     maps::Array,
@@ -144,7 +138,7 @@ fn poll_loaded_program_id(name: &str) -> impl Iterator<Item = Option<u32>> + '_
             // program in the middle of a `loaded_programs()` call.
             loaded_programs()
                 .filter_map(|prog| prog.ok())
-                .find_map(|prog| (prog.name() == name.as_bytes()).then(|| prog.id()))
+                .find_map(|prog| (prog.name() == name.as_bytes()).then(|| prog.id().unwrap().get()))
         })
 }
 
@@ -221,42 +215,6 @@ fn unload_xdp() {
     assert_unloaded("pass");
 }
 
-#[test]
-fn test_loaded_at() {
-    let mut bpf = Ebpf::load(crate::TEST).unwrap();
-    let prog: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap();
-
-    // SystemTime is not monotonic, which can cause this test to flake. We don't expect the clock
-    // timestamp to continuously jump around, so we add some retries. If the test is ever correct,
-    // we know that the value returned by loaded_at() was reasonable relative to SystemTime::now().
-    let mut failures = Vec::new();
-    for _ in 0..5 {
-        let t1 = SystemTime::now();
-        prog.load().unwrap();
-        let t2 = SystemTime::now();
-        let loaded_at = prog.info().unwrap().loaded_at();
-        prog.unload().unwrap();
-        let range = t1..t2;
-        if range.contains(&loaded_at) {
-            failures.clear();
-            break;
-        }
-        failures.push(LoadedAtRange(loaded_at, range));
-    }
-    assert!(
-        failures.is_empty(),
-        "loaded_at was not in range: {failures:?}",
-    );
-
-    struct LoadedAtRange(SystemTime, std::ops::Range<SystemTime>);
-    impl std::fmt::Debug for LoadedAtRange {
-        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-            let Self(loaded_at, range) = self;
-            write!(f, "{range:?}.contains({loaded_at:?})")
-        }
-    }
-}
-
 #[test]
 fn unload_kprobe() {
     let mut bpf = Ebpf::load(crate::TEST).unwrap();

+ 1 - 58
test/integration-test/src/tests/smoke.rs

@@ -1,6 +1,5 @@
 use aya::{
-    maps::loaded_maps,
-    programs::{loaded_programs, Extension, TracePoint, Xdp, XdpFlags},
+    programs::{Extension, TracePoint, Xdp, XdpFlags},
     util::KernelVersion,
     Ebpf, EbpfLoader,
 };
@@ -70,59 +69,3 @@ fn extension() {
         .load(pass.fd().unwrap().try_clone().unwrap(), "xdp_pass")
         .unwrap();
 }
-
-#[test]
-fn list_loaded_programs() {
-    // Load a program.
-    let mut bpf = Ebpf::load(crate::PASS).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_programs() api doesn't panic.
-    let prog = loaded_programs()
-        .map(|p| p.unwrap())
-        .find(|p| p.name_as_str().unwrap() == "pass")
-        .unwrap();
-
-    // Ensure all relevant helper functions don't panic.
-    prog.name();
-    prog.id();
-    prog.tag();
-    prog.program_type();
-    prog.gpl_compatible();
-    prog.map_ids().unwrap();
-    prog.btf_id();
-    prog.size_translated();
-    prog.memory_locked().unwrap();
-    prog.verified_instruction_count();
-    prog.loaded_at();
-    prog.fd().unwrap();
-    prog.run_time();
-    prog.run_count();
-}
-
-#[test]
-fn list_loaded_maps() {
-    // Load a program with maps.
-    let mut bpf = Ebpf::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();
-}

+ 59 - 0
test/integration-test/src/utils.rs

@@ -70,3 +70,62 @@ impl Drop for NetNsGuard {
         println!("Exited network namespace {}", self.name);
     }
 }
+
+/// Performs `assert!` macro. If the assertion fails and host kernel version
+/// is above feature version, then fail test.
+macro_rules! kernel_assert {
+    ($cond:expr, $version:expr $(,)?) => {
+        let pass: bool = $cond;
+        if !pass {
+            let feat_version: aya::util::KernelVersion = $version;
+            let current = aya::util::KernelVersion::current().unwrap();
+            let cond_literal = stringify!($cond);
+            if current >= feat_version {
+                // Host kernel is expected to have the feat but does not
+                panic!(
+                    r#"  assertion `{cond_literal}` failed: expected host kernel v{current} to have v{feat_version} feature"#,
+                );
+            } else {
+                // Continue with tests since host is not expected to have feat
+                eprintln!(
+                    r#"ignoring assertion at {}:{}
+  assertion `{cond_literal}` failed: continuing since host kernel v{current} is not expected to have v{feat_version} feature"#,
+                    file!(), line!(),
+                );
+            }
+        }
+    };
+}
+
+pub(crate) use kernel_assert;
+
+/// Performs `assert_eq!` macro. If the assertion fails and host kernel version
+/// is above feature version, then fail test.
+macro_rules! kernel_assert_eq {
+    ($left:expr, $right:expr, $version:expr $(,)?) => {
+        if $left != $right {
+            let feat_version: aya::util::KernelVersion = $version;
+            let current = aya::util::KernelVersion::current().unwrap();
+            if current >= feat_version {
+                // Host kernel is expected to have the feat but does not
+                panic!(
+                    r#"  assertion `left == right` failed: expected host kernel v{current} to have v{feat_version} feature
+    left: {:?}
+   right: {:?}"#,
+                    $left, $right,
+                );
+            } else {
+                // Continue with tests since host is not expected to have feat
+                eprintln!(
+                    r#"ignoring assertion at {}:{}
+  assertion `left == right` failed: continuing since host kernel v{current} is not expected to have v{feat_version} feature
+    left: {:?}
+   right: {:?}"#,
+                    file!(), line!(), $left, $right,
+                );
+            }
+        }
+    };
+}
+
+pub(crate) use kernel_assert_eq;

+ 63 - 25
xtask/public-api/aya-obj.txt

@@ -1458,6 +1458,9 @@ impl core::convert::From<aya_obj::programs::cgroup_sockopt::CgroupSockoptAttachT
 pub fn aya_obj::generated::bpf_attach_type::from(s: aya_obj::programs::cgroup_sockopt::CgroupSockoptAttachType) -> aya_obj::generated::bpf_attach_type
 impl core::convert::From<aya_obj::programs::xdp::XdpAttachType> for aya_obj::generated::bpf_attach_type
 pub fn aya_obj::generated::bpf_attach_type::from(value: aya_obj::programs::xdp::XdpAttachType) -> Self
+impl core::convert::TryFrom<u32> for aya_obj::generated::bpf_attach_type
+pub type aya_obj::generated::bpf_attach_type::Error = aya_obj::InvalidTypeBinding<u32>
+pub fn aya_obj::generated::bpf_attach_type::try_from(attach_type: u32) -> core::result::Result<Self, Self::Error>
 impl core::fmt::Debug for aya_obj::generated::bpf_attach_type
 pub fn aya_obj::generated::bpf_attach_type::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
 impl core::hash::Hash for aya_obj::generated::bpf_attach_type
@@ -1593,6 +1596,9 @@ pub fn aya_obj::generated::bpf_link_type::clone(&self) -> aya_obj::generated::bp
 impl core::cmp::Eq for aya_obj::generated::bpf_link_type
 impl core::cmp::PartialEq for aya_obj::generated::bpf_link_type
 pub fn aya_obj::generated::bpf_link_type::eq(&self, other: &aya_obj::generated::bpf_link_type) -> bool
+impl core::convert::TryFrom<u32> for aya_obj::generated::bpf_link_type
+pub type aya_obj::generated::bpf_link_type::Error = aya_obj::InvalidTypeBinding<u32>
+pub fn aya_obj::generated::bpf_link_type::try_from(link_type: u32) -> core::result::Result<Self, Self::Error>
 impl core::fmt::Debug for aya_obj::generated::bpf_link_type
 pub fn aya_obj::generated::bpf_link_type::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
 impl core::hash::Hash for aya_obj::generated::bpf_link_type
@@ -1673,7 +1679,7 @@ impl core::cmp::Eq for aya_obj::generated::bpf_map_type
 impl core::cmp::PartialEq for aya_obj::generated::bpf_map_type
 pub fn aya_obj::generated::bpf_map_type::eq(&self, other: &aya_obj::generated::bpf_map_type) -> bool
 impl core::convert::TryFrom<u32> for aya_obj::generated::bpf_map_type
-pub type aya_obj::generated::bpf_map_type::Error = aya_obj::maps::InvalidMapTypeError
+pub type aya_obj::generated::bpf_map_type::Error = aya_obj::InvalidTypeBinding<u32>
 pub fn aya_obj::generated::bpf_map_type::try_from(map_type: u32) -> core::result::Result<Self, Self::Error>
 impl core::fmt::Debug for aya_obj::generated::bpf_map_type
 pub fn aya_obj::generated::bpf_map_type::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
@@ -1749,6 +1755,9 @@ pub fn aya_obj::generated::bpf_prog_type::clone(&self) -> aya_obj::generated::bp
 impl core::cmp::Eq for aya_obj::generated::bpf_prog_type
 impl core::cmp::PartialEq for aya_obj::generated::bpf_prog_type
 pub fn aya_obj::generated::bpf_prog_type::eq(&self, other: &aya_obj::generated::bpf_prog_type) -> bool
+impl core::convert::TryFrom<u32> for aya_obj::generated::bpf_prog_type
+pub type aya_obj::generated::bpf_prog_type::Error = aya_obj::InvalidTypeBinding<u32>
+pub fn aya_obj::generated::bpf_prog_type::try_from(prog_type: u32) -> core::result::Result<Self, Self::Error>
 impl core::fmt::Debug for aya_obj::generated::bpf_prog_type
 pub fn aya_obj::generated::bpf_prog_type::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
 impl core::hash::Hash for aya_obj::generated::bpf_prog_type
@@ -6603,6 +6612,7 @@ pub type aya_obj::generated::_bindgen_ty_7 = core::ffi::c_uint
 pub type aya_obj::generated::_bindgen_ty_8 = core::ffi::c_uint
 pub type aya_obj::generated::_bindgen_ty_9 = core::ffi::c_uint
 pub type aya_obj::generated::_bindgen_ty_92 = core::ffi::c_uint
+pub mod aya_obj::links
 pub mod aya_obj::maps
 pub enum aya_obj::maps::Map
 pub aya_obj::maps::Map::Btf(aya_obj::maps::BtfMap)
@@ -6806,30 +6816,6 @@ impl<T> core::clone::CloneToUninit for aya_obj::maps::BtfMapDef where T: core::c
 pub unsafe fn aya_obj::maps::BtfMapDef::clone_to_uninit(&self, dst: *mut T)
 impl<T> core::convert::From<T> for aya_obj::maps::BtfMapDef
 pub fn aya_obj::maps::BtfMapDef::from(t: T) -> T
-pub struct aya_obj::maps::InvalidMapTypeError
-pub aya_obj::maps::InvalidMapTypeError::map_type: u32
-impl core::marker::Freeze for aya_obj::maps::InvalidMapTypeError
-impl core::marker::Send for aya_obj::maps::InvalidMapTypeError
-impl core::marker::Sync for aya_obj::maps::InvalidMapTypeError
-impl core::marker::Unpin for aya_obj::maps::InvalidMapTypeError
-impl core::panic::unwind_safe::RefUnwindSafe for aya_obj::maps::InvalidMapTypeError
-impl core::panic::unwind_safe::UnwindSafe for aya_obj::maps::InvalidMapTypeError
-impl<T, U> core::convert::Into<U> for aya_obj::maps::InvalidMapTypeError where U: core::convert::From<T>
-pub fn aya_obj::maps::InvalidMapTypeError::into(self) -> U
-impl<T, U> core::convert::TryFrom<U> for aya_obj::maps::InvalidMapTypeError where U: core::convert::Into<T>
-pub type aya_obj::maps::InvalidMapTypeError::Error = core::convert::Infallible
-pub fn aya_obj::maps::InvalidMapTypeError::try_from(value: U) -> core::result::Result<T, <T as core::convert::TryFrom<U>>::Error>
-impl<T, U> core::convert::TryInto<U> for aya_obj::maps::InvalidMapTypeError where U: core::convert::TryFrom<T>
-pub type aya_obj::maps::InvalidMapTypeError::Error = <U as core::convert::TryFrom<T>>::Error
-pub fn aya_obj::maps::InvalidMapTypeError::try_into(self) -> core::result::Result<U, <U as core::convert::TryFrom<T>>::Error>
-impl<T> core::any::Any for aya_obj::maps::InvalidMapTypeError where T: 'static + core::marker::Sized
-pub fn aya_obj::maps::InvalidMapTypeError::type_id(&self) -> core::any::TypeId
-impl<T> core::borrow::Borrow<T> for aya_obj::maps::InvalidMapTypeError where T: core::marker::Sized
-pub fn aya_obj::maps::InvalidMapTypeError::borrow(&self) -> &T
-impl<T> core::borrow::BorrowMut<T> for aya_obj::maps::InvalidMapTypeError where T: core::marker::Sized
-pub fn aya_obj::maps::InvalidMapTypeError::borrow_mut(&mut self) -> &mut T
-impl<T> core::convert::From<T> for aya_obj::maps::InvalidMapTypeError
-pub fn aya_obj::maps::InvalidMapTypeError::from(t: T) -> T
 pub struct aya_obj::maps::LegacyMap
 pub aya_obj::maps::LegacyMap::data: alloc::vec::Vec<u8>
 pub aya_obj::maps::LegacyMap::def: aya_obj::maps::bpf_map_def
@@ -7123,6 +7109,8 @@ pub fn aya_obj::Features::bpf_probe_read_kernel(&self) -> bool
 pub fn aya_obj::Features::btf(&self) -> core::option::Option<&aya_obj::btf::BtfFeatures>
 pub fn aya_obj::Features::cpumap_prog_id(&self) -> bool
 pub fn aya_obj::Features::devmap_prog_id(&self) -> bool
+pub fn aya_obj::Features::prog_info_gpl_compatible(&self) -> bool
+pub fn aya_obj::Features::prog_info_map_ids(&self) -> bool
 impl core::default::Default for aya_obj::Features
 pub fn aya_obj::Features::default() -> aya_obj::Features
 impl core::fmt::Debug for aya_obj::Features
@@ -7191,6 +7179,30 @@ impl<T> core::clone::CloneToUninit for aya_obj::Function where T: core::clone::C
 pub unsafe fn aya_obj::Function::clone_to_uninit(&self, dst: *mut T)
 impl<T> core::convert::From<T> for aya_obj::Function
 pub fn aya_obj::Function::from(t: T) -> T
+pub struct aya_obj::obj::InvalidTypeBinding<T>
+pub aya_obj::obj::InvalidTypeBinding::value: T
+impl<T> core::marker::Freeze for aya_obj::InvalidTypeBinding<T> where T: core::marker::Freeze
+impl<T> core::marker::Send for aya_obj::InvalidTypeBinding<T> where T: core::marker::Send
+impl<T> core::marker::Sync for aya_obj::InvalidTypeBinding<T> where T: core::marker::Sync
+impl<T> core::marker::Unpin for aya_obj::InvalidTypeBinding<T> where T: core::marker::Unpin
+impl<T> core::panic::unwind_safe::RefUnwindSafe for aya_obj::InvalidTypeBinding<T> where T: core::panic::unwind_safe::RefUnwindSafe
+impl<T> core::panic::unwind_safe::UnwindSafe for aya_obj::InvalidTypeBinding<T> where T: core::panic::unwind_safe::UnwindSafe
+impl<T, U> core::convert::Into<U> for aya_obj::InvalidTypeBinding<T> where U: core::convert::From<T>
+pub fn aya_obj::InvalidTypeBinding<T>::into(self) -> U
+impl<T, U> core::convert::TryFrom<U> for aya_obj::InvalidTypeBinding<T> where U: core::convert::Into<T>
+pub type aya_obj::InvalidTypeBinding<T>::Error = core::convert::Infallible
+pub fn aya_obj::InvalidTypeBinding<T>::try_from(value: U) -> core::result::Result<T, <T as core::convert::TryFrom<U>>::Error>
+impl<T, U> core::convert::TryInto<U> for aya_obj::InvalidTypeBinding<T> where U: core::convert::TryFrom<T>
+pub type aya_obj::InvalidTypeBinding<T>::Error = <U as core::convert::TryFrom<T>>::Error
+pub fn aya_obj::InvalidTypeBinding<T>::try_into(self) -> core::result::Result<U, <U as core::convert::TryFrom<T>>::Error>
+impl<T> core::any::Any for aya_obj::InvalidTypeBinding<T> where T: 'static + core::marker::Sized
+pub fn aya_obj::InvalidTypeBinding<T>::type_id(&self) -> core::any::TypeId
+impl<T> core::borrow::Borrow<T> for aya_obj::InvalidTypeBinding<T> where T: core::marker::Sized
+pub fn aya_obj::InvalidTypeBinding<T>::borrow(&self) -> &T
+impl<T> core::borrow::BorrowMut<T> for aya_obj::InvalidTypeBinding<T> where T: core::marker::Sized
+pub fn aya_obj::InvalidTypeBinding<T>::borrow_mut(&mut self) -> &mut T
+impl<T> core::convert::From<T> for aya_obj::InvalidTypeBinding<T>
+pub fn aya_obj::InvalidTypeBinding<T>::from(t: T) -> T
 pub struct aya_obj::obj::Object
 pub aya_obj::obj::Object::btf: core::option::Option<aya_obj::btf::Btf>
 pub aya_obj::obj::Object::btf_ext: core::option::Option<aya_obj::btf::BtfExt>
@@ -7957,6 +7969,8 @@ pub fn aya_obj::Features::bpf_probe_read_kernel(&self) -> bool
 pub fn aya_obj::Features::btf(&self) -> core::option::Option<&aya_obj::btf::BtfFeatures>
 pub fn aya_obj::Features::cpumap_prog_id(&self) -> bool
 pub fn aya_obj::Features::devmap_prog_id(&self) -> bool
+pub fn aya_obj::Features::prog_info_gpl_compatible(&self) -> bool
+pub fn aya_obj::Features::prog_info_map_ids(&self) -> bool
 impl core::default::Default for aya_obj::Features
 pub fn aya_obj::Features::default() -> aya_obj::Features
 impl core::fmt::Debug for aya_obj::Features
@@ -8025,6 +8039,30 @@ impl<T> core::clone::CloneToUninit for aya_obj::Function where T: core::clone::C
 pub unsafe fn aya_obj::Function::clone_to_uninit(&self, dst: *mut T)
 impl<T> core::convert::From<T> for aya_obj::Function
 pub fn aya_obj::Function::from(t: T) -> T
+pub struct aya_obj::InvalidTypeBinding<T>
+pub aya_obj::InvalidTypeBinding::value: T
+impl<T> core::marker::Freeze for aya_obj::InvalidTypeBinding<T> where T: core::marker::Freeze
+impl<T> core::marker::Send for aya_obj::InvalidTypeBinding<T> where T: core::marker::Send
+impl<T> core::marker::Sync for aya_obj::InvalidTypeBinding<T> where T: core::marker::Sync
+impl<T> core::marker::Unpin for aya_obj::InvalidTypeBinding<T> where T: core::marker::Unpin
+impl<T> core::panic::unwind_safe::RefUnwindSafe for aya_obj::InvalidTypeBinding<T> where T: core::panic::unwind_safe::RefUnwindSafe
+impl<T> core::panic::unwind_safe::UnwindSafe for aya_obj::InvalidTypeBinding<T> where T: core::panic::unwind_safe::UnwindSafe
+impl<T, U> core::convert::Into<U> for aya_obj::InvalidTypeBinding<T> where U: core::convert::From<T>
+pub fn aya_obj::InvalidTypeBinding<T>::into(self) -> U
+impl<T, U> core::convert::TryFrom<U> for aya_obj::InvalidTypeBinding<T> where U: core::convert::Into<T>
+pub type aya_obj::InvalidTypeBinding<T>::Error = core::convert::Infallible
+pub fn aya_obj::InvalidTypeBinding<T>::try_from(value: U) -> core::result::Result<T, <T as core::convert::TryFrom<U>>::Error>
+impl<T, U> core::convert::TryInto<U> for aya_obj::InvalidTypeBinding<T> where U: core::convert::TryFrom<T>
+pub type aya_obj::InvalidTypeBinding<T>::Error = <U as core::convert::TryFrom<T>>::Error
+pub fn aya_obj::InvalidTypeBinding<T>::try_into(self) -> core::result::Result<U, <U as core::convert::TryFrom<T>>::Error>
+impl<T> core::any::Any for aya_obj::InvalidTypeBinding<T> where T: 'static + core::marker::Sized
+pub fn aya_obj::InvalidTypeBinding<T>::type_id(&self) -> core::any::TypeId
+impl<T> core::borrow::Borrow<T> for aya_obj::InvalidTypeBinding<T> where T: core::marker::Sized
+pub fn aya_obj::InvalidTypeBinding<T>::borrow(&self) -> &T
+impl<T> core::borrow::BorrowMut<T> for aya_obj::InvalidTypeBinding<T> where T: core::marker::Sized
+pub fn aya_obj::InvalidTypeBinding<T>::borrow_mut(&mut self) -> &mut T
+impl<T> core::convert::From<T> for aya_obj::InvalidTypeBinding<T>
+pub fn aya_obj::InvalidTypeBinding<T>::from(t: T) -> T
 pub struct aya_obj::Object
 pub aya_obj::Object::btf: core::option::Option<aya_obj::btf::Btf>
 pub aya_obj::Object::btf_ext: core::option::Option<aya_obj::btf::BtfExt>

+ 169 - 18
xtask/public-api/aya.txt

@@ -1371,8 +1371,8 @@ impl core::convert::From<aya::maps::MapError> for aya::programs::ProgramError
 pub fn aya::programs::ProgramError::from(source: aya::maps::MapError) -> Self
 impl core::convert::From<aya::sys::SyscallError> for aya::maps::MapError
 pub fn aya::maps::MapError::from(source: aya::sys::SyscallError) -> Self
-impl core::convert::From<aya_obj::maps::InvalidMapTypeError> for aya::maps::MapError
-pub fn aya::maps::MapError::from(e: aya_obj::maps::InvalidMapTypeError) -> Self
+impl core::convert::From<aya_obj::obj::InvalidTypeBinding<u32>> for aya::maps::MapError
+pub fn aya::maps::MapError::from(e: aya_obj::obj::InvalidTypeBinding<u32>) -> Self
 impl core::convert::From<std::io::error::Error> for aya::maps::MapError
 pub fn aya::maps::MapError::from(source: std::io::error::Error) -> Self
 impl core::error::Error for aya::maps::MapError
@@ -1405,6 +1405,80 @@ impl<T> core::borrow::BorrowMut<T> for aya::maps::MapError where T: core::marker
 pub fn aya::maps::MapError::borrow_mut(&mut self) -> &mut T
 impl<T> core::convert::From<T> for aya::maps::MapError
 pub fn aya::maps::MapError::from(t: T) -> T
+#[non_exhaustive] pub enum aya::maps::MapType
+pub aya::maps::MapType::Arena = 33
+pub aya::maps::MapType::Array = 2
+pub aya::maps::MapType::ArrayOfMaps = 12
+pub aya::maps::MapType::BloomFilter = 30
+pub aya::maps::MapType::CgroupArray = 8
+pub aya::maps::MapType::CgroupStorage = 19
+pub aya::maps::MapType::CgrpStorage = 32
+pub aya::maps::MapType::CpuMap = 16
+pub aya::maps::MapType::DevMap = 14
+pub aya::maps::MapType::DevMapHash = 25
+pub aya::maps::MapType::Hash = 1
+pub aya::maps::MapType::HashOfMaps = 13
+pub aya::maps::MapType::InodeStorage = 28
+pub aya::maps::MapType::LpmTrie = 11
+pub aya::maps::MapType::LruHash = 9
+pub aya::maps::MapType::LruPerCpuHash = 10
+pub aya::maps::MapType::PerCpuArray = 6
+pub aya::maps::MapType::PerCpuCgroupStorage = 21
+pub aya::maps::MapType::PerCpuHash = 5
+pub aya::maps::MapType::PerfEventArray = 4
+pub aya::maps::MapType::ProgramArray = 3
+pub aya::maps::MapType::Queue = 22
+pub aya::maps::MapType::ReuseportSockArray = 20
+pub aya::maps::MapType::RingBuf = 27
+pub aya::maps::MapType::SkStorage = 24
+pub aya::maps::MapType::SockHash = 18
+pub aya::maps::MapType::SockMap = 15
+pub aya::maps::MapType::Stack = 23
+pub aya::maps::MapType::StackTrace = 7
+pub aya::maps::MapType::StructOps = 26
+pub aya::maps::MapType::TaskStorage = 29
+pub aya::maps::MapType::Unspecified = 0
+pub aya::maps::MapType::UserRingBuf = 31
+pub aya::maps::MapType::XskMap = 17
+impl core::clone::Clone for aya::maps::MapType
+pub fn aya::maps::MapType::clone(&self) -> aya::maps::MapType
+impl core::cmp::PartialEq for aya::maps::MapType
+pub fn aya::maps::MapType::eq(&self, other: &aya::maps::MapType) -> bool
+impl core::convert::TryFrom<aya_obj::generated::linux_bindings_x86_64::bpf_map_type> for aya::maps::MapType
+pub type aya::maps::MapType::Error = aya::maps::MapError
+pub fn aya::maps::MapType::try_from(map_type: aya_obj::generated::linux_bindings_x86_64::bpf_map_type) -> core::result::Result<Self, Self::Error>
+impl core::fmt::Debug for aya::maps::MapType
+pub fn aya::maps::MapType::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+impl core::marker::Copy for aya::maps::MapType
+impl core::marker::StructuralPartialEq for aya::maps::MapType
+impl core::marker::Freeze for aya::maps::MapType
+impl core::marker::Send for aya::maps::MapType
+impl core::marker::Sync for aya::maps::MapType
+impl core::marker::Unpin for aya::maps::MapType
+impl core::panic::unwind_safe::RefUnwindSafe for aya::maps::MapType
+impl core::panic::unwind_safe::UnwindSafe for aya::maps::MapType
+impl<T, U> core::convert::Into<U> for aya::maps::MapType where U: core::convert::From<T>
+pub fn aya::maps::MapType::into(self) -> U
+impl<T, U> core::convert::TryFrom<U> for aya::maps::MapType where U: core::convert::Into<T>
+pub type aya::maps::MapType::Error = core::convert::Infallible
+pub fn aya::maps::MapType::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::MapType where U: core::convert::TryFrom<T>
+pub type aya::maps::MapType::Error = <U as core::convert::TryFrom<T>>::Error
+pub fn aya::maps::MapType::try_into(self) -> core::result::Result<U, <U as core::convert::TryFrom<T>>::Error>
+impl<T> alloc::borrow::ToOwned for aya::maps::MapType where T: core::clone::Clone
+pub type aya::maps::MapType::Owned = T
+pub fn aya::maps::MapType::clone_into(&self, target: &mut T)
+pub fn aya::maps::MapType::to_owned(&self) -> T
+impl<T> core::any::Any for aya::maps::MapType where T: 'static + core::marker::Sized
+pub fn aya::maps::MapType::type_id(&self) -> core::any::TypeId
+impl<T> core::borrow::Borrow<T> for aya::maps::MapType where T: core::marker::Sized
+pub fn aya::maps::MapType::borrow(&self) -> &T
+impl<T> core::borrow::BorrowMut<T> for aya::maps::MapType where T: core::marker::Sized
+pub fn aya::maps::MapType::borrow_mut(&mut self) -> &mut T
+impl<T> core::clone::CloneToUninit for aya::maps::MapType where T: core::clone::Clone
+pub unsafe fn aya::maps::MapType::clone_to_uninit(&self, dst: *mut T)
+impl<T> core::convert::From<T> for aya::maps::MapType
+pub fn aya::maps::MapType::from(t: T) -> T
 pub struct aya::maps::Array<T, V: aya::Pod>
 impl<T: core::borrow::Borrow<aya::maps::MapData>, V: aya::Pod> aya::maps::array::Array<T, V>
 pub fn aya::maps::array::Array<T, V>::get(&self, index: &u32, flags: u64) -> core::result::Result<V, aya::maps::MapError>
@@ -1801,14 +1875,14 @@ 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::id(&self) -> core::option::Option<core::num::nonzero::NonZeroU32>
+pub fn aya::maps::MapInfo::key_size(&self) -> core::option::Option<core::num::nonzero::NonZeroU32>
 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::map_type(&self) -> core::result::Result<aya::maps::MapType, aya::maps::MapError>
+pub fn aya::maps::MapInfo::max_entries(&self) -> core::option::Option<core::num::nonzero::NonZeroU32>
 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
+pub fn aya::maps::MapInfo::value_size(&self) -> core::option::Option<core::num::nonzero::NonZeroU32>
 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::Freeze for aya::maps::MapInfo
@@ -6644,7 +6718,7 @@ impl aya::programs::Program
 pub fn aya::programs::Program::fd(&self) -> core::result::Result<&aya::programs::ProgramFd, aya::programs::ProgramError>
 pub fn aya::programs::Program::info(&self) -> core::result::Result<aya::programs::ProgramInfo, aya::programs::ProgramError>
 pub fn aya::programs::Program::pin<P: core::convert::AsRef<std::path::Path>>(&mut self, path: P) -> core::result::Result<(), aya::pin::PinError>
-pub fn aya::programs::Program::prog_type(&self) -> aya_obj::generated::linux_bindings_x86_64::bpf_prog_type
+pub fn aya::programs::Program::prog_type(&self) -> aya::programs::ProgramType
 pub fn aya::programs::Program::unload(self) -> core::result::Result<(), aya::programs::ProgramError>
 impl core::fmt::Debug for aya::programs::Program
 pub fn aya::programs::Program::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
@@ -6892,6 +6966,79 @@ impl<T> core::borrow::BorrowMut<T> for aya::programs::ProgramError where T: core
 pub fn aya::programs::ProgramError::borrow_mut(&mut self) -> &mut T
 impl<T> core::convert::From<T> for aya::programs::ProgramError
 pub fn aya::programs::ProgramError::from(t: T) -> T
+#[non_exhaustive] pub enum aya::programs::ProgramType
+pub aya::programs::ProgramType::CgroupDevice = 15
+pub aya::programs::ProgramType::CgroupSkb = 8
+pub aya::programs::ProgramType::CgroupSock = 9
+pub aya::programs::ProgramType::CgroupSockAddr = 18
+pub aya::programs::ProgramType::CgroupSockopt = 25
+pub aya::programs::ProgramType::CgroupSysctl = 23
+pub aya::programs::ProgramType::Extension = 28
+pub aya::programs::ProgramType::FlowDissector = 22
+pub aya::programs::ProgramType::KProbe = 2
+pub aya::programs::ProgramType::LircMode2 = 20
+pub aya::programs::ProgramType::Lsm = 29
+pub aya::programs::ProgramType::LwtInput = 10
+pub aya::programs::ProgramType::LwtOutput = 11
+pub aya::programs::ProgramType::LwtSeg6local = 19
+pub aya::programs::ProgramType::LwtXmit = 12
+pub aya::programs::ProgramType::Netfilter = 32
+pub aya::programs::ProgramType::PerfEvent = 7
+pub aya::programs::ProgramType::RawTracePoint = 17
+pub aya::programs::ProgramType::RawTracePointWritable = 24
+pub aya::programs::ProgramType::SchedAction = 4
+pub aya::programs::ProgramType::SchedClassifier = 3
+pub aya::programs::ProgramType::SkLookup = 30
+pub aya::programs::ProgramType::SkMsg = 16
+pub aya::programs::ProgramType::SkReuseport = 21
+pub aya::programs::ProgramType::SkSkb = 14
+pub aya::programs::ProgramType::SockOps = 13
+pub aya::programs::ProgramType::SocketFilter = 1
+pub aya::programs::ProgramType::StructOps = 27
+pub aya::programs::ProgramType::Syscall = 31
+pub aya::programs::ProgramType::TracePoint = 5
+pub aya::programs::ProgramType::Tracing = 26
+pub aya::programs::ProgramType::Unspecified = 0
+pub aya::programs::ProgramType::Xdp = 6
+impl core::clone::Clone for aya::programs::ProgramType
+pub fn aya::programs::ProgramType::clone(&self) -> aya::programs::ProgramType
+impl core::cmp::PartialEq for aya::programs::ProgramType
+pub fn aya::programs::ProgramType::eq(&self, other: &aya::programs::ProgramType) -> bool
+impl core::convert::TryFrom<aya_obj::generated::linux_bindings_x86_64::bpf_prog_type> for aya::programs::ProgramType
+pub type aya::programs::ProgramType::Error = aya::programs::ProgramError
+pub fn aya::programs::ProgramType::try_from(prog_type: aya_obj::generated::linux_bindings_x86_64::bpf_prog_type) -> core::result::Result<Self, Self::Error>
+impl core::fmt::Debug for aya::programs::ProgramType
+pub fn aya::programs::ProgramType::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+impl core::marker::Copy for aya::programs::ProgramType
+impl core::marker::StructuralPartialEq for aya::programs::ProgramType
+impl core::marker::Freeze for aya::programs::ProgramType
+impl core::marker::Send for aya::programs::ProgramType
+impl core::marker::Sync for aya::programs::ProgramType
+impl core::marker::Unpin for aya::programs::ProgramType
+impl core::panic::unwind_safe::RefUnwindSafe for aya::programs::ProgramType
+impl core::panic::unwind_safe::UnwindSafe for aya::programs::ProgramType
+impl<T, U> core::convert::Into<U> for aya::programs::ProgramType where U: core::convert::From<T>
+pub fn aya::programs::ProgramType::into(self) -> U
+impl<T, U> core::convert::TryFrom<U> for aya::programs::ProgramType where U: core::convert::Into<T>
+pub type aya::programs::ProgramType::Error = core::convert::Infallible
+pub fn aya::programs::ProgramType::try_from(value: U) -> core::result::Result<T, <T as core::convert::TryFrom<U>>::Error>
+impl<T, U> core::convert::TryInto<U> for aya::programs::ProgramType where U: core::convert::TryFrom<T>
+pub type aya::programs::ProgramType::Error = <U as core::convert::TryFrom<T>>::Error
+pub fn aya::programs::ProgramType::try_into(self) -> core::result::Result<U, <U as core::convert::TryFrom<T>>::Error>
+impl<T> alloc::borrow::ToOwned for aya::programs::ProgramType where T: core::clone::Clone
+pub type aya::programs::ProgramType::Owned = T
+pub fn aya::programs::ProgramType::clone_into(&self, target: &mut T)
+pub fn aya::programs::ProgramType::to_owned(&self) -> T
+impl<T> core::any::Any for aya::programs::ProgramType where T: 'static + core::marker::Sized
+pub fn aya::programs::ProgramType::type_id(&self) -> core::any::TypeId
+impl<T> core::borrow::Borrow<T> for aya::programs::ProgramType where T: core::marker::Sized
+pub fn aya::programs::ProgramType::borrow(&self) -> &T
+impl<T> core::borrow::BorrowMut<T> for aya::programs::ProgramType where T: core::marker::Sized
+pub fn aya::programs::ProgramType::borrow_mut(&mut self) -> &mut T
+impl<T> core::clone::CloneToUninit for aya::programs::ProgramType where T: core::clone::Clone
+pub unsafe fn aya::programs::ProgramType::clone_to_uninit(&self, dst: *mut T)
+impl<T> core::convert::From<T> for aya::programs::ProgramType
+pub fn aya::programs::ProgramType::from(t: T) -> T
 pub enum aya::programs::SamplePolicy
 pub aya::programs::SamplePolicy::Frequency(u64)
 pub aya::programs::SamplePolicy::Period(u64)
@@ -7913,22 +8060,23 @@ pub fn aya::programs::ProgramFd::from(t: T) -> T
 pub struct aya::programs::ProgramInfo(_)
 impl aya::programs::ProgramInfo
 pub fn aya::programs::ProgramInfo::btf_id(&self) -> core::option::Option<core::num::nonzero::NonZeroU32>
+pub fn aya::programs::ProgramInfo::created_by_uid(&self) -> core::option::Option<u32>
 pub fn aya::programs::ProgramInfo::fd(&self) -> core::result::Result<aya::programs::ProgramFd, aya::programs::ProgramError>
 pub fn aya::programs::ProgramInfo::from_pin<P: core::convert::AsRef<std::path::Path>>(path: P) -> core::result::Result<Self, aya::programs::ProgramError>
-pub fn aya::programs::ProgramInfo::gpl_compatible(&self) -> bool
-pub fn aya::programs::ProgramInfo::id(&self) -> u32
-pub fn aya::programs::ProgramInfo::loaded_at(&self) -> std::time::SystemTime
-pub fn aya::programs::ProgramInfo::map_ids(&self) -> core::result::Result<alloc::vec::Vec<u32>, aya::programs::ProgramError>
+pub fn aya::programs::ProgramInfo::gpl_compatible(&self) -> core::option::Option<bool>
+pub fn aya::programs::ProgramInfo::id(&self) -> core::option::Option<core::num::nonzero::NonZeroU32>
+pub fn aya::programs::ProgramInfo::loaded_at(&self) -> core::option::Option<std::time::SystemTime>
+pub fn aya::programs::ProgramInfo::map_ids(&self) -> core::result::Result<core::option::Option<alloc::vec::Vec<core::num::nonzero::NonZeroU32>>, aya::programs::ProgramError>
 pub fn aya::programs::ProgramInfo::memory_locked(&self) -> core::result::Result<u32, aya::programs::ProgramError>
 pub fn aya::programs::ProgramInfo::name(&self) -> &[u8]
 pub fn aya::programs::ProgramInfo::name_as_str(&self) -> core::option::Option<&str>
-pub fn aya::programs::ProgramInfo::program_type(&self) -> u32
+pub fn aya::programs::ProgramInfo::program_type(&self) -> core::result::Result<aya::programs::ProgramType, aya::programs::ProgramError>
 pub fn aya::programs::ProgramInfo::run_count(&self) -> u64
 pub fn aya::programs::ProgramInfo::run_time(&self) -> core::time::Duration
-pub fn aya::programs::ProgramInfo::size_jitted(&self) -> u32
-pub fn aya::programs::ProgramInfo::size_translated(&self) -> u32
-pub fn aya::programs::ProgramInfo::tag(&self) -> u64
-pub fn aya::programs::ProgramInfo::verified_instruction_count(&self) -> u32
+pub fn aya::programs::ProgramInfo::size_jitted(&self) -> core::option::Option<core::num::nonzero::NonZeroU32>
+pub fn aya::programs::ProgramInfo::size_translated(&self) -> core::option::Option<core::num::nonzero::NonZeroU32>
+pub fn aya::programs::ProgramInfo::tag(&self) -> core::option::Option<core::num::nonzero::NonZeroU64>
+pub fn aya::programs::ProgramInfo::verified_instruction_count(&self) -> core::option::Option<core::num::nonzero::NonZeroU32>
 impl core::fmt::Debug for aya::programs::ProgramInfo
 pub fn aya::programs::ProgramInfo::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
 impl core::marker::Freeze for aya::programs::ProgramInfo
@@ -8765,6 +8913,8 @@ impl core::cmp::PartialOrd for aya::util::KernelVersion
 pub fn aya::util::KernelVersion::partial_cmp(&self, other: &aya::util::KernelVersion) -> core::option::Option<core::cmp::Ordering>
 impl core::fmt::Debug for aya::util::KernelVersion
 pub fn aya::util::KernelVersion::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+impl core::fmt::Display for aya::util::KernelVersion
+pub fn aya::util::KernelVersion::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
 impl core::marker::Copy for aya::util::KernelVersion
 impl core::marker::StructuralPartialEq for aya::util::KernelVersion
 impl core::marker::Freeze for aya::util::KernelVersion
@@ -8789,6 +8939,8 @@ impl<T> alloc::borrow::ToOwned for aya::util::KernelVersion where T: core::clone
 pub type aya::util::KernelVersion::Owned = T
 pub fn aya::util::KernelVersion::clone_into(&self, target: &mut T)
 pub fn aya::util::KernelVersion::to_owned(&self) -> T
+impl<T> alloc::string::ToString for aya::util::KernelVersion where T: core::fmt::Display + core::marker::Sized
+pub fn aya::util::KernelVersion::to_string(&self) -> alloc::string::String
 impl<T> core::any::Any for aya::util::KernelVersion where T: 'static + core::marker::Sized
 pub fn aya::util::KernelVersion::type_id(&self) -> core::any::TypeId
 impl<T> core::borrow::Borrow<T> for aya::util::KernelVersion where T: core::marker::Sized
@@ -9089,7 +9241,6 @@ impl aya::Pod for u8
 impl<K: aya::Pod> aya::Pod for aya::maps::lpm_trie::Key<K>
 impl<T: aya::Pod, const N: usize> aya::Pod for [T; N]
 pub fn aya::features() -> &'static aya_obj::obj::Features
-pub fn aya::loaded_programs() -> impl core::iter::traits::iterator::Iterator<Item = core::result::Result<aya::programs::ProgramInfo, aya::programs::ProgramError>>
 pub type aya::Bpf = aya::Ebpf
 pub type aya::BpfError = aya::EbpfError
 pub type aya::BpfLoader<'a> = aya::EbpfLoader<'a>