use sha2::Digest;
use std::path::{Path, PathBuf};
use std::process::Command;
const PROGRAMS: &[(&str, &str, &str)] = &[
("xdp_main", "xdp_main.c", "xdp_main.o"),
("xdp_redirect", "xdp_redirect.c", "xdp_redirect.o"),
("xdp_stats", "xdp_stats.c", "xdp_stats.o"),
];
fn main() {
if std::env::var("CARGO_CFG_TARGET_OS").map(|v| v != "linux").unwrap_or(false) {
return;
}
let manifest_dir = match std::env::var("CARGO_MANIFEST_DIR") {
Ok(dir) => dir,
Err(_) => {
eprintln!("cargo:error=CARGO_MANIFEST_DIR not set, cannot proceed");
return;
}
};
let bpf_dir = PathBuf::from(&manifest_dir).join("..").join("..").join("bpf");
let prebuilt_dir = bpf_dir.join("prebuilt");
let src_dir = bpf_dir.join("src");
let include_dir = bpf_dir.join("include");
if !bpf_dir.exists() {
eprintln!("cargo:warning=bpf/ directory not found (published crate mode), skipping BPF build");
return;
}
if let Err(e) = std::fs::create_dir_all(&prebuilt_dir) {
eprintln!(
"cargo:error=Failed to create prebuilt dir {}: {}",
prebuilt_dir.display(),
e
);
std::process::exit(1);
}
let mut need_compile = false;
for (_name, _src, obj) in PROGRAMS {
let obj_path = prebuilt_dir.join(obj);
match std::fs::metadata(&obj_path) {
Ok(meta) if meta.len() > 0 => {
}
_ => {
need_compile = true;
break;
}
}
}
if need_compile {
eprintln!("cargo:warning=eBPF prebuilt artifacts missing or empty, compiling...");
if let Err(e) = compile_all(&src_dir, &prebuilt_dir, &include_dir) {
eprintln!("cargo:error={}", e);
let allow_missing = std::env::var("ZENITH_ALLOW_MISSING_BPF")
.map(|v| v == "1" || v == "true")
.unwrap_or(false);
if !allow_missing {
eprintln!(
"cargo:error=eBPF compilation failed. Set ZENITH_ALLOW_MISSING_BPF=1 \
only for development/testing without kernel."
);
std::process::exit(1);
} else {
eprintln!("cargo:warning=Creating empty placeholders (ZENITH_ALLOW_MISSING_BPF=1 set)");
for (_name, _src, obj) in PROGRAMS {
let obj_path = prebuilt_dir.join(obj);
if !obj_path.exists() {
let _ = std::fs::write(&obj_path, &[] as &[u8]);
}
}
}
}
}
if let Err(e) = verify_elf_integrity(&prebuilt_dir) {
eprintln!("cargo:error=eBPF ELF 完整性校验失败: {}", e);
let allow_missing = std::env::var("ZENITH_ALLOW_MISSING_BPF")
.map(|v| v == "1" || v == "true")
.unwrap_or(false);
if !allow_missing {
std::process::exit(1);
}
}
if let Err(e) = generate_hashes(&prebuilt_dir, &manifest_dir) {
eprintln!("cargo:error=Failed to generate hash file: {}", e);
std::process::exit(1);
}
println!("cargo:rerun-if-changed={}", src_dir.display());
println!("cargo:rerun-if-changed={}", include_dir.display());
println!("cargo:rerun-if-changed={}", prebuilt_dir.display());
println!("cargo:rerun-if-env-changed=ZENITH_ALLOW_MISSING_BPF");
println!("cargo:rerun-if-env-changed=ZENITH_BPF_STRICT_CHECK");
}
fn verify_elf_integrity(prebuilt_dir: &Path) -> Result<(), String> {
const ELF_MAGIC: [u8; 4] = [0x7f, b'E', b'L', b'F'];
const MIN_ELF_SIZE: usize = 64;
for (_name, _src, obj) in PROGRAMS {
let obj_path = prebuilt_dir.join(obj);
let data = std::fs::read(&obj_path)
.map_err(|e| format!("读取 {} 失败: {}", obj_path.display(), e))?;
if data.is_empty() {
continue;
}
if data.len() < 4 || data[..4] != ELF_MAGIC {
return Err(format!(
"{} ELF magic 错误:期望 {:?},实际 {:?}",
obj,
ELF_MAGIC,
if data.len() >= 4 { &data[..4] } else { &data }
));
}
if data.len() < MIN_ELF_SIZE {
return Err(format!(
"{} 文件过小:{} 字节 < {} 字节(ELF64 头部最小尺寸)",
obj,
data.len(),
MIN_ELF_SIZE
));
}
if data[4] != 2 {
return Err(format!(
"{} ELF class 错误:期望 2 (ELF64),实际 {}",
obj, data[4]
));
}
if data.len() >= 20 {
let e_machine = u16::from_le_bytes([data[18], data[19]]);
if e_machine != 247 {
return Err(format!(
"{} e_machine 错误:期望 247 (EM_BPF),实际 {}。\
可能 clang -target bpf 未正确设置",
obj, e_machine
));
}
}
eprintln!("cargo:warning=ELF 校验通过: {} ({} bytes, ELF64, EM_BPF)", obj, data.len());
}
Ok(())
}
fn compile_all(src_dir: &Path, out_dir: &Path, include_dir: &Path) -> Result<(), String> {
let clang = find_clang()?;
for (_name, src, obj) in PROGRAMS {
let src_path = src_dir.join(src);
let obj_path = out_dir.join(obj);
if !src_path.exists() {
return Err(format!("Source file not found: {}", src_path.display()));
}
let status = Command::new(&clang)
.args([
"-O2",
"-target",
"bpf",
"-g",
"-c",
src_path.to_str().ok_or("Invalid source path")?,
"-o",
obj_path.to_str().ok_or("Invalid output path")?,
"-I",
include_dir.to_str().ok_or("Invalid include path")?,
"-I",
"/usr/include",
"-Wall",
"-Werror",
"-fno-stack-protector",
"-Wno-macro-redefined",
"-Wno-unused-value",
"-Wno-pointer-to-int-cast",
])
.status()
.map_err(|e| format!("Failed to execute clang: {}", e))?;
if !status.success() {
return Err(format!(
"clang compilation failed for {} (exit code: {:?})",
src,
status.code()
));
}
eprintln!("cargo:warning=Compiled {} successfully", obj);
}
Ok(())
}
fn find_clang() -> Result<PathBuf, String> {
if let Ok(clang_path) = std::env::var("CLANG_PATH") {
let p = PathBuf::from(&clang_path);
if p.exists() {
return Ok(p);
}
}
match Command::new("clang").arg("--version").output() {
Ok(output) if output.status.success() => Ok(PathBuf::from("clang")),
_ => {
for ver in ["clang-18", "clang-17", "clang-16", "clang-15", "clang-14"] {
if Command::new(ver).arg("--version").output().map(|o| o.status.success()).unwrap_or(false) {
return Ok(PathBuf::from(ver));
}
}
Err("clang not found. Please install clang (>=14) or set CLANG_PATH.".to_string())
}
}
}
fn generate_hashes(prebuilt_dir: &Path, manifest_dir: &str) -> Result<(), String> {
let out_dir = PathBuf::from(manifest_dir).join("src").join("generated");
std::fs::create_dir_all(&out_dir)
.map_err(|e| format!("Failed to create generated dir: {}", e))?;
let hashes_path = out_dir.join("bpf_hashes.rs");
let mut hashes = Vec::new();
for (name, _src, obj) in PROGRAMS {
let obj_path = prebuilt_dir.join(obj);
let hash = if obj_path.exists() {
compute_sha256(&obj_path)?
} else {
compute_sha256_bytes(&[])?
};
hashes.push((name, hash));
}
let mut content = String::with_capacity(512);
content.push_str("// Auto-generated SHA-256 hashes for eBPF prebuilt bytecode\n");
content.push_str("// Generated by build.rs - DO NOT EDIT\n\n");
content.push_str("/// 预编译 eBPF 字节码的期望 SHA-256 哈希值表(程序名 → 哈希)。\n");
content.push_str("pub const EXPECTED_HASHES: &[(&str, &str)] = &[\n");
for (name, hash) in &hashes {
content.push_str(&format!(" (\"{}\", \"{}\"),\n", name, hash));
}
content.push_str("];\n");
match std::fs::read_to_string(&hashes_path) {
Ok(existing) if existing == content => {
eprintln!("cargo:warning=SHA-256 hashes unchanged, skip rewriting {}", hashes_path.display());
}
_ => {
std::fs::write(&hashes_path, &content)
.map_err(|e| format!("Failed to write hash file: {}", e))?;
eprintln!("cargo:warning=Generated SHA-256 hashes in {}", hashes_path.display());
}
}
Ok(())
}
fn compute_sha256(path: &Path) -> Result<String, String> {
let data = std::fs::read(path)
.map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
compute_sha256_bytes(&data)
}
fn compute_sha256_bytes(data: &[u8]) -> Result<String, String> {
let digest = sha2::Sha256::digest(data);
let mut hex = String::with_capacity(digest.len() * 2);
for byte in digest {
hex.push_str(&format!("{:02x}", byte));
}
Ok(hex)
}