diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 04bf10e6..3c111f92 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -21,8 +21,14 @@ jobs: - uses: actions/checkout@v2 - uses: Swatinem/rust-cache@v1 + - name: Install bpf-linker + run: cargo install bpf-linker + - name: Build run: cargo build --verbose + - name: Build examples + run: cargo xtask examples + - name: Run tests run: RUST_BACKTRACE=full cargo test --verbose diff --git a/README.md b/README.md index b5990e2b..4d5ee638 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,19 @@ let cgroup = File::open("/sys/fs/cgroup/unified")?; ingress.attach(cgroup, CgroupSkbAttachType::Ingress)?; ``` +### Examples + +There are some examples of how to use Aya to write BPF programs and userspace program in the `examples` directory. These can be compiled by running `cargo xtask examples`. +The BPF examples require you to have a rust nightly toolchain installed. + +`cargo xtask examples` + +# Run + +## XDP + +`sudo ./examples/target/debug/xdp_userspace ./examples/target/bpfel-unknown-none/release/xdp wlp2s0` + ## Community Join [the conversation on Discord][chat-url] to discuss anything related to aya. diff --git a/examples/Cargo.toml b/examples/Cargo.toml new file mode 100644 index 00000000..3a654e71 --- /dev/null +++ b/examples/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["user", "bpf"] \ No newline at end of file diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..9618c94b --- /dev/null +++ b/examples/README.md @@ -0,0 +1,17 @@ +aya-examples +============ + +# Prerequisites + +1. Install a rust nightly toolchain: `rustup install nightly` +1. Install bpf-linker: `cargo install bpf-linker` + +# Build + +From the project root directory, run `cargo xtask examples` + +# Run + +## XDP + +`sudo ./examples/target/debug/xdp_userspace wlp2s0` \ No newline at end of file diff --git a/examples/bpf/Cargo.toml b/examples/bpf/Cargo.toml new file mode 100644 index 00000000..0b5c94fa --- /dev/null +++ b/examples/bpf/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "bpf" +version = "0.1.0" +edition = "2018" +publish = false + +[features] +default = [] +user = [ "aya" ] + +[dependencies] +aya-bpf = { path = "../../bpf/aya-bpf" } +aya = { path = "../../aya", optional=true } + +[lib] +path = "src/lib.rs" + +[[bin]] +name = "xdp" +path = "src/xdp/main.rs" \ No newline at end of file diff --git a/examples/bpf/src/lib.rs b/examples/bpf/src/lib.rs new file mode 100644 index 00000000..371dc962 --- /dev/null +++ b/examples/bpf/src/lib.rs @@ -0,0 +1,3 @@ +#![no_std] + +pub mod xdp; \ No newline at end of file diff --git a/examples/bpf/src/xdp/main.rs b/examples/bpf/src/xdp/main.rs new file mode 100644 index 00000000..bf147071 --- /dev/null +++ b/examples/bpf/src/xdp/main.rs @@ -0,0 +1,38 @@ +#![no_std] +#![no_main] + +use aya_bpf::bindings::xdp_action; +use aya_bpf::cty::c_long; +use aya_bpf::macros::{map, xdp}; +use aya_bpf::maps::Array; +use aya_bpf::programs::XdpContext; + +use bpf::xdp::XdpData; + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + unreachable!() +} + +#[map] +static mut xdp_stats_map : Array = Array::with_max_entries(1,0); + +#[xdp(name = "xdp_stats")] +pub fn xdp_stats(ctx: XdpContext) -> u32 { + match unsafe { try_xdp_stats(ctx) } { + Ok(ret) => ret, + Err(_) => xdp_action::XDP_DROP, + } +} + +unsafe fn try_xdp_stats(_ctx: XdpContext) -> Result { + let data = match xdp_stats_map.get(0) { + Some(data) => data, + None => return Err(0) + }; + let xdp_data = XdpData{ + packet_count: data.packet_count + 1, + }; + xdp_stats_map.set(0, &xdp_data, 0)?; + Ok(xdp_action::XDP_PASS) +} \ No newline at end of file diff --git a/examples/bpf/src/xdp/mod.rs b/examples/bpf/src/xdp/mod.rs new file mode 100644 index 00000000..73b19635 --- /dev/null +++ b/examples/bpf/src/xdp/mod.rs @@ -0,0 +1,8 @@ +#[derive(Copy, Clone)] +#[repr(C)] +pub struct XdpData { + pub packet_count : u32, +} + +#[cfg(feature = "user")] +unsafe impl aya::Pod for XdpData {} \ No newline at end of file diff --git a/examples/user/Cargo.toml b/examples/user/Cargo.toml new file mode 100644 index 00000000..a856d0a4 --- /dev/null +++ b/examples/user/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "user" +version = "0.1.0" +edition = "2018" +publish = false + +[dependencies] +aya = { path = "../../aya" } +bpf = { path = "../bpf", features=["user"] } +anyhow = "1.0.42" + +[[bin]] +name = "xdp_userspace" +path = "src/xdp.rs" \ No newline at end of file diff --git a/examples/user/src/xdp.rs b/examples/user/src/xdp.rs new file mode 100644 index 00000000..845a7363 --- /dev/null +++ b/examples/user/src/xdp.rs @@ -0,0 +1,53 @@ +use aya::Bpf; +use aya::maps::{Array, MapRefMut}; +use aya::programs::{Xdp, XdpFlags}; +use std::{ + convert::TryFrom, + convert::TryInto, + env, + fs, + thread, + time::Duration, +}; + +use bpf::xdp::XdpData; + +fn main() { + if let Err(e) = try_main() { + eprintln!("error: {:#}", e); + } +} + +fn try_main() -> Result<(), anyhow::Error> { + let path = match env::args().nth(1) { + Some(iface) => iface, + None => panic!("not path provided"), + }; + let iface = match env::args().nth(2) { + Some(iface) => iface, + None => "eth0".to_string(), + }; + + let data = fs::read(path)?; + let mut bpf = Bpf::load( + &data, + None)?; + + // get the `xdp_stats` program compiled into `xdp`. + let probe: &mut Xdp = bpf.program_mut("xdp_stats")?.try_into()?; + + // load the program into the kernel + probe.load()?; + + // attach to the interface + probe.attach(&iface, XdpFlags::default())?; + + let xdp_stats_map : Array:: = Array::try_from(bpf.map_mut("xdp_stats_map")?)?; + + for _i in 1..10 { + let data = xdp_stats_map.get(&0, 0)?; + println!("packets received: {}", data.packet_count); + thread::sleep(Duration::from_secs(1)); + }; + Ok(()) +} \ No newline at end of file diff --git a/xtask/src/examples/mod.rs b/xtask/src/examples/mod.rs new file mode 100644 index 00000000..b6e72d10 --- /dev/null +++ b/xtask/src/examples/mod.rs @@ -0,0 +1,29 @@ +use std::path::PathBuf; +use std::process::Command; + +use structopt::StructOpt; + +#[derive(StructOpt)] +pub struct Options {} + +pub fn examples(_opts: Options) -> Result<(), anyhow::Error> { + let dir = PathBuf::from("examples"); + + // build bpf examples + let status = Command::new("cargo") + .current_dir(&dir) + .args(&["+nightly", "build", "--verbose", "--release", "--package=bpf", "--target=bpfel-unknown-none", "-Z", "build-std=core"]) + .status() + .expect("failed to build bpf examples"); + assert!(status.success()); + + // build userspace examples + let status = Command::new("cargo") + .current_dir(&dir) + .args(&["build", "--verbose", "--package=user"]) + .status() + .expect("failed to build userspace examples"); + assert!(status.success()); + + Ok(()) +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 14a923a5..4321a42b 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,4 +1,5 @@ mod codegen; +mod examples; use std::process::exit; @@ -12,6 +13,7 @@ pub struct Options { #[derive(StructOpt)] enum Command { Codegen(codegen::Options), + Examples(examples::Options), } fn main() { @@ -20,6 +22,7 @@ fn main() { use Command::*; let ret = match opts.command { Codegen(opts) => codegen::codegen(opts), + Examples(opts) => examples::examples(opts), }; if let Err(e) = ret {