Skip to content

Commit faaad10

Browse files
committed
build: generate vmlinux at compile time
Previously SeaBee relied on the kernel version to decide which eBPF code to compile and load. This method proved unreliable since many distros backport features to old kernel versions. In order to make sure that our code always builds correctly, this commit strips vmlinux.h from the respoitory and adds code to the build.rs script to generate vmlinux.h and detect features in it. This enables our test cases to pass on rocky 9 linux. Signed-off-by: Alan Wandke <[email protected]>
1 parent ebd758b commit faaad10

File tree

15 files changed

+129411
-295072
lines changed

15 files changed

+129411
-295072
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@
44
__pycache__
55
seabee-root-private-key.pem
66
seabee-root-public-key.pem
7+
bpf/include/vmlinux.h
8+
bpf/include/vmlinux_features.h

Cargo.lock

Lines changed: 2 additions & 51 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ ctrlc = { version = "3.4", features = ["termination"] }
1212
libbpf-rs = { version = "0.25.0-beta.1", default-features = false, features = ["vendored"] }
1313
libtest-mimic = "0.7"
1414
nix = "0.28"
15-
procfs = { version = "0.17", default-features = false }
1615
serde = { version = "1.0", features = ["derive"] }
1716
serde_json = "1.0"
1817
strum = "0.26"

bpf/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,3 @@ bindgen = { version = "0.69", default-features = false, features = ["runtime"] }
2323
doxygen-rs = "0.4"
2424
libbpf-rs.workspace = true
2525
libbpf-cargo = { version = "0.25.0-beta.1", default-features = false}
26-
procfs.workspace = true

bpf/build.rs

Lines changed: 99 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,29 @@
11
// SPDX-License-Identifier: Apache-2.0
2-
use std::env;
3-
use std::fs;
2+
use std::collections::HashSet;
3+
use std::io::{BufRead, BufReader, Write};
44
use std::path::{Path, PathBuf};
5+
use std::process::{Command, Stdio};
6+
use std::{env, fs};
57

6-
use anyhow::{Context, Result};
8+
use anyhow::{anyhow, Context, Result};
79
use libbpf_cargo::SkeletonBuilder;
8-
use procfs::KernelVersion;
10+
11+
const VMLINUX_PATH: &str = "include/vmlinux.h";
12+
const VMLINUX_FEATURES: [&str; 1] = ["bpf_map_create"];
913

1014
/// Tells Cargo to rerun the build if the supplied file has changed
1115
fn track_file(header: &str) {
1216
println!("cargo:rerun-if-changed={header}");
1317
}
1418

