Skip to content

Commit

Permalink
feat(aya): Add is_program_type_supported
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
dave-tucker committed Jan 31, 2025
1 parent 0429ed2 commit f9edaf9
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 37 deletions.
28 changes: 25 additions & 3 deletions aya/src/bpf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,18 @@ 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,
is_btf_datasec_supported, is_btf_decl_tag_supported, is_btf_enum64_supported,
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},
};
Expand Down Expand Up @@ -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
Expand Down
40 changes: 40 additions & 0 deletions aya/src/programs/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
88 changes: 55 additions & 33 deletions aya/src/sys/bpf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 };
Expand All @@ -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;
Expand All @@ -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;

Expand All @@ -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;

Expand Down Expand Up @@ -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;
Expand Down
15 changes: 14 additions & 1 deletion test/integration-test/src/tests/smoke.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
use aya::{
programs::{Extension, TracePoint, Xdp, XdpFlags},
is_program_type_supported,
programs::{Extension, ProgramType, TracePoint, Xdp, XdpFlags},
util::KernelVersion,
Ebpf, EbpfLoader,
};
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();
Expand Down

0 comments on commit f9edaf9

Please sign in to comment.