Skip to content

Commit 7af8bf8

Browse files
committed
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 <[email protected]>
1 parent 1fe12b9 commit 7af8bf8

File tree

5 files changed

+137
-37
lines changed

5 files changed

+137
-37
lines changed

aya/src/bpf.rs

+25-3
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,18 @@ use crate::{
3030
programs::{
3131
BtfTracePoint, CgroupDevice, CgroupSkb, CgroupSkbAttachType, CgroupSock, CgroupSockAddr,
3232
CgroupSockopt, CgroupSysctl, Extension, FEntry, FExit, Iter, KProbe, LircMode2, Lsm,
33-
PerfEvent, ProbeKind, Program, ProgramData, ProgramError, RawTracePoint, SchedClassifier,
34-
SkLookup, SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint, UProbe, Xdp,
33+
PerfEvent, ProbeKind, Program, ProgramData, ProgramError, ProgramType, RawTracePoint,
34+
SchedClassifier, SkLookup, SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint,
35+
UProbe, Xdp,
3536
},
3637
sys::{
3738
bpf_load_btf, is_bpf_cookie_supported, is_bpf_global_data_supported,
3839
is_btf_datasec_supported, is_btf_decl_tag_supported, is_btf_enum64_supported,
3940
is_btf_float_supported, is_btf_func_global_supported, is_btf_func_supported,
4041
is_btf_supported, is_btf_type_tag_supported, is_info_gpl_compatible_supported,
4142
is_info_map_ids_supported, is_perf_link_supported, is_probe_read_kernel_supported,
42-
is_prog_id_supported, is_prog_name_supported, retry_with_verifier_logs,
43+
is_prog_id_supported, is_prog_name_supported, is_prog_type_supported,
44+
retry_with_verifier_logs,
4345
},
4446
util::{bytes_of, bytes_of_slice, nr_cpus, page_size},
4547
};
@@ -105,6 +107,26 @@ pub fn features() -> &'static Features {
105107
&FEATURES
106108
}
107109

110+
/// Returns whether a program type is supported by the running kernel.
111+
///
112+
/// # Errors
113+
///
114+
/// Returns an error if an unexpected error occurs while checking the program
115+
/// type support.
116+
///
117+
/// # Example
118+
///
119+
/// ```no_run
120+
/// use aya::{is_program_type_supported, programs::ProgramType};
121+
///
122+
/// let supported = is_program_type_supported(ProgramType::Xdp)?;
123+
/// # Ok::<(), aya::EbpfError>(())
124+
/// ```
125+
pub fn is_program_type_supported(program_type: ProgramType) -> Result<bool, EbpfError> {
126+
is_prog_type_supported(program_type.into())
127+
.map_err(|e| EbpfError::ProgramError(ProgramError::from(e)))
128+
}
129+
108130
/// Builder style API for advanced loading of eBPF programs.
109131
///
110132
/// Loading eBPF code involves a few steps, including loading maps and applying

aya/src/programs/info.rs

+40
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,46 @@ pub enum ProgramType {
475475
Netfilter = bpf_prog_type::BPF_PROG_TYPE_NETFILTER as isize,
476476
}
477477

