aya,aya-obj: add feature probing program type

Adds API that probes whether kernel supports a program type.
reviewable/pr1063/r29
Tyrone Wu 6 months ago
parent 0237e36dbe
commit 0071895ece
No known key found for this signature in database
GPG Key ID: 978B1A1B79210AD6

@ -89,6 +89,7 @@ object = { version = "0.36", default-features = false }
once_cell = { version = "1.20.1", default-features = false } once_cell = { version = "1.20.1", default-features = false }
proc-macro2 = { version = "1", default-features = false } proc-macro2 = { version = "1", default-features = false }
proc-macro2-diagnostics = { version = "0.10.1", default-features = false } proc-macro2-diagnostics = { version = "0.10.1", default-features = false }
procfs = { version = "0.17.0", default-features = false }
public-api = { version = "0.47.0", default-features = false } public-api = { version = "0.47.0", default-features = false }
quote = { version = "1", default-features = false } quote = { version = "1", default-features = false }
rand = { version = "0.9", default-features = false } rand = { version = "0.9", default-features = false }

@ -27,7 +27,7 @@ use libc::{ENOENT, ENOSPC};
use crate::{ use crate::{
Btf, FEATURES, Pod, VerifierLogLevel, Btf, FEATURES, Pod, VerifierLogLevel,
maps::{MapData, PerCpuValues}, maps::{MapData, PerCpuValues},
programs::links::LinkRef, programs::{ProgramType, links::LinkRef},
sys::{Syscall, SyscallError, syscall}, sys::{Syscall, SyscallError, syscall},
util::KernelVersion, util::KernelVersion,
}; };
@ -706,7 +706,7 @@ pub(crate) fn bpf_btf_get_fd_by_id(id: u32) -> Result<crate::MockableFd, Syscall
} }
pub(crate) fn is_prog_name_supported() -> bool { pub(crate) fn is_prog_name_supported() -> bool {
with_trivial_prog(|attr| { with_trivial_prog(ProgramType::TracePoint, |attr| {
let u = unsafe { &mut attr.__bindgen_anon_3 }; let u = unsafe { &mut attr.__bindgen_anon_3 };
let name = c"aya_name_check"; let name = c"aya_name_check";
let name_bytes = name.to_bytes(); let name_bytes = name.to_bytes();
@ -727,7 +727,7 @@ fn new_insn(code: u8, dst_reg: u8, src_reg: u8, offset: i16, imm: i32) -> bpf_in
insn insn
} }
fn with_trivial_prog<T, F>(op: F) -> T pub(super) fn with_trivial_prog<T, F>(program_type: ProgramType, op: F) -> T
where where
F: FnOnce(&mut bpf_attr) -> T, F: FnOnce(&mut bpf_attr) -> T,
{ {
@ -743,14 +743,70 @@ where
u.insn_cnt = insns.len() as u32; u.insn_cnt = insns.len() as u32;
u.insns = insns.as_ptr() as u64; u.insns = insns.as_ptr() as u64;
u.prog_type = bpf_prog_type::BPF_PROG_TYPE_TRACEPOINT as u32;
// `expected_attach_type` field was added in v4.17 https://elixir.bootlin.com/linux/v4.17/source/include/uapi/linux/bpf.h#L310.
let expected_attach_type = match program_type {
ProgramType::SkMsg => Some(bpf_attach_type::BPF_SK_MSG_VERDICT),
ProgramType::CgroupSockAddr => Some(bpf_attach_type::BPF_CGROUP_INET4_BIND),
ProgramType::LircMode2 => Some(bpf_attach_type::BPF_LIRC_MODE2),
ProgramType::SkReuseport => Some(bpf_attach_type::BPF_SK_REUSEPORT_SELECT),
ProgramType::FlowDissector => Some(bpf_attach_type::BPF_FLOW_DISSECTOR),
ProgramType::CgroupSysctl => Some(bpf_attach_type::BPF_CGROUP_SYSCTL),
ProgramType::CgroupSockopt => Some(bpf_attach_type::BPF_CGROUP_GETSOCKOPT),
ProgramType::Tracing => Some(bpf_attach_type::BPF_TRACE_FENTRY),
ProgramType::Lsm => Some(bpf_attach_type::BPF_LSM_MAC),
ProgramType::SkLookup => Some(bpf_attach_type::BPF_SK_LOOKUP),
ProgramType::Netfilter => Some(bpf_attach_type::BPF_NETFILTER),
// Program types below v4.17, or do not accept `expected_attach_type`, should leave the
// field unset.
// Types below v4.17:
ProgramType::Unspecified
| ProgramType::SocketFilter
| ProgramType::KProbe
| ProgramType::SchedClassifier
| ProgramType::SchedAction
| ProgramType::TracePoint
| ProgramType::Xdp
| ProgramType::PerfEvent
| ProgramType::CgroupSkb
| ProgramType::CgroupSock
| ProgramType::LwtInput
| ProgramType::LwtOutput
| ProgramType::LwtXmit
| ProgramType::SockOps
| ProgramType::SkSkb
| ProgramType::CgroupDevice
// Types that do not accept `expected_attach_type`:
| ProgramType::RawTracePoint
| ProgramType::LwtSeg6local
| ProgramType::RawTracePointWritable
| ProgramType::StructOps
| ProgramType::Extension
| ProgramType::Syscall => None,
};
match program_type {
ProgramType::KProbe => {
if let Ok(current_version) = KernelVersion::current() {
u.kern_version = current_version.code();
}
}
// syscall required to be sleepable: https://elixir.bootlin.com/linux/v5.14/source/kernel/bpf/verifier.c#L13240
ProgramType::Syscall => u.prog_flags = aya_obj::generated::BPF_F_SLEEPABLE,
_ => {}
}
u.prog_type = program_type as u32;
if let Some(expected_attach_type) = expected_attach_type {
u.expected_attach_type = expected_attach_type as u32;
}
op(&mut attr) op(&mut attr)
} }
/// Tests whether `nr_map_ids` & `map_ids` fields in `bpf_prog_info` is available. /// Tests whether `nr_map_ids` & `map_ids` fields in `bpf_prog_info` is available.
pub(crate) fn is_info_map_ids_supported() -> bool { pub(crate) fn is_info_map_ids_supported() -> bool {
with_trivial_prog(|attr| { with_trivial_prog(ProgramType::TracePoint, |attr| {
let prog_fd = match bpf_prog_load(attr) { let prog_fd = match bpf_prog_load(attr) {
Ok(fd) => fd, Ok(fd) => fd,
Err(_) => return false, Err(_) => return false,
@ -764,7 +820,7 @@ pub(crate) fn is_info_map_ids_supported() -> bool {
/// Tests whether `gpl_compatible` field in `bpf_prog_info` is available. /// Tests whether `gpl_compatible` field in `bpf_prog_info` is available.
pub(crate) fn is_info_gpl_compatible_supported() -> bool { pub(crate) fn is_info_gpl_compatible_supported() -> bool {
with_trivial_prog(|attr| { with_trivial_prog(ProgramType::TracePoint, |attr| {
let prog_fd = match bpf_prog_load(attr) { let prog_fd = match bpf_prog_load(attr) {
Ok(fd) => fd, Ok(fd) => fd,
Err(_) => return false, Err(_) => return false,
@ -805,7 +861,7 @@ pub(crate) fn is_probe_read_kernel_supported() -> bool {
} }
pub(crate) fn is_perf_link_supported() -> bool { pub(crate) fn is_perf_link_supported() -> bool {
with_trivial_prog(|attr| { with_trivial_prog(ProgramType::TracePoint, |attr| {
if let Ok(fd) = bpf_prog_load(attr) { if let Ok(fd) = bpf_prog_load(attr) {
let fd = fd.as_fd(); let fd = fd.as_fd();
// Uses an invalid target FD so we get EBADF if supported. // Uses an invalid target FD so we get EBADF if supported.
@ -1073,7 +1129,7 @@ pub(crate) fn is_btf_type_tag_supported() -> bool {
bpf_load_btf(btf_bytes.as_slice(), &mut [], Default::default()).is_ok() bpf_load_btf(btf_bytes.as_slice(), &mut [], Default::default()).is_ok()
} }
fn bpf_prog_load(attr: &mut bpf_attr) -> io::Result<crate::MockableFd> { pub(super) fn bpf_prog_load(attr: &mut bpf_attr) -> io::Result<crate::MockableFd> {
// SAFETY: BPF_PROG_LOAD returns a new file descriptor. // SAFETY: BPF_PROG_LOAD returns a new file descriptor.
unsafe { fd_sys_bpf(bpf_cmd::BPF_PROG_LOAD, attr) } unsafe { fd_sys_bpf(bpf_cmd::BPF_PROG_LOAD, attr) }
} }

@ -0,0 +1,130 @@
//! Probes and identifies available eBPF features supported by the host kernel.
use aya_obj::btf::{Btf, BtfKind};
use libc::{E2BIG, EINVAL};
use super::{SyscallError, bpf_prog_load, with_trivial_prog};
use crate::programs::{ProgramError, ProgramType};
/// Whether the host kernel supports the [`ProgramType`].
///
/// # Examples
///
/// ```no_run
/// # use aya::{programs::ProgramType, sys::is_program_supported};
/// #
/// match is_program_supported(ProgramType::Xdp) {
/// Ok(true) => println!("XDP supported :)"),
/// Ok(false) => println!("XDP not supported :("),
/// Err(err) => println!("Uh oh! Unexpected error: {:?}", err),
/// }
/// ```
///
/// # Errors
///
/// Returns [`ProgramError::SyscallError`] if a syscall fails with an unexpected
/// error, or [`ProgramError::Btf`] for BTF related errors.
///
/// Certain errors are expected and handled internally; only unanticipated
/// failures during probing will result in these errors.
pub fn is_program_supported(program_type: ProgramType) -> Result<bool, ProgramError> {
if program_type == ProgramType::Unspecified {
return Ok(false);
}
// Verifier log is used in tracing, extension, and lsm to help detect support if loading fails.
// The expected target message for these types is:
// `Tracing programs must provide btf_id\nprocessed 0 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0\n\0`
// https://elixir.bootlin.com/linux/v5.5/source/kernel/bpf/verifier.c#L9535
let mut verifier_log = [0_u8; 136];
// The tracing and lsm types require a valid `attach_btf_id` for a successful load. However,
// if the symbols cannot be found in the BTF, then leave the field unset/0.
// Although extension also requires `attach_btf_id`, we intentionally leave it unset since a
// successful load requires additional setup with another prog loaded with BTF.
//
// When `attach_btf_id` is unset, then loading will fail, and so we examine verifier log
// for the expected message.
let attach_btf_id = match program_type {
// `bpf_fentry_test1` symbol from https://elixir.bootlin.com/linux/v5.5/source/net/bpf/test_run.c#L112
ProgramType::Tracing => Some("bpf_fentry_test1"),
// `bpf_lsm_bpf` symbol from https://elixir.bootlin.com/linux/v5.7/source/include/linux/lsm_hook_defs.h#L364
// or https://elixir.bootlin.com/linux/v5.11/source/kernel/bpf/bpf_lsm.c#L135 on later versions
ProgramType::Lsm => Some("bpf_lsm_bpf"),
_ => None,
}
.map(|func_name| {
Btf::from_sys_fs()
.and_then(|btf| btf.id_by_type_name_kind(func_name, BtfKind::Func))
.unwrap_or(0)
});
let error = match with_trivial_prog(program_type, |attr| {
// SAFETY: union access
let u = unsafe { &mut attr.__bindgen_anon_3 };
if let Some(attach_btf_id) = attach_btf_id {
u.attach_btf_id = attach_btf_id;
}
match program_type {
// If loading fails for these types due to unset `attach_btf_id`, then we defer to
// verifier log to verify whether type is supported.
ProgramType::Tracing | ProgramType::Extension | ProgramType::Lsm => {
u.log_buf = verifier_log.as_mut_ptr() as u64;
u.log_level = 1;
u.log_size = verifier_log.len() as u32;
}
_ => {}
}
bpf_prog_load(attr).err().map(|io_error| {
ProgramError::SyscallError(SyscallError {
call: "bpf_prog_load",
io_error,
})
})
}) {
Some(err) => err,
None => return Ok(true),
};
// Loading may fail for some types (namely tracing, extension, lsm, & struct_ops), so we
// perform additional examination on the OS error and/or verifier logs.
match &error {
ProgramError::SyscallError(err) => {
match err.io_error.raw_os_error() {
// For most types, `EINVAL` typically indicates it is not supported.
// However, further examination is required for tracing, extension, and lsm.
Some(EINVAL) => {
// At this point for tracing, extension, and lsm, loading failed due to unset
// `attach_btf_id`, so we examine verifier log for the target message.
// Message originates in `check_attach_btf_id()` in v5.5 to v5.9 https://elixir.bootlin.com/linux/v5.5/source/kernel/bpf/verifier.c#L9535,
// then moved to `bpf_check_attach_target()` in 5.10 and onward https://elixir.bootlin.com/linux/v5.9/source/kernel/bpf/verifier.c#L10849.
//
// If target message is present in the logs, then loading process has reached
// up to the verifier section, which indicates that the kernel is at least
// aware of the program type variants.
// If logs is empty, then it was immediately rejected by the kernel, meaning
// the types are not supported.
let supported = matches!(
program_type,
ProgramType::Tracing | ProgramType::Extension | ProgramType::Lsm
) && verifier_log
.starts_with(b"Tracing programs must provide btf_id");
Ok(supported)
}
// `E2BIG` from `bpf_check_uarg_tail_zero()` https://elixir.bootlin.com/linux/v4.18/source/kernel/bpf/syscall.c#L71
// indicates that the kernel detected non-zero fields in `bpf_attr` that does not
// exist at its current version.
Some(E2BIG) => Ok(false),
// `ENOTSUPP` from `check_struct_ops_btf_id()` https://elixir.bootlin.com/linux/v5.6/source/kernel/bpf/verifier.c#L9740
// indicates that it reached the verifier section, meaning the kernel is at least
// aware of the type's existence. Otherwise, it will produce `EINVAL`, meaning the
// type is immediately rejected and does not exist.
Some(524) if program_type == ProgramType::StructOps => Ok(true),
_ => Err(error),
}
}
_ => Err(error),
}
}

@ -1,6 +1,7 @@
//! A collection of system calls for performing eBPF related operations. //! A collection of system calls for performing eBPF related operations.
mod bpf; mod bpf;
mod feature_probe;
mod netlink; mod netlink;
mod perf_event; mod perf_event;
@ -17,6 +18,7 @@ use aya_obj::generated::{bpf_attr, bpf_cmd, perf_event_attr};
pub(crate) use bpf::*; pub(crate) use bpf::*;
#[cfg(test)] #[cfg(test)]
pub(crate) use fake::*; pub(crate) use fake::*;
pub use feature_probe::is_program_supported;
#[doc(hidden)] #[doc(hidden)]
pub use netlink::netlink_set_link_up; pub use netlink::netlink_set_link_up;
pub(crate) use netlink::*; pub(crate) use netlink::*;

@ -27,6 +27,7 @@ libc = { workspace = true }
log = { workspace = true } log = { workspace = true }
netns-rs = { workspace = true } netns-rs = { workspace = true }
object = { workspace = true, features = ["elf", "read_core", "std"] } object = { workspace = true, features = ["elf", "read_core", "std"] }
procfs = { workspace = true, features = ["flate2"] }
rand = { workspace = true, features = ["thread_rng"] } rand = { workspace = true, features = ["thread_rng"] }
rbpf = { workspace = true } rbpf = { workspace = true }
scopeguard = { workspace = true } scopeguard = { workspace = true }

@ -1,6 +1,7 @@
mod bpf_probe_read; mod bpf_probe_read;
mod btf_relocations; mod btf_relocations;
mod elf; mod elf;
mod feature_probe;
mod info; mod info;
mod iter; mod iter;
mod load; mod load;

@ -0,0 +1,131 @@
//! Test feature probing against kernel version.
use aya::{Btf, programs::ProgramType, sys::is_program_supported, util::KernelVersion};
use procfs::kernel_config;
use crate::utils::kernel_assert;
#[test]
fn probe_supported_programs() {
let kernel_config = kernel_config().unwrap_or_default();
macro_rules! is_supported {
($prog_type:expr) => {
is_program_supported($prog_type).unwrap()
};
}
let kern_version = KernelVersion::new(3, 19, 0);
kernel_assert!(is_supported!(ProgramType::SocketFilter), kern_version);
let kern_version = KernelVersion::new(4, 1, 0);
kernel_assert!(is_supported!(ProgramType::KProbe), kern_version);
kernel_assert!(is_supported!(ProgramType::SchedClassifier), kern_version);
kernel_assert!(is_supported!(ProgramType::SchedAction), kern_version);
let kern_version = KernelVersion::new(4, 7, 0);
kernel_assert!(is_supported!(ProgramType::TracePoint), kern_version);
let kern_version = KernelVersion::new(4, 8, 0);
kernel_assert!(is_supported!(ProgramType::Xdp), kern_version);
let kern_version = KernelVersion::new(4, 9, 0);
kernel_assert!(is_supported!(ProgramType::PerfEvent), kern_version);
let kern_version = KernelVersion::new(4, 10, 0);
kernel_assert!(is_supported!(ProgramType::CgroupSkb), kern_version);
kernel_assert!(is_supported!(ProgramType::CgroupSock), kern_version);
kernel_assert!(is_supported!(ProgramType::LwtInput), kern_version);
kernel_assert!(is_supported!(ProgramType::LwtOutput), kern_version);
kernel_assert!(is_supported!(ProgramType::LwtXmit), kern_version);
let kern_version = KernelVersion::new(4, 13, 0);
kernel_assert!(is_supported!(ProgramType::SockOps), kern_version);
let kern_version = KernelVersion::new(4, 14, 0);
kernel_assert!(is_supported!(ProgramType::SkSkb), kern_version);
let kern_version = KernelVersion::new(4, 15, 0);
kernel_assert!(is_supported!(ProgramType::CgroupDevice), kern_version);
let kern_version = KernelVersion::new(4, 17, 0);
kernel_assert!(is_supported!(ProgramType::SkMsg), kern_version);
kernel_assert!(is_supported!(ProgramType::RawTracePoint), kern_version);
kernel_assert!(is_supported!(ProgramType::CgroupSockAddr), kern_version);
let kern_version = KernelVersion::new(4, 18, 0);
kernel_assert!(is_supported!(ProgramType::LwtSeg6local), kern_version);
// `lirc_mode2` requires CONFIG_BPF_LIRC_MODE2=y
let lirc_mode2_config = matches!(
kernel_config.get("CONFIG_BPF_LIRC_MODE2"),
Some(procfs::ConfigSetting::Yes)
);
let lirc_mode2 = is_supported!(ProgramType::LircMode2);
kernel_assert!(
if aya::util::KernelVersion::current().unwrap() >= kern_version {
lirc_mode2 == lirc_mode2_config
} else {
lirc_mode2
},
kern_version
);
if !lirc_mode2_config {
eprintln!("CONFIG_BPF_LIRC_MODE2 required for lirc_mode2 program type");
}
let kern_version = KernelVersion::new(4, 19, 0);
kernel_assert!(is_supported!(ProgramType::SkReuseport), kern_version);
let kern_version = KernelVersion::new(4, 20, 0);
kernel_assert!(is_supported!(ProgramType::FlowDissector), kern_version);
let kern_version = KernelVersion::new(5, 2, 0);
kernel_assert!(is_supported!(ProgramType::CgroupSysctl), kern_version);
kernel_assert!(
is_supported!(ProgramType::RawTracePointWritable),
kern_version
);
let kern_version = KernelVersion::new(5, 3, 0);
kernel_assert!(is_supported!(ProgramType::CgroupSockopt), kern_version);
let kern_version = KernelVersion::new(5, 5, 0);
kernel_assert!(is_supported!(ProgramType::Tracing), kern_version); // Requires `CONFIG_DEBUG_INFO_BTF=y`
let kern_version = KernelVersion::new(5, 6, 0);
kernel_assert!(is_supported!(ProgramType::StructOps), kern_version);
kernel_assert!(is_supported!(ProgramType::Extension), kern_version);
let kern_version = KernelVersion::new(5, 7, 0);
// `lsm` requires `CONFIG_DEBUG_INFO_BTF=y` & `CONFIG_BPF_LSM=y`
// Ways to check if `CONFIG_BPF_LSM` is enabled:
// - kernel config has `CONFIG_BPF_LSM=y`, but config is not always exposed.
// - an LSM hooks is present in BTF, e.g. `bpf_lsm_bpf`. hooks are found in `bpf_lsm.c`
let lsm_enabled = matches!(
kernel_config.get("CONFIG_BPF_LSM"),
Some(procfs::ConfigSetting::Yes)
) || Btf::from_sys_fs()
.and_then(|btf| btf.id_by_type_name_kind("bpf_lsm_bpf", aya_obj::btf::BtfKind::Func))
.is_ok();
let lsm = is_supported!(ProgramType::Lsm);
kernel_assert!(
if aya::util::KernelVersion::current().unwrap() >= kern_version {
lsm == lsm_enabled
} else {
lsm
},
kern_version
);
if !lsm_enabled {
eprintln!("CONFIG_BPF_LSM required for lsm program type");
}
let kern_version = KernelVersion::new(5, 9, 0);
kernel_assert!(is_supported!(ProgramType::SkLookup), kern_version);
let kern_version = KernelVersion::new(5, 14, 0);
kernel_assert!(is_supported!(ProgramType::Syscall), kern_version);
let kern_version = KernelVersion::new(6, 4, 0);
kernel_assert!(is_supported!(ProgramType::Netfilter), kern_version);
}

@ -14,7 +14,7 @@ use aya_obj::programs::XdpAttachType;
use test_log::test; use test_log::test;
const MAX_RETRIES: usize = 100; const MAX_RETRIES: usize = 100;
const RETRY_DURATION: Duration = Duration::from_millis(10); pub(crate) const RETRY_DURATION: Duration = Duration::from_millis(10);
#[test] #[test]
fn long_name() { fn long_name() {

@ -10076,6 +10076,7 @@ pub fn aya::sys::SyscallError::borrow_mut(&mut self) -> &mut T
impl<T> core::convert::From<T> for aya::sys::SyscallError impl<T> core::convert::From<T> for aya::sys::SyscallError
pub fn aya::sys::SyscallError::from(t: T) -> T pub fn aya::sys::SyscallError::from(t: T) -> T
pub fn aya::sys::enable_stats(stats_type: aya::sys::Stats) -> core::result::Result<std::os::fd::owned::OwnedFd, aya::sys::SyscallError> pub fn aya::sys::enable_stats(stats_type: aya::sys::Stats) -> core::result::Result<std::os::fd::owned::OwnedFd, aya::sys::SyscallError>
pub fn aya::sys::is_program_supported(program_type: aya::programs::ProgramType) -> core::result::Result<bool, aya::programs::ProgramError>
pub mod aya::util pub mod aya::util
pub struct aya::util::KernelVersion pub struct aya::util::KernelVersion
impl aya::util::KernelVersion impl aya::util::KernelVersion

Loading…
Cancel
Save