feat(aya): Add is_program_type_supported

This adds a new API to test whether a given program type is supported.

This is to support 3 usecases:

1. A project like bpfman (which uses Aya) may wish to prevent users with
   a list of program types that are supported on the target system
2. A user of Aya may wish to test whether Fentry/Fexit programs are
   supported and code their own behaviour to fallback to Kprobes
3. Our own integration tests can be made to skip certain program tests
   when kernel features are missing.

Signed-off-by: Dave Tucker <dave@dtucker.co.uk>
pull/70/head
Dave Tucker 4 weeks ago
parent 0429ed2fa2
commit f9edaf9c23

@ -30,8 +30,9 @@ use crate::{
programs::{
BtfTracePoint, CgroupDevice, CgroupSkb, CgroupSkbAttachType, CgroupSock, CgroupSockAddr,
CgroupSockopt, CgroupSysctl, Extension, FEntry, FExit, Iter, KProbe, LircMode2, Lsm,
PerfEvent, ProbeKind, Program, ProgramData, ProgramError, RawTracePoint, SchedClassifier,
SkLookup, SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint, UProbe, Xdp,
PerfEvent, ProbeKind, Program, ProgramData, ProgramError, ProgramType, RawTracePoint,
SchedClassifier, SkLookup, SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint,
UProbe, Xdp,
},
sys::{
bpf_load_btf, is_bpf_cookie_supported, is_bpf_global_data_supported,
@ -39,7 +40,8 @@ use crate::{
is_btf_float_supported, is_btf_func_global_supported, is_btf_func_supported,
is_btf_supported, is_btf_type_tag_supported, is_info_gpl_compatible_supported,
is_info_map_ids_supported, is_perf_link_supported, is_probe_read_kernel_supported,
is_prog_id_supported, is_prog_name_supported, retry_with_verifier_logs,
is_prog_id_supported, is_prog_name_supported, is_prog_type_supported,
retry_with_verifier_logs,
},
util::{bytes_of, bytes_of_slice, nr_cpus, page_size},
};
@ -105,6 +107,26 @@ pub fn features() -> &'static Features {
&FEATURES
}
/// Returns whether a program type is supported by the running kernel.
///
/// # Errors
///
/// Returns an error if an unexpected error occurs while checking the program
/// type support.
///
/// # Example
///
/// ```no_run
/// use aya::{ProgramType, is_program_type_supported};
///
/// let supported = is_program_type_supported(ProgramType::Xdp)?;
/// # Ok::<(), aya::EbpfError>(())
/// ```
pub fn is_program_type_supported(program_type: ProgramType) -> Result<bool, EbpfError> {
is_prog_type_supported(program_type.into())
.map_err(|e| EbpfError::ProgramError(ProgramError::from(e)))
}
/// Builder style API for advanced loading of eBPF programs.
///
/// Loading eBPF code involves a few steps, including loading maps and applying