478+
impl From<ProgramType> for bpf_prog_type {
479+
fn from(value: ProgramType) -> Self {
480+
match value {
481+
ProgramType::Unspecified => Self::BPF_PROG_TYPE_UNSPEC,
482+
ProgramType::SocketFilter => Self::BPF_PROG_TYPE_SOCKET_FILTER,
483+
ProgramType::KProbe => Self::BPF_PROG_TYPE_KPROBE,
484+
ProgramType::SchedClassifier => Self::BPF_PROG_TYPE_SCHED_CLS,
485+
ProgramType::SchedAction => Self::BPF_PROG_TYPE_SCHED_ACT,
486+
ProgramType::TracePoint => Self::BPF_PROG_TYPE_TRACEPOINT,
487+
ProgramType::Xdp => Self::BPF_PROG_TYPE_XDP,
488+
ProgramType::PerfEvent => Self::BPF_PROG_TYPE_PERF_EVENT,
489+
ProgramType::CgroupSkb => Self::BPF_PROG_TYPE_CGROUP_SKB,
490+
ProgramType::CgroupSock => Self::BPF_PROG_TYPE_CGROUP_SOCK,
491+
ProgramType::LwtInput => Self::BPF_PROG_TYPE_LWT_IN,
492+
ProgramType::LwtOutput => Self::BPF_PROG_TYPE_LWT_OUT,
493+
ProgramType::LwtXmit => Self::BPF_PROG_TYPE_LWT_XMIT,
494+
ProgramType::SockOps => Self::BPF_PROG_TYPE_SOCK_OPS,
495+
ProgramType::SkSkb => Self::BPF_PROG_TYPE_SK_SKB,
496+
ProgramType::CgroupDevice => Self::BPF_PROG_TYPE_CGROUP_DEVICE,
497+
ProgramType::SkMsg => Self::BPF_PROG_TYPE_SK_MSG,
498+
ProgramType::RawTracePoint => Self::BPF_PROG_TYPE_RAW_TRACEPOINT,
499+
ProgramType::CgroupSockAddr => Self::BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
500+
ProgramType::LwtSeg6local => Self::BPF_PROG_TYPE_LWT_SEG6LOCAL,
501+
ProgramType::LircMode2 => Self::BPF_PROG_TYPE_LIRC_MODE2,
502+
ProgramType::SkReuseport => Self::BPF_PROG_TYPE_SK_REUSEPORT,
503+
ProgramType::FlowDissector => Self::BPF_PROG_TYPE_FLOW_DISSECTOR,
504+
ProgramType::CgroupSysctl => Self::BPF_PROG_TYPE_CGROUP_SYSCTL,
505+
ProgramType::RawTracePointWritable => Self::BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE,
506+
ProgramType::CgroupSockopt => Self::BPF_PROG_TYPE_CGROUP_SOCKOPT,
507+
ProgramType::Tracing => Self::BPF_PROG_TYPE_TRACING,
508+
ProgramType::StructOps => Self::BPF_PROG_TYPE_STRUCT_OPS,
509+
ProgramType::Extension => Self::BPF_PROG_TYPE_EXT,
510+
ProgramType::Lsm => Self::BPF_PROG_TYPE_LSM,
511+
ProgramType::SkLookup => Self::BPF_PROG_TYPE_SK_LOOKUP,
512+
ProgramType::Syscall => Self::BPF_PROG_TYPE_SYSCALL,
513+
ProgramType::Netfilter => Self::BPF_PROG_TYPE_NETFILTER,
514+
}
515+
}
516+
}
517+
478518
impl TryFrom<bpf_prog_type> for ProgramType {
479519
type Error = ProgramError;
480520

aya/src/sys/bpf.rs

+55-33
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::{
88
};
99

1010
use assert_matches::assert_matches;
11-
use libc::{ENOENT, ENOSPC};
11+
use libc::{E2BIG, EINVAL, ENOENT, ENOSPC};
1212
use obj::{
1313
btf::{BtfEnum64, Enum64},
1414
generated::bpf_stats_type,
@@ -736,6 +736,56 @@ pub(crate) fn bpf_btf_get_fd_by_id(id: u32) -> Result<crate::MockableFd, Syscall
736736
})
737737
}
738738

