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.
pull/641/head
Tamir Duberstein 1 year ago
parent 7f25956aea
commit b611038d5b
No known key found for this signature in database

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

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

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

@ -1,4 +1,6 @@
//! Cgroup device programs. //! Cgroup device programs.
use procfs::KernelVersion;
use std::os::fd::{AsRawFd, RawFd}; use std::os::fd::{AsRawFd, RawFd};
use crate::{ use crate::{
@ -6,7 +8,7 @@ use crate::{
programs::{ programs::{
define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError, 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. /// 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 prog_fd = self.data.fd_or_err()?;
let cgroup_fd = cgroup.as_raw_fd(); let cgroup_fd = cgroup.as_raw_fd();
let k_ver = kernel_version().unwrap(); if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
if k_ver >= (5, 7, 0) {
let link_fd = bpf_link_create(prog_fd, cgroup_fd, BPF_CGROUP_DEVICE, None, 0).map_err( let link_fd = bpf_link_create(prog_fd, cgroup_fd, BPF_CGROUP_DEVICE, None, 0).map_err(
|(_, io_error)| ProgramError::SyscallError { |(_, io_error)| ProgramError::SyscallError {
call: "bpf_link_create", call: "bpf_link_create",

@ -1,4 +1,6 @@
//! Cgroup skb programs. //! Cgroup skb programs.
use procfs::KernelVersion;
use std::{ use std::{
hash::Hash, hash::Hash,
os::fd::{AsRawFd, RawFd}, os::fd::{AsRawFd, RawFd},
@ -13,7 +15,7 @@ use crate::{
programs::{ programs::{
define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError, 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. /// 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::Ingress => BPF_CGROUP_INET_INGRESS,
CgroupSkbAttachType::Egress => BPF_CGROUP_INET_EGRESS, CgroupSkbAttachType::Egress => BPF_CGROUP_INET_EGRESS,
}; };
let k_ver = kernel_version().unwrap(); if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
if k_ver >= (5, 7, 0) {
let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err( let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err(
|(_, io_error)| ProgramError::SyscallError { |(_, io_error)| ProgramError::SyscallError {
call: "bpf_link_create", call: "bpf_link_create",

@ -1,6 +1,8 @@
//! Cgroup socket programs. //! Cgroup socket programs.
pub use aya_obj::programs::CgroupSockAttachType; pub use aya_obj::programs::CgroupSockAttachType;
use procfs::KernelVersion;
use std::{ use std::{
hash::Hash, hash::Hash,
os::fd::{AsRawFd, RawFd}, os::fd::{AsRawFd, RawFd},
@ -12,7 +14,7 @@ use crate::{
programs::{ programs::{
define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError, 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. /// 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 prog_fd = self.data.fd_or_err()?;
let cgroup_fd = cgroup.as_raw_fd(); let cgroup_fd = cgroup.as_raw_fd();
let attach_type = self.data.expected_attach_type.unwrap(); let attach_type = self.data.expected_attach_type.unwrap();
let k_ver = kernel_version().unwrap(); if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
if k_ver >= (5, 7, 0) {
let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err( let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err(
|(_, io_error)| ProgramError::SyscallError { |(_, io_error)| ProgramError::SyscallError {
call: "bpf_link_create", call: "bpf_link_create",

@ -1,6 +1,8 @@
//! Cgroup socket address programs. //! Cgroup socket address programs.
pub use aya_obj::programs::CgroupSockAddrAttachType; pub use aya_obj::programs::CgroupSockAddrAttachType;
use procfs::KernelVersion;
use std::{ use std::{
hash::Hash, hash::Hash,
os::fd::{AsRawFd, RawFd}, os::fd::{AsRawFd, RawFd},
@ -12,7 +14,7 @@ use crate::{
programs::{ programs::{
define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError, 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`). /// 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 prog_fd = self.data.fd_or_err()?;
let cgroup_fd = cgroup.as_raw_fd(); let cgroup_fd = cgroup.as_raw_fd();
let attach_type = self.data.expected_attach_type.unwrap(); let attach_type = self.data.expected_attach_type.unwrap();
let k_ver = kernel_version().unwrap(); if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
if k_ver >= (5, 7, 0) {
let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err( let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err(
|(_, io_error)| ProgramError::SyscallError { |(_, io_error)| ProgramError::SyscallError {
call: "bpf_link_create", call: "bpf_link_create",

@ -1,6 +1,8 @@
//! Cgroup socket option programs. //! Cgroup socket option programs.
pub use aya_obj::programs::CgroupSockoptAttachType; pub use aya_obj::programs::CgroupSockoptAttachType;
use procfs::KernelVersion;
use std::{ use std::{
hash::Hash, hash::Hash,
os::fd::{AsRawFd, RawFd}, os::fd::{AsRawFd, RawFd},
@ -12,7 +14,7 @@ use crate::{
programs::{ programs::{
define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError, 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. /// 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 prog_fd = self.data.fd_or_err()?;
let cgroup_fd = cgroup.as_raw_fd(); let cgroup_fd = cgroup.as_raw_fd();
let attach_type = self.data.expected_attach_type.unwrap(); let attach_type = self.data.expected_attach_type.unwrap();
let k_ver = kernel_version().unwrap(); if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
if k_ver >= (5, 7, 0) {
let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err( let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err(
|(_, io_error)| ProgramError::SyscallError { |(_, io_error)| ProgramError::SyscallError {
call: "bpf_link_create", call: "bpf_link_create",

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

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

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

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

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

@ -5,15 +5,9 @@ mod perf_event;
#[cfg(test)] #[cfg(test)]
mod fake; mod fake;
use std::io; use std::{io, mem};
#[cfg(not(test))]
use std::{ffi::CString, mem};
#[cfg(not(test))]
use std::{fs::File, io::Read};
#[cfg(not(test))] use libc::{c_int, c_long, pid_t, SYS_bpf, SYS_perf_event_open};
use libc::utsname;
use libc::{c_int, c_long, pid_t};
pub(crate) use bpf::*; pub(crate) use bpf::*;
#[cfg(test)] #[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)>; pub(crate) type SysResult = Result<c_long, (c_long, io::Error)>;
#[cfg_attr(test, allow(dead_code))]
pub(crate) enum Syscall<'a> { pub(crate) enum Syscall<'a> {
Bpf { Bpf {
cmd: bpf_cmd, cmd: bpf_cmd,
@ -46,126 +39,28 @@ pub(crate) enum Syscall<'a> {
} }
fn syscall(call: Syscall) -> SysResult { fn syscall(call: Syscall) -> SysResult {
#[cfg(not(test))]
return unsafe { syscall_impl(call) };
#[cfg(test)] #[cfg(test)]
return TEST_SYSCALL.with(|test_impl| unsafe { test_impl.borrow()(call) }); 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))] #[cfg_attr(test, allow(unreachable_code))]
fn ubuntu_kernel_version() -> Result<(u32, u32, u32), ()> { match unsafe {
if let Ok(mut file) = File::open("/proc/version_signature") { match call {
let mut buf = String::new(); Syscall::Bpf { cmd, attr } => {
let mut major = 0u32; libc::syscall(SYS_bpf, cmd, attr, mem::size_of::<bpf_attr>())
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));
} }
} Syscall::PerfEventOpen {
} attr,
pid,
Err(()) cpu,
} group,
flags,
#[cfg(not(test))] } => libc::syscall(SYS_perf_event_open, &attr, pid, cpu, group, flags),
pub(crate) fn kernel_version() -> Result<(u32, u32, u32), ()> { Syscall::PerfEventIoctl { fd, request, arg } => {
if let Ok(version) = ubuntu_kernel_version() { libc::ioctl(fd, request.try_into().unwrap(), arg) as libc::c_long
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));
} }
} }
} {
let format = CString::new("%u.%u.%u").unwrap(); ret @ 0.. => Ok(ret),
if libc::sscanf( ret => Err((ret, io::Error::last_os_error())),
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))
} }
} }

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

@ -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");
}
}

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

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

Loading…
Cancel
Save