diff --git a/aya-obj/src/btf/btf.rs b/aya-obj/src/btf/btf.rs index 295fb55e..a9049b1e 100644 --- a/aya-obj/src/btf/btf.rs +++ b/aya-obj/src/btf/btf.rs @@ -135,7 +135,7 @@ pub enum BtfError { #[source] io_error: std::io::Error, /// The error log produced by the kernel verifier. - verifier_log: Cow<'static, str>, + verifier_log: String, }, /// offset not found for symbol diff --git a/aya/Cargo.toml b/aya/Cargo.toml index 2abcfc47..e91da921 100644 --- a/aya/Cargo.toml +++ b/aya/Cargo.toml @@ -24,6 +24,7 @@ object = { version = "0.31", default-features = false, features = [ "elf", ] } parking_lot = { version = "0.12.0", features = ["send_guard"] } +text_io = "0.1.12" thiserror = "1" tokio = { version = "1.24.0", features = [ "macros", @@ -31,7 +32,6 @@ tokio = { version = "1.24.0", features = [ "rt-multi-thread", "net", ], optional = true } -procfs = { version = "0.15.1", default-features = false } [dev-dependencies] futures = { version = "0.3.12", default-features = false, features = ["std"] } diff --git a/aya/src/maps/mod.rs b/aya/src/maps/mod.rs index 3961c212..2332e1f5 100644 --- a/aya/src/maps/mod.rs +++ b/aya/src/maps/mod.rs @@ -47,9 +47,9 @@ use std::{ ptr, }; +use crate::util::KernelVersion; use libc::{getrlimit, rlimit, RLIMIT_MEMLOCK, RLIM_INFINITY}; use log::warn; -use procfs::KernelVersion; use thiserror::Error; use crate::{ diff --git a/aya/src/programs/cgroup_device.rs b/aya/src/programs/cgroup_device.rs index b1a8e39d..e3580b28 100644 --- a/aya/src/programs/cgroup_device.rs +++ b/aya/src/programs/cgroup_device.rs @@ -1,6 +1,6 @@ //! Cgroup device programs. -use procfs::KernelVersion; +use crate::util::KernelVersion; use std::os::fd::{AsRawFd, RawFd}; use crate::{ diff --git a/aya/src/programs/cgroup_skb.rs b/aya/src/programs/cgroup_skb.rs index a76bb14a..f67b56fe 100644 --- a/aya/src/programs/cgroup_skb.rs +++ b/aya/src/programs/cgroup_skb.rs @@ -1,6 +1,6 @@ //! Cgroup skb programs. -use procfs::KernelVersion; +use crate::util::KernelVersion; use std::{ hash::Hash, os::fd::{AsRawFd, RawFd}, diff --git a/aya/src/programs/cgroup_sock.rs b/aya/src/programs/cgroup_sock.rs index 19ac6cf8..9d66f9aa 100644 --- a/aya/src/programs/cgroup_sock.rs +++ b/aya/src/programs/cgroup_sock.rs @@ -2,7 +2,7 @@ pub use aya_obj::programs::CgroupSockAttachType; -use procfs::KernelVersion; +use crate::util::KernelVersion; use std::{ hash::Hash, os::fd::{AsRawFd, RawFd}, diff --git a/aya/src/programs/cgroup_sock_addr.rs b/aya/src/programs/cgroup_sock_addr.rs index 72eca7ec..045a40d2 100644 --- a/aya/src/programs/cgroup_sock_addr.rs +++ b/aya/src/programs/cgroup_sock_addr.rs @@ -2,7 +2,7 @@ pub use aya_obj::programs::CgroupSockAddrAttachType; -use procfs::KernelVersion; +use crate::util::KernelVersion; use std::{ hash::Hash, os::fd::{AsRawFd, RawFd}, diff --git a/aya/src/programs/cgroup_sockopt.rs b/aya/src/programs/cgroup_sockopt.rs index 9d8b314a..95b89dd7 100644 --- a/aya/src/programs/cgroup_sockopt.rs +++ b/aya/src/programs/cgroup_sockopt.rs @@ -2,7 +2,7 @@ pub use aya_obj::programs::CgroupSockoptAttachType; -use procfs::KernelVersion; +use crate::util::KernelVersion; use std::{ hash::Hash, os::fd::{AsRawFd, RawFd}, diff --git a/aya/src/programs/cgroup_sysctl.rs b/aya/src/programs/cgroup_sysctl.rs index c3ce1011..3cd20195 100644 --- a/aya/src/programs/cgroup_sysctl.rs +++ b/aya/src/programs/cgroup_sysctl.rs @@ -1,6 +1,6 @@ //! Cgroup sysctl programs. -use procfs::KernelVersion; +use crate::util::KernelVersion; use std::{ hash::Hash, os::fd::{AsRawFd, RawFd}, diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index 3ca6854c..68ebe748 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -64,10 +64,9 @@ pub mod uprobe; mod utils; pub mod xdp; +use crate::util::KernelVersion; use libc::ENOSPC; -use procfs::KernelVersion; use std::{ - borrow::Cow, ffi::CString, io, os::unix::io::{AsRawFd, RawFd}, @@ -143,7 +142,7 @@ pub enum ProgramError { #[source] io_error: io::Error, /// The error log produced by the kernel verifier. - verifier_log: Cow<'static, str>, + verifier_log: String, }, /// A syscall failed. diff --git a/aya/src/programs/probe.rs b/aya/src/programs/probe.rs index c6320e1a..80a5c088 100644 --- a/aya/src/programs/probe.rs +++ b/aya/src/programs/probe.rs @@ -1,5 +1,5 @@ +use crate::util::KernelVersion; use libc::pid_t; -use procfs::KernelVersion; use std::{ fs::{self, OpenOptions}, io::{self, Write}, @@ -50,7 +50,7 @@ pub(crate) fn attach>( ) -> Result { // https://github.com/torvalds/linux/commit/e12f03d7031a977356e3d7b75a68c2185ff8d155 // Use debugfs to create probe - if KernelVersion::current().unwrap() >= KernelVersion::new(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()?, diff --git a/aya/src/programs/xdp.rs b/aya/src/programs/xdp.rs index 01843928..a85c87c2 100644 --- a/aya/src/programs/xdp.rs +++ b/aya/src/programs/xdp.rs @@ -1,8 +1,8 @@ //! eXpress Data Path (XDP) programs. +use crate::util::KernelVersion; 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; diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs index 5a7dc3c5..5809d4c6 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -1,5 +1,4 @@ use std::{ - borrow::Cow, cmp::{self, min}, ffi::{CStr, CString}, io, @@ -8,12 +7,12 @@ use std::{ slice, }; +use crate::util::KernelVersion; use libc::{c_char, c_long, close, ENOENT, ENOSPC}; use obj::{ maps::{bpf_map_def, LegacyMap}, BpfSectionKind, }; -use procfs::KernelVersion; use crate::{ generated::{ @@ -993,13 +992,10 @@ pub(crate) fn bpf_prog_get_next_id(id: u32) -> Result, (c_long, io:: } } -pub(crate) fn retry_with_verifier_logs( +pub(crate) fn retry_with_verifier_logs( max_retries: usize, - f: F, -) -> (SysResult, Cow<'static, str>) -where - F: Fn(&mut [u8]) -> SysResult, -{ + f: impl Fn(&mut [u8]) -> SysResult, +) -> (SysResult, String) { const MIN_LOG_BUF_SIZE: usize = 1024 * 10; const MAX_LOG_BUF_SIZE: usize = (std::u32::MAX >> 8) as usize; @@ -1023,11 +1019,7 @@ where if let Some(pos) = log_buf.iter().position(|b| *b == 0) { log_buf.truncate(pos); } - let log_buf = if log_buf.is_empty() { - "none".into() - } else { - String::from_utf8(log_buf).unwrap().into() - }; + let log_buf = String::from_utf8(log_buf).unwrap(); break (ret, log_buf); } diff --git a/aya/src/util.rs b/aya/src/util.rs index b0bb3781..aaca0f5f 100644 --- a/aya/src/util.rs +++ b/aya/src/util.rs @@ -1,11 +1,12 @@ //! Utility functions. use std::{ collections::BTreeMap, - ffi::CString, + error::Error, + ffi::{CStr, CString}, fs::{self, File}, - io::{self, BufReader}, + io::{self, BufRead, BufReader}, mem, slice, - str::FromStr, + str::{FromStr, Utf8Error}, }; use crate::{ @@ -13,9 +14,136 @@ use crate::{ Pod, }; -use libc::{if_nametoindex, sysconf, _SC_PAGESIZE}; +use libc::{if_nametoindex, sysconf, uname, utsname, _SC_PAGESIZE}; -use io::BufRead; +/// Represents a kernel version, in major.minor.release version. +// Adapted from https://docs.rs/procfs/latest/procfs/sys/kernel/struct.Version.html. +#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd)] +pub struct KernelVersion { + pub(crate) major: u8, + pub(crate) minor: u8, + pub(crate) patch: u16, +} + +/// An error encountered while fetching the current kernel version. +#[derive(thiserror::Error, Debug)] +pub enum CurrentKernelVersionError { + /// The kernel version string could not be read. + #[error("failed to read kernel version")] + IOError(#[from] io::Error), + /// The kernel version string could not be parsed. + #[error("failed to parse kernel version")] + ParseError(#[from] text_io::Error), + /// The kernel version string was not valid UTF-8. + #[error("kernel version string is not valid UTF-8")] + Utf8Error(#[from] Utf8Error), +} + +impl KernelVersion { + /// Constructor. + pub fn new(major: u8, minor: u8, patch: u16) -> Self { + Self { + major, + minor, + patch, + } + } + + /// Returns the kernel version of the currently running kernel. + pub fn current() -> Result { + let kernel_version = Self::get_kernel_version(); + + // The kernel version is clamped to 4.19.255 on kernels 4.19.222 and above. + // + // See https://github.com/torvalds/linux/commit/a256aac. + const CLAMPED_KERNEL_MAJOR: u8 = 4; + const CLAMPED_KERNEL_MINOR: u8 = 19; + if let Ok(Self { + major: CLAMPED_KERNEL_MAJOR, + minor: CLAMPED_KERNEL_MINOR, + patch: 222.., + }) = kernel_version + { + return Ok(Self::new(CLAMPED_KERNEL_MAJOR, CLAMPED_KERNEL_MINOR, 255)); + } + + kernel_version + } + + // This is ported from https://github.com/torvalds/linux/blob/3f01e9f/tools/lib/bpf/libbpf_probes.c#L21-L101. + + fn get_ubuntu_kernel_version() -> Result, CurrentKernelVersionError> { + const UBUNTU_KVER_FILE: &str = "/proc/version_signature"; + let s = match fs::read(UBUNTU_KVER_FILE) { + Ok(s) => s, + Err(e) => { + if e.kind() == io::ErrorKind::NotFound { + return Ok(None); + } + return Err(e.into()); + } + }; + let ubuntu: String; + let ubuntu_version: String; + let major: u8; + let minor: u8; + let patch: u16; + text_io::try_scan!(s.iter().copied() => "{} {} {}.{}.{}\n", ubuntu, ubuntu_version, major, minor, patch); + Ok(Some(Self::new(major, minor, patch))) + } + + fn get_debian_kernel_version( + info: &utsname, + ) -> Result, CurrentKernelVersionError> { + // Safety: man 2 uname: + // + // The length of the arrays in a struct utsname is unspecified (see NOTES); the fields are + // terminated by a null byte ('\0'). + let p = unsafe { CStr::from_ptr(info.version.as_ptr()) }; + let p = p.to_str()?; + let p = match p.split_once("Debian ") { + Some((_prefix, suffix)) => suffix, + None => return Ok(None), + }; + let major: u8; + let minor: u8; + let patch: u16; + text_io::try_scan!(p.bytes() => "{}.{}.{}", major, minor, patch); + Ok(Some(Self::new(major, minor, patch))) + } + + fn get_kernel_version() -> Result { + if let Some(v) = Self::get_ubuntu_kernel_version()? { + return Ok(v); + } + + let mut info = unsafe { mem::zeroed::() }; + if unsafe { uname(&mut info) } != 0 { + return Err(io::Error::last_os_error().into()); + } + + if let Some(v) = Self::get_debian_kernel_version(&info)? { + return Ok(v); + } + + // Safety: man 2 uname: + // + // The length of the arrays in a struct utsname is unspecified (see NOTES); the fields are + // terminated by a null byte ('\0'). + let p = unsafe { CStr::from_ptr(info.release.as_ptr()) }; + let p = p.to_str()?; + // Unlike sscanf, text_io::try_scan! does not stop at the first non-matching character. + let p = match p.split_once(|c: char| c != '.' && !c.is_ascii_digit()) { + Some((prefix, _suffix)) => prefix, + None => p, + }; + let major: u8; + let minor: u8; + let patch: u16; + text_io::try_scan!(p.bytes() => "{}.{}.{}", major, minor, patch); + Ok(Self::new(major, minor, patch)) + } +} const ONLINE_CPUS: &str = "/sys/devices/system/cpu/online"; pub(crate) const POSSIBLE_CPUS: &str = "/sys/devices/system/cpu/possible"; diff --git a/test/integration-test/Cargo.toml b/test/integration-test/Cargo.toml index 36f8270b..09770325 100644 --- a/test/integration-test/Cargo.toml +++ b/test/integration-test/Cargo.toml @@ -12,7 +12,6 @@ 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" tempfile = "3.3.0" tokio = { version = "1.24", features = ["rt", "rt-multi-thread", "sync", "time"] } diff --git a/test/integration-test/tests/btf_relocations.rs b/test/integration-test/tests/btf_relocations.rs index 069bc76c..37a138bf 100644 --- a/test/integration-test/tests/btf_relocations.rs +++ b/test/integration-test/tests/btf_relocations.rs @@ -1,9 +1,8 @@ use anyhow::{bail, Context as _, Result}; -use procfs::KernelVersion; use std::{path::PathBuf, process::Command, thread::sleep, time::Duration}; use tempfile::TempDir; -use aya::{maps::Array, programs::TracePoint, BpfLoader, Btf, Endianness}; +use aya::{maps::Array, programs::TracePoint, util::KernelVersion, BpfLoader, Btf, Endianness}; // In the tests below we often use values like 0xAAAAAAAA or -0x7AAAAAAA. Those values have no // special meaning, they just have "nice" bit patterns that can be helpful while debugging. diff --git a/test/integration-test/tests/load.rs b/test/integration-test/tests/load.rs index 47a851cf..3f35e06e 100644 --- a/test/integration-test/tests/load.rs +++ b/test/integration-test/tests/load.rs @@ -1,4 +1,3 @@ -use procfs::KernelVersion; use std::{convert::TryInto as _, thread, time}; use aya::{ @@ -8,6 +7,7 @@ use aya::{ links::{FdLink, PinnedLink}, loaded_programs, KProbe, TracePoint, Xdp, XdpFlags, }, + util::KernelVersion, Bpf, }; diff --git a/test/integration-test/tests/smoke.rs b/test/integration-test/tests/smoke.rs index 2bc10ff4..304772e8 100644 --- a/test/integration-test/tests/smoke.rs +++ b/test/integration-test/tests/smoke.rs @@ -1,8 +1,7 @@ -use procfs::KernelVersion; - use aya::{ include_bytes_aligned, programs::{Extension, Xdp, XdpFlags}, + util::KernelVersion, Bpf, BpfLoader, };