bpf_feature/
bpf.rs

1//! Features related specifically to eBPF program development
2//!
3//! This feature set can be used to determine which eBPF program types, maps &
4//! helpers are available to your runtime.
5use bpf_rs::libbpf_sys::bpf_prog_load;
6use bpf_rs::{BpfHelper, Error as BpfSysError, MapType, ProgramType};
7use nix::errno::Errno;
8use std::collections::HashMap;
9use std::ptr;
10use thiserror::Error as ThisError;
11
12#[cfg(feature = "serde")]
13use crate::serde_ext;
14#[cfg(feature = "serde")]
15use bpf_rs_macros::SerializeFromDisplay;
16#[cfg(feature = "serde")]
17use serde::Serialize;
18
19/// Captures potential errors from detection techniques
20#[non_exhaustive]
21#[derive(ThisError, Debug)]
22#[cfg_attr(feature = "serde", derive(SerializeFromDisplay))]
23pub enum BpfError {
24    /// [`bpf(2)`](https://man7.org/linux/man-pages/man2/bpf.2.html) syscall is
25    /// not available
26    #[error("no bpf syscall on system")]
27    NoBpfSyscall,
28    /// If an error occurs during probing of a feature, we propagate it to the
29    /// client
30    #[error("bpf-rs::Error: {0}")]
31    ProbeErr(#[from] BpfSysError),
32}
33
34/// Results for each eBPF detection technique
35///
36/// The determination of support for these features relies on the implementations
37/// provided by [libbpf](https://github.com/libbpf/libbpf).
38#[derive(Debug)]
39#[cfg_attr(feature = "serde", derive(Serialize))]
40pub struct Bpf {
41    /// Attempts to load a simple program without error to determine if syscall
42    /// is available
43    pub has_bpf_syscall: bool,
44    /// For each program type, we determine definite support or propagate
45    /// the resulting error to the client.
46    ///
47    /// Internally, this relies on libbpf's `libbpf_probe_bpf_prog_type` implementation
48    /// which currently attempts to load a basic program of each type to determine
49    /// support
50    #[cfg_attr(feature = "serde", serde(serialize_with = "serde_ext::to_list"))]
51    pub program_types: HashMap<ProgramType, Result<bool, BpfError>>,
52    /// For each program type, we determine definite support or propagate
53    /// the resulting error to the client
54    ///
55    /// Internally, this relies on libbpf's `libbpf_probe_bpf_map_type` implementation
56    /// which currently attempts to create a map of each type to determine
57    /// support
58    #[cfg_attr(feature = "serde", serde(serialize_with = "serde_ext::to_list"))]
59    pub map_types: HashMap<MapType, Result<bool, BpfError>>,
60    /// Returns a list of supported helpers (or error if probe fails) for each
61    /// program type.
62    ///
63    /// Note: If the program type is **NOT** supported, then the list
64    /// will be empty. If the program type is supported but an error occurs on the
65    /// individual helper probe, that error will be propagated to the list.
66    #[cfg_attr(feature = "serde", serde(serialize_with = "serde_ext::to_list_inner"))]
67    pub helpers: HashMap<ProgramType, Vec<Result<BpfHelper, BpfError>>>,
68}
69
70impl Bpf {
71    fn probe_syscall() -> bool {
72        Errno::clear();
73        unsafe {
74            bpf_prog_load(
75                ProgramType::Unspec.into(),
76                ptr::null(),
77                ptr::null(),
78                ptr::null(),
79                0,
80                ptr::null(),
81            );
82        }
83        Errno::last() != Errno::ENOSYS
84    }
85
86    fn probe_program_types() -> HashMap<ProgramType, Result<bool, BpfError>> {
87        ProgramType::iter()
88            .map(|program_type| {
89                (
90                    program_type,
91                    program_type.probe().map_err(BpfError::ProbeErr),
92                )
93            })
94            .collect()
95    }
96
97    fn probe_map_types() -> HashMap<MapType, Result<bool, BpfError>> {
98        MapType::iter()
99            .map(|map_type| (map_type, map_type.probe().map_err(BpfError::ProbeErr)))
100            .collect()
101    }
102
103    fn probe_helpers(full: bool) -> HashMap<ProgramType, Vec<Result<BpfHelper, BpfError>>> {
104        ProgramType::iter()
105            .map(|program_type| {
106                // NOTE: Due to libbpf's `libbpf_probe_bpf_helper` implementation, it may return true
107                // for helpers of **unsupported** program types so the user is forced to check
108                // against this before probing for helper support.
109                match program_type.probe() {
110                    Ok(true) => {
111                        let helpers = BpfHelper::iter()
112                            .filter_map(|helper| {
113                                if !full {
114                                    match helper {
115                                        BpfHelper::TracePrintk
116                                        | BpfHelper::TraceVprintk
117                                        | BpfHelper::ProbeWriteUser => return None,
118                                        _ => {}
119                                    };
120                                }
121
122                                match program_type.probe_helper(helper) {
123                                    Ok(true) => Some(Ok(helper)),
124                                    Ok(false) => None,
125                                    Err(err) => Some(Err(BpfError::ProbeErr(err))),
126                                }
127                            })
128                            .collect();
129                        (program_type, helpers)
130                    }
131                    Ok(false) | Err(_) => (program_type, vec![]),
132                }
133            })
134            .collect()
135    }
136}
137
138/// Options that can be passed into [`features`]
139#[derive(Default)]
140pub struct BpfFeaturesOpts {
141    /// For compatibility purposes with bpftool, the helpers determined support for
142    /// is not the complete set. A few always-available helpers are filtered out
143    /// such as `bpf_trace_printk`, `bpf_trace_vprintk`, and `bpf_probe_write_user`.
144    ///
145    /// Default: `false`
146    pub full_helpers: bool,
147}
148
149/// This module's main function to run [`Bpf`] feature detection set
150pub fn features(opts: BpfFeaturesOpts) -> Result<Bpf, BpfError> {
151    if !Bpf::probe_syscall() {
152        return Err(BpfError::NoBpfSyscall);
153    }
154
155    Ok(Bpf {
156        has_bpf_syscall: true,
157        program_types: Bpf::probe_program_types(),
158        map_types: Bpf::probe_map_types(),
159        helpers: Bpf::probe_helpers(opts.full_helpers),
160    })
161}