From 136f109cb557547aa9b5b18c80f273d14e02750c Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 10 Oct 2024 16:07:26 -0400 Subject: [PATCH 1/4] Replace xtask builds with build scripts Adapt https://github.com/aya-rs/aya/commit/3d463a3 and subsequent work to the template. This has worked very well for us in the main project, and our users should get the same hotness. Note that xtask is still used for running, as it is in the main project. --- Cargo.toml | 13 +-- README.md | 29 +----- test.sh | 9 +- xtask/Cargo.toml | 2 +- xtask/src/build.rs | 41 -------- xtask/src/build_ebpf.rs | 68 ------------ xtask/src/lib.rs | 1 + xtask/src/main.rs | 23 +---- xtask/src/run.rs | 76 ++++++-------- {{project-name}}-ebpf/Cargo.toml | 4 + {{project-name}}-ebpf/build.rs | 30 ++++++ {{project-name}}-ebpf/src/lib.rs | 3 + {{project-name}}/Cargo.toml | 17 +++ {{project-name}}/build.rs | 172 +++++++++++++++++++++++++++++++ {{project-name}}/src/main.rs | 11 +- 15 files changed, 283 insertions(+), 216 deletions(-) delete mode 100644 xtask/src/build.rs delete mode 100644 xtask/src/build_ebpf.rs create mode 100644 xtask/src/lib.rs create mode 100644 {{project-name}}-ebpf/build.rs create mode 100644 {{project-name}}-ebpf/src/lib.rs create mode 100644 {{project-name}}/build.rs diff --git a/Cargo.toml b/Cargo.toml index b411146..a833d12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ aya-log = { version = "0.2.1", default-features = false } aya-log-ebpf = { version = "0.1.1", default-features = false } anyhow = { version = "1", default-features = false } +cargo_metadata = { version = "0.18.0", default-features = false } # `std` feature is currently required to build `clap`. # # See https://github.com/clap-rs/clap/blob/61f5ee5/clap_builder/src/lib.rs#L15. @@ -18,18 +19,14 @@ env_logger = { version = "0.11.5", default-features = false } libc = { version = "0.2.159", default-features = false } log = { version = "0.4.22", default-features = false } tokio = { version = "1.40.0", default-features = false } +which = { version = "6.0.0", default-features = false } [profile.dev] -opt-level = 3 -debug = false -overflow-checks = false -lto = true panic = "abort" -incremental = false -codegen-units = 1 -rpath = false [profile.release] -lto = true panic = "abort" + +[profile.release.package.{{project-name}}-ebpf] +debug = 2 codegen-units = 1 diff --git a/README.md b/README.md index bee17f6..c870678 100644 --- a/README.md +++ b/README.md @@ -4,29 +4,10 @@ 1. Install bpf-linker: `cargo install bpf-linker` -## Build eBPF +## Build & Run -```bash -cargo xtask build-ebpf -``` +Use `cargo build`, `cargo check`, etc. as normal. Run your program with `xtask run`. -To perform a release build you can use the `--release` flag. -You may also change the target architecture with the `--target` flag. - -## Build Userspace - -```bash -cargo build -``` - -## Build eBPF and Userspace - -```bash -cargo xtask build -``` - -## Run - -```bash -RUST_LOG=info cargo xtask run -``` +Cargo build scripts are used to automatically build the eBPF correctly and include it in the +program. When not using `xtask run`, eBPF code generation is skipped for a faster developer +experience; this compromise necessitates the use of `xtask` to actually build the eBPF. diff --git a/test.sh b/test.sh index 87a7e6a..9e0354c 100755 --- a/test.sh +++ b/test.sh @@ -51,12 +51,11 @@ esac cargo generate --path "${TEMPLATE_DIR}" -n test -d program_type="${PROG_TYPE}" ${ADDITIONAL_ARGS} pushd test -cargo xtask build -cargo xtask build --release +cargo build --package test +cargo build --package test --release # We cannot run clippy over the whole workspace at once due to feature unification. Since both test -# and test-ebpf both depend on test-common and test activates test-common's aya dependency, we end -# up trying to compile the panic handler twice: once from the bpf program, and again from std via -# aya. +# and test-ebpf depend on test-common and test activates test-common's aya dependency, we end up +# trying to compile the panic handler twice: once from the bpf program, and again from std via aya. cargo clippy --exclude test-ebpf --all-targets --workspace -- --deny warnings cargo clippy --package test-ebpf --all-targets -- --deny warnings popd diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index ed6bada..6f639a8 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -4,5 +4,5 @@ version = "0.1.0" edition = "2021" [dependencies] -anyhow = { workspace = true } +anyhow = { workspace = true, default-features = true } clap = { workspace = true, default-features = true, features = ["derive"] } diff --git a/xtask/src/build.rs b/xtask/src/build.rs deleted file mode 100644 index 04709d9..0000000 --- a/xtask/src/build.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::process::Command; - -use anyhow::Context as _; -use clap::Parser; - -use crate::build_ebpf::{build_ebpf, Architecture, Options as BuildOptions}; - -#[derive(Debug, Parser)] -pub struct Options { - /// Set the endianness of the BPF target - #[clap(default_value = "bpfel-unknown-none", long)] - pub bpf_target: Architecture, - /// Build and run the release target - #[clap(long)] - pub release: bool, -} - -/// Build our ebpf program and the userspace program. -pub fn build(opts: Options) -> Result<(), anyhow::Error> { - let Options { - bpf_target, - release, - } = opts; - - // Build our ebpf program. - build_ebpf(BuildOptions { - target: bpf_target, - release, - })?; - - // Build our userspace program. - let mut cmd = Command::new("cargo"); - cmd.arg("build"); - if release { - cmd.arg("--release"); - } - let status = cmd.status().context("failed to build userspace")?; - anyhow::ensure!(status.success(), "failed to build userspace program: {}", status); - - Ok(()) -} diff --git a/xtask/src/build_ebpf.rs b/xtask/src/build_ebpf.rs deleted file mode 100644 index ca83f1d..0000000 --- a/xtask/src/build_ebpf.rs +++ /dev/null @@ -1,68 +0,0 @@ -use std::process::Command; - -use anyhow::Context as _; -use clap::Parser; - -#[derive(Debug, Clone)] -pub enum Architecture { - BpfEl, - BpfEb, -} - -impl Architecture { - pub fn as_str(&self) -> &'static str { - match self { - Architecture::BpfEl => "bpfel-unknown-none", - Architecture::BpfEb => "bpfeb-unknown-none", - } - } -} - -impl std::str::FromStr for Architecture { - type Err = &'static str; - - fn from_str(s: &str) -> Result { - Ok(match s { - "bpfel-unknown-none" => Architecture::BpfEl, - "bpfeb-unknown-none" => Architecture::BpfEb, - _ => return Err("invalid target"), - }) - } -} - -impl std::fmt::Display for Architecture { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.as_str()) - } -} - -#[derive(Debug, Parser)] -pub struct Options { - /// Set the endianness of the BPF target - #[clap(default_value = "bpfel-unknown-none", long)] - pub target: Architecture, - /// Build the release target - #[clap(long)] - pub release: bool, -} - -pub fn build_ebpf(opts: Options) -> Result<(), anyhow::Error> { - let Options { target, release } = opts; - - let mut cmd = Command::new("cargo"); - cmd.current_dir("{{project-name}}-ebpf") - // Command::new creates a child process which inherits all env variables. This means env - // vars set by the cargo xtask command are also inherited. RUSTUP_TOOLCHAIN is removed so - // the rust-toolchain.toml file in the -ebpf folder is honored. - .env_remove("RUSTUP_TOOLCHAIN") - .args(["build", "--target", target.as_str()]); - - if release { - cmd.arg("--release"); - } - - let status = cmd.status().context("failed to build bpf program")?; - anyhow::ensure!(status.success(), "failed to build bpf program: {}", status); - - Ok(()) -} diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs new file mode 100644 index 0000000..fd8cbb0 --- /dev/null +++ b/xtask/src/lib.rs @@ -0,0 +1 @@ +pub const AYA_BUILD_EBPF: &str = "AYA_BUILD_EBPF"; diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 5079458..b59e543 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,9 +1,6 @@ -mod build_ebpf; -mod build; mod run; -use std::process::exit; - +use anyhow::Result; use clap::Parser; #[derive(Debug, Parser)] @@ -14,23 +11,13 @@ pub struct Options { #[derive(Debug, Parser)] enum Command { - BuildEbpf(build_ebpf::Options), - Build(build::Options), Run(run::Options), } -fn main() { - let opts = Options::parse(); - - use Command::*; - let ret = match opts.command { - BuildEbpf(opts) => build_ebpf::build_ebpf(opts), - Run(opts) => run::run(opts), - Build(opts) => build::build(opts), - }; +fn main() -> Result<()> { + let Options { command } = Parser::parse(); - if let Err(e) = ret { - eprintln!("{e:#}"); - exit(1); + match command { + Command::Run(opts) => run::run(opts), } } diff --git a/xtask/src/run.rs b/xtask/src/run.rs index d50bd11..16c80a8 100644 --- a/xtask/src/run.rs +++ b/xtask/src/run.rs @@ -1,55 +1,47 @@ -use std::process::Command; +use std::{ffi::OsString, process::Command}; -use anyhow::Context as _; +use anyhow::{bail, Context as _, Result}; use clap::Parser; - -use crate::{build::{build, Options as BuildOptions}, build_ebpf::Architecture}; +use xtask::AYA_BUILD_EBPF; #[derive(Debug, Parser)] pub struct Options { - /// Set the endianness of the BPF target - #[clap(default_value = "bpfel-unknown-none", long)] - pub bpf_target: Architecture, - /// Build and run the release target + /// Build and run the release target. #[clap(long)] - pub release: bool, - /// The command used to wrap your application + release: bool, + /// The command used to wrap your application. #[clap(short, long, default_value = "sudo -E")] - pub runner: String, - /// Arguments to pass to your application - #[clap(name = "args", last = true)] - pub run_args: Vec, + runner: String, + /// Arguments to pass to your application. + #[clap(global = true, last = true)] + run_args: Vec, } - -/// Build and run the project -pub fn run(opts: Options) -> Result<(), anyhow::Error> { - // Build our ebpf program and the project - build(BuildOptions{ - bpf_target: opts.bpf_target, - release: opts.release, - }).context("Error while building project")?; - - // profile we are building (release or debug) - let profile = if opts.release { "release" } else { "debug" }; - let bin_path = format!("target/{profile}/{{project-name}}"); - - // arguments to pass to the application - let mut run_args: Vec<_> = opts.run_args.iter().map(String::as_str).collect(); - - // configure args - let mut args: Vec<_> = opts.runner.trim().split_terminator(' ').collect(); - args.push(bin_path.as_str()); - args.append(&mut run_args); - - // run the command - let status = Command::new(args.first().expect("No first argument")) - .args(args.iter().skip(1)) +/// Build and run the project. +pub fn run(opts: Options) -> Result<()> { + let Options { + release, + runner, + run_args, + } = opts; + + let mut cmd = Command::new("cargo"); + cmd.env(AYA_BUILD_EBPF, "true"); + cmd.args(["run", "--package", "{{project-name}}", "--config"]); + if release { + cmd.arg(format!("target.\"cfg(all())\".runner=\"{}\"", runner)); + cmd.arg("--release"); + } else { + cmd.arg(format!("target.\"cfg(all())\".runner=\"{}\"", runner)); + } + if !run_args.is_empty() { + cmd.arg("--").args(run_args); + } + let status = cmd .status() - .expect("failed to run the command"); - - if !status.success() { - anyhow::bail!("Failed to run `{}`", args.join(" ")); + .with_context(|| format!("failed to run {cmd:?}"))?; + if status.code() != Some(0) { + bail!("{cmd:?} failed: {status:?}") } Ok(()) } diff --git a/{{project-name}}-ebpf/Cargo.toml b/{{project-name}}-ebpf/Cargo.toml index 4f5f274..578c50e 100644 --- a/{{project-name}}-ebpf/Cargo.toml +++ b/{{project-name}}-ebpf/Cargo.toml @@ -9,6 +9,10 @@ edition = "2021" aya-ebpf = { workspace = true } aya-log-ebpf = { workspace = true } +[build-dependencies] +which = { workspace = true } +xtask = { path = "../xtask" } + [[bin]] name = "{{ project-name }}" path = "src/main.rs" diff --git a/{{project-name}}-ebpf/build.rs b/{{project-name}}-ebpf/build.rs new file mode 100644 index 0000000..101ade2 --- /dev/null +++ b/{{project-name}}-ebpf/build.rs @@ -0,0 +1,30 @@ +use std::env; + +use which::which; +use xtask::AYA_BUILD_EBPF; + +/// Building this crate has an undeclared dependency on the `bpf-linker` binary. This would be +/// better expressed by [artifact-dependencies][bindeps] but issues such as +/// https://github.com/rust-lang/cargo/issues/12385 make their use impractical for the time being. +/// +/// This file implements an imperfect solution: it causes cargo to rebuild the crate whenever the +/// mtime of `which bpf-linker` changes. Note that possibility that a new bpf-linker is added to +/// $PATH ahead of the one used as the cache key still exists. Solving this in the general case +/// would require rebuild-if-changed-env=PATH *and* rebuild-if-changed={every-directory-in-PATH} +/// which would likely mean far too much cache invalidation. +/// +/// [bindeps]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html?highlight=feature#artifact-dependencies +fn main() { + println!("cargo:rerun-if-env-changed={}", AYA_BUILD_EBPF); + + let build_ebpf = env::var(AYA_BUILD_EBPF) + .as_deref() + .map(str::parse) + .map(Result::unwrap) + .unwrap_or_default(); + + if build_ebpf { + let bpf_linker = which("bpf-linker").unwrap(); + println!("cargo:rerun-if-changed={}", bpf_linker.to_str().unwrap()); + } +} diff --git a/{{project-name}}-ebpf/src/lib.rs b/{{project-name}}-ebpf/src/lib.rs new file mode 100644 index 0000000..3ac3e59 --- /dev/null +++ b/{{project-name}}-ebpf/src/lib.rs @@ -0,0 +1,3 @@ +#![no_std] + +// This file exists to enable the library target. diff --git a/{{project-name}}/Cargo.toml b/{{project-name}}/Cargo.toml index 83c71d2..23dd42c 100644 --- a/{{project-name}}/Cargo.toml +++ b/{{project-name}}/Cargo.toml @@ -18,6 +18,23 @@ tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread", "net" clap = { workspace = true, features = ["derive"] } {% endif -%} +[build-dependencies] +cargo_metadata = { workspace = true } +# TODO(https://github.com/rust-lang/cargo/issues/12375): this should be an artifact dependency, but +# it's not possible to tell cargo to use `-Z build-std` to build it. We cargo-in-cargo in the build +# script to build this, but we want to teach cargo about the dependecy so that cache invalidation +# works properly. +# +# Note also that https://github.com/rust-lang/cargo/issues/10593 occurs when `target = ...` is added +# to an artifact dependency; it seems possible to work around that by setting `resolver = "1"` in +# Cargo.toml in the workspace root. +# +# Finally note that *any* usage of `artifact = ...` in *any* Cargo.toml in the workspace breaks +# workflows with stable cargo; stable cargo outright refuses to load manifests that use unstable +# features. +{{project-name}}-ebpf = { path = "../{{project-name}}-ebpf" } +xtask = { path = "../xtask"} + [[bin]] name = "{{project-name}}" path = "src/main.rs" diff --git a/{{project-name}}/build.rs b/{{project-name}}/build.rs new file mode 100644 index 0000000..61caab3 --- /dev/null +++ b/{{project-name}}/build.rs @@ -0,0 +1,172 @@ +use std::{ + env, fs, + io::{BufRead as _, BufReader}, + path::PathBuf, + process::{Child, Command, Stdio}, +}; + +use cargo_metadata::{ + Artifact, CompilerMessage, Message, Metadata, MetadataCommand, Package, Target, +}; +use xtask::AYA_BUILD_EBPF; + +/// This crate has a runtime dependency on artifacts produced by the `{{project-name}}-ebpf` crate. +/// This would be better expressed as one or more [artifact-dependencies][bindeps] but issues such +/// as: +/// +/// * https://github.com/rust-lang/cargo/issues/12374 +/// * https://github.com/rust-lang/cargo/issues/12375 +/// * https://github.com/rust-lang/cargo/issues/12385 +/// +/// prevent their use for the time being. +/// +/// This file, along with the xtask crate, allows analysis tools such as `cargo check`, `cargo +/// clippy`, and even `cargo build` to work as users expect. Prior to this file's existence, this +/// crate's undeclared dependency on artifacts from `{{project-name}}-ebpf` would cause build (and +/// `cargo check`, and `cargo clippy`) failures until the user ran certain other commands in the +/// workspace. Conversely, those same tools (e.g. cargo test --no-run) would produce stale results +/// if run naively because they'd make use of artifacts from a previous build of +/// `{{project-name}}-ebpf`. +/// +/// Note that this solution is imperfect: in particular it has to balance correctness with +/// performance; an environment variable is used to replace true builds of `{{project-name}}-ebpf` +/// with stubs to preserve the property that code generation and linking (in +/// `{{project-name}}-ebpf`) do not occur on metadata-only actions such as `cargo check` or `cargo +/// clippy` of this crate. This means that naively attempting to `cargo test --no-run` this crate +/// will produce binaries that fail at runtime because the stubs are inadequate for actually running +/// the tests. +/// +/// [bindeps]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html?highlight=feature#artifact-dependencies +fn main() { + println!("cargo:rerun-if-env-changed={}", AYA_BUILD_EBPF); + + let build_ebpf = env::var(AYA_BUILD_EBPF) + .as_deref() + .map(str::parse) + .map(Result::unwrap) + .unwrap_or_default(); + + let Metadata { packages, .. } = MetadataCommand::new().no_deps().exec().unwrap(); + let ebpf_package = packages + .into_iter() + .find(|Package { name, .. }| name == "{{project-name}}-ebpf") + .unwrap(); + + let out_dir = env::var_os("OUT_DIR").unwrap(); + let out_dir = PathBuf::from(out_dir); + + let endian = env::var_os("CARGO_CFG_TARGET_ENDIAN").unwrap(); + let target = if endian == "big" { + "bpfeb" + } else if endian == "little" { + "bpfel" + } else { + panic!("unsupported endian={:?}", endian) + }; + + if build_ebpf { + let arch = env::var_os("CARGO_CFG_TARGET_ARCH").unwrap(); + + let target = format!("{target}-unknown-none"); + + let Package { manifest_path, .. } = ebpf_package; + let ebpf_dir = manifest_path.parent().unwrap(); + + // We have a build-dependency on `{{project-name}}-ebpf`, so cargo will automatically rebuild us + // if `{{project-name}}-ebpf`'s *library* target or any of its dependencies change. Since we + // depend on `{{project-name}}-ebpf`'s *binary* targets, that only gets us half of the way. This + // stanza ensures cargo will rebuild us on changes to the binaries too, which gets us the + // rest of the way. + println!("cargo:rerun-if-changed={}", ebpf_dir.as_str()); + + let mut cmd = Command::new("cargo"); + cmd.args([ + "build", + "-Z", + "build-std=core", + "--bins", + "--message-format=json", + "--release", + "--target", + &target, + ]); + + cmd.env("CARGO_CFG_BPF_TARGET_ARCH", arch); + + // Workaround to make sure that the rust-toolchain.toml is respected. + for key in ["RUSTUP_TOOLCHAIN", "RUSTC"] { + cmd.env_remove(key); + } + cmd.current_dir(ebpf_dir); + + // Workaround for https://github.com/rust-lang/cargo/issues/6412 where cargo flocks itself. + let ebpf_target_dir = out_dir.join("{{project-name}}-ebpf"); + cmd.arg("--target-dir").arg(&ebpf_target_dir); + + let mut child = cmd + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap_or_else(|err| panic!("failed to spawn {cmd:?}: {err}")); + let Child { stdout, stderr, .. } = &mut child; + + // Trampoline stdout to cargo warnings. + let stderr = stderr.take().unwrap(); + let stderr = BufReader::new(stderr); + let stderr = std::thread::spawn(move || { + for line in stderr.lines() { + let line = line.unwrap(); + println!("cargo:warning={line}"); + } + }); + + let stdout = stdout.take().unwrap(); + let stdout = BufReader::new(stdout); + let mut executables = Vec::new(); + for message in Message::parse_stream(stdout) { + #[allow(clippy::collapsible_match)] + match message.expect("valid JSON") { + Message::CompilerArtifact(Artifact { + executable, + target: Target { name, .. }, + .. + }) => { + if let Some(executable) = executable { + executables.push((name, executable.into_std_path_buf())); + } + } + Message::CompilerMessage(CompilerMessage { message, .. }) => { + for line in message.rendered.unwrap_or_default().split('\n') { + println!("cargo:warning={line}"); + } + } + Message::TextLine(line) => { + println!("cargo:warning={line}"); + } + _ => {} + } + } + + let status = child + .wait() + .unwrap_or_else(|err| panic!("failed to wait for {cmd:?}: {err}")); + assert_eq!(status.code(), Some(0), "{cmd:?} failed: {status:?}"); + + stderr.join().map_err(std::panic::resume_unwind).unwrap(); + + for (name, binary) in executables { + let dst = out_dir.join(name); + let _: u64 = fs::copy(&binary, &dst) + .unwrap_or_else(|err| panic!("failed to copy {binary:?} to {dst:?}: {err}")); + } + } else { + let Package { targets, .. } = ebpf_package; + for Target { name, kind, .. } in targets { + if *kind != ["bin"] { + continue; + } + let dst = out_dir.join(name); + fs::write(&dst, []).unwrap_or_else(|err| panic!("failed to create {dst:?}: {err}")); + } + } +} diff --git a/{{project-name}}/src/main.rs b/{{project-name}}/src/main.rs index 312bbd6..3e77a18 100644 --- a/{{project-name}}/src/main.rs +++ b/{{project-name}}/src/main.rs @@ -64,7 +64,7 @@ struct Opt { {% endif -%} #[tokio::main] -async fn main() -> Result<(), anyhow::Error> { +async fn main() -> anyhow::Result<()> { {%- if program_types_with_opts contains program_type %} let opt = Opt::parse(); {% endif %} @@ -85,14 +85,7 @@ async fn main() -> Result<(), anyhow::Error> { // runtime. This approach is recommended for most real-world use cases. If you would // like to specify the eBPF program at runtime rather than at compile-time, you can // reach for `Bpf::load_file` instead. - #[cfg(debug_assertions)] - let mut ebpf = Ebpf::load(include_bytes_aligned!( - "../../target/bpfel-unknown-none/debug/{{project-name}}" - ))?; - #[cfg(not(debug_assertions))] - let mut ebpf = Ebpf::load(include_bytes_aligned!( - "../../target/bpfel-unknown-none/release/{{project-name}}" - ))?; + let mut ebpf = Ebpf::load(include_bytes_aligned!(concat!(env!("OUT_DIR"), "/{{project-name}}")))?; if let Err(e) = EbpfLogger::init(&mut ebpf) { // This can happen if you remove all log statements from your eBPF program. warn!("failed to initialize eBPF logger: {}", e); From a73f88d45e05b53c76f4a9247fc30ebcab5e39be Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 10 Oct 2024 16:14:20 -0400 Subject: [PATCH 2/4] Add rustfmt to CI --- .github/workflows/ci.yml | 2 +- rustfmt.toml | 4 ++ test.sh | 1 + {{project-name}}-ebpf/src/main.rs | 64 ++++++++----------------------- {{project-name}}/src/main.rs | 41 ++++++++++++-------- 5 files changed, 46 insertions(+), 66 deletions(-) create mode 100644 rustfmt.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4d40e9..614c897 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: - uses: dtolnay/rust-toolchain@nightly with: - components: rust-src + components: rust-src,rustfmt - uses: dtolnay/rust-toolchain@stable with: diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..53f7b6d --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,4 @@ +group_imports = "StdExternalCrate" +imports_granularity = "Crate" +reorder_imports = true +unstable_features = true diff --git a/test.sh b/test.sh index 9e0354c..b744fd2 100755 --- a/test.sh +++ b/test.sh @@ -51,6 +51,7 @@ esac cargo generate --path "${TEMPLATE_DIR}" -n test -d program_type="${PROG_TYPE}" ${ADDITIONAL_ARGS} pushd test +cargo +nightly fmt --all -- --check cargo build --package test cargo build --package test --release # We cannot run clippy over the whole workspace at once due to feature unification. Since both test diff --git a/{{project-name}}-ebpf/src/main.rs b/{{project-name}}-ebpf/src/main.rs index 9a0a61f..63be060 100644 --- a/{{project-name}}-ebpf/src/main.rs +++ b/{{project-name}}-ebpf/src/main.rs @@ -34,13 +34,10 @@ fn try_{{crate_name}}(ctx: RetProbeContext) -> Result { Ok(0) } {%- when "fentry" %} -use aya_ebpf::{ - macros::fentry, - programs::FEntryContext, -}; +use aya_ebpf::{macros::fentry, programs::FEntryContext}; use aya_log_ebpf::info; -#[fentry(function="{{fn_name}}")] +#[fentry(function = "{{fn_name}}")] pub fn {{crate_name}}(ctx: FEntryContext) -> u32 { match try_{{crate_name}}(ctx) { Ok(ret) => ret, @@ -53,13 +50,10 @@ fn try_{{crate_name}}(ctx: FEntryContext) -> Result { Ok(0) } {%- when "fexit" %} -use aya_ebpf::{ - macros::fexit, - programs::FExitContext, -}; +use aya_ebpf::{macros::fexit, programs::FExitContext}; use aya_log_ebpf::info; -#[fexit(function="{{fn_name}}")] +#[fexit(function = "{{fn_name}}")] pub fn {{crate_name}}(ctx: FExitContext) -> u32 { match try_{{crate_name}}(ctx) { Ok(ret) => ret, @@ -72,10 +66,7 @@ fn try_{{crate_name}}(ctx: FExitContext) -> Result { Ok(0) } {%- when "uprobe" %} -use aya_ebpf::{ - macros::uprobe, - programs::ProbeContext, -}; +use aya_ebpf::{macros::uprobe, programs::ProbeContext}; use aya_log_ebpf::info; #[uprobe] @@ -91,10 +82,7 @@ fn try_{{crate_name}}(ctx: ProbeContext) -> Result { Ok(0) } {%- when "uretprobe" %} -use aya_ebpf::{ - macros::uretprobe, - programs::RetProbeContext, -}; +use aya_ebpf::{macros::uretprobe, programs::RetProbeContext}; use aya_log_ebpf::info; #[uretprobe] @@ -110,10 +98,7 @@ fn try_{{crate_name}}(ctx: RetProbeContext) -> Result { Ok(0) } {%- when "sock_ops" %} -use aya_ebpf::{ - macros::sock_ops, - programs::SockOpsContext, -}; +use aya_ebpf::{macros::sock_ops, programs::SockOpsContext}; use aya_log_ebpf::info; #[sock_ops] @@ -135,7 +120,6 @@ use aya_ebpf::{ programs::SkMsgContext, }; use aya_log_ebpf::info; - use {{crate_name}}_common::SockKey; #[map] @@ -186,10 +170,7 @@ fn try_{{crate_name}}(ctx: TcContext) -> Result { Ok(TC_ACT_PIPE) } {%- when "cgroup_skb" %} -use aya_ebpf::{ - macros::cgroup_skb, - programs::SkBuffContext, -}; +use aya_ebpf::{macros::cgroup_skb, programs::SkBuffContext}; use aya_log_ebpf::info; #[cgroup_skb] @@ -205,10 +186,7 @@ fn try_{{crate_name}}(ctx: SkBuffContext) -> Result { Ok(0) } {%- when "tracepoint" %} -use aya_ebpf::{ - macros::tracepoint, - programs::TracePointContext, -}; +use aya_ebpf::{macros::tracepoint, programs::TracePointContext}; use aya_log_ebpf::info; #[tracepoint] @@ -224,10 +202,7 @@ fn try_{{crate_name}}(ctx: TracePointContext) -> Result { Ok(0) } {%- when "lsm" %} -use aya_ebpf::{ - macros::lsm, - programs::LsmContext, -}; +use aya_ebpf::{macros::lsm, programs::LsmContext}; use aya_log_ebpf::info; #[lsm(hook = "{{lsm_hook}}")] @@ -243,13 +218,10 @@ fn try_{{lsm_hook}}(ctx: LsmContext) -> Result { Ok(0) } {%- when "tp_btf" %} -use aya_ebpf::{ - macros::btf_tracepoint, - programs::BtfTracePointContext, -}; +use aya_ebpf::{macros::btf_tracepoint, programs::BtfTracePointContext}; use aya_log_ebpf::info; -#[btf_tracepoint(function="{{tracepoint_name}}")] +#[btf_tracepoint(function = "{{tracepoint_name}}")] pub fn {{tracepoint_name}}(ctx: BtfTracePointContext) -> i32 { match try_{{tracepoint_name}}(ctx) { Ok(ret) => ret, @@ -262,20 +234,14 @@ fn try_{{tracepoint_name}}(ctx: BtfTracePointContext) -> Result { Ok(0) } {%- when "socket_filter" %} -use aya_ebpf::{ - macros::socket_filter, - programs::SkBuffContext, -}; +use aya_ebpf::{macros::socket_filter, programs::SkBuffContext}; #[socket_filter] pub fn {{crate_name}}(_ctx: SkBuffContext) -> i64 { 0 } {%- when "cgroup_sysctl" %} -use aya_ebpf::{ - macros::cgroup_sysctl, - programs::SysctlContext, -}; +use aya_ebpf::{macros::cgroup_sysctl, programs::SysctlContext}; use aya_log_ebpf::info; #[cgroup_sysctl] @@ -310,7 +276,7 @@ fn try_{{crate_name}}(ctx: SockoptContext) -> Result { use aya_ebpf::{macros::raw_tracepoint, programs::RawTracePointContext}; use aya_log_ebpf::info; -#[raw_tracepoint(tracepoint="{{tracepoint_name}}")] +#[raw_tracepoint(tracepoint = "{{tracepoint_name}}")] pub fn {{crate_name}}(ctx: RawTracePointContext) -> i32 { match try_{{crate_name}}(ctx) { Ok(ret) => ret, diff --git a/{{project-name}}/src/main.rs b/{{project-name}}/src/main.rs index 3e77a18..15103ea 100644 --- a/{{project-name}}/src/main.rs +++ b/{{project-name}}/src/main.rs @@ -8,43 +8,45 @@ use aya::{programs::FExit, Btf}; {%- when "uprobe", "uretprobe" -%} use aya::programs::UProbe; {%- when "sock_ops" -%} -use aya::programs::{SockOps, links::CgroupAttachMode}; +use aya::programs::{links::CgroupAttachMode, SockOps}; {%- when "sk_msg" -%} -use aya::maps::SockHash; -use aya::programs::SkMsg; +use aya::{maps::SockHash, programs::SkMsg}; use {{crate_name}}_common::SockKey; {%- when "xdp" -%} -use anyhow::Context; +use anyhow::Context as _; use aya::programs::{Xdp, XdpFlags}; {%- when "classifier" -%} use aya::programs::{tc, SchedClassifier, TcAttachType}; {%- when "cgroup_skb" -%} -use aya::programs::{CgroupSkb, CgroupSkbAttachType, links::CgroupAttachMode}; +use aya::programs::{links::CgroupAttachMode, CgroupSkb, CgroupSkbAttachType}; {%- when "cgroup_sysctl" -%} -use aya::programs::{CgroupSysctl, links::CgroupAttachMode}; +use aya::programs::{links::CgroupAttachMode, CgroupSysctl}; {%- when "cgroup_sockopt" -%} -use aya::programs::{CgroupSockopt, links::CgroupAttachMode}; +use aya::programs::{links::CgroupAttachMode, CgroupSockopt}; {%- when "tracepoint" -%} use aya::programs::TracePoint; {%- when "lsm" -%} use aya::{programs::Lsm, Btf}; {%- when "perf_event" -%} -use aya::programs::{perf_event, PerfEvent}; -use aya::util::online_cpus; +use aya::{ + programs::{perf_event, PerfEvent}, + util::online_cpus, +}; {%- when "tp_btf" -%} use aya::{programs::BtfTracePoint, Btf}; {%- when "socket_filter" -%} use std::net::TcpStream; + use aya::programs::SocketFilter; {%- when "raw_tracepoint" -%} use aya::programs::RawTracePoint; {%- endcase %} -use aya::{include_bytes_aligned, Ebpf}; -use aya_log::EbpfLogger; {% if program_types_with_opts contains program_type -%} use clap::Parser; {% endif -%} -use log::{info, warn, debug}; + +#[rustfmt::skip] +use log::{debug, info, warn}; use tokio::signal; {% if program_types_with_opts contains program_type -%} @@ -85,8 +87,11 @@ async fn main() -> anyhow::Result<()> { // runtime. This approach is recommended for most real-world use cases. If you would // like to specify the eBPF program at runtime rather than at compile-time, you can // reach for `Bpf::load_file` instead. - let mut ebpf = Ebpf::load(include_bytes_aligned!(concat!(env!("OUT_DIR"), "/{{project-name}}")))?; - if let Err(e) = EbpfLogger::init(&mut ebpf) { + let mut ebpf = aya::Ebpf::load(aya::include_bytes_aligned!(concat!( + env!("OUT_DIR"), + "/{{project-name}}" + )))?; + if let Err(e) = aya_log::EbpfLogger::init(&mut ebpf) { // This can happen if you remove all log statements from your eBPF program. warn!("failed to initialize eBPF logger: {}", e); } @@ -115,7 +120,7 @@ async fn main() -> anyhow::Result<()> { program.load()?; program.attach(cgroup, CgroupAttachMode::default())?; {%- when "sk_msg" -%} - let sock_map: SockHash::<_, SockKey> = ebpf.map("{{sock_map}}").unwrap().try_into()?; + let sock_map: SockHash<_, SockKey> = ebpf.map("{{sock_map}}").unwrap().try_into()?; let map_fd = sock_map.fd().try_clone()?; let prog: &mut SkMsg = ebpf.program_mut("{{crate_name}}").unwrap().try_into()?; @@ -138,7 +143,11 @@ async fn main() -> anyhow::Result<()> { let program: &mut CgroupSkb = ebpf.program_mut("{{crate_name}}").unwrap().try_into()?; let cgroup = std::fs::File::open(opt.cgroup_path)?; program.load()?; - program.attach(cgroup, CgroupSkbAttachType::{{direction}}, CgroupAttachMode::default())?; + program.attach( + cgroup, + CgroupSkbAttachType::{{direction}}, + CgroupAttachMode::default(), + )?; {%- when "tracepoint" -%} let program: &mut TracePoint = ebpf.program_mut("{{crate_name}}").unwrap().try_into()?; program.load()?; From 31713b48ea2e4a1d0b064b9483166a9c35881147 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Fri, 11 Oct 2024 05:47:35 -0400 Subject: [PATCH 3/4] Add Rust 1.80.1 to CI --- .github/workflows/ci.yml | 6 +++++- {{project-name}}/Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 614c897..3e17667 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,9 @@ jobs: strategy: fail-fast: false matrix: + rust: + - stable + - 1.80.1 program: - kprobe - kretprobe @@ -49,8 +52,9 @@ jobs: with: components: rust-src,rustfmt - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@master with: + toolchain: ${{ matrix.rust }} components: clippy - uses: Swatinem/rust-cache@v2 diff --git a/{{project-name}}/Cargo.toml b/{{project-name}}/Cargo.toml index 23dd42c..c5af123 100644 --- a/{{project-name}}/Cargo.toml +++ b/{{project-name}}/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] {{project-name}}-common = { path = "../{{project-name}}-common", features = ["user"] } -anyhow = { workspace = true } +anyhow = { workspace = true, default-features = true } aya = { workspace = true } aya-log = { workspace = true } env_logger = {workspace = true } From 9b76a3a21dacb2d312e2116c6762de2f77c8c1bd Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Fri, 11 Oct 2024 11:16:37 -0400 Subject: [PATCH 4/4] Appease shellcheck --- test.sh | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/test.sh b/test.sh index b744fd2..43984bd 100755 --- a/test.sh +++ b/test.sh @@ -3,53 +3,54 @@ set -ex TEMPLATE_DIR=$1 -if [ -z "$TEMPLATE_DIR" ]; then echo "template dir required"; exit 1; fi +if [ -z "${TEMPLATE_DIR}" ]; then echo "template dir required"; exit 1; fi PROG_TYPE=$2 -if [ -z "$PROG_TYPE" ]; then echo "program type required"; exit 1; fi +if [ -z "${PROG_TYPE}" ]; then echo "program type required"; exit 1; fi TMP_DIR=$(mktemp -d) clean_up() { + # shellcheck disable=SC2317 rm -rf "${TMP_DIR}" } trap clean_up EXIT -pushd $TMP_DIR -case "$PROG_TYPE" in +pushd "${TMP_DIR}" +case "${PROG_TYPE}" in "cgroup_sockopt") - ADDITIONAL_ARGS="-d sockopt_target=getsockopt" + ADDITIONAL_ARGS=(-d sockopt_target=getsockopt) ;; "classifier"|"cgroup_skb") - ADDITIONAL_ARGS="-d direction=Ingress" + ADDITIONAL_ARGS=(-d direction=Ingress) ;; "fentry"|"fexit") - ADDITIONAL_ARGS="-d fn_name=try_to_wake_up" + ADDITIONAL_ARGS=(-d fn_name=try_to_wake_up) ;; "kprobe"|"kretprobe") - ADDITIONAL_ARGS="-d kprobe=test" + ADDITIONAL_ARGS=(-d kprobe=test) ;; "lsm") - ADDITIONAL_ARGS="-d lsm_hook=file_open" + ADDITIONAL_ARGS=(-d lsm_hook=file_open) ;; "raw_tracepoint") - ADDITIONAL_ARGS="-d tracepoint_name=sys_enter" + ADDITIONAL_ARGS=(-d tracepoint_name=sys_enter) ;; "sk_msg") - ADDITIONAL_ARGS="-d sock_map=SOCK_MAP" + ADDITIONAL_ARGS=(-d sock_map=SOCK_MAP) ;; "tp_btf") - ADDITIONAL_ARGS="-d tracepoint_name=net_dev_queue" + ADDITIONAL_ARGS=(-d tracepoint_name=net_dev_queue) ;; "tracepoint") - ADDITIONAL_ARGS="-d tracepoint_category=net -d tracepoint_name=net_dev_queue" + ADDITIONAL_ARGS=(-d tracepoint_category=net -d tracepoint_name=net_dev_queue) ;; "uprobe"|"uretprobe") - ADDITIONAL_ARGS="-d uprobe_target=testlib -d uprobe_fn_name=testfn" + ADDITIONAL_ARGS=(-d uprobe_target=testlib -d uprobe_fn_name=testfn) ;; *) - ADDITIONAL_ARGS='' + ADDITIONAL_ARGS=() esac -cargo generate --path "${TEMPLATE_DIR}" -n test -d program_type="${PROG_TYPE}" ${ADDITIONAL_ARGS} +cargo generate --path "${TEMPLATE_DIR}" -n test -d program_type="${PROG_TYPE}" "${ADDITIONAL_ARGS[@]}" pushd test cargo +nightly fmt --all -- --check cargo build --package test