bpf_feature/
kernel_config.rs

1//! Features derived from compile-time kernel configuration
2//!
3//! The Linux kernel accepts an assortment of flags that can be enabled or disabled
4//! at compilation. These configuration flags are used to determine which eBPF
5//! features are eventually available on the running kernel.
6//!
7//! Depending on your distribution, your kernel config can be available in a number
8//! of locations such as:
9//!
10//! - `/proc/config.gz`
11//! - `/boot/config`
12//! - `/boot/config-$(uname -r)`
13//!
14//! This module will search for and read your kernel configuration for relevant
15//! eBPF flags. If you believe a flag is missing or incorrectly added to
16//! the set in [`KERNEL_CONFIG_KEYS`],  please file [an issue](https://github.com/bpfdeploy-io/bpf-rs).
17use flate2::bufread::GzDecoder;
18use nix::sys::utsname;
19use std::{
20    collections::HashMap,
21    fmt::Display,
22    fs::File,
23    io::{BufRead, BufReader},
24};
25use thiserror::Error as ThisError;
26
27#[cfg(feature = "serde")]
28use bpf_rs_macros::SerializeFromDisplay;
29#[cfg(feature = "serde")]
30use serde::Serialize;
31
32/// Entire set of kernel config flags to determine support of
33pub const KERNEL_CONFIG_KEYS: [&str; 35] = [
34    "CONFIG_BPF",
35    "CONFIG_BPF_SYSCALL",
36    "CONFIG_HAVE_EBPF_JIT",
37    "CONFIG_BPF_JIT",
38    "CONFIG_BPF_JIT_ALWAYS_ON",
39    "CONFIG_DEBUG_INFO_BTF",
40    "CONFIG_DEBUG_INFO_BTF_MODULES",
41    "CONFIG_CGROUPS",
42    "CONFIG_CGROUP_BPF",
43    "CONFIG_CGROUP_NET_CLASSID",
44    "CONFIG_SOCK_CGROUP_DATA",
45    "CONFIG_BPF_EVENTS",
46    "CONFIG_KPROBE_EVENTS",
47    "CONFIG_UPROBE_EVENTS",
48    "CONFIG_TRACING",
49    "CONFIG_FTRACE_SYSCALLS",
50    "CONFIG_FUNCTION_ERROR_INJECTION",
51    "CONFIG_BPF_KPROBE_OVERRIDE",
52    "CONFIG_NET",
53    "CONFIG_XDP_SOCKETS",
54    "CONFIG_LWTUNNEL_BPF",
55    "CONFIG_NET_ACT_BPF",
56    "CONFIG_NET_CLS_BPF",
57    "CONFIG_NET_CLS_ACT",
58    "CONFIG_NET_SCH_INGRESS",
59    "CONFIG_XFRM",
60    "CONFIG_IP_ROUTE_CLASSID",
61    "CONFIG_IPV6_SEG6_BPF",
62    "CONFIG_BPF_LIRC_MODE2",
63    "CONFIG_BPF_STREAM_PARSER",
64    "CONFIG_NETFILTER_XT_MATCH_BPF",
65    "CONFIG_BPFILTER",
66    "CONFIG_BPFILTER_UMH",
67    "CONFIG_TEST_BPF",
68    "CONFIG_HZ",
69];
70
71/// Possible errors when reading a kernel's config file
72#[non_exhaustive]
73#[derive(ThisError, Debug)]
74#[cfg_attr(feature = "serde", derive(Serialize))]
75pub enum KernelConfigError {
76    /// Kernel config file was not found
77    #[error("can't open file")]
78    NotFound,
79    /// Could not parse contents of config file
80    #[error("file data format unknown")]
81    ContentsUnknown,
82    /// IO error reading the config file
83    #[error("can't read from file")]
84    ReadFail,
85}
86
87/// Variant of possible config values (e.g. `y`, `n`, `m` etc.)
88#[derive(Debug)]
89#[cfg_attr(feature = "serde", derive(SerializeFromDisplay))]
90pub enum ConfigValue {
91    /// This kernel feature is available.
92    Y,
93    /// This kernel feature is **NOT** available.
94    ///
95    /// This might mean that you need to upgrade your kernel, flag an issue
96    /// with your Linux distro or compile your own kernel to get the necessary
97    /// functionality.
98    N,
99    /// This kernel feature is available *as a module only*.
100    ///
101    /// This means that the feature is available on your system as a kernel
102    /// module but might require privileged enabling of it to gain functionality.
103    M,
104    NotSet,
105    /// This kernel flag is an unstructured value determined at compile time
106    Other(String),
107}
108
109impl Display for ConfigValue {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        match self {
112            ConfigValue::Y => write!(f, "y"),
113            ConfigValue::N => write!(f, "n"),
114            ConfigValue::M => write!(f, "m"),
115            ConfigValue::NotSet => write!(f, "not set"),
116            ConfigValue::Other(value) => write!(f, "{}", value),
117        }
118    }
119}
120
121type KernelConfigValues = HashMap<&'static str, ConfigValue>;
122
123/// Primarily just a wrapper for kernel config values
124#[derive(Debug)]
125#[cfg_attr(feature = "serde", derive(Serialize))]
126pub struct KernelConfig {
127    /// A HashMap of kernel config values with the key being the
128    /// flag and the value derived from reading the kernel config file
129    #[cfg_attr(feature = "serde", serde(flatten))]
130    pub values: KernelConfigValues,
131}
132
133impl KernelConfig {
134    fn probe_kernel_config() -> Result<KernelConfigValues, KernelConfigError> {
135        let utsn = utsname::uname();
136
137        let config_reader: Box<dyn BufRead> =
138            match File::open(format!("/boot/config-{}", utsn.release())) {
139                Err(_) => {
140                    let compressed_config =
141                        File::open("/proc/config.gz").map_err(|_| KernelConfigError::NotFound)?;
142                    let decoder = GzDecoder::new(BufReader::new(compressed_config));
143                    Box::new(BufReader::new(decoder))
144                }
145                Ok(f) => Box::new(BufReader::new(f)),
146            };
147
148        let mut lines_iter = config_reader.lines();
149        let _ = lines_iter
150            .next()
151            .transpose()
152            .map_err(|_| KernelConfigError::ReadFail)?
153            .ok_or(KernelConfigError::ReadFail)?;
154        let line = lines_iter
155            .next()
156            .transpose()
157            .map_err(|_| KernelConfigError::ReadFail)?
158            .ok_or(KernelConfigError::ReadFail)?;
159
160        if !line.starts_with("# Automatically generated file; DO NOT EDIT.") {
161            return Err(KernelConfigError::ContentsUnknown);
162        }
163
164        let mut config = HashMap::from(KERNEL_CONFIG_KEYS.map(|key| (key, ConfigValue::NotSet)));
165
166        for line_item in lines_iter {
167            let line = line_item.map_err(|_| KernelConfigError::ReadFail)?;
168            if !line.starts_with("CONFIG_") {
169                continue;
170            }
171
172            let split_items: Vec<_> = line.split('=').collect();
173            if split_items.len() < 2 {
174                continue;
175            }
176
177            let line_key = split_items[0];
178            let line_value = split_items[1];
179
180            for key in KERNEL_CONFIG_KEYS {
181                if key != line_key {
182                    continue;
183                }
184
185                config.insert(
186                    key,
187                    match line_value {
188                        "y" => ConfigValue::Y,
189                        "m" => ConfigValue::M,
190                        "n" => ConfigValue::N,
191                        _ => ConfigValue::Other(line_value.to_string()),
192                    },
193                );
194            }
195        }
196
197        Ok(config)
198    }
199}
200
201/// This module's main function to read and determine support of kernel config
202/// flags
203pub fn features() -> Result<KernelConfig, KernelConfigError> {
204    Ok(KernelConfig {
205        values: KernelConfig::probe_kernel_config()?,
206    })
207}