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}