diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index acce6c16c..f45557207 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 3bfa89f39..4f9b45b85 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 1c2fadefd..08596a453 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 0d57aea9f..34b20e9c3 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 3c8367b75..cee3d0aae 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>