diff --git a/docs/book/start/getting-data-to-user-space.md b/docs/book/start/getting-data-to-user-space.md new file mode 100644 index 0000000..1b81d93 --- /dev/null +++ b/docs/book/start/getting-data-to-user-space.md @@ -0,0 +1,147 @@ +# Getting Data to User-Space + +In previous chapters we were logging packets using aya-log. But what if we want +to send data about the packet (or in general - any kind of data) for the +user-space program to use? + +This chapter is going to explain how to use perf buffers and define structures +in the *-common* crate to send data from eBPF program to user-space. + +!!! example "Source Code" + + Full code for the example in this chapter is available [here](https://github.com/aya-rs/book/tree/main/examples/xdp-perfbuf-custom-data) + +## Sharing Data + +To get data from kernel-space to user-space we use an eBPF map. There are +numerous types of maps to chose from, but in this example we'll be using a +`PerfEventArray`. + +This time, instead of logging the information, we are going to write it into a +struct and output it to user-space. The data structure that we'll need to send +information to user-space will need to hold an IPv4 address and an action for +Permit/Deny, we'll encode both as a `u32`. + +```rust linenums="1" title="xdp-perfbuf-custom-data-common/src/lib.rs" +--8<-- "examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-common/src/lib.rs" +``` + +1. We implement the `aya::Pod` trait for our struct since it is Plain Old Data +as can be safely converted to a byte-slice and back. + +!!! tip "Alignment, padding and verifier errors" + + At program load time, the eBPF verifier checks that all the memory used is + properly initialized. This can be a problem if - to ensure alignment - the + compiler inserts padding bytes between fields in your types. + + **Example:** + + ```rust + #[repr(C)] + struct SourceInfo { + source_port: u16, + source_ip: u32, + } + + let port = ...; + let ip = ...; + let si = SourceInfo { source_port: port, source_ip: ip }; + ``` + + In the example above, the compiler will insert two extra bytes between the + struct fields `source_port` and `source_ip` to make sure that `source_ip` is + correctly aligned to a 4 bytes address (assuming `mem::align_of::() == + 4`). Since padding bytes are typically not initialized by the compiler, + this will result in the infamous `invalid indirect read from stack` verifier + error. + + To avoid the error, you can either manually ensure that all the fields in + your types are correctly aligned (eg by explicitly adding padding or by + making field types larger to enforce alignment) or use `#[repr(packed)]`. + Since the latter comes with its own footguns and can perform less + efficiently, explicitly adding padding or tweaking alignment is recommended. + + **Solution ensuring alignment using larger types:** + + ```rust + #[repr(C)] + struct SourceInfo { + source_port: u32, + source_ip: u32, + } + + let port = ...; + let ip = ...; + let si = SourceInfo { source_port: port, source_ip: ip }; + ``` + + **Solution with explicit padding:** + + ```rust + #[repr(C)] + struct SourceInfo { + source_port: u16, + padding: u16, + source_ip: u32, + } + + let port = ...; + let ip = ...; + let si = SourceInfo { source_port: port, padding: 0, source_ip: ip }; + ``` + +## Getting Packet Data From The Context And Into the Map + +Our eBPF program code is going to be similar to the one in the previous chapters. +It's going to get the information about source IP address and port from packet +headers. + +The difference is that once we get the data from headers, instead of logging +them, we create a `PacketLog` struct and output it to our `PerfEventArray` + +The resulting code looks like this: + +```rust linenums="1" title="xdp-perfbuf-custom-data-ebpf/src/main.rs" +--8<-- "examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-ebpf/src/main.rs" +``` + +1. Create our map +2. Outputting the event to the `PerfEventArray` + +Don't forget to rebuild your eBPF program! + +## Reading Data + +In order to read from the `AsyncPerfEventArray`, we have to call `AsyncPerfEventArray::open()` for each online CPU, then we have to poll the file descriptor for events. +While this is do-able using `PerfEventArray` and `mio` or `epoll`, the code is much less easy to follow. Instead, we'll use `tokio`, which was added to our template for us. + +We'll need to add a dependency on `bytes = "1"` to `xdp-log/Cargo.toml` since this will make it easier +to deal with the chunks of bytes yielded by the `AsyncPerfEventArray`. + +Here's the code: + +```rust linenums="1" title="xdp-perfbuf-custom-data/src/main.rs" +--8<-- "examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data/src/main.rs" +``` + +1. Define our map +2. Call `open()` for each online CPU +3. Spawn a `tokio::task` +4. Create buffers +5. Read events in to buffers +6. Use `read_unaligned` to read our data into a `PacketLog`. +7. Log the event to the console. + +## Running the program + +As before, the interface can be overwritten by providing the interface name as a parameter, for example, `RUST_LOG=info cargo xtask run -- --iface wlp2s0`. + +```console +$ RUST_LOG=info cargo xtask run +[2023-01-25T08:57:41Z INFO xdp_perfbuf_custom_data] SRC IP: 60.235.240.157, SRC_PORT: 443 +[2023-01-25T08:57:41Z INFO xdp_perfbuf_custom_data] SRC IP: 98.21.76.76, SRC_PORT: 443 +[2023-01-25T08:57:41Z INFO xdp_perfbuf_custom_data] SRC IP: 95.194.217.172, SRC_PORT: 443 +[2023-01-25T08:57:41Z INFO xdp_perfbuf_custom_data] SRC IP: 95.194.217.172, SRC_PORT: 443 +[2023-01-25T08:57:41Z INFO xdp_perfbuf_custom_data] SRC IP: 95.10.251.142, SRC_PORT: 443 +``` diff --git a/examples/xdp-perfbuf-custom-data/.cargo/config.toml b/examples/xdp-perfbuf-custom-data/.cargo/config.toml new file mode 100644 index 0000000..f0ccbc9 --- /dev/null +++ b/examples/xdp-perfbuf-custom-data/.cargo/config.toml @@ -0,0 +1,2 @@ +[alias] +xtask = "run --package xtask --" \ No newline at end of file diff --git a/examples/xdp-perfbuf-custom-data/.gitignore b/examples/xdp-perfbuf-custom-data/.gitignore new file mode 100644 index 0000000..54f741e --- /dev/null +++ b/examples/xdp-perfbuf-custom-data/.gitignore @@ -0,0 +1,13 @@ +### https://raw.github.com/github/gitignore/master/Rust.gitignore + +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/examples/xdp-perfbuf-custom-data/.vim/coc-settings.json b/examples/xdp-perfbuf-custom-data/.vim/coc-settings.json new file mode 100644 index 0000000..aad6f8f --- /dev/null +++ b/examples/xdp-perfbuf-custom-data/.vim/coc-settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.linkedProjects": ["Cargo.toml", "xdp-perfbuf-custom-data-ebpf/Cargo.toml"] +} diff --git a/examples/xdp-perfbuf-custom-data/.vscode/settings.json b/examples/xdp-perfbuf-custom-data/.vscode/settings.json new file mode 100644 index 0000000..aad6f8f --- /dev/null +++ b/examples/xdp-perfbuf-custom-data/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.linkedProjects": ["Cargo.toml", "xdp-perfbuf-custom-data-ebpf/Cargo.toml"] +} diff --git a/examples/xdp-perfbuf-custom-data/Cargo.toml b/examples/xdp-perfbuf-custom-data/Cargo.toml new file mode 100644 index 0000000..20148b4 --- /dev/null +++ b/examples/xdp-perfbuf-custom-data/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["xdp-perfbuf-custom-data", "xdp-perfbuf-custom-data-common", "xtask"] diff --git a/examples/xdp-perfbuf-custom-data/README.md b/examples/xdp-perfbuf-custom-data/README.md new file mode 100644 index 0000000..b1b556b --- /dev/null +++ b/examples/xdp-perfbuf-custom-data/README.md @@ -0,0 +1,28 @@ +# xdp-perfbuf-custom-data + +## Prerequisites + +1. Install a rust stable toolchain: `rustup install stable` +1. Install a rust nightly toolchain: `rustup install nightly` +1. Install bpf-linker: `cargo install bpf-linker` + +## Build eBPF + +```bash +cargo xtask build-ebpf +``` + +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 +``` + +## Run + +```bash +RUST_LOG=info cargo xtask run +``` diff --git a/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-common/Cargo.toml b/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-common/Cargo.toml new file mode 100644 index 0000000..df8d5d5 --- /dev/null +++ b/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-common/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "xdp-perfbuf-custom-data-common" +version = "0.1.0" +edition = "2021" + +[features] +default = [] +user = [ "aya" ] + +[dependencies] +aya = { version = ">=0.11", optional=true } + +[lib] +path = "src/lib.rs" diff --git a/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-common/src/lib.rs b/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-common/src/lib.rs new file mode 100644 index 0000000..61f32c2 --- /dev/null +++ b/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-common/src/lib.rs @@ -0,0 +1,11 @@ +#![no_std] + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct PacketLog { + pub ipv4_address: u32, + pub port: u32, +} + +#[cfg(feature = "user")] +unsafe impl aya::Pod for PacketLog {} // (1) diff --git a/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-ebpf/.cargo/config.toml b/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-ebpf/.cargo/config.toml new file mode 100644 index 0000000..5d7e591 --- /dev/null +++ b/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-ebpf/.cargo/config.toml @@ -0,0 +1,6 @@ +[build] +target-dir = "../target" +target = "bpfel-unknown-none" + +[unstable] +build-std = ["core"] \ No newline at end of file diff --git a/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-ebpf/.vim/coc-settings.json b/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-ebpf/.vim/coc-settings.json new file mode 100644 index 0000000..e2211a6 --- /dev/null +++ b/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-ebpf/.vim/coc-settings.json @@ -0,0 +1,4 @@ +{ + "rust-analyzer.cargo.target": "bpfel-unknown-none", + "rust-analyzer.checkOnSave.allTargets": false +} diff --git a/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-ebpf/.vscode/settings.json b/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-ebpf/.vscode/settings.json new file mode 100644 index 0000000..e2211a6 --- /dev/null +++ b/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-ebpf/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "rust-analyzer.cargo.target": "bpfel-unknown-none", + "rust-analyzer.checkOnSave.allTargets": false +} diff --git a/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-ebpf/Cargo.toml b/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-ebpf/Cargo.toml new file mode 100644 index 0000000..5273b28 --- /dev/null +++ b/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-ebpf/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "xdp-perfbuf-custom-data-ebpf" +version = "0.1.0" +edition = "2021" + +[dependencies] +aya-bpf = { git = "https://github.com/aya-rs/aya", branch = "main" } +aya-log-ebpf = { git = "https://github.com/aya-rs/aya", branch = "main" } +xdp-perfbuf-custom-data-common = { path = "../xdp-perfbuf-custom-data-common" } +network-types = "0.0.4" + +[[bin]] +name = "xdp-perfbuf-custom-data" +path = "src/main.rs" + +[profile.dev] +opt-level = 3 +debug = false +debug-assertions = false +overflow-checks = false +lto = true +panic = "abort" +incremental = false +codegen-units = 1 +rpath = false + +[profile.release] +lto = true +panic = "abort" +codegen-units = 1 + +[workspace] +members = [] diff --git a/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-ebpf/rust-toolchain.toml b/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-ebpf/rust-toolchain.toml new file mode 100644 index 0000000..4893202 --- /dev/null +++ b/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-ebpf/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel="nightly-2023-01-10" +# The source code of rustc, provided by the rust-src component, is needed for +# building eBPF programs. +components = [ "rustc", "rust-std", "cargo", "rust-docs", "rustfmt", "clippy", "rust-src" ] diff --git a/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-ebpf/src/main.rs b/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-ebpf/src/main.rs new file mode 100644 index 0000000..598b666 --- /dev/null +++ b/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-ebpf/src/main.rs @@ -0,0 +1,84 @@ +#![no_std] +#![no_main] + +use core::mem; + +use aya_bpf::{ + bindings::xdp_action, + macros::{map, xdp}, + maps::PerfEventArray, + programs::XdpContext, +}; +use network_types::{ + eth::{EthHdr, EtherType}, + ip::{IpProto, Ipv4Hdr}, + tcp::TcpHdr, + udp::UdpHdr, +}; + +use xdp_perfbuf_custom_data_common::PacketLog; + +#[map(name = "EVENTS")] // (1) +static mut EVENTS: PerfEventArray = + PerfEventArray::::with_max_entries(1024, 0); + +#[xdp(name = "xdp_perfbuf_custom_data")] +pub fn xdp_perfbuf_custom_data(ctx: XdpContext) -> u32 { + match try_xdp_perfbuf_custom_data(ctx) { + Ok(ret) => ret, + Err(_) => xdp_action::XDP_ABORTED, + } +} + +#[inline(always)] +unsafe fn ptr_at(ctx: &XdpContext, offset: usize) -> Result<*const T, ()> { + let start = ctx.data(); + let end = ctx.data_end(); + let len = mem::size_of::(); + + if start + offset + len > end { + return Err(()); + } + + Ok((start + offset) as *const T) +} + +fn try_xdp_perfbuf_custom_data(ctx: XdpContext) -> Result { + let ethhdr: *const EthHdr = unsafe { ptr_at(&ctx, 0)? }; + match unsafe { (*ethhdr).ether_type } { + EtherType::Ipv4 => {} + _ => return Ok(xdp_action::XDP_PASS), + } + + let ipv4hdr: *const Ipv4Hdr = unsafe { ptr_at(&ctx, EthHdr::LEN)? }; + let source_addr = unsafe { (*ipv4hdr).src_addr }; + + let source_port = match unsafe { (*ipv4hdr).proto } { + IpProto::Tcp => { + let tcphdr: *const TcpHdr = + unsafe { ptr_at(&ctx, EthHdr::LEN + Ipv4Hdr::LEN) }?; + u16::from_be(unsafe { (*tcphdr).source }) + } + IpProto::Udp => { + let udphdr: *const UdpHdr = + unsafe { ptr_at(&ctx, EthHdr::LEN + Ipv4Hdr::LEN) }?; + u16::from_be(unsafe { (*udphdr).source }) + } + _ => return Err(()), + }; + + let log_entry = PacketLog { + ipv4_address: source_addr, + port: source_port as u32, + }; + unsafe { + EVENTS.output(&ctx, &log_entry, 0); // (2) + } + + Ok(xdp_action::XDP_PASS) +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + unsafe { core::hint::unreachable_unchecked() } +} diff --git a/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data/Cargo.toml b/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data/Cargo.toml new file mode 100644 index 0000000..a9e97d4 --- /dev/null +++ b/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "xdp-perfbuf-custom-data" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +aya = { version = ">=0.11", features=["async_tokio"] } +aya-log = "0.1" +xdp-perfbuf-custom-data-common = { path = "../xdp-perfbuf-custom-data-common", features=["user"] } +anyhow = "1.0.42" +clap = { version = "4.0", features = ["derive"] } +env_logger = "0.10" +log = "0.4" +tokio = { version = "1.23", features = ["macros", "rt", "rt-multi-thread", "net", "signal"] } +bytes = "1.3" + +[[bin]] +name = "xdp-perfbuf-custom-data" +path = "src/main.rs" diff --git a/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data/src/main.rs b/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data/src/main.rs new file mode 100644 index 0000000..543153a --- /dev/null +++ b/examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data/src/main.rs @@ -0,0 +1,87 @@ +use std::net; + +use anyhow::Context; +use aya::{ + include_bytes_aligned, + maps::perf::AsyncPerfEventArray, + programs::{Xdp, XdpFlags}, + util::online_cpus, + Bpf, +}; +use bytes::BytesMut; +use clap::Parser; +use log::info; +use tokio::{signal, task}; + +use xdp_perfbuf_custom_data_common::PacketLog; + +#[derive(Debug, Parser)] +struct Opt { + #[clap(short, long, default_value = "eth0")] + iface: String, +} + +#[tokio::main] +async fn main() -> Result<(), anyhow::Error> { + let opt = Opt::parse(); + + env_logger::init(); + + // This will include your eBPF object file as raw bytes at compile-time and load it at + // 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 bpf = Bpf::load(include_bytes_aligned!( + "../../target/bpfel-unknown-none/debug/xdp-perfbuf-custom-data" + ))?; + #[cfg(not(debug_assertions))] + let mut bpf = Bpf::load(include_bytes_aligned!( + "../../target/bpfel-unknown-none/release/xdp-perfbuf-custom-data" + ))?; + let program: &mut Xdp = bpf + .program_mut("xdp_perfbuf_custom_data") + .unwrap() + .try_into()?; + program.load()?; + program.attach(&opt.iface, XdpFlags::default()) + .context("failed to attach the XDP program with default flags - try changing XdpFlags::default() to XdpFlags::SKB_MODE")?; + + // (1) + let mut perf_array = AsyncPerfEventArray::try_from(bpf.map_mut("EVENTS")?)?; + + let cpus = online_cpus()?; + let num_cpus = cpus.len(); + for cpu_id in cpus { + // (2) + let mut buf = perf_array.open(cpu_id, None)?; + + // (3) + task::spawn(async move { + // (4) + let mut buffers = (0..num_cpus) + .map(|_| BytesMut::with_capacity(1024)) + .collect::>(); + + loop { + // (5) + let events = buf.read_events(&mut buffers).await.unwrap(); + for i in 0..events.read { + let buf = &mut buffers[i]; + let ptr = buf.as_ptr() as *const PacketLog; + // (6) + let data = unsafe { ptr.read_unaligned() }; + let src_addr = net::Ipv4Addr::from(data.ipv4_address); + // (7) + info!("SRC IP: {}, SRC_PORT: {}", src_addr, data.port); + } + } + }); + } + + info!("Waiting for Ctrl-C..."); + signal::ctrl_c().await?; + info!("Exiting..."); + + Ok(()) +} diff --git a/examples/xdp-perfbuf-custom-data/xtask/Cargo.toml b/examples/xdp-perfbuf-custom-data/xtask/Cargo.toml new file mode 100644 index 0000000..89080c9 --- /dev/null +++ b/examples/xdp-perfbuf-custom-data/xtask/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "xtask" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1" +clap = { version = "4.0", features = ["derive"] } diff --git a/examples/xdp-perfbuf-custom-data/xtask/src/build_ebpf.rs b/examples/xdp-perfbuf-custom-data/xtask/src/build_ebpf.rs new file mode 100644 index 0000000..600dd79 --- /dev/null +++ b/examples/xdp-perfbuf-custom-data/xtask/src/build_ebpf.rs @@ -0,0 +1,68 @@ +use std::{path::PathBuf, process::Command}; + +use clap::Parser; + +#[derive(Debug, Copy, Clone)] +pub enum Architecture { + BpfEl, + BpfEb, +} + +impl std::str::FromStr for Architecture { + type Err = String; + + fn from_str(s: &str) -> Result { + Ok(match s { + "bpfel-unknown-none" => Architecture::BpfEl, + "bpfeb-unknown-none" => Architecture::BpfEb, + _ => return Err("invalid target".to_owned()), + }) + } +} + +impl std::fmt::Display for Architecture { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Architecture::BpfEl => "bpfel-unknown-none", + Architecture::BpfEb => "bpfeb-unknown-none", + }) + } +} + +#[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 dir = PathBuf::from("xdp-perfbuf-custom-data-ebpf"); + let target = format!("--target={}", opts.target); + let mut args = vec![ + "build", + "--verbose", + target.as_str(), + "-Z", + "build-std=core", + ]; + if opts.release { + args.push("--release") + } + + // 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. + + let status = Command::new("cargo") + .current_dir(dir) + .env_remove("RUSTUP_TOOLCHAIN") + .args(&args) + .status() + .expect("failed to build bpf program"); + assert!(status.success()); + Ok(()) +} diff --git a/examples/xdp-perfbuf-custom-data/xtask/src/main.rs b/examples/xdp-perfbuf-custom-data/xtask/src/main.rs new file mode 100644 index 0000000..c1c594e --- /dev/null +++ b/examples/xdp-perfbuf-custom-data/xtask/src/main.rs @@ -0,0 +1,33 @@ +mod build_ebpf; +mod run; + +use std::process::exit; + +use clap::Parser; + +#[derive(Debug, Parser)] +pub struct Options { + #[clap(subcommand)] + command: Command, +} + +#[derive(Debug, Parser)] +enum Command { + BuildEbpf(build_ebpf::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), + }; + + if let Err(e) = ret { + eprintln!("{e:#}"); + exit(1); + } +} diff --git a/examples/xdp-perfbuf-custom-data/xtask/src/run.rs b/examples/xdp-perfbuf-custom-data/xtask/src/run.rs new file mode 100644 index 0000000..0cb103a --- /dev/null +++ b/examples/xdp-perfbuf-custom-data/xtask/src/run.rs @@ -0,0 +1,67 @@ +use std::{os::unix::process::CommandExt, 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, + /// 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, +} + +/// Build the project +fn build(opts: &Options) -> Result<(), anyhow::Error> { + let mut args = vec!["build"]; + if opts.release { + args.push("--release") + } + let status = Command::new("cargo") + .args(&args) + .status() + .expect("failed to build userspace"); + assert!(status.success()); + Ok(()) +} + +/// Build and run the project +pub fn run(opts: Options) -> Result<(), anyhow::Error> { + // build our ebpf program followed by our application + build_ebpf(BuildOptions { + target: opts.bpf_target, + release: opts.release, + }) + .context("Error while building eBPF program")?; + build(&opts).context("Error while building userspace application")?; + + // profile we are building (release or debug) + let profile = if opts.release { "release" } else { "debug" }; + let bin_path = format!("target/{profile}/xdp-perfbuf-custom-data"); + + // 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); + + // spawn the command + let err = Command::new(args.first().expect("No first argument")) + .args(args.iter().skip(1)) + .exec(); + + // we shouldn't get here unless the command failed to spawn + Err(anyhow::Error::from(err).context(format!("Failed to run `{}`", args.join(" ")))) +} diff --git a/mkdocs.yml b/mkdocs.yml index 5631ca0..0f27e7c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -67,6 +67,7 @@ nav: - Hello XDP!: book/start/hello-xdp.md - Logging Packets: book/start/logging-packets.md - Dropping Packets: book/start/dropping-packets.md + - Getting Data to User-Space: book/start/getting-data-to-user-space.md - Working With Aya: - book/aya/index.md - Program Lifecycle: book/aya/lifecycle.md