739+
// This program is to test the availability of eBPF features.
740+
// It is a simple program that returns immediately and should always pass the
741+
// verifier regardless of the program type.
742+
// The fields conforming an encoded basic instruction are stored in the following order:
743+
// opcode:8 src_reg:4 dst_reg:4 offset:16 imm:32 - In little-endian BPF.
744+
// opcode:8 dst_reg:4 src_reg:4 offset:16 imm:32 - In big-endian BPF.
745+
// Multi-byte fields ('imm' and 'offset') are stored using endian order.
746+
// https://www.kernel.org/doc/html/v6.4-rc7/bpf/instruction-set.html#instruction-encoding
747+
const TEST_PROG: &[u8] = &[
748+
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0
749+
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit
750+
];
751+
752+
pub(crate) fn is_prog_type_supported(kind: bpf_prog_type) -> Result<bool, SyscallError> {
753+
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
754+
let u = unsafe { &mut attr.__bindgen_anon_3 };
755+
u.prog_type = kind as u32;
756+
let mut name: [c_char; 16] = [0; 16];
757+
let cstring = CString::new("aya_prog_check").unwrap();
758+
let name_bytes = cstring.to_bytes();
759+
let len = cmp::min(name.len(), name_bytes.len());
760+
name[..len].copy_from_slice(unsafe {
761+
slice::from_raw_parts(name_bytes.as_ptr() as *const c_char, len)
762+
});
763+
u.prog_name = name;
764+
765+
let gpl = b"GPL\0";
766+
u.license = gpl.as_ptr() as u64;
767+
768+
let insns = copy_instructions(TEST_PROG).unwrap();
769+
u.insn_cnt = insns.len() as u32;
770+
u.insns = insns.as_ptr() as u64;
771+
u.prog_type = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32;
772+
773+
let res = bpf_prog_load(&mut attr);
774+
match res {
775+
Ok(_) => Ok(true),
776+
Err((_, e)) => {
777+
if e.raw_os_error() == Some(EINVAL) || e.raw_os_error() == Some(E2BIG) {
778+
Ok(false)
779+
} else {
780+
Err(SyscallError {
781+
call: "bpf_prog_load",
782+
io_error: e,
783+
})
784+
}
785+
}
786+
}
787+
}
788+
739789
pub(crate) fn is_prog_name_supported() -> bool {
740790
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
741791
let u = unsafe { &mut attr.__bindgen_anon_3 };
@@ -748,20 +798,10 @@ pub(crate) fn is_prog_name_supported() -> bool {
748798
});
749799
u.prog_name = name;
750800

751-
// The fields conforming an encoded basic instruction are stored in the following order:
752-
// opcode:8 src_reg:4 dst_reg:4 offset:16 imm:32 - In little-endian BPF.
753-
// opcode:8 dst_reg:4 src_reg:4 offset:16 imm:32 - In big-endian BPF.
754-
// Multi-byte fields ('imm' and 'offset') are stored using endian order.
755-
// https://www.kernel.org/doc/html/v6.4-rc7/bpf/instruction-set.html#instruction-encoding
756-
let prog: &[u8] = &[
757-
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0
758-
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit
759-
];
760-
761801
let gpl = b"GPL\0";
762802
u.license = gpl.as_ptr() as u64;
763803

