From f9edaf9c23cd5ca9c98cac70863d315c89b99aa1 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 +++- 4 files changed, 134 insertions(+), 37 deletions(-) diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index acce6c16..120b605c 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::{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 { + 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();