From 7af8bf8a66841f062e0eac9cc27dc3689675db31 Mon Sep 17 00:00:00 2001 From: Dave Tucker Date: Fri, 24 Jan 2025 16:34:46 +0000 Subject: [PATCH] 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 --- aya/src/bpf.rs | 28 +++++++- aya/src/programs/info.rs | 40 +++++++++++ aya/src/sys/bpf.rs | 88 +++++++++++++++--------- test/integration-test/src/tests/smoke.rs | 15 +++- xtask/public-api/aya.txt | 3 + 5 files changed, 137 insertions(+), 37 deletions(-) diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index acce6c16..f4555720 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -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::{is_program_type_supported, programs::ProgramType}; +/// +/// let supported = is_program_type_supported(ProgramType::Xdp)?; +/// # Ok::<(), aya::EbpfError>(()) +/// ``` +pub fn is_program_type_supported(program_type: ProgramType) -> Result { + 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 diff --git a/aya/src/programs/info.rs b/aya/src/programs/info.rs index 3bfa89f3..4f9b45b8 100644 --- a/aya/src/programs/info.rs +++ b/aya/src/programs/info.rs @@ -475,6 +475,46 @@ pub enum ProgramType { Netfilter = bpf_prog_type::BPF_PROG_TYPE_NETFILTER as isize, } +impl From 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 for ProgramType { type Error = ProgramError; diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs index 1c2fadef..08596a45 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -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 Result { + let mut attr = unsafe { mem::zeroed::() }; + 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::() }; 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::() }; 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; diff --git a/test/integration-test/src/tests/smoke.rs b/test/integration-test/src/tests/smoke.rs index 0d57aea9..34b20e9c 100644 --- a/test/integration-test/src/tests/smoke.rs +++ b/test/integration-test/src/tests/smoke.rs @@ -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(); diff --git a/xtask/public-api/aya.txt b/xtask/public-api/aya.txt index 3c8367b7..cee3d0aa 100644 --- a/xtask/public-api/aya.txt +++ b/xtask/public-api/aya.txt @@ -7651,6 +7651,8 @@ impl core::clone::Clone for aya::programs::ProgramType pub fn aya::programs::ProgramType::clone(&self) -> aya::programs::ProgramType impl core::cmp::PartialEq for aya::programs::ProgramType pub fn aya::programs::ProgramType::eq(&self, other: &aya::programs::ProgramType) -> bool +impl core::convert::From for aya_obj::generated::linux_bindings_x86_64::bpf_prog_type +pub fn aya_obj::generated::linux_bindings_x86_64::bpf_prog_type::from(value: aya::programs::ProgramType) -> Self impl core::convert::TryFrom for aya::programs::ProgramType pub type aya::programs::ProgramType::Error = aya::programs::ProgramError pub fn aya::programs::ProgramType::try_from(prog_type: aya_obj::generated::linux_bindings_x86_64::bpf_prog_type) -> core::result::Result @@ -10034,6 +10036,7 @@ impl aya::Pod for u8 impl aya::Pod for aya::maps::lpm_trie::Key impl aya::Pod for [T; N] pub fn aya::features() -> &'static aya_obj::obj::Features +pub fn aya::is_program_type_supported(program_type: aya::programs::ProgramType) -> core::result::Result pub type aya::Bpf = aya::Ebpf pub type aya::BpfError = aya::EbpfError pub type aya::BpfLoader<'a> = aya::EbpfLoader<'a>