Procházet zdrojové kódy

Use procfs crate for kernel version parsing

This allows the logic to be shared between aya and the integration tests
without exposing additional public API surface.
Tamir Duberstein před 1 rokem
rodič
revize
b611038d5b

+ 23 - 47
aya-obj/src/obj.rs

@@ -109,7 +109,7 @@ pub struct Object {
     /// Program license
     pub license: CString,
     /// Kernel version
-    pub kernel_version: KernelVersion,
+    pub kernel_version: Option<u32>,
     /// Program BTF
     pub btf: Option<Btf>,
     /// Program BTF.ext
@@ -135,7 +135,7 @@ pub struct Program {
     /// The license
     pub license: CString,
     /// The kernel version
-    pub kernel_version: KernelVersion,
+    pub kernel_version: Option<u32>,
     /// The section containing the program
     pub section: ProgramSection,
     /// The section index of the program
@@ -579,7 +579,7 @@ impl Object {
         let kernel_version = if let Some(section) = obj.section_by_name("version") {
             parse_version(Section::try_from(&section)?.data, endianness)?
         } else {
-            KernelVersion::Any
+            None
         };
 
         let mut bpf_obj = Object::new(endianness, license, kernel_version);
@@ -631,7 +631,7 @@ impl Object {
         Ok(bpf_obj)
     }
 
-    fn new(endianness: Endianness, license: CString, kernel_version: KernelVersion) -> Object {
+    fn new(endianness: Endianness, license: CString, kernel_version: Option<u32>) -> Object {
         Object {
             endianness,
             license,
@@ -1256,7 +1256,7 @@ fn parse_license(data: &[u8]) -> Result<CString, ParseError> {
         .to_owned())
 }
 
-fn parse_version(data: &[u8], endianness: object::Endianness) -> Result<KernelVersion, ParseError> {
+fn parse_version(data: &[u8], endianness: object::Endianness) -> Result<Option<u32>, ParseError> {
     let data = match data.len() {
         4 => data.try_into().unwrap(),
         _ => {
@@ -1271,9 +1271,10 @@ fn parse_version(data: &[u8], endianness: object::Endianness) -> Result<KernelVe
         object::Endianness::Little => u32::from_le_bytes(data),
     };
 
-    Ok(match v {
-        KERNEL_VERSION_ANY => KernelVersion::Any,
-        v => KernelVersion::Version(v),
+    Ok(if v == KERNEL_VERSION_ANY {
+        None
+    } else {
+        Some(v)
     })
 }
 
@@ -1301,24 +1302,6 @@ fn get_map_field(btf: &Btf, type_id: u32) -> Result<u32, BtfError> {
     Ok(arr.len)
 }
 
-/// The parsed kernel version
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub enum KernelVersion {
-    /// Specified version
-    Version(u32),
-    /// Any version
-    Any,
-}
-
-impl From<KernelVersion> for u32 {
-    fn from(version: KernelVersion) -> u32 {
-        match version {
-            KernelVersion::Any => KERNEL_VERSION_ANY,
-            KernelVersion::Version(v) => v,
-        }
-    }
-}
-
 // Parsed '.bss' '.data' and '.rodata' sections. These sections are arrays of
 // bytes and are relocated based on their section index.
 fn parse_data_map_section(section: &Section) -> Result<Map, ParseError> {
@@ -1592,23 +1575,20 @@ mod tests {
             Err(ParseError::InvalidKernelVersion { .. })
         ));
 
-        assert_eq!(
-            parse_version(&0xFFFF_FFFEu32.to_le_bytes(), Endianness::Little)
-                .expect("failed to parse magic version"),
-            KernelVersion::Any
-        );
+        assert!(matches!(
+            parse_version(&0xFFFF_FFFEu32.to_le_bytes(), Endianness::Little),
+            Ok(None)
+        ));
 
-        assert_eq!(
-            parse_version(&0xFFFF_FFFEu32.to_be_bytes(), Endianness::Big)
-                .expect("failed to parse magic version"),
-            KernelVersion::Any
-        );
+        assert!(matches!(
+            parse_version(&0xFFFF_FFFEu32.to_be_bytes(), Endianness::Big),
+            Ok(None)
+        ));
 
-        assert_eq!(
-            parse_version(&1234u32.to_le_bytes(), Endianness::Little)
-                .expect("failed to parse magic version"),
-            KernelVersion::Version(1234)
-        );
+        assert!(matches!(
+            parse_version(&1234u32.to_le_bytes(), Endianness::Little),
+            Ok(Some(1234))
+        ));
     }
 
     #[test]
@@ -1699,11 +1679,7 @@ mod tests {
     }
 
     fn fake_obj() -> Object {
-        Object::new(
-            Endianness::Little,
-            CString::new("GPL").unwrap(),
-            KernelVersion::Any,
-        )
+        Object::new(Endianness::Little, CString::new("GPL").unwrap(), None)
     }
 
     #[test]
@@ -1753,7 +1729,7 @@ mod tests {
             obj.parse_program(&fake_section(BpfSectionKind::Program,"kprobe/foo", bytes_of(&fake_ins()))),
             Ok((Program {
                 license,
-                kernel_version: KernelVersion::Any,
+                kernel_version: None,
                 section: ProgramSection::KProbe { .. },
                 .. }, Function {
                     name,

+ 18 - 8
aya/Cargo.toml

@@ -11,21 +11,31 @@ documentation = "https://docs.rs/aya"
 edition = "2021"
 
 [dependencies]
-libc = { version = "0.2.105" }
+async-io = { version = "1.3", optional = true }
 aya-obj = { path = "../aya-obj", version = "0.1.0", features = ["std"] }
-thiserror = "1"
-object = { version = "0.31", default-features = false, features = ["std", "read_core", "elf"] }
 bitflags = "2.2.1"
 bytes = "1"
 lazy_static = "1"
-parking_lot = { version = "0.12.0", features = ["send_guard"] }
-tokio = { version = "1.24.0", features = ["macros", "rt", "rt-multi-thread", "net"], optional = true }
-async-io = { version = "1.3", optional = true }
+libc = { version = "0.2.105" }
 log = "0.4"
+object = { version = "0.31", default-features = false, features = [
+    "std",
+    "read_core",
+    "elf",
+] }
+parking_lot = { version = "0.12.0", features = ["send_guard"] }
+thiserror = "1"
+tokio = { version = "1.24.0", features = [
+    "macros",
+    "rt",
+    "rt-multi-thread",
+    "net",
+], optional = true }
+procfs = { version = "0.15.1", default-features = false }
 
 [dev-dependencies]
-matches = "0.1.8"
 futures = { version = "0.3.12", default-features = false, features = ["std"] }
+matches = "0.1.8"
 
 [features]
 default = []
@@ -35,4 +45,4 @@ async_std = ["async-io", "async"]
 
 [package.metadata.docs.rs]
 all-features = true
-rustdoc-args = ["--cfg", "docsrs","-D", "warnings"]
+rustdoc-args = ["--cfg", "docsrs", "-D", "warnings"]

+ 18 - 12
aya/src/maps/mod.rs

@@ -49,6 +49,7 @@ use std::{
 
 use libc::{getrlimit, rlimit, RLIMIT_MEMLOCK, RLIM_INFINITY};
 use log::warn;
+use procfs::KernelVersion;
 use thiserror::Error;
 
 use crate::{
@@ -56,7 +57,7 @@ use crate::{
     pin::PinError,
     sys::{
         bpf_create_map, bpf_get_object, bpf_map_get_info_by_fd, bpf_map_get_next_key,
-        bpf_pin_object, kernel_version,
+        bpf_pin_object,
     },
     util::nr_cpus,
     PinningType, Pod,
@@ -489,18 +490,23 @@ impl MapData {
 
         let c_name = CString::new(name).map_err(|_| MapError::InvalidName { name: name.into() })?;
 
-        let fd = bpf_create_map(&c_name, &self.obj, self.btf_fd).map_err(|(code, io_error)| {
-            let k_ver = kernel_version().unwrap();
-            if k_ver < (5, 11, 0) {
-                maybe_warn_rlimit();
-            }
+        #[cfg(not(test))]
+        let kernel_version = KernelVersion::current().unwrap();
+        #[cfg(test)]
+        let kernel_version = KernelVersion::new(0xff, 0xff, 0xff);
+        let fd = bpf_create_map(&c_name, &self.obj, self.btf_fd, kernel_version).map_err(
+            |(code, io_error)| {
+                if kernel_version < KernelVersion::new(5, 11, 0) {
+                    maybe_warn_rlimit();
+                }
 
-            MapError::CreateError {
-                name: name.into(),
-                code,
-                io_error,
-            }
-        })? as RawFd;
+                MapError::CreateError {
+                    name: name.into(),
+                    code,
+                    io_error,
+                }
+            },
+        )? as RawFd;
 
         self.fd = Some(fd);
 

+ 4 - 3
aya/src/programs/cgroup_device.rs

@@ -1,4 +1,6 @@
 //! Cgroup device programs.
+
+use procfs::KernelVersion;
 use std::os::fd::{AsRawFd, RawFd};
 
 use crate::{
@@ -6,7 +8,7 @@ use crate::{
     programs::{
         define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
     },
-    sys::{bpf_link_create, bpf_prog_attach, kernel_version},
+    sys::{bpf_link_create, bpf_prog_attach},
 };
 
 /// A program used to watch or prevent device interaction from a cgroup.
@@ -62,8 +64,7 @@ impl CgroupDevice {
         let prog_fd = self.data.fd_or_err()?;
         let cgroup_fd = cgroup.as_raw_fd();
 
-        let k_ver = kernel_version().unwrap();
-        if k_ver >= (5, 7, 0) {
+        if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
             let link_fd = bpf_link_create(prog_fd, cgroup_fd, BPF_CGROUP_DEVICE, None, 0).map_err(
                 |(_, io_error)| ProgramError::SyscallError {
                     call: "bpf_link_create",

+ 4 - 3
aya/src/programs/cgroup_skb.rs

@@ -1,4 +1,6 @@
 //! Cgroup skb programs.
+
+use procfs::KernelVersion;
 use std::{
     hash::Hash,
     os::fd::{AsRawFd, RawFd},
@@ -13,7 +15,7 @@ use crate::{
     programs::{
         define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
     },
-    sys::{bpf_link_create, bpf_prog_attach, kernel_version},
+    sys::{bpf_link_create, bpf_prog_attach},
 };
 
 /// A program used to inspect or filter network activity for a given cgroup.
@@ -96,8 +98,7 @@ impl CgroupSkb {
             CgroupSkbAttachType::Ingress => BPF_CGROUP_INET_INGRESS,
             CgroupSkbAttachType::Egress => BPF_CGROUP_INET_EGRESS,
         };
-        let k_ver = kernel_version().unwrap();
-        if k_ver >= (5, 7, 0) {
+        if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
             let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err(
                 |(_, io_error)| ProgramError::SyscallError {
                     call: "bpf_link_create",

+ 4 - 3
aya/src/programs/cgroup_sock.rs

@@ -1,6 +1,8 @@
 //! Cgroup socket programs.
+
 pub use aya_obj::programs::CgroupSockAttachType;
 
+use procfs::KernelVersion;
 use std::{
     hash::Hash,
     os::fd::{AsRawFd, RawFd},
@@ -12,7 +14,7 @@ use crate::{
     programs::{
         define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
     },
-    sys::{bpf_link_create, bpf_prog_attach, kernel_version},
+    sys::{bpf_link_create, bpf_prog_attach},
 };
 
 /// A program that is called on socket creation, bind and release.
@@ -71,8 +73,7 @@ impl CgroupSock {
         let prog_fd = self.data.fd_or_err()?;
         let cgroup_fd = cgroup.as_raw_fd();
         let attach_type = self.data.expected_attach_type.unwrap();
-        let k_ver = kernel_version().unwrap();
-        if k_ver >= (5, 7, 0) {
+        if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
             let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err(
                 |(_, io_error)| ProgramError::SyscallError {
                     call: "bpf_link_create",

+ 4 - 3
aya/src/programs/cgroup_sock_addr.rs

@@ -1,6 +1,8 @@
 //! Cgroup socket address programs.
+
 pub use aya_obj::programs::CgroupSockAddrAttachType;
 
+use procfs::KernelVersion;
 use std::{
     hash::Hash,
     os::fd::{AsRawFd, RawFd},
@@ -12,7 +14,7 @@ use crate::{
     programs::{
         define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
     },
-    sys::{bpf_link_create, bpf_prog_attach, kernel_version},
+    sys::{bpf_link_create, bpf_prog_attach},
 };
 
 /// A program that can be used to inspect or modify socket addresses (`struct sockaddr`).
@@ -72,8 +74,7 @@ impl CgroupSockAddr {
         let prog_fd = self.data.fd_or_err()?;
         let cgroup_fd = cgroup.as_raw_fd();
         let attach_type = self.data.expected_attach_type.unwrap();
-        let k_ver = kernel_version().unwrap();
-        if k_ver >= (5, 7, 0) {
+        if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
             let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err(
                 |(_, io_error)| ProgramError::SyscallError {
                     call: "bpf_link_create",

+ 4 - 3
aya/src/programs/cgroup_sockopt.rs

@@ -1,6 +1,8 @@
 //! Cgroup socket option programs.
+
 pub use aya_obj::programs::CgroupSockoptAttachType;
 
+use procfs::KernelVersion;
 use std::{
     hash::Hash,
     os::fd::{AsRawFd, RawFd},
@@ -12,7 +14,7 @@ use crate::{
     programs::{
         define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
     },
-    sys::{bpf_link_create, bpf_prog_attach, kernel_version},
+    sys::{bpf_link_create, bpf_prog_attach},
 };
 
 /// A program that can be used to get or set options on sockets.
@@ -69,8 +71,7 @@ impl CgroupSockopt {
         let prog_fd = self.data.fd_or_err()?;
         let cgroup_fd = cgroup.as_raw_fd();
         let attach_type = self.data.expected_attach_type.unwrap();
-        let k_ver = kernel_version().unwrap();
-        if k_ver >= (5, 7, 0) {
+        if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
             let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err(
                 |(_, io_error)| ProgramError::SyscallError {
                     call: "bpf_link_create",

+ 4 - 3
aya/src/programs/cgroup_sysctl.rs

@@ -1,4 +1,6 @@
 //! Cgroup sysctl programs.
+
+use procfs::KernelVersion;
 use std::{
     hash::Hash,
     os::fd::{AsRawFd, RawFd},
@@ -9,7 +11,7 @@ use crate::{
     programs::{
         define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
     },
-    sys::{bpf_link_create, bpf_prog_attach, kernel_version},
+    sys::{bpf_link_create, bpf_prog_attach},
 };
 
 /// A program used to watch for sysctl changes.
@@ -64,8 +66,7 @@ impl CgroupSysctl {
         let prog_fd = self.data.fd_or_err()?;
         let cgroup_fd = cgroup.as_raw_fd();
 
-        let k_ver = kernel_version().unwrap();
-        if k_ver >= (5, 7, 0) {
+        if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
             let link_fd = bpf_link_create(prog_fd, cgroup_fd, BPF_CGROUP_SYSCTL, None, 0).map_err(
                 |(_, io_error)| ProgramError::SyscallError {
                     call: "bpf_link_create",

+ 10 - 8
aya/src/programs/mod.rs

@@ -65,6 +65,7 @@ mod utils;
 pub mod xdp;
 
 use libc::ENOSPC;
+use procfs::KernelVersion;
 use std::{
     ffi::CString,
     io,
@@ -105,7 +106,7 @@ pub use xdp::{Xdp, XdpError, XdpFlags};
 use crate::{
     generated::{bpf_attach_type, bpf_prog_info, bpf_prog_type},
     maps::MapError,
-    obj::{self, btf::BtfError, Function, KernelVersion},
+    obj::{self, btf::BtfError, Function},
     pin::PinError,
     sys::{
         bpf_btf_get_fd_by_id, bpf_get_object, bpf_load_program, bpf_pin_object,
@@ -573,13 +574,14 @@ fn load_program<T: Link>(
         },
     ) = obj;
 
-    let target_kernel_version = match *kernel_version {
-        KernelVersion::Any => {
-            let (major, minor, patch) = crate::sys::kernel_version().unwrap();
-            (major << 16) + (minor << 8) + patch
-        }
-        _ => (*kernel_version).into(),
-    };
+    let target_kernel_version = kernel_version.unwrap_or_else(|| {
+        let KernelVersion {
+            major,
+            minor,
+            patch,
+        } = KernelVersion::current().unwrap();
+        (u32::from(major) << 16) + (u32::from(minor) << 8) + u32::from(patch)
+    });
 
     let mut logger = VerifierLog::new();
 

+ 3 - 3
aya/src/programs/probe.rs

@@ -1,4 +1,5 @@
 use libc::pid_t;
+use procfs::KernelVersion;
 use std::{
     fs::{self, OpenOptions},
     io::{self, Write},
@@ -13,7 +14,7 @@ use crate::{
         trace_point::read_sys_fs_trace_point_id, uprobe::UProbeError, utils::find_tracefs_path,
         Link, ProgramData, ProgramError,
     },
-    sys::{kernel_version, perf_event_open_probe, perf_event_open_trace_point},
+    sys::{perf_event_open_probe, perf_event_open_trace_point},
 };
 
 static PROBE_NAME_INDEX: AtomicUsize = AtomicUsize::new(0);
@@ -49,8 +50,7 @@ pub(crate) fn attach<T: Link + From<PerfLinkInner>>(
 ) -> Result<T::Id, ProgramError> {
     // https://github.com/torvalds/linux/commit/e12f03d7031a977356e3d7b75a68c2185ff8d155
     // Use debugfs to create probe
-    let k_ver = kernel_version().unwrap();
-    if k_ver < (4, 17, 0) {
+    if KernelVersion::current().unwrap() >= KernelVersion::new(4, 17, 0) {
         let (fd, event_alias) = create_as_trace_point(kind, fn_name, offset, pid)?;
         let link = T::from(perf_attach_debugfs(
             program_data.fd_or_err()?,

+ 5 - 8
aya/src/programs/xdp.rs

@@ -1,6 +1,8 @@
 //! eXpress Data Path (XDP) programs.
+
 use bitflags;
 use libc::if_nametoindex;
+use procfs::KernelVersion;
 use std::{convert::TryFrom, ffi::CString, hash::Hash, io, mem, os::unix::io::RawFd};
 use thiserror::Error;
 
@@ -15,10 +17,7 @@ use crate::{
     programs::{
         define_link_wrapper, load_program, FdLink, Link, LinkError, ProgramData, ProgramError,
     },
-    sys::{
-        bpf_link_create, bpf_link_get_info_by_fd, bpf_link_update, kernel_version,
-        netlink_set_xdp_fd,
-    },
+    sys::{bpf_link_create, bpf_link_get_info_by_fd, bpf_link_update, netlink_set_xdp_fd},
 };
 
 /// The type returned when attaching an [`Xdp`] program fails on kernels `< 5.9`.
@@ -126,8 +125,7 @@ impl Xdp {
         let prog_fd = self.data.fd_or_err()?;
         let if_index = if_index as RawFd;
 
-        let k_ver = kernel_version().unwrap();
-        if k_ver >= (5, 9, 0) {
+        if KernelVersion::current().unwrap() >= KernelVersion::new(5, 9, 0) {
             let link_fd = bpf_link_create(prog_fd, if_index, BPF_XDP, None, flags.bits()).map_err(
                 |(_, io_error)| ProgramError::SyscallError {
                     call: "bpf_link_create",
@@ -224,8 +222,7 @@ impl Link for NlLink {
     }
 
     fn detach(self) -> Result<(), ProgramError> {
-        let k_ver = kernel_version().unwrap();
-        let flags = if k_ver >= (5, 7, 0) {
+        let flags = if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
             self.flags.bits() | XDP_FLAGS_REPLACE
         } else {
             self.flags.bits()

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

@@ -12,6 +12,7 @@ use obj::{
     maps::{bpf_map_def, LegacyMap},
     BpfSectionKind,
 };
+use procfs::KernelVersion;
 
 use crate::{
     generated::{
@@ -27,12 +28,17 @@ use crate::{
         },
         copy_instructions,
     },
-    sys::{kernel_version, syscall, SysResult, Syscall},
+    sys::{syscall, SysResult, Syscall},
     util::VerifierLog,
     Btf, Pod, BPF_OBJ_NAME_LEN,
 };
 
-pub(crate) fn bpf_create_map(name: &CStr, def: &obj::Map, btf_fd: Option<RawFd>) -> SysResult {
+pub(crate) fn bpf_create_map(
+    name: &CStr,
+    def: &obj::Map,
+    btf_fd: Option<RawFd>,
+    kernel_version: KernelVersion,
+) -> SysResult {
     let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
 
     let u = unsafe { &mut attr.__bindgen_anon_1 };
@@ -78,8 +84,7 @@ pub(crate) fn bpf_create_map(name: &CStr, def: &obj::Map, btf_fd: Option<RawFd>)
     // https://github.com/torvalds/linux/commit/ad5b177bd73f5107d97c36f56395c4281fb6f089
     // The map name was added as a parameter in kernel 4.15+ so we skip adding it on
     // older kernels for compatibility
-    let k_ver = kernel_version().unwrap();
-    if k_ver >= (4, 15, 0) {
+    if kernel_version >= KernelVersion::new(4, 15, 0) {
         // u.map_name is 16 bytes max and must be NULL terminated
         let name_len = cmp::min(name.to_bytes().len(), BPF_OBJ_NAME_LEN - 1);
         u.map_name[..name_len]

+ 19 - 124
aya/src/sys/mod.rs

@@ -5,15 +5,9 @@ mod perf_event;
 #[cfg(test)]
 mod fake;
 
-use std::io;
-#[cfg(not(test))]
-use std::{ffi::CString, mem};
-#[cfg(not(test))]
-use std::{fs::File, io::Read};
+use std::{io, mem};
 
-#[cfg(not(test))]
-use libc::utsname;
-use libc::{c_int, c_long, pid_t};
+use libc::{c_int, c_long, pid_t, SYS_bpf, SYS_perf_event_open};
 
 pub(crate) use bpf::*;
 #[cfg(test)]
@@ -25,7 +19,6 @@ use crate::generated::{bpf_attr, bpf_cmd, perf_event_attr};
 
 pub(crate) type SysResult = Result<c_long, (c_long, io::Error)>;
 
-#[cfg_attr(test, allow(dead_code))]
 pub(crate) enum Syscall<'a> {
     Bpf {
         cmd: bpf_cmd,
@@ -46,126 +39,28 @@ pub(crate) enum Syscall<'a> {
 }
 
 fn syscall(call: Syscall) -> SysResult {
-    #[cfg(not(test))]
-    return unsafe { syscall_impl(call) };
-
     #[cfg(test)]
     return TEST_SYSCALL.with(|test_impl| unsafe { test_impl.borrow()(call) });
-}
-
-#[cfg(not(test))]
-unsafe fn syscall_impl(call: Syscall) -> SysResult {
-    use libc::{SYS_bpf, SYS_perf_event_open};
-
-    use Syscall::*;
-    let ret = match call {
-        Bpf { cmd, attr } => libc::syscall(SYS_bpf, cmd, attr, mem::size_of::<bpf_attr>()),
-        PerfEventOpen {
-            attr,
-            pid,
-            cpu,
-            group,
-            flags,
-        } => libc::syscall(SYS_perf_event_open, &attr, pid, cpu, group, flags),
-        PerfEventIoctl { fd, request, arg } => {
-            libc::ioctl(fd, request.try_into().unwrap(), arg) as libc::c_long
-        }
-    };
-
-    if ret < 0 {
-        return Err((ret, io::Error::last_os_error()));
-    }
-
-    Ok(ret)
-}
-
-#[cfg(test)]
-pub(crate) fn kernel_version() -> Result<(u32, u32, u32), ()> {
-    Ok((0xff, 0xff, 0xff))
-}
 
-#[cfg(not(test))]
-fn ubuntu_kernel_version() -> Result<(u32, u32, u32), ()> {
-    if let Ok(mut file) = File::open("/proc/version_signature") {
-        let mut buf = String::new();
-        let mut major = 0u32;
-        let mut minor = 0u32;
-        let mut patch = 0u32;
-        let format = CString::new("%*s %*s %u.%u.%u\n").unwrap();
-
-        file.read_to_string(&mut buf).map_err(|_| ())?;
-
-        unsafe {
-            if libc::sscanf(
-                buf.as_ptr() as *const _,
-                format.as_ptr(),
-                &mut major as *mut u32,
-                &mut minor as *mut _,
-                &mut patch as *mut _,
-            ) == 3
-            {
-                return Ok((major, minor, patch));
+    #[cfg_attr(test, allow(unreachable_code))]
+    match unsafe {
+        match call {
+            Syscall::Bpf { cmd, attr } => {
+                libc::syscall(SYS_bpf, cmd, attr, mem::size_of::<bpf_attr>())
             }
-        }
-    }
-
-    Err(())
-}
-
-#[cfg(not(test))]
-pub(crate) fn kernel_version() -> Result<(u32, u32, u32), ()> {
-    if let Ok(version) = ubuntu_kernel_version() {
-        return Ok(version);
-    }
-
-    unsafe {
-        let mut v = mem::zeroed::<utsname>();
-        if libc::uname(&mut v as *mut _) != 0 {
-            return Err(());
-        }
-
-        let mut major = 0u32;
-        let mut minor = 0u32;
-        let mut patch = 0u32;
-
-        let debian_marker = CString::new("Debian").unwrap();
-
-        let p = libc::strstr(v.version.as_ptr(), debian_marker.as_ptr());
-
-        if !p.is_null() {
-            let debian_format = CString::new("Debian %u.%u.%u").map_err(|_| ())?;
-
-            if libc::sscanf(
-                p,
-                debian_format.as_ptr(),
-                &mut major as *mut u32,
-                &mut minor as *mut _,
-                &mut patch as *mut _,
-            ) == 3
-            {
-                // On Debian 10, kernels after 4.19.229 expect 4.19.255 due to broken Makefile patches.
-                let patch_level_limit = if major == 4 && minor == 19 { 230 } else { 255 };
-
-                if patch >= patch_level_limit {
-                    patch = 255;
-                }
-
-                return Ok((major, minor, patch));
+            Syscall::PerfEventOpen {
+                attr,
+                pid,
+                cpu,
+                group,
+                flags,
+            } => libc::syscall(SYS_perf_event_open, &attr, pid, cpu, group, flags),
+            Syscall::PerfEventIoctl { fd, request, arg } => {
+                libc::ioctl(fd, request.try_into().unwrap(), arg) as libc::c_long
             }
         }
-
-        let format = CString::new("%u.%u.%u").unwrap();
-        if libc::sscanf(
-            v.release.as_ptr(),
-            format.as_ptr(),
-            &mut major as *mut u32,
-            &mut minor as *mut _,
-            &mut patch as *mut _,
-        ) != 3
-        {
-            return Err(());
-        }
-
-        Ok((major, minor, patch))
+    } {
+        ret @ 0.. => Ok(ret),
+        ret => Err((ret, io::Error::last_os_error())),
     }
 }

+ 1 - 1
test/integration-test/Cargo.toml

@@ -12,7 +12,7 @@ aya-obj = { path = "../../aya-obj" }
 libc = { version = "0.2.105" }
 log = "0.4"
 object = { version = "0.31", default-features = false, features = ["std", "read_core", "elf"] }
+procfs = "0.15.1"
 rbpf = "0.2.0"
-regex = "1"
 tempfile = "3.3.0"
 tokio = { version = "1.24", features = ["rt", "rt-multi-thread", "sync", "time"] }

+ 0 - 23
test/integration-test/tests/common.rs

@@ -1,23 +0,0 @@
-use anyhow::bail;
-use libc::{uname, utsname};
-use regex::Regex;
-use std::{cell::OnceCell, ffi::CStr, mem};
-
-pub fn kernel_version() -> anyhow::Result<(u8, u8, u8)> {
-    static mut RE: OnceCell<Regex> = OnceCell::new();
-    let re =
-        unsafe { &mut RE }.get_or_init(|| Regex::new(r"^([0-9]+)\.([0-9]+)\.([0-9]+)").unwrap());
-    let mut data: utsname = unsafe { mem::zeroed() };
-    let ret = unsafe { uname(&mut data) };
-    assert!(ret >= 0, "libc::uname failed.");
-    let release_cstr = unsafe { CStr::from_ptr(data.release.as_ptr()) };
-    let release = release_cstr.to_string_lossy();
-    if let Some(caps) = re.captures(&release) {
-        let major = caps.get(1).unwrap().as_str().parse().unwrap();
-        let minor = caps.get(2).unwrap().as_str().parse().unwrap();
-        let patch = caps.get(3).unwrap().as_str().parse().unwrap();
-        Ok((major, minor, patch))
-    } else {
-        bail!("no kernel version found");
-    }
-}

+ 3 - 5
test/integration-test/tests/load.rs

@@ -1,3 +1,4 @@
+use procfs::KernelVersion;
 use std::{convert::TryInto as _, thread, time};
 
 use aya::{
@@ -10,9 +11,6 @@ use aya::{
     Bpf,
 };
 
-mod common;
-use common::kernel_version;
-
 const MAX_RETRIES: u32 = 100;
 const RETRY_DURATION_MS: u64 = 10;
 
@@ -133,7 +131,7 @@ fn unload_kprobe() {
 
 #[test]
 fn pin_link() {
-    if kernel_version().unwrap() < (5, 9, 0) {
+    if KernelVersion::current().unwrap() < KernelVersion::new(5, 9, 0) {
         eprintln!("skipping test, XDP uses netlink");
         return;
     }
@@ -168,7 +166,7 @@ fn pin_link() {
 
 #[test]
 fn pin_lifecycle() {
-    if kernel_version().unwrap() < (5, 9, 0) {
+    if KernelVersion::current().unwrap() < KernelVersion::new(5, 9, 0) {
         eprintln!("skipping test, XDP uses netlink");
         return;
     }

+ 4 - 10
test/integration-test/tests/smoke.rs

@@ -1,12 +1,11 @@
+use procfs::KernelVersion;
+
 use aya::{
     include_bytes_aligned,
     programs::{Extension, Xdp, XdpFlags},
     Bpf, BpfLoader,
 };
 
-mod common;
-use common::kernel_version;
-
 #[test]
 fn xdp() {
     let bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/pass");
@@ -18,15 +17,10 @@ fn xdp() {
 
 #[test]
 fn extension() {
-    let (major, minor, _) = kernel_version().unwrap();
-    if major < 5 || (minor == 5 && minor < 9) {
-        eprintln!(
-            "skipping as {}.{} does not meet version requirement of 5.9",
-            major, minor
-        );
+    if KernelVersion::current().unwrap() < KernelVersion::new(5, 9, 0) {
+        eprintln!("skipping test, XDP uses netlink");
         return;
     }
-    // TODO: Check kernel version == 5.9 or later
     let main_bytes =
         include_bytes_aligned!("../../../target/bpfel-unknown-none/release/main.bpf.o");
     let mut bpf = Bpf::load(main_bytes).unwrap();