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