764-
let insns = copy_instructions(prog).unwrap();
804+
let insns = copy_instructions(TEST_PROG).unwrap();
765805
u.insn_cnt = insns.len() as u32;
766806
u.insns = insns.as_ptr() as u64;
767807
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 {
776816

777817
u.prog_type = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32;
778818

779-
let prog: &[u8] = &[
780-
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0
781-
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit
782-
];
783-
let insns = copy_instructions(prog).unwrap();
819+
let insns = copy_instructions(TEST_PROG).unwrap();
784820
u.insn_cnt = insns.len() as u32;
785821
u.insns = insns.as_ptr() as u64;
786822

@@ -804,11 +840,7 @@ pub(crate) fn is_info_gpl_compatible_supported() -> bool {
804840

805841
u.prog_type = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32;
806842

807-
let prog: &[u8] = &[
808-
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0
809-
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit
810-
];
811-
let insns = copy_instructions(prog).unwrap();
843+
let insns = copy_instructions(TEST_PROG).unwrap();
812844
u.insn_cnt = insns.len() as u32;
813845
u.insns = insns.as_ptr() as u64;
814846

@@ -868,20 +900,10 @@ pub(crate) fn is_perf_link_supported() -> bool {
868900
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
869901
let u = unsafe { &mut attr.__bindgen_anon_3 };
870902

871-
// The fields conforming an encoded basic instruction are stored in the following order:
872-
// opcode:8 src_reg:4 dst_reg:4 offset:16 imm:32 - In little-endian BPF.
873-
// opcode:8 dst_reg:4 src_reg:4 offset:16 imm:32 - In big-endian BPF.
874-
// Multi-byte fields ('imm' and 'offset') are stored using endian order.
875-
// https://www.kernel.org/doc/html/v6.4-rc7/bpf/instruction-set.html#instruction-encoding
876-
let prog: &[u8] = &[
877-
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0
878-
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit
879-
];
880-
881903
let gpl = b"GPL\0";
882904
u.license = gpl.as_ptr() as u64;
883905

884-
let insns = copy_instructions(prog).unwrap();
906+
let insns = copy_instructions(TEST_PROG).unwrap();
885907
u.insn_cnt = insns.len() as u32;
886908
u.insns = insns.as_ptr() as u64;
887909
u.prog_type = bpf_prog_type::BPF_PROG_TYPE_TRACEPOINT as u32;

test/integration-test/src/tests/smoke.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
11
use aya::{
2-
programs::{Extension, TracePoint, Xdp, XdpFlags},
2+
is_program_type_supported,
3+
programs::{Extension, ProgramType, TracePoint, Xdp, XdpFlags},
34
util::KernelVersion,
45
Ebpf, EbpfLoader,
56
};
67
use test_log::test;
78

89
use crate::utils::NetNsGuard;
910

11+
#[test]
12+
fn progam_is_supported() {
13+
// All of these program types have been supported for a long time and are
14+
// used in our tests without any special checks.
15+
16+
assert!(is_program_type_supported(ProgramType::Xdp).unwrap());
17+
assert!(is_program_type_supported(ProgramType::TracePoint).unwrap());
18+
// Kprobe and uprobe are the same program type
19+
assert!(is_program_type_supported(ProgramType::KProbe).unwrap());
20+
assert!(is_program_type_supported(ProgramType::SchedClassifier).unwrap());
21+
}
22+
1023
#[test]
1124
fn xdp() {
1225
let kernel_version = KernelVersion::current().unwrap();

xtask/public-api/aya.txt

+3
Original file line numberDiff line numberDiff line change
@@ -7651,6 +7651,8 @@ impl core::clone::Clone for aya::programs::ProgramType
76517651
pub fn aya::programs::ProgramType::clone(&self) -> aya::programs::ProgramType
76527652
impl core::cmp::PartialEq for aya::programs::ProgramType
76537653
pub fn aya::programs::ProgramType::eq(&self, other: &aya::programs::ProgramType) -> bool
7654+
impl core::convert::From<aya::programs::ProgramType> for aya_obj::generated::linux_bindings_x86_64::bpf_prog_type
7655+
pub fn aya_obj::generated::linux_bindings_x86_64::bpf_prog_type::from(value: aya::programs::ProgramType) -> Self
76547656
impl core::convert::TryFrom<aya_obj::generated::linux_bindings_x86_64::bpf_prog_type> for aya::programs::ProgramType
76557657
pub type aya::programs::ProgramType::Error = aya::programs::ProgramError
76567658
pub fn aya::programs::ProgramType::try_from(prog_type: aya_obj::generated::linux_bindings_x86_64::bpf_prog_type) -> core::result::Result<Self, Self::Error>
@@ -10034,6 +10036,7 @@ impl aya::Pod for u8
1003410036
impl<K: aya::Pod> aya::Pod for aya::maps::lpm_trie::Key<K>
1003510037
impl<T: aya::Pod, const N: usize> aya::Pod for [T; N]
1003610038
pub fn aya::features() -> &'static aya_obj::obj::Features
10039+
pub fn aya::is_program_type_supported(program_type: aya::programs::ProgramType) -> core::result::Result<bool, aya::EbpfError>
1003710040
pub type aya::Bpf = aya::Ebpf
1003810041
pub type aya::BpfError = aya::EbpfError
1003910042
pub type aya::BpfLoader<'a> = aya::EbpfLoader<'a>

0 commit comments

Comments
 (0)