diff --git a/.gitignore b/.gitignore index 0248fbf2..bf144acc 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ libbpf/ site/ header.html .idea/ + +kerneltest/initramfs.cpio +kerneltest/kernels/* diff --git a/kerneltest/.gitkeep b/kerneltest/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/kerneltest/kernels/.gitkeep b/kerneltest/kernels/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 671a987c..1729c655 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -14,3 +14,10 @@ proc-macro2 = "1" indoc = "2.0" lazy_static = "1" serde_json = "1" +reqwest = { version = "0.11.18", default-features = false, features = [ + "blocking", + "default-tls", +] } +which = "4.4.0" +itertools = "0.11.0" +tempfile = "3.6.0" diff --git a/xtask/src/kernel_test.rs b/xtask/src/kernel_test.rs new file mode 100644 index 00000000..3476fc58 --- /dev/null +++ b/xtask/src/kernel_test.rs @@ -0,0 +1,152 @@ +use std::{ + path::{Path, PathBuf}, + process::Command, +}; + +use anyhow::Context as _; +use clap::Parser; +use itertools::Itertools; + +use crate::{build_ebpf, integration_test}; + +#[derive(Debug, Parser)] +pub struct Options { + /// Set the endianness of the BPF target + #[clap(default_value = "bpfel-unknown-none", long)] + pub bpf_target: build_ebpf::Architecture, + /// Build and run the release target + #[clap(long)] + pub release: bool, + /// libbpf directory + #[clap(long, action)] + pub libbpf_dir: String, + /// Kernel version to use. + #[clap(name = "kernel-version", long)] + pub kernel_version: String, + /// Path to the kerneltest root. + #[clap(name = "kerneltest-root", long, default_value = "kerneltest")] + pub kerneltest_root: String, + /// Arguments to pass to your application + #[clap(name = "args", last = true)] + pub run_args: Vec, +} + +/// MUSL_TARGET is the target triple for the musl libc +/// platform. It defaults to statically linking binaries. +/// We need a statically linked binary to run in the initramfs +/// created by bluebox. +const MUSL_TARGET: &str = "x86_64-unknown-linux-musl"; + +pub(crate) fn kernel_test(opts: Options) -> Result<(), anyhow::Error> { + let Options { + bpf_target, + release, + libbpf_dir, + run_args, + kernel_version, + kerneltest_root, + } = opts; + build_ebpf::build_ebpf(build_ebpf::BuildEbpfOptions { + target: bpf_target, + libbpf_dir: PathBuf::from(&libbpf_dir), + }) + .context("failed to build ebpf")?; + let integration_test_bin = integration_test::build(integration_test::BuildOptions { + release, + target: Some(String::from(MUSL_TARGET)), + })?; + let kerneltest_root = PathBuf::from(&kerneltest_root); + let initramfs_path = build_initramfs(&kerneltest_root, &integration_test_bin, &run_args)?; + let kernel_image_path = get_kernel(&kernel_version, &kerneltest_root)?; + run(&initramfs_path, &kernel_image_path) +} + +const BLUEBOX_BINARY: &str = "bluebox"; + +fn build_initramfs( + kerneltest_root: &Path, + integration_test_bin: &Path, + integration_test_args: &[String], +) -> Result { + let integration_test_bin = integration_test_bin + .to_str() + .context("illegal path to integration test binary")?; + let args = match integration_test_args { + [] => String::new(), + _ => std::iter::once(":\"") + .chain(itertools::Itertools::intersperse( + integration_test_args.iter().map(|v| v.as_str()), + " ", + )) + .chain(std::iter::once("\"")) + .collect_vec() + .join(""), + }; + let initramfs_path = PathBuf::from(kerneltest_root).join("initramfs.cpio"); + which::which(BLUEBOX_BINARY) + .with_context(|| format!("{BLUEBOX_BINARY} not found")) + .context("try installing with `go install github.com/florianl/bluebox@latest`")?; + let args = format!("{integration_test_bin}{args}"); + match Command::new(BLUEBOX_BINARY) + .arg("-e") + .arg(&args) + .arg("-o") + .arg(&initramfs_path) + .status() + { + Err(err) => Err(anyhow::anyhow!("failed to build initramfs: {}", err)), + Ok(status) if !status.success() => Err(anyhow::anyhow!( + "failed to build initramfs: status code {}", + status + )), + Ok(_) => Ok(initramfs_path), + } +} + +fn get_kernel(kernel_version: &str, kerneltest_root: &Path) -> Result { + let kernel_name = format!("linux-{}.bz", kernel_version); + let image_path = PathBuf::from(kerneltest_root) + .join("kernels") + .join(kernel_name); + if image_path.exists() && image_path.is_file() { + return Ok(image_path); + } + + let mut tmp = tempfile::NamedTempFile::new().context("creating temp file for kernel image")?; + let url = format!( + "https://github.com/cilium/ci-kernels/raw/a15c0b2aa7cf32640c03764fa79b0a815608ddce/linux-{kernel_version}.bz" + ); + + reqwest::blocking::get(url) + .context("fetching kernel")? + .copy_to(&mut tmp) + .context("writing kernel file")?; + tmp.persist_noclobber(&image_path) + .context(format!("persisting kernel file {:?}", &image_path))?; + Ok(image_path) +} + +const QEMU_BINARY: &str = "qemu-system-x86_64"; + +fn run(initramfs: &std::path::Path, kernel_image: &Path) -> Result<(), anyhow::Error> { + which::which(QEMU_BINARY).with_context(|| format!("{QEMU_BINARY} not found"))?; + let args = vec![ + "-no-reboot", + "-append", + "printk.devkmsg=on kernel.panic=-1 crashkernel=256M", + "-kernel", + kernel_image.to_str().context("illegal kernel image path")?, + "-initrd", + initramfs.to_str().context("illegal initramfs path")?, + "-nographic", + "-append", + "console=ttyS0", + "-m", + "1.5G", + ]; + match Command::new(QEMU_BINARY).args(args).status() { + Err(err) => Err(anyhow::anyhow!("failed to run qemu: {}", err)), + Ok(status) if !status.success() => Err(anyhow::anyhow!("failed to run qemu: {}", status)), + Ok(_) => Ok(()), + } +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index e50e8675..72db7f45 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -3,6 +3,7 @@ mod build_test; mod codegen; mod docs; mod integration_test; +mod kernel_test; pub(crate) mod utils; @@ -22,6 +23,7 @@ enum Command { BuildIntegrationTest(build_test::Options), BuildIntegrationTestEbpf(build_ebpf::BuildEbpfOptions), IntegrationTest(integration_test::Options), + KernelTest(kernel_test::Options), } fn main() { @@ -34,6 +36,7 @@ fn main() { BuildIntegrationTest(opts) => build_test::build_test(opts), BuildIntegrationTestEbpf(opts) => build_ebpf::build_ebpf(opts), IntegrationTest(opts) => integration_test::run(opts), + KernelTest(opts) => kernel_test::kernel_test(opts), }; if let Err(e) = ret {