@ -475,6 +475,46 @@ pub enum ProgramType {
Netfilter = bpf_prog_type::BPF_PROG_TYPE_NETFILTER as isize,
}
impl From<ProgramType> for bpf_prog_type {
fn from(value: ProgramType) -> Self {
match value {
ProgramType::Unspecified => Self::BPF_PROG_TYPE_UNSPEC,
ProgramType::SocketFilter => Self::BPF_PROG_TYPE_SOCKET_FILTER,
ProgramType::KProbe => Self::BPF_PROG_TYPE_KPROBE,
ProgramType::SchedClassifier => Self::BPF_PROG_TYPE_SCHED_CLS,
ProgramType::SchedAction => Self::BPF_PROG_TYPE_SCHED_ACT,
ProgramType::TracePoint => Self::BPF_PROG_TYPE_TRACEPOINT,
ProgramType::Xdp => Self::BPF_PROG_TYPE_XDP,
ProgramType::PerfEvent => Self::BPF_PROG_TYPE_PERF_EVENT,
ProgramType::CgroupSkb => Self::BPF_PROG_TYPE_CGROUP_SKB,
ProgramType::CgroupSock => Self::BPF_PROG_TYPE_CGROUP_SOCK,
ProgramType::LwtInput => Self::BPF_PROG_TYPE_LWT_IN,
ProgramType::LwtOutput => Self::BPF_PROG_TYPE_LWT_OUT,
ProgramType::LwtXmit => Self::BPF_PROG_TYPE_LWT_XMIT,
ProgramType::SockOps => Self::BPF_PROG_TYPE_SOCK_OPS,
ProgramType::SkSkb => Self::BPF_PROG_TYPE_SK_SKB,
ProgramType::CgroupDevice => Self::BPF_PROG_TYPE_CGROUP_DEVICE,
ProgramType::SkMsg => Self::BPF_PROG_TYPE_SK_MSG,
ProgramType::RawTracePoint => Self::BPF_PROG_TYPE_RAW_TRACEPOINT,
ProgramType::CgroupSockAddr => Self::BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
ProgramType::LwtSeg6local => Self::BPF_PROG_TYPE_LWT_SEG6LOCAL,
ProgramType::LircMode2 => Self::BPF_PROG_TYPE_LIRC_MODE2,
ProgramType::SkReuseport => Self::BPF_PROG_TYPE_SK_REUSEPORT,
ProgramType::FlowDissector => Self::BPF_PROG_TYPE_FLOW_DISSECTOR,
ProgramType::CgroupSysctl => Self::BPF_PROG_TYPE_CGROUP_SYSCTL,
ProgramType::RawTracePointWritable => Self::BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE,
ProgramType::CgroupSockopt => Self::BPF_PROG_TYPE_CGROUP_SOCKOPT,
ProgramType::Tracing => Self::BPF_PROG_TYPE_TRACING,
ProgramType::StructOps => Self::BPF_PROG_TYPE_STRUCT_OPS,
ProgramType::Extension => Self::BPF_PROG_TYPE_EXT,
ProgramType::Lsm => Self::BPF_PROG_TYPE_LSM,
ProgramType::SkLookup => Self::BPF_PROG_TYPE_SK_LOOKUP,
ProgramType::Syscall => Self::BPF_PROG_TYPE_SYSCALL,
ProgramType::Netfilter => Self::BPF_PROG_TYPE_NETFILTER,
}
}
}
impl TryFrom<bpf_prog_type> for ProgramType {
type Error = ProgramError;

@ -8,7 +8,7 @@ use std::{
};
use assert_matches::assert_matches;
use libc::{ENOENT, ENOSPC};
use libc::{E2BIG, EINVAL, ENOENT, ENOSPC};
use obj::{
btf::{BtfEnum64, Enum64},
generated::bpf_stats_type,
@ -736,6 +736,56 @@ pub(crate) fn bpf_btf_get_fd_by_id(id: u32) -> Result<crate::MockableFd, Syscall
})
}
// This program is to test the availability of eBPF features.
// It is a simple program that returns immediately and should always pass the
// verifier regardless of the program type.
// The fields conforming an encoded basic instruction are stored in the following order:
// opcode:8 src_reg:4 dst_reg:4 offset:16 imm:32 - In little-endian BPF.
// opcode:8 dst_reg:4 src_reg:4 offset:16 imm:32 - In big-endian BPF.
// Multi-byte fields ('imm' and 'offset') are stored using endian order.
// https://www.kernel.org/doc/html/v6.4-rc7/bpf/instruction-set.html#instruction-encoding
const TEST_PROG: &[u8] = &[
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit
];
pub(crate) fn is_prog_type_supported(kind: bpf_prog_type) -> Result<bool, SyscallError> {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
let u = unsafe { &mut attr.__bindgen_anon_3 };
u.prog_type = kind as u32;
let mut name: [c_char; 16] = [0; 16];
let cstring = CString::new("aya_prog_check").unwrap();
let name_bytes = cstring.to_bytes();
let len = cmp::min(name.len(), name_bytes.len());
name[..len].copy_from_slice(unsafe {
slice::from_raw_parts(name_bytes.as_ptr() as *const c_char, len)
});
u.prog_name = name;
let gpl = b"GPL\0";
u.license = gpl.as_ptr() as u64;
let insns = copy_instructions(TEST_PROG).unwrap();
u.insn_cnt = insns.len() as u32;
u.insns = insns.as_ptr() as u64;
u.prog_type = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32;
let res = bpf_prog_load(&mut attr);
match res {
Ok(_) => Ok(true),
Err((_, e)) => {
if e.raw_os_error() == Some(EINVAL) || e.raw_os_error() == Some(E2BIG) {
Ok(false)
} else {
Err(SyscallError {
call: "bpf_prog_load",
io_error: e,
})
}
}
}
}
pub(crate) fn is_prog_name_supported() -> bool {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
let u = unsafe { &mut attr.__bindgen_anon_3 };
@ -748,20 +798,10 @@ pub(crate) fn is_prog_name_supported() -> bool {
});
u.prog_name = name;
// The fields conforming an encoded basic instruction are stored in the following order:
// opcode:8 src_reg:4 dst_reg:4 offset:16 imm:32 - In little-endian BPF.
// opcode:8 dst_reg:4 src_reg:4 offset:16 imm:32 - In big-endian BPF.
// Multi-byte fields ('imm' and 'offset') are stored using endian order.
// https://www.kernel.org/doc/html/v6.4-rc7/bpf/instruction-set.html#instruction-encoding
let prog: &[u8] = &[
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit
];
let gpl = b"GPL\0";
u.license = gpl.as_ptr() as u64;
let insns = copy_instructions(prog).unwrap();
let insns = copy_instructions(TEST_PROG).unwrap();
u.insn_cnt = insns.len() as u32;
u.insns = insns.as_ptr() as u64;
u.prog_type = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32;
@ -776,11 +816,7 @@ pub(crate) fn is_info_map_ids_supported() -> bool {
u.prog_type = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32;
let prog: &[u8] = &[
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit
];
let insns = copy_instructions(prog).unwrap();
let insns = copy_instructions(TEST_PROG).unwrap();
u.insn_cnt = insns.len() as u32;
u.insns = insns.as_ptr() as u64;
@ -804,11 +840,7 @@ pub(crate) fn is_info_gpl_compatible_supported() -> bool {
u.prog_type = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32;
let prog: &[u8] = &[
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit
];
let insns = copy_instructions(prog).unwrap();
let insns = copy_instructions(TEST_PROG).unwrap();
u.insn_cnt = insns.len() as u32;
u.insns = insns.as_ptr() as u64;
@ -868,20 +900,10 @@ pub(crate) fn is_perf_link_supported() -> bool {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
let u = unsafe { &mut attr.__bindgen_anon_3 };
// The fields conforming an encoded basic instruction are stored in the following order:
// opcode:8 src_reg:4 dst_reg:4 offset:16 imm:32 - In little-endian BPF.
// opcode:8 dst_reg:4 src_reg:4 offset:16 imm:32 - In big-endian BPF.
// Multi-byte fields ('imm' and 'offset') are stored using endian order.
// https://www.kernel.org/doc/html/v6.4-rc7/bpf/instruction-set.html#instruction-encoding
let prog: &[u8] = &[
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit
];
let gpl = b"GPL\0";
u.license = gpl.as_ptr() as u64;
let insns = copy_instructions(prog).unwrap();
let insns = copy_instructions(TEST_PROG).unwrap();
u.insn_cnt = insns.len() as u32;
u.insns = insns.as_ptr() as u64;
u.prog_type = bpf_prog_type::BPF_PROG_TYPE_TRACEPOINT as u32;

@ -1,5 +1,6 @@
use aya::{
programs::{Extension, TracePoint, Xdp, XdpFlags},
is_program_type_supported,
programs::{Extension, ProgramType, TracePoint, Xdp, XdpFlags},
util::KernelVersion,
Ebpf, EbpfLoader,
};
@ -7,6 +8,18 @@ use test_log::test;
use crate::utils::NetNsGuard;
#[test]
fn progam_is_supported() {
// All of these program types have been supported for a long time and are
// used in our tests without any special checks.
assert!(is_program_type_supported(ProgramType::Xdp).unwrap());
assert!(is_program_type_supported(ProgramType::TracePoint).unwrap());
// Kprobe and uprobe are the same program type
assert!(is_program_type_supported(ProgramType::KProbe).unwrap());
assert!(is_program_type_supported(ProgramType::SchedClassifier).unwrap());
}
#[test]
fn xdp() {
let kernel_version = KernelVersion::current().unwrap();

Loading…
Cancel
Save