From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id BDF7C1FF0C0 for ; Wed, 04 Nov 2026 18:29:49 +0100 (CET) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id E410921413; Wed, 04 Nov 2026 18:29:44 +0100 (CET) From: Hannes Laimer To: pve-devel@lists.proxmox.com Subject: [PATCH proxmox v8 1/6] ebpf: add the crate with its BPF build Date: Wed, 4 Nov 2026 18:29:13 +0100 Message-ID: <20261104172918.2643810-2-h.laimer@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20261104172918.2643810-1-h.laimer@proxmox.com> References: <20261104172918.2643810-1-h.laimer@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1793813368818 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.250 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) PROLO_LEO1 0.1 Meta Catches all Leo drug variations so far RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: YRZBB2QHQVMZH7ECBFEGOJRUEOMJOUWE X-Message-ID-Hash: YRZBB2QHQVMZH7ECBFEGOJRUEOMJOUWE X-MailFrom: h.laimer@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Each subsystem is a cargo feature. The build script compiles the C programs of the enabled ones with clang, against the kernel's UAPI headers. Signed-off-by: Hannes Laimer --- Cargo.toml | 2 + proxmox-ebpf/Cargo.toml | 21 +++++++ proxmox-ebpf/build.rs | 99 ++++++++++++++++++++++++++++++++ proxmox-ebpf/include/bpf_debug.h | 12 ++++ proxmox-ebpf/src/lib.rs | 3 + 5 files changed, 137 insertions(+) create mode 100644 proxmox-ebpf/Cargo.toml create mode 100644 proxmox-ebpf/build.rs create mode 100644 proxmox-ebpf/include/bpf_debug.h create mode 100644 proxmox-ebpf/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 87e43b545793..a98c05810e3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ members = [ "proxmox-dns-api", "proxmox-fixed-string", "proxmox-docgen", + "proxmox-ebpf", "proxmox-http", "proxmox-http-error", "proxmox-human-byte", @@ -90,6 +91,7 @@ rust-version = "1.88" # external dependencies anyhow = "1.0" +aya = "0.13" base32 = "0.4" base64 = "0.22" bitflags = "2.4" diff --git a/proxmox-ebpf/Cargo.toml b/proxmox-ebpf/Cargo.toml new file mode 100644 index 000000000000..4cfd0b3258ed --- /dev/null +++ b/proxmox-ebpf/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "proxmox-ebpf" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +exclude.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[features] +# When enabled, BPF programs compile with -DBPF_DEBUG so their DBG() macros +# expand to bpf_printk. Off by default +bpf-debug = [] + +[dependencies] +anyhow.workspace = true +aya.workspace = true +log.workspace = true +nix = { workspace = true, features = ["fs"] } diff --git a/proxmox-ebpf/build.rs b/proxmox-ebpf/build.rs new file mode 100644 index 000000000000..9cb143d955a8 --- /dev/null +++ b/proxmox-ebpf/build.rs @@ -0,0 +1,99 @@ +use std::env; +use std::path::{Path, PathBuf}; +use std::process::Command; + +// one clang version wherever the crate is built, the package depends on it by name +const CLANG: &str = "clang-21"; + +fn main() { + let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + let src_dir = manifest_dir.join("src"); + let global_include = manifest_dir.join("include"); + let bpf_debug = env::var_os("CARGO_FEATURE_BPF_DEBUG").is_some(); + + // re-run when a subsystem appears + println!("cargo:rerun-if-changed={}", src_dir.display()); + println!("cargo:rerun-if-changed={}", global_include.display()); + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-env-changed=CARGO_FEATURE_BPF_DEBUG"); + + for sub_entry in std::fs::read_dir(&src_dir).expect("read src/") { + let sub_path = sub_entry.expect("read src/ entry").path(); + let bpf_dir = sub_path.join("bpf"); + if !bpf_dir.is_dir() { + continue; + } + // each subsystem is a cargo feature of the same name, skip disabled ones + let feature = sub_path + .file_name() + .and_then(|s| s.to_str()) + .expect("subsystem dir name") + .to_uppercase() + .replace('-', "_"); + if env::var_os(format!("CARGO_FEATURE_{feature}")).is_none() { + continue; + } + println!("cargo:rerun-if-changed={}", bpf_dir.display()); + + for entry in std::fs::read_dir(&bpf_dir).expect("read bpf dir") { + let path = entry.expect("read bpf entry").path(); + let is_program = path + .file_name() + .and_then(|s| s.to_str()) + .is_some_and(|s| s.ends_with(".bpf.c")); + if !is_program { + continue; + } + compile_bpf(&path, &bpf_dir, &global_include, &out_dir, bpf_debug); + } + } +} + +fn multiarch() -> String { + let out = Command::new("dpkg-architecture") + .arg("-qDEB_HOST_MULTIARCH") + .output() + .expect("failed to invoke dpkg-architecture, is dpkg-dev installed?"); + assert!(out.status.success(), "dpkg-architecture failed"); + String::from_utf8(out.stdout).unwrap().trim().to_owned() +} + +fn compile_bpf( + src: &Path, + local_include: &Path, + global_include: &Path, + out_dir: &Path, + debug: bool, +) { + // foo.bpf.c -> foo.bpf.o (file_stem strips only the final extension) + let stem = src.file_stem().unwrap().to_str().unwrap(); + let obj_path = out_dir.join(format!("{stem}.o")); + + let mut cmd = Command::new(CLANG); + // we need -g for clang to emit BTF + cmd.args(["-O2", "-g", "-Wall", "-target", "bpf"]) + .arg("-I") + .arg(global_include) + .arg("-I") + .arg(local_include) + // the UAPI headers reach asm/ through the multiarch directory, which clang searches for + // the host target only + .arg("-I") + .arg(format!("/usr/include/{}", multiarch())); + if debug { + cmd.arg("-DBPF_DEBUG"); + } + let status = cmd + .arg("-c") + .arg(src) + .arg("-o") + .arg(&obj_path) + .status() + .unwrap_or_else(|e| panic!("failed to invoke {CLANG}: {e}")); + assert!( + status.success(), + "clang failed to compile {}", + src.display() + ); +} diff --git a/proxmox-ebpf/include/bpf_debug.h b/proxmox-ebpf/include/bpf_debug.h new file mode 100644 index 000000000000..29bd0b0f7ef9 --- /dev/null +++ b/proxmox-ebpf/include/bpf_debug.h @@ -0,0 +1,12 @@ +#ifndef BPF_DEBUG_H +#define BPF_DEBUG_H + +#include + +#ifdef BPF_DEBUG +#define DBG(fmt, ...) bpf_printk(fmt, ##__VA_ARGS__) +#else +#define DBG(fmt, ...) ((void)0) +#endif + +#endif diff --git a/proxmox-ebpf/src/lib.rs b/proxmox-ebpf/src/lib.rs new file mode 100644 index 000000000000..b19895faaa18 --- /dev/null +++ b/proxmox-ebpf/src/lib.rs @@ -0,0 +1,3 @@ +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +//! eBPF subsystems, one per concern, each behind a cargo feature of its name. Nothing here runs +//! on its own, consumers pull in only the subsystem they drive. -- 2.47.3