1519
/// Converts a BPF source code path to a skeleton path for [libbpf_rs]
16-
fn get_skel_path(src_file: &Path, version: Option<&KernelVersion>) -> String {
20+
fn get_skel_path(src_file: &Path) -> PathBuf {
1721
if let Some(bpf) = src_file.file_stem() {
1822
if let Some(stem) = Path::new(bpf).file_stem() {
19-
let mut skel_path = String::new();
20-
skel_path.push_str(&stem.to_string_lossy());
21-
// if a kernel version is specified, add that at the end of the name
22-
if let Some(version) = version {
23-
skel_path.push_str(&format!(
24-
"_{}_{}_{}",
25-
version.major, version.minor, version.patch
26-
));
27-
}
28-
skel_path.push_str(".skel.rs");
29-
return skel_path;
23+
let mut skel = PathBuf::new();
24+
skel.push(stem);
25+
skel.set_extension("skel.rs");
26+
return skel;
3027
}
3128
}
3229
panic!(
@@ -35,53 +32,14 @@ fn get_skel_path(src_file: &Path, version: Option<&KernelVersion>) -> String {
3532
);
3633
}
3734

38-
/// Converts [KernelVersion] to BPF_CODE_VERSION to be compared against the
39-
/// `KERNEL_VERSION` macro for conditional compilation
40-
pub fn kernel_version_to_bpf_code_version(version: &KernelVersion) -> u32 {
41-
((version.major as u32) << 16)
42-
+ ((version.minor as u32) << 8)
43-
+ std::cmp::max(version.patch as u32, 255)
44-
}
45-
4635
/// Uses [libbpf_cargo::SkeletonBuilder] to compile BPF source code to a skeleton
47-
fn compile_bpf_obj(
48-
src_file: &PathBuf,
49-
out_path: &Path,
50-
versions: Option<&[KernelVersion]>,
51-
) -> Result<()> {
52-
let mut clang_args: Vec<String> = vec![
53-
"-Iinclude".to_string(),
54-
format!("-I{}", out_path.to_string_lossy()),
55-
];
56-
let mut skel_build = SkeletonBuilder::new();
57-
skel_build.source(src_file);
58-
// if multiple versions are specified
59-
if let Some(versions) = versions {
60-
// compile the default case (every version before the first specified)
61-
clang_args.push("-DBPF_CODE_VERSION=0".to_owned());
62-
skel_build
63-
.clang_args(&clang_args)
64-
.build_and_generate(out_path.join(get_skel_path(src_file, None)))?;
65-
clang_args.pop();
66-
// compile each version of the skeleton separately
67-
for version in versions {
68-
clang_args.push(format!(
69-
"-DBPF_CODE_VERSION={}",
70-
kernel_version_to_bpf_code_version(version)
71-
));
72-
skel_build
73-
.clang_args(&clang_args)
74-
.build_and_generate(out_path.join(get_skel_path(src_file, Some(version))))?;
75-
clang_args.pop();
76-
}
77-
}
78-
// otherwise, assume all versions are supported with the same skeleton
79-
else {
80-
skel_build
81-
.clang_args(&clang_args)
82-
.build_and_generate(out_path.join(get_skel_path(src_file, None)))?;
83-
}
84-
Ok(())
36+
fn compile_bpf_obj(src_file: &PathBuf, out_path: &Path) -> Result<PathBuf> {
37+
let bpf_skel_path = out_path.join(get_skel_path(src_file));
38+
SkeletonBuilder::new()
39+
.source(src_file)
40+
.clang_args([&format!("-I{}", out_path.to_string_lossy()), "-Iinclude"])
41+
.build_and_generate(&bpf_skel_path)?;
42+
Ok(bpf_skel_path)
8543
}
8644

8745
#[derive(Debug)]
@@ -148,14 +106,11 @@ fn generate_header_bindings(hdr_file: &Path, out_path: &Path) -> Result<PathBuf>
148106
///
149107
/// Copied from https://github.com/libbpf/libbpf-rs/blob/aacaec1b7dfaa4bf9112d2f4168d77dfceee499f/libbpf-cargo/src/build.rs#L55
150108
fn extract_libbpf_headers_to_disk(target_dir: &Path) -> Result<Option<PathBuf>> {
151-
use std::fs::OpenOptions;
152-
use std::io::Write;
153-
154109
let dir = target_dir.join("bpf");
155110
fs::create_dir_all(&dir)?;
156111
for (filename, contents) in libbpf_rs::libbpf_sys::API_HEADERS.iter() {
157112
let path = dir.as_path().join(filename);
158-
let mut file = OpenOptions::new()
113+
let mut file = fs::OpenOptions::new()
159114
.write(true)
160115
.create(true)
161116
.truncate(true)
@@ -170,12 +125,7 @@ fn extract_libbpf_headers_to_disk(target_dir: &Path) -> Result<Option<PathBuf>>
170125
///
171126
/// You can pass a vector of filenames to explicitly ignore if they are known to cause
172127
/// problems for either Clang or bindgen (e.g. "vmlinux.h")
173-
fn build(
174-
out_path: &Path,
175-
base_path: &str,
176-
ignore_files: Vec<&str>,
177-
versions: Option<&[KernelVersion]>,
178-
) -> Result<()> {
128+
fn build(out_path: &Path, base_path: &str, ignore_files: Vec<&str>) -> Result<()> {
179129
for path in fs::read_dir(base_path)?
180130
.filter_map(|r| r.ok())
181131
.map(|r| r.path())
@@ -189,7 +139,7 @@ fn build(
189139
continue;
190140
}
191141
if path_str.ends_with(".bpf.c") {
192-
compile_bpf_obj(&path, out_path, versions)?;
142+
compile_bpf_obj(&path, out_path)?;
193143
}
194144
if path_str.ends_with(".h") {
195145
generate_header_bindings(&path, out_path)?;
@@ -198,9 +148,82 @@ fn build(
198148
Ok(())
199149
}
200150

151+
// Creates vmlinux (truncates if exists)
152+
fn generate_vmlinux() -> Result<()> {
153+
let vmlinux_file = fs::File::create(VMLINUX_PATH)?;
154+
// bpftool is installed in the update_test_dependencies.sh which
155+
// is run as part of the update_root_dependencies.sh
156+
let status = Command::new("bpftool")
157+
.args([
158+
"btf",
159+
"dump",
160+
"file",
161+
"/sys/kernel/btf/vmlinux",
162+
"format",
163+
"c",
164+
])
165+
.stdout(Stdio::from(vmlinux_file))
166+
.status()?;
167+
if !status.success() {
168+
return Err(anyhow!(
169+
"failed to generate vmlinux using bpftool: {}",
170+
status
171+
));
172+
}
173+
174+
Ok(())
175+
}
176+
177+
fn detect_vmlinux_features() -> Result<HashSet<String>> {
178+
// detect features
179+
let file = fs::File::open(VMLINUX_PATH)?;
180+
let reader = BufReader::new(file);
181+
let mut found = HashSet::new();
182+
for line in reader.lines() {
183+
let line = line?;
184+
for feat in VMLINUX_FEATURES {
185+
if line.contains(feat) {
186+
found.insert(feat.to_string());
187+
}
188+
}
189+
}
190+
191+
// validate features
192+
if found.contains("bpf_map_create") && found.contains("bpf_map_alloc_security") {
193+
return Err(anyhow!(
194+
"Conflicting function definitions for security_bpf_map_create"
195+
));
196+
} else if !found.contains("bpf_map_create") && !found.contains("bpf_map_alloc_security") {
197+
return Err(anyhow!(
198+
"No function definition found for security_bpf_map_create"
199+
));
200+
}
201+
202+
Ok(found)
203+
}
204+
205+
fn export_features_to_header(features: HashSet<String>) -> Result<()> {
206+
let mut f = fs::File::create("include/vmlinux_features.h")?;
207+
208+
writeln!(f, "// Auto-generated header from build.rs")?;
209+
210+
for flag in features {
211+
let macro_name = flag.to_uppercase();
212+
writeln!(f, "#define HAS_{}", macro_name)?;
213+
}
214+
215+
Ok(())
216+
}
217+
201218
fn main() -> Result<()> {
202219
let out_path = PathBuf::from(env::var_os("OUT_DIR").context("OUT_DIR must be set")?);
203220
extract_libbpf_headers_to_disk(&out_path)?;
221+
// Create vmlinux and do feature detection based on it
222+
if !Path::new(VMLINUX_PATH).exists() {
223+
generate_vmlinux()?;
224+
let features = detect_vmlinux_features()?;
225+
export_features_to_header(features)?;
226+
}
204227
// Build common
205228
build(
206229
&out_path,
@@ -213,16 +236,10 @@ fn main() -> Result<()> {
213236
"seabee_maps.h",
214237
"seabee_utils.h",
215238
],
216-
None,
217239
)?;
218240
// Build bpf code
219-
build(
220-
&out_path,
221-
"src/seabee",
222-
vec!["seabee_log.h"],
223-
Some(&[KernelVersion::new(6, 1, 0), KernelVersion::new(6, 9, 0)]),
224-
)?;
225-
build(&out_path, "src/kernel_api", vec![], None)?;
226-
build(&out_path, "src/tests", vec![], None)?;
241+
build(&out_path, "src/seabee", vec!["seabee_log.h"])?;
242+
build(&out_path, "src/kernel_api", vec![])?;
243+
build(&out_path, "src/tests", vec![])?;
227244
Ok(())
228245
}

0 commit comments

Comments
 (0)