mirror of https://github.com/aya-rs/aya
				
				
				
			test: Replace RTF with Rust
This commit replaces the existing RTF test runner with a simple rust binary package called - integration-test. integration-test depends on integration-ebpf, which contains test eBPF code written in Rust and C. `cargo xtask build-integration-test-ebpf` can be used to build this code and supress rust-analyzer warnings. It does require `bpf-linker`, but that is highly likely to be available to developers of Aya. It also requires a checkout of `libbpf` to extract headers like bpf-helpers.h. Since everything is compiled into a single binary, it can be run be run locally using `cargo xtask integration-test` or remotely using `./run.sh` which re-uses the bash script from the old test framework to spawn a VM in which to run the tests. Signed-off-by: Dave Tucker <dave@dtucker.co.uk>pull/335/head
							parent
							
								
									6188c9dee3
								
							
						
					
					
						commit
						79101e748a
					
				| @ -1,53 +0,0 @@ | ||||
| name: aya-test-image | ||||
| 
 | ||||
| on: | ||||
|   schedule: | ||||
|     - cron: "42 2 * * 0" | ||||
|   push: | ||||
|     branches: | ||||
|       - 'main' | ||||
|     paths: | ||||
|       - 'images/**' | ||||
|       - '.github/workflows/images.yml' | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - 'main' | ||||
|     paths: | ||||
|       - 'images/**' | ||||
|       - '.github/workflows/images.yml' | ||||
| 
 | ||||
| env: | ||||
|   REGISTRY: ghcr.io | ||||
|   IMAGE_NAME: aya-rs/aya-test-rtf | ||||
| 
 | ||||
| jobs: | ||||
|   rtf: | ||||
|     runs-on: ubuntu-latest | ||||
|     permissions: | ||||
|       contents: read | ||||
|       packages: write | ||||
| 
 | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
| 
 | ||||
|       - name: Log in to the Container registry | ||||
|         uses: docker/login-action@v1 | ||||
|         with: | ||||
|           registry: ${{ env.REGISTRY }} | ||||
|           username: ${{ github.actor }} | ||||
|           password: ${{ secrets.GITHUB_TOKEN }} | ||||
| 
 | ||||
|       - name: Extract metadata (tags, labels) for Docker | ||||
|         id: meta | ||||
|         uses: docker/metadata-action@v3 | ||||
|         with: | ||||
|           images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | ||||
| 
 | ||||
|       - name: Build and push Docker image | ||||
|         uses: docker/build-push-action@v2 | ||||
|         with: | ||||
|           context: images | ||||
|           file: images/Dockerfile.rtf | ||||
|           push: true | ||||
|           tags: ${{ steps.meta.outputs.tags }} | ||||
|           labels: ${{ steps.meta.outputs.labels }} | ||||
| @ -1,3 +1,4 @@ | ||||
| { | ||||
|   "rust-analyzer.linkedProjects": ["Cargo.toml", "bpf/Cargo.toml"] | ||||
|   "rust-analyzer.linkedProjects": ["Cargo.toml", "bpf/Cargo.toml", "test/integration-ebpf/Cargo.toml"], | ||||
|   "rust-analyzer.checkOnSave.allTargets": false | ||||
| } | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| { | ||||
|   "rust-analyzer.linkedProjects": ["Cargo.toml", "bpf/Cargo.toml"] | ||||
|   "rust-analyzer.linkedProjects": ["Cargo.toml", "bpf/Cargo.toml", "test/integration-ebpf/Cargo.toml"], | ||||
|   "rust-analyzer.checkOnSave.allTargets": false | ||||
| } | ||||
|  | ||||
| @ -1,2 +1,3 @@ | ||||
| [workspace] | ||||
| members = ["aya", "aya-gen", "xtask"] | ||||
| members = ["aya", "aya-gen", "test/integration-test", "test/integration-test-macros", "xtask"] | ||||
| default-members = ["aya", "aya-gen"] | ||||
|  | ||||
| @ -0,0 +1 @@ | ||||
| ../rustfmt.toml | ||||
| @ -1,38 +0,0 @@ | ||||
| FROM fedora:35 | ||||
| 
 | ||||
| # Rust Nightly | ||||
| RUN curl https://sh.rustup.rs -sSf | sh -s -- \ | ||||
|     --default-toolchain nightly \ | ||||
|     --component rustfmt \ | ||||
|     --component clippy \ | ||||
|     --component rust-src \ | ||||
|     --target x86_64-unknown-linux-musl \ | ||||
|     -y | ||||
| 
 | ||||
| ENV PATH "/root/.cargo/bin:$PATH" | ||||
| 
 | ||||
| # Pre-requisites | ||||
| RUN dnf install \ | ||||
|     --setopt=install_weak_deps=False --best -qy \ | ||||
|     golang \ | ||||
|     qemu-system-x86 \ | ||||
|     cloud-utils \ | ||||
|     genisoimage \ | ||||
|     libbpf-devel \ | ||||
|     clang \ | ||||
|     openssl-devel \ | ||||
|     musl-libc \ | ||||
|     git && dnf clean all \ | ||||
|   	&& rm -rf /var/cache/yum | ||||
| 
 | ||||
| RUN cargo install \ | ||||
|     bpf-linker \ | ||||
|     rust-script \ | ||||
|     sccache | ||||
| 
 | ||||
| RUN go install github.com/linuxkit/rtf@latest | ||||
| ENV PATH "/root/go/bin:$PATH" | ||||
| ENV RUSTC_WRAPPER "sccache" | ||||
| 
 | ||||
| ENTRYPOINT ["rtf"] | ||||
| CMD ["-vvv", "run"] | ||||
| @ -1,3 +1 @@ | ||||
| _results | ||||
| _tmp | ||||
| _images | ||||
| .tmp | ||||
|  | ||||
| @ -1,48 +1,52 @@ | ||||
| Aya Regression Tests | ||||
| ==================== | ||||
| Aya Integration Tests | ||||
| ===================== | ||||
| 
 | ||||
| The aya regression test suite is a set of tests to ensure that | ||||
| The aya integration test suite is a set of tests to ensure that | ||||
| common usage behaviours work on real Linux distros | ||||
| ## Prerequisites | ||||
| 
 | ||||
| This assumes you have a working Rust and Go toolchain on the host machine | ||||
| ### Linux | ||||
| 
 | ||||
| 1. `rustup target add x86_64-unknown-linux-musl` | ||||
| 1. Install [`rtf`](https://github.com/linuxkit/rtf): `go install github.com/linuxkit/rtf@latest` | ||||
| 1. Install rust-script: `cargo install rust-script` | ||||
| 1. Install `qemu` and `cloud-init-utils` package - or any package that provides `cloud-localds` | ||||
| To run locally all you need is: | ||||
| 
 | ||||
| It is not required, but the tests run significantly faster if you use `sccache` | ||||
| 1. Rust nightly | ||||
| 1. A checkout of `libbpf` | ||||
| 1. `cargo install bpf-linker` | ||||
| 1. `bpftool` | ||||
| 
 | ||||
| You may also use the docker image to run the tests: | ||||
| ### Other OSs | ||||
| 
 | ||||
| ``` | ||||
| docker run -it --rm --device /dev/kvm -v/home/dave/dev/aya-rs/aya:/src -w /src/test ghcr.io/aya-rs/aya-test-rtf:main | ||||
| ``` | ||||
| 1. A POSIX shell | ||||
| 1. A checkout of `libbpf` | ||||
| 1. `rustup target add x86_64-unknown-linux-musl` | ||||
| 1. `cargo install bpf-linker` | ||||
| 1. Install `qemu` and `cloud-init-utils` package - or any package that provides `cloud-localds` | ||||
| 
 | ||||
| ## Usage | ||||
| 
 | ||||
| To read more about how to use `rtf`, see the [documentation](https://github.com/linuxkit/rtf/blob/master/docs/USER_GUIDE.md) | ||||
| From the root of this repository: | ||||
| 
 | ||||
| ### Run the tests with verbose output | ||||
| ### Native | ||||
| 
 | ||||
| ``` | ||||
| rtf -vvv run | ||||
| cargo xtask integration-test --libbpf-dir /path/to/libbpf | ||||
| ``` | ||||
| ### Run the tests using an older kernel | ||||
| 
 | ||||
| ### Virtualized | ||||
| 
 | ||||
| 
 | ||||
| ``` | ||||
| AYA_TEST_IMAGE=centos8 rtf -vvv run | ||||
| ./test/run.sh /path/to/libbpf | ||||
| ``` | ||||
| 
 | ||||
| ### Writing a test | ||||
| 
 | ||||
| Tests should follow this pattern: | ||||
| 
 | ||||
| - The eBPF code should be in a file named `${NAME}.ebpf.rs` | ||||
| - The userspace code should be in a file named `${NAME}.rs` | ||||
| - The userspace program should make assertions and exit with a non-zero return code to signal failure | ||||
| - VM start and stop is handled by the framework | ||||
| - Any files copied to the VM should be cleaned up afterwards | ||||
| Tests should follow these guidelines: | ||||
| 
 | ||||
| See `./cases` for examples | ||||
| - Rust eBPF code should live in `integration-ebpf/${NAME}.rs` and included in `integration-ebpf/Cargo.toml` | ||||
| - C eBPF code should live in `integration-test/src/bpf/${NAME}.bpf.c`. It's automatically compiled and made available as `${OUT_DIR}/${NAME}.bpf.o`. | ||||
| - Any bytecode should be included in the integration test binary using `include_bytes_aligned!` | ||||
| - Tests should be added to `integration-test/src/test` | ||||
| - You may add a new module, or use an existing one | ||||
| - Integration tests must use the `#[integration_test]` macro to be included in the build | ||||
| - Test functions should return `anyhow::Result<()>` since this allows the use of `?` to return errors. | ||||
| - You may either `panic!` when an assertion fails or `bail!`. The former is preferred since the stack trace will point directly to the failed line. | ||||
|  | ||||
| @ -1,19 +0,0 @@ | ||||
| //! ```cargo
 | ||||
| //! [dependencies]
 | ||||
| //! aya = { path = "../../../../aya" }
 | ||||
| //! ```
 | ||||
| 
 | ||||
| use aya::{ | ||||
|     Bpf, | ||||
|     programs::{Xdp, XdpFlags}, | ||||
| }; | ||||
| use std::convert::TryInto; | ||||
| 
 | ||||
| fn main() { | ||||
|     println!("Loading XDP program"); | ||||
|     let mut bpf = Bpf::load_file("pass.o").unwrap(); | ||||
|     let dispatcher: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); | ||||
|     dispatcher.load().unwrap(); | ||||
|     dispatcher.attach("eth0", XdpFlags::default()).unwrap(); | ||||
|     println!("Success..."); | ||||
| } | ||||
| @ -1,29 +0,0 @@ | ||||
| #!/bin/sh | ||||
| # SUMMARY: Check that a simple XDP program an be loaded | ||||
| # LABELS: | ||||
| 
 | ||||
| set -e | ||||
| 
 | ||||
| # Source libraries. Uncomment if needed/defined | ||||
| #. "${RT_LIB}" | ||||
| . "${RT_PROJECT_ROOT}/_lib/lib.sh" | ||||
| 
 | ||||
| NAME=pass | ||||
| 
 | ||||
| clean_up() { | ||||
|     rm -rf ${NAME}.o ${NAME} | ||||
|     exec_vm rm -f ${NAME} ${NAME}.o | ||||
| } | ||||
| 
 | ||||
| trap clean_up EXIT | ||||
| 
 | ||||
| # Test code goes here | ||||
| compile_ebpf "$(pwd)/${NAME}.ebpf.rs" | ||||
| compile_user "$(pwd)/${NAME}.rs" | ||||
| 
 | ||||
| scp_vm ${NAME}.o | ||||
| scp_vm ${NAME} | ||||
| 
 | ||||
| exec_vm sudo ./${NAME} | ||||
| 
 | ||||
| exit 0 | ||||
| @ -1,24 +0,0 @@ | ||||
| //! ```cargo
 | ||||
| //! [dependencies]
 | ||||
| //! aya = { path = "../../../../aya" }
 | ||||
| //! ```
 | ||||
| 
 | ||||
| use aya::{ | ||||
|     Bpf, BpfLoader, | ||||
|     programs::{Extension, ProgramFd, Xdp, XdpFlags}, | ||||
| }; | ||||
| use std::convert::TryInto; | ||||
| 
 | ||||
| fn main() { | ||||
|     println!("Loading Root XDP program"); | ||||
|     let mut bpf = Bpf::load_file("main.o").unwrap(); | ||||
|     let pass: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); | ||||
|     pass.load().unwrap(); | ||||
|     pass.attach("lo", XdpFlags::default()).unwrap(); | ||||
| 
 | ||||
|     println!("Loading Extension Program"); | ||||
|     let mut bpf = BpfLoader::new().extension("drop").load_file("ext.o").unwrap(); | ||||
|     let drop_: &mut Extension = bpf.program_mut("drop").unwrap().try_into().unwrap(); | ||||
|     drop_.load(pass.fd().unwrap(), "xdp_pass").unwrap(); | ||||
|     println!("Success..."); | ||||
| } | ||||
| @ -1,33 +0,0 @@ | ||||
| #!/bin/sh | ||||
| # SUMMARY: Check that a simple XDP program an be loaded | ||||
| # LABELS: | ||||
| 
 | ||||
| set -e | ||||
| 
 | ||||
| # Source libraries. Uncomment if needed/defined | ||||
| #. "${RT_LIB}" | ||||
| . "${RT_PROJECT_ROOT}/_lib/lib.sh" | ||||
| 
 | ||||
| NAME=ext | ||||
| 
 | ||||
| clean_up() { | ||||
|     rm -rf main.o ${NAME}.o ${NAME} | ||||
|     exec_vm rm -f main.o ${NAME}.o ${NAME} | ||||
| } | ||||
| 
 | ||||
| trap clean_up EXIT | ||||
| 
 | ||||
| # Test code goes here | ||||
| min_kernel_version 5.9 | ||||
| 
 | ||||
| compile_c_ebpf "$(pwd)/main.bpf.c" | ||||
| compile_c_ebpf "$(pwd)/${NAME}.bpf.c" | ||||
| compile_user "$(pwd)/${NAME}.rs" | ||||
| 
 | ||||
| scp_vm main.o | ||||
| scp_vm ${NAME}.o | ||||
| scp_vm ${NAME} | ||||
| 
 | ||||
| exec_vm sudo ./${NAME} | ||||
| 
 | ||||
| exit 0 | ||||
| @ -1,36 +0,0 @@ | ||||
| #!/bin/sh | ||||
| # SUMMARY: Smoke tests to check that simple programs can be loaded on a VM | ||||
| # LABELS: | ||||
| 
 | ||||
| # Source libraries. Uncomment if needed/defined | ||||
| # . "${RT_LIB}" | ||||
| . "${RT_PROJECT_ROOT}/_lib/lib.sh" | ||||
| 
 | ||||
| set -e | ||||
| 
 | ||||
| group_init() { | ||||
|     # Group initialisation code goes here | ||||
|     return 0 | ||||
| } | ||||
| 
 | ||||
| group_deinit() { | ||||
|     # Group de-initialisation code goes here | ||||
|     return 0 | ||||
| } | ||||
| 
 | ||||
| CMD=$1 | ||||
| case $CMD in | ||||
| init) | ||||
|     group_init | ||||
|     res=$? | ||||
|     ;; | ||||
| deinit) | ||||
|     group_deinit | ||||
|     res=$? | ||||
|     ;; | ||||
| *) | ||||
|     res=1 | ||||
|     ;; | ||||
| esac | ||||
| 
 | ||||
| exit $res | ||||
| @ -1,20 +0,0 @@ | ||||
| //! ```cargo
 | ||||
| //! [dependencies]
 | ||||
| //! aya = { path = "../../../../aya" }
 | ||||
| //! ```
 | ||||
| 
 | ||||
| use aya::{ | ||||
|     Bpf, | ||||
|     programs::{Xdp, XdpFlags}, | ||||
| }; | ||||
| use std::convert::TryInto; | ||||
| use std::{thread, time}; | ||||
| 
 | ||||
| fn main() { | ||||
|     println!("Loading XDP program"); | ||||
|     let mut bpf = Bpf::load_file("name_test.o").unwrap(); | ||||
|     let dispatcher: &mut Xdp = bpf.program_mut("ihaveaverylongname").unwrap().try_into().unwrap(); | ||||
|     dispatcher.load().unwrap(); | ||||
|     dispatcher.attach("eth0", XdpFlags::default()).unwrap(); | ||||
|     thread::sleep(time::Duration::from_secs(20)); | ||||
| } | ||||
| @ -1,32 +0,0 @@ | ||||
| #!/bin/sh | ||||
| # SUMMARY: Check that long names are properly truncated | ||||
| # LABELS: | ||||
| 
 | ||||
| set -e | ||||
| 
 | ||||
| # Source libraries. Uncomment if needed/defined | ||||
| #. "${RT_LIB}" | ||||
| . "${RT_PROJECT_ROOT}/_lib/lib.sh" | ||||
| 
 | ||||
| NAME=name_test | ||||
| 
 | ||||
| clean_up() { | ||||
|     rm -rf ebpf user ${NAME}.o ${NAME} | ||||
|     exec_vm sudo pkill -9 ${NAME} | ||||
|     exec_vm rm ${NAME} ${NAME}.o | ||||
| } | ||||
| 
 | ||||
| trap clean_up EXIT | ||||
| 
 | ||||
| # Test code goes here | ||||
| compile_ebpf ${NAME}.ebpf.rs | ||||
| compile_user ${NAME}.rs | ||||
| 
 | ||||
| scp_vm ${NAME}.o | ||||
| scp_vm ${NAME} | ||||
| 
 | ||||
| exec_vm sudo ./${NAME}& | ||||
| prog_list=$(exec_vm sudo bpftool prog) | ||||
| echo "${prog_list}" | grep -q "xdp  name ihaveaverylongn  tag" | ||||
| 
 | ||||
| exit 0 | ||||
| @ -1,33 +0,0 @@ | ||||
| //! ```cargo
 | ||||
| //! [dependencies]
 | ||||
| //! log = "0.4"
 | ||||
| //! simplelog = "0.11"
 | ||||
| //! aya = { path = "../../../../aya" }
 | ||||
| //! ```
 | ||||
| 
 | ||||
| use aya::{ | ||||
|     Bpf, | ||||
|     programs::{Xdp, XdpFlags}, | ||||
| }; | ||||
| use log::info; | ||||
| use std::convert::TryInto; | ||||
| 
 | ||||
| use simplelog::{ColorChoice, ConfigBuilder, LevelFilter, TermLogger, TerminalMode}; | ||||
| 
 | ||||
| fn main() { | ||||
|     TermLogger::init( | ||||
|         LevelFilter::Debug, | ||||
|         ConfigBuilder::new() | ||||
|             .set_target_level(LevelFilter::Error) | ||||
|             .set_location_level(LevelFilter::Error) | ||||
|             .build(), | ||||
|         TerminalMode::Mixed, | ||||
|         ColorChoice::Auto, | ||||
|     ).unwrap(); | ||||
|     info!("Loading XDP program"); | ||||
|     let mut bpf = Bpf::load_file("multimap.o").unwrap(); | ||||
|     let pass: &mut Xdp = bpf.program_mut("stats").unwrap().try_into().unwrap(); | ||||
|     pass.load().unwrap(); | ||||
|     pass.attach("eth0", XdpFlags::default()).unwrap(); | ||||
|     info!("Success..."); | ||||
| } | ||||
| @ -1,29 +0,0 @@ | ||||
| #!/bin/sh | ||||
| # SUMMARY: Check that a program with multiple maps in the maps section loads | ||||
| # LABELS: | ||||
| 
 | ||||
| set -e | ||||
| 
 | ||||
| # Source libraries. Uncomment if needed/defined | ||||
| #. "${RT_LIB}" | ||||
| . "${RT_PROJECT_ROOT}/_lib/lib.sh" | ||||
| 
 | ||||
| NAME=multimap | ||||
| 
 | ||||
| clean_up() { | ||||
|     rm -rf ${NAME}.o ${NAME} | ||||
|     exec_vm rm -f ${NAME}.o ${NAME} | ||||
| } | ||||
| 
 | ||||
| trap clean_up EXIT | ||||
| 
 | ||||
| # Test code goes here | ||||
| compile_c_ebpf "$(pwd)/${NAME}.bpf.c" | ||||
| compile_user "$(pwd)/${NAME}.rs" | ||||
| 
 | ||||
| scp_vm ${NAME}.o | ||||
| scp_vm ${NAME} | ||||
| 
 | ||||
| exec_vm sudo ./${NAME} | ||||
| 
 | ||||
| exit 0 | ||||
| @ -1,57 +0,0 @@ | ||||
| //! ```cargo
 | ||||
| //! [dependencies]
 | ||||
| //! aya = { path = "../../../../aya" }
 | ||||
| //! ```
 | ||||
| 
 | ||||
| use aya::{ | ||||
|     programs::{Xdp, XdpFlags}, | ||||
|     Bpf, | ||||
| }; | ||||
| use std::convert::TryInto; | ||||
| use std::process::Command; | ||||
| 
 | ||||
| fn is_loaded() -> bool { | ||||
|     let output = Command::new("bpftool").args(&["prog"]).output().unwrap(); | ||||
|     let stdout = String::from_utf8(output.stdout).unwrap(); | ||||
|     stdout.contains("test_unload") | ||||
| } | ||||
| 
 | ||||
| fn assert_loaded(loaded: bool) { | ||||
|     let state = is_loaded(); | ||||
|     if state == loaded { | ||||
|         return; | ||||
|     } | ||||
|     panic!("Expected loaded: {} but was loaded: {}", loaded, state); | ||||
| } | ||||
| 
 | ||||
| fn main() { | ||||
|     println!("Loading XDP program"); | ||||
|     let mut bpf = Bpf::load_file("test.o").unwrap(); | ||||
|     let dispatcher: &mut Xdp = bpf.program_mut("test_unload").unwrap().try_into().unwrap(); | ||||
| 
 | ||||
|     dispatcher.load().unwrap(); | ||||
| 
 | ||||
|     let link = dispatcher.attach("eth0", XdpFlags::default()).unwrap(); | ||||
| 
 | ||||
|     { | ||||
|         let link_owned = dispatcher.take_link(link); | ||||
| 
 | ||||
|         dispatcher.unload().unwrap(); | ||||
| 
 | ||||
|         assert_loaded(true); | ||||
|     }; | ||||
| 
 | ||||
|     assert_loaded(false); | ||||
| 
 | ||||
|     dispatcher.load().unwrap(); | ||||
| 
 | ||||
|     assert_loaded(true); | ||||
| 
 | ||||
|     dispatcher.attach("eth0", XdpFlags::default()).unwrap(); | ||||
| 
 | ||||
|     assert_loaded(true); | ||||
| 
 | ||||
|     dispatcher.unload().unwrap(); | ||||
| 
 | ||||
|     assert_loaded(false); | ||||
| } | ||||
| @ -1,27 +0,0 @@ | ||||
| #!/bin/sh | ||||
| # SUMMARY: Check that the program can be unloaded | ||||
| # LABELS: | ||||
| 
 | ||||
| set -ex | ||||
| 
 | ||||
| # Source libraries. Uncomment if needed/defined | ||||
| #. "${RT_LIB}" | ||||
| . "${RT_PROJECT_ROOT}/_lib/lib.sh" | ||||
| 
 | ||||
| NAME=test | ||||
| 
 | ||||
| clean_up() { | ||||
|     rm -rf ebpf user ${NAME}.o ${NAME} | ||||
|     exec_vm rm ${NAME} ${NAME}.o | ||||
| } | ||||
| 
 | ||||
| trap clean_up EXIT | ||||
| 
 | ||||
| # Test code goes here | ||||
| compile_ebpf ${NAME}.ebpf.rs | ||||
| compile_user ${NAME}.rs | ||||
| 
 | ||||
| scp_vm ${NAME}.o | ||||
| scp_vm ${NAME} | ||||
| 
 | ||||
| exec_vm sudo ./${NAME} | ||||
| @ -1,36 +0,0 @@ | ||||
| #!/bin/sh | ||||
| # SUMMARY: Tests to check loader features | ||||
| # LABELS: | ||||
| 
 | ||||
| # Source libraries. Uncomment if needed/defined | ||||
| # . "${RT_LIB}" | ||||
| . "${RT_PROJECT_ROOT}/_lib/lib.sh" | ||||
| 
 | ||||
| set -e | ||||
| 
 | ||||
| group_init() { | ||||
|     # Group initialisation code goes here | ||||
|     return 0 | ||||
| } | ||||
| 
 | ||||
| group_deinit() { | ||||
|     # Group de-initialisation code goes here | ||||
|     return 0 | ||||
| } | ||||
| 
 | ||||
| CMD=$1 | ||||
| case $CMD in | ||||
| init) | ||||
|     group_init | ||||
|     res=$? | ||||
|     ;; | ||||
| deinit) | ||||
|     group_deinit | ||||
|     res=$? | ||||
|     ;; | ||||
| *) | ||||
|     res=1 | ||||
|     ;; | ||||
| esac | ||||
| 
 | ||||
| exit $res | ||||
											
												Binary file not shown.
											
										
									
								| @ -1,25 +0,0 @@ | ||||
| #!/bin/sh | ||||
| # SUMMARY: Check that maps are correctly represented in ELF files | ||||
| # LABELS: | ||||
| 
 | ||||
| set -ex | ||||
| 
 | ||||
| # Source libraries. Uncomment if needed/defined | ||||
| #. "${RT_LIB}" | ||||
| . "${RT_PROJECT_ROOT}/_lib/lib.sh" | ||||
| 
 | ||||
| NAME=map_test | ||||
| 
 | ||||
| clean_up() { | ||||
|     rm -rf ebpf user ${NAME}.o | ||||
| } | ||||
| 
 | ||||
| trap clean_up EXIT | ||||
| 
 | ||||
| # Test code goes here | ||||
| compile_ebpf ${NAME}.ebpf.rs | ||||
| 
 | ||||
| readelf --sections ${NAME}.o | grep -q "maps" | ||||
| readelf --syms ${NAME}.o | grep -q "BAR" | ||||
| 
 | ||||
| exit 0 | ||||
| @ -1,36 +0,0 @@ | ||||
| #!/bin/sh | ||||
| # SUMMARY: Tests to check ELF from aya-bpf | ||||
| # LABELS: | ||||
| 
 | ||||
| # Source libraries. Uncomment if needed/defined | ||||
| # . "${RT_LIB}" | ||||
| . "${RT_PROJECT_ROOT}/_lib/lib.sh" | ||||
| 
 | ||||
| set -e | ||||
| 
 | ||||
| group_init() { | ||||
|     # Group initialisation code goes here | ||||
|     return 0 | ||||
| } | ||||
| 
 | ||||
| group_deinit() { | ||||
|     # Group de-initialisation code goes here | ||||
|     return 0 | ||||
| } | ||||
| 
 | ||||
| CMD=$1 | ||||
| case $CMD in | ||||
| init) | ||||
|     group_init | ||||
|     res=$? | ||||
|     ;; | ||||
| deinit) | ||||
|     group_deinit | ||||
|     res=$? | ||||
|     ;; | ||||
| *) | ||||
|     res=1 | ||||
|     ;; | ||||
| esac | ||||
| 
 | ||||
| exit $res | ||||
| @ -1,85 +0,0 @@ | ||||
| //! ```cargo | ||||
| //! [dependencies] | ||||
| //! libbpf-sys = { version = "0.6.1-1" } | ||||
| //! anyhow = "1" | ||||
| //! ``` | ||||
| 
 | ||||
| use std::{ | ||||
|     env, | ||||
|     fs::{self, OpenOptions}, | ||||
|     io::Write, | ||||
|     path::Path, | ||||
|     process::Command, | ||||
|     string::String, | ||||
| }; | ||||
| use anyhow::{bail, Context, Result}; | ||||
| static CLANG_DEFAULT: &str = "/usr/bin/clang"; | ||||
| 
 | ||||
| /// Extract vendored libbpf headers from libbpf-sys. | ||||
| fn extract_libbpf_headers<P: AsRef<Path>>(include_path: P) -> Result<()> { | ||||
|     let dir = include_path.as_ref().join("bpf"); | ||||
|     fs::create_dir_all(&dir)?; | ||||
|     for (filename, contents) in libbpf_sys::API_HEADERS.iter() { | ||||
|         let path = dir.as_path().join(filename); | ||||
|         let mut file = OpenOptions::new().write(true).create(true).open(path)?; | ||||
|         file.write_all(contents.as_bytes())?; | ||||
|     } | ||||
| 
 | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| /// Build eBPF programs with clang and libbpf headers. | ||||
| fn build_ebpf<P: Clone + AsRef<Path>>(in_file: P, out_file: P, include_path: P) -> Result<()> { | ||||
|     extract_libbpf_headers(include_path.clone())?; | ||||
|     let clang = match env::var("CLANG") { | ||||
|         Ok(val) => val, | ||||
|         Err(_) => String::from(CLANG_DEFAULT), | ||||
|     }; | ||||
|     let arch = match std::env::consts::ARCH { | ||||
|         "x86_64" => "x86", | ||||
|         "aarch64" => "arm64", | ||||
|         _ => std::env::consts::ARCH, | ||||
|     }; | ||||
|     let mut cmd = Command::new(clang); | ||||
|     cmd.arg(format!("-I{}", include_path.as_ref().to_string_lossy())) | ||||
|         .arg("-g") | ||||
|         .arg("-O2") | ||||
|         .arg("-target") | ||||
|         .arg("bpf") | ||||
|         .arg("-c") | ||||
|         .arg(format!("-D__TARGET_ARCH_{}", arch)) | ||||
|         .arg(in_file.as_ref().as_os_str()) | ||||
|         .arg("-o") | ||||
|         .arg(out_file.as_ref().as_os_str()); | ||||
| 
 | ||||
|     let output = cmd.output().context("Failed to execute clang")?; | ||||
|     if !output.status.success() { | ||||
|         bail!( | ||||
|             "Failed to compile eBPF programs\n \ | ||||
|             stdout=\n \ | ||||
|             {}\n \ | ||||
|             stderr=\n \ | ||||
|             {}\n", | ||||
|             String::from_utf8(output.stdout).unwrap(), | ||||
|             String::from_utf8(output.stderr).unwrap() | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| fn main() -> Result<()> { | ||||
|     let args: Vec<String> = env::args().collect(); | ||||
|     if args.len() != 3 { | ||||
|         bail!("requires 2 arguments. src and dst") | ||||
|     } | ||||
|     let path = env::current_dir()?; | ||||
|     let src = Path::new(&args[1]); | ||||
|     let dst = Path::new(&args[2]); | ||||
| 
 | ||||
|     let include_path = path.join("include"); | ||||
|     fs::create_dir_all(include_path.clone())?; | ||||
|     build_ebpf(src, dst, &include_path)?; | ||||
| 
 | ||||
|     Ok(()) | ||||
| } | ||||
| @ -1,36 +0,0 @@ | ||||
| #!/bin/sh | ||||
| # NAME: aya | ||||
| # SUMMARY: Aya Regression Tests | ||||
| 
 | ||||
| # Source libraries. Uncomment if needed/defined | ||||
| # . "${RT_LIB}" | ||||
| . "${RT_PROJECT_ROOT}/_lib/lib.sh" | ||||
| 
 | ||||
| group_init() { | ||||
|     # Group initialisation code goes here | ||||
|     [ -r "${AYA_TMPDIR}" ] && rm -rf "${AYA_TMPDIR}" | ||||
|     mkdir "${AYA_TMPDIR}" | ||||
|     start_vm | ||||
| } | ||||
| 
 | ||||
| group_deinit() { | ||||
|     # Group de-initialisation code goes here | ||||
|     stop_vm | ||||
| } | ||||
| 
 | ||||
| CMD=$1 | ||||
| case $CMD in | ||||
| init) | ||||
|     group_init | ||||
|     res=$? | ||||
|     ;; | ||||
| deinit) | ||||
|     group_deinit | ||||
|     res=$? | ||||
|     ;; | ||||
| *) | ||||
|     res=1 | ||||
|     ;; | ||||
| esac | ||||
| 
 | ||||
| exit $res | ||||
| @ -0,0 +1,6 @@ | ||||
| [build] | ||||
| target-dir = "../../target" | ||||
| target = "bpfel-unknown-none" | ||||
| 
 | ||||
| [unstable] | ||||
| build-std = ["core"] | ||||
| @ -0,0 +1,36 @@ | ||||
| [package] | ||||
| name = "integration-ebpf" | ||||
| version = "0.1.0" | ||||
| edition = "2018" | ||||
| publish = false | ||||
| 
 | ||||
| [dependencies] | ||||
| aya-bpf = { path = "../../bpf/aya-bpf" } | ||||
| 
 | ||||
| [[bin]] | ||||
| name = "map_test" | ||||
| path = "src/map_test.rs" | ||||
| 
 | ||||
| [[bin]] | ||||
| name = "name_test" | ||||
| path = "src/name_test.rs" | ||||
| 
 | ||||
| [[bin]] | ||||
| name = "pass" | ||||
| path = "src/pass.rs" | ||||
| 
 | ||||
| [[bin]] | ||||
| name = "test" | ||||
| path = "src/test.rs" | ||||
| 
 | ||||
| [profile.dev] | ||||
| panic = "abort" | ||||
| opt-level = 2 | ||||
| overflow-checks = false | ||||
| 
 | ||||
| [profile.release] | ||||
| panic = "abort" | ||||
| debug = 2 | ||||
| 
 | ||||
| [workspace] | ||||
| members = [] | ||||
| @ -0,0 +1,2 @@ | ||||
| [toolchain] | ||||
| channel="nightly" | ||||
| @ -0,0 +1 @@ | ||||
| ../../rustfmt.toml | ||||
| @ -1,16 +1,11 @@ | ||||
| //! ```cargo
 | ||||
| //! [dependencies]
 | ||||
| //! aya-bpf = { path = "../../../../bpf/aya-bpf" }
 | ||||
| //! ```
 | ||||
| 
 | ||||
| #![no_std] | ||||
| #![no_main] | ||||
| 
 | ||||
| use aya_bpf::{ | ||||
|     bindings::xdp_action, | ||||
|     macros::{map, xdp}, | ||||
|     programs::XdpContext, | ||||
|     maps::Array, | ||||
|     programs::XdpContext, | ||||
| }; | ||||
| 
 | ||||
| #[map] | ||||
| @ -1,8 +1,3 @@ | ||||
| //! ```cargo
 | ||||
| //! [dependencies]
 | ||||
| //! aya-bpf = { path = "../../../../bpf/aya-bpf" }
 | ||||
| //! ```
 | ||||
| 
 | ||||
| #![no_std] | ||||
| #![no_main] | ||||
| 
 | ||||
| @ -0,0 +1,12 @@ | ||||
| [package] | ||||
| name = "integration-test-macros" | ||||
| version = "0.1.0" | ||||
| edition = "2018" | ||||
| publish = false | ||||
| 
 | ||||
| [dependencies] | ||||
| quote = "1" | ||||
| syn = {version = "1.0", features = ["full"]} | ||||
| 
 | ||||
| [lib] | ||||
| proc-macro = true | ||||
| @ -0,0 +1,19 @@ | ||||
| use proc_macro::TokenStream; | ||||
| use quote::quote; | ||||
| use syn::{parse_macro_input, ItemFn}; | ||||
| 
 | ||||
| #[proc_macro_attribute] | ||||
| pub fn integration_test(_attr: TokenStream, item: TokenStream) -> TokenStream { | ||||
|     let item = parse_macro_input!(item as ItemFn); | ||||
|     let name = &item.sig.ident; | ||||
|     let name_str = &item.sig.ident.to_string(); | ||||
|     let expanded = quote! { | ||||
|         #item | ||||
| 
 | ||||
|         inventory::submit!(IntegrationTest { | ||||
|             name: concat!(module_path!(), "::", #name_str), | ||||
|             test_fn: #name, | ||||
|         }); | ||||
|     }; | ||||
|     TokenStream::from(expanded) | ||||
| } | ||||
| @ -0,0 +1,17 @@ | ||||
| [package] | ||||
| name = "integration-test" | ||||
| version = "0.1.0" | ||||
| edition = "2018" | ||||
| publish = false | ||||
| 
 | ||||
| [dependencies] | ||||
| anyhow = "1" | ||||
| aya = { path = "../../aya" } | ||||
| inventory = "0.2" | ||||
| integration-test-macros = { path = "../integration-test-macros" } | ||||
| lazy_static = "1" | ||||
| libc = { version = "0.2.105" } | ||||
| log = "0.4" | ||||
| object = { version = "0.29", default-features = false, features = ["std", "read_core", "elf"] } | ||||
| regex = "1" | ||||
| simplelog = "0.12" | ||||
| @ -0,0 +1,27 @@ | ||||
| use log::info; | ||||
| use simplelog::{ColorChoice, ConfigBuilder, LevelFilter, TermLogger, TerminalMode}; | ||||
| 
 | ||||
| mod tests; | ||||
| use tests::IntegrationTest; | ||||
| 
 | ||||
| fn main() -> anyhow::Result<()> { | ||||
|     TermLogger::init( | ||||
|         LevelFilter::Debug, | ||||
|         ConfigBuilder::new() | ||||
|             .set_target_level(LevelFilter::Error) | ||||
|             .set_location_level(LevelFilter::Error) | ||||
|             .build(), | ||||
|         TerminalMode::Mixed, | ||||
|         ColorChoice::Auto, | ||||
|     )?; | ||||
| 
 | ||||
|     // Run the tests
 | ||||
|     for t in inventory::iter::<IntegrationTest> { | ||||
|         info!("Running {}", t.name); | ||||
|         if let Err(e) = (t.test_fn)() { | ||||
|             panic!("{}", e) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     Ok(()) | ||||
| } | ||||
| @ -0,0 +1,27 @@ | ||||
| use super::{integration_test, IntegrationTest}; | ||||
| 
 | ||||
| use anyhow::bail; | ||||
| use aya::include_bytes_aligned; | ||||
| use object::{Object, ObjectSymbol}; | ||||
| 
 | ||||
| #[integration_test] | ||||
| fn test_maps() -> anyhow::Result<()> { | ||||
|     let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/map_test"); | ||||
|     let obj_file = object::File::parse(bytes)?; | ||||
|     if obj_file.section_by_name("maps").is_none() { | ||||
|         bail!("No 'maps' ELF section"); | ||||
|     } | ||||
|     let mut found = false; | ||||
|     for sym in obj_file.symbols() { | ||||
|         if let Ok(name) = sym.name() { | ||||
|             if name == "BAR" { | ||||
|                 found = true; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     if !found { | ||||
|         bail!("No symbol 'BAR' in ELF file") | ||||
|     } | ||||
|     Ok(()) | ||||
| } | ||||
| @ -0,0 +1,75 @@ | ||||
| use std::{convert::TryInto, process::Command}; | ||||
| 
 | ||||
| use aya::{ | ||||
|     include_bytes_aligned, | ||||
|     programs::{Xdp, XdpFlags}, | ||||
|     Bpf, | ||||
| }; | ||||
| 
 | ||||
| use super::{integration_test, IntegrationTest}; | ||||
| 
 | ||||
| #[integration_test] | ||||
| fn long_name() -> anyhow::Result<()> { | ||||
|     let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/name_test"); | ||||
|     let mut bpf = Bpf::load(bytes)?; | ||||
|     let name_prog: &mut Xdp = bpf.program_mut("ihaveaverylongname").unwrap().try_into()?; | ||||
|     name_prog.load().unwrap(); | ||||
|     name_prog.attach("lo", XdpFlags::default())?; | ||||
| 
 | ||||
|     // We used to be able to assert with bpftool that the program name was short.
 | ||||
|     // It seem though that it now uses the name from the ELF symbol table instead.
 | ||||
|     // Therefore, as long as we were able to load the program, this is good enough.
 | ||||
| 
 | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| #[integration_test] | ||||
| fn multiple_maps() -> anyhow::Result<()> { | ||||
|     let bytes = | ||||
|         include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/multimap.bpf.o"); | ||||
|     let mut bpf = Bpf::load(bytes)?; | ||||
|     let pass: &mut Xdp = bpf.program_mut("stats").unwrap().try_into().unwrap(); | ||||
|     pass.load().unwrap(); | ||||
|     pass.attach("lo", XdpFlags::default()).unwrap(); | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| fn is_loaded() -> bool { | ||||
|     let output = Command::new("bpftool").args(&["prog"]).output().unwrap(); | ||||
|     let stdout = String::from_utf8(output.stdout).unwrap(); | ||||
|     stdout.contains("test_unload") | ||||
| } | ||||
| 
 | ||||
| fn assert_loaded(loaded: bool) { | ||||
|     let state = is_loaded(); | ||||
|     if state == loaded { | ||||
|         return; | ||||
|     } | ||||
|     panic!("Expected loaded: {} but was loaded: {}", loaded, state); | ||||
| } | ||||
| 
 | ||||
| #[integration_test] | ||||
| fn unload() -> anyhow::Result<()> { | ||||
|     let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/test"); | ||||
|     let mut bpf = Bpf::load(bytes)?; | ||||
|     let prog: &mut Xdp = bpf.program_mut("test_unload").unwrap().try_into().unwrap(); | ||||
|     prog.load().unwrap(); | ||||
|     let link = prog.attach("lo", XdpFlags::default()).unwrap(); | ||||
|     { | ||||
|         let _link_owned = prog.take_link(link); | ||||
|         prog.unload().unwrap(); | ||||
|         assert_loaded(true); | ||||
|     }; | ||||
| 
 | ||||
|     assert_loaded(false); | ||||
|     prog.load().unwrap(); | ||||
| 
 | ||||
|     assert_loaded(true); | ||||
|     prog.attach("lo", XdpFlags::default()).unwrap(); | ||||
| 
 | ||||
|     assert_loaded(true); | ||||
|     prog.unload().unwrap(); | ||||
| 
 | ||||
|     assert_loaded(false); | ||||
|     Ok(()) | ||||
| } | ||||
| @ -0,0 +1,37 @@ | ||||
| use anyhow::bail; | ||||
| use lazy_static::lazy_static; | ||||
| use libc::{uname, utsname}; | ||||
| use regex::Regex; | ||||
| use std::{ffi::CStr, mem}; | ||||
| 
 | ||||
| pub mod elf; | ||||
| pub mod load; | ||||
| pub mod smoke; | ||||
| 
 | ||||
| pub use integration_test_macros::integration_test; | ||||
| #[derive(Debug)] | ||||
| pub struct IntegrationTest { | ||||
|     pub name: &'static str, | ||||
|     pub test_fn: fn() -> anyhow::Result<()>, | ||||
| } | ||||
| 
 | ||||
| pub(crate) fn kernel_version() -> anyhow::Result<(u8, u8, u8)> { | ||||
|     lazy_static! { | ||||
|         static ref RE: Regex = Regex::new(r"^([0-9]+)\.([0-9]+)\.([0-9]+)").unwrap(); | ||||
|     } | ||||
|     let mut data: utsname = unsafe { mem::zeroed() }; | ||||
|     let ret = unsafe { uname(&mut data) }; | ||||
|     assert!(ret >= 0, "libc::uname failed."); | ||||
|     let release_cstr = unsafe { CStr::from_ptr(data.release.as_ptr()) }; | ||||
|     let release = release_cstr.to_string_lossy(); | ||||
|     if let Some(caps) = RE.captures(&release) { | ||||
|         let major = caps.get(1).unwrap().as_str().parse().unwrap(); | ||||
|         let minor = caps.get(2).unwrap().as_str().parse().unwrap(); | ||||
|         let patch = caps.get(3).unwrap().as_str().parse().unwrap(); | ||||
|         Ok((major, minor, patch)) | ||||
|     } else { | ||||
|         bail!("no kernel version found"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| inventory::collect!(IntegrationTest); | ||||
| @ -0,0 +1,45 @@ | ||||
| use std::convert::TryInto; | ||||
| 
 | ||||
| use aya::{ | ||||
|     include_bytes_aligned, | ||||
|     programs::{Extension, Xdp, XdpFlags}, | ||||
|     Bpf, BpfLoader, | ||||
| }; | ||||
| use log::info; | ||||
| 
 | ||||
| use super::{integration_test, kernel_version, IntegrationTest}; | ||||
| 
 | ||||
| #[integration_test] | ||||
| fn xdp() -> anyhow::Result<()> { | ||||
|     let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/pass"); | ||||
|     let mut bpf = Bpf::load(bytes)?; | ||||
|     let dispatcher: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); | ||||
|     dispatcher.load().unwrap(); | ||||
|     dispatcher.attach("lo", XdpFlags::default()).unwrap(); | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| #[integration_test] | ||||
| fn extension() -> anyhow::Result<()> { | ||||
|     let (major, minor, _) = kernel_version()?; | ||||
|     if major < 5 || minor < 9 { | ||||
|         info!( | ||||
|             "skipping as {}.{} does not meet version requirement of 5.9", | ||||
|             major, minor | ||||
|         ); | ||||
|         return Ok(()); | ||||
|     } | ||||
|     // TODO: Check kernel version == 5.9 or later
 | ||||
|     let main_bytes = | ||||
|         include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/main.bpf.o"); | ||||
|     let mut bpf = Bpf::load(main_bytes)?; | ||||
|     let pass: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); | ||||
|     pass.load().unwrap(); | ||||
|     pass.attach("lo", XdpFlags::default()).unwrap(); | ||||
| 
 | ||||
|     let ext_bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/ext.bpf.o"); | ||||
|     let mut bpf = BpfLoader::new().extension("drop").load(ext_bytes).unwrap(); | ||||
|     let drop_: &mut Extension = bpf.program_mut("drop").unwrap().try_into().unwrap(); | ||||
|     drop_.load(pass.fd().unwrap(), "xdp_pass").unwrap(); | ||||
|     Ok(()) | ||||
| } | ||||
| @ -0,0 +1,162 @@ | ||||
| use std::{ | ||||
|     env, fs, | ||||
|     path::{Path, PathBuf}, | ||||
|     process::Command, | ||||
| }; | ||||
| 
 | ||||
| use anyhow::{bail, Context}; | ||||
| use clap::Parser; | ||||
| 
 | ||||
| use crate::utils::WORKSPACE_ROOT; | ||||
| 
 | ||||
| #[derive(Debug, Copy, Clone)] | ||||
| pub enum Architecture { | ||||
|     BpfEl, | ||||
|     BpfEb, | ||||
| } | ||||
| 
 | ||||
| impl std::str::FromStr for Architecture { | ||||
|     type Err = String; | ||||
| 
 | ||||
|     fn from_str(s: &str) -> Result<Self, Self::Err> { | ||||
|         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, | ||||
|     /// Libbpf dir, required for compiling C code
 | ||||
|     #[clap(long, action)] | ||||
|     pub libbpf_dir: PathBuf, | ||||
| } | ||||
| 
 | ||||
| pub fn build_ebpf(opts: Options) -> anyhow::Result<()> { | ||||
|     build_rust_ebpf(&opts)?; | ||||
|     build_c_ebpf(&opts) | ||||
| } | ||||
| 
 | ||||
| fn build_rust_ebpf(opts: &Options) -> anyhow::Result<()> { | ||||
|     let mut dir = PathBuf::from(WORKSPACE_ROOT.to_string()); | ||||
|     dir.push("test/integration-ebpf"); | ||||
| 
 | ||||
|     let target = format!("--target={}", opts.target); | ||||
|     let mut args = vec![ | ||||
|         "+nightly", | ||||
|         "build", | ||||
|         "--verbose", | ||||
|         target.as_str(), | ||||
|         "-Z", | ||||
|         "build-std=core", | ||||
|     ]; | ||||
|     if opts.release { | ||||
|         args.push("--release") | ||||
|     } | ||||
|     let status = Command::new("cargo") | ||||
|         .current_dir(&dir) | ||||
|         .args(&args) | ||||
|         .status() | ||||
|         .expect("failed to build bpf program"); | ||||
|     assert!(status.success()); | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| fn get_libbpf_headers<P: AsRef<Path>>(libbpf_dir: P, include_path: P) -> anyhow::Result<()> { | ||||
|     let dir = include_path.as_ref(); | ||||
|     fs::create_dir_all(&dir)?; | ||||
|     let status = Command::new("make") | ||||
|         .current_dir(libbpf_dir.as_ref().join("src")) | ||||
|         .arg(format!("INCLUDEDIR={}", dir.as_os_str().to_string_lossy())) | ||||
|         .arg("install_headers") | ||||
|         .status() | ||||
|         .expect("failed to build get libbpf headers"); | ||||
|     assert!(status.success()); | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| fn build_c_ebpf(opts: &Options) -> anyhow::Result<()> { | ||||
|     let mut src = PathBuf::from(WORKSPACE_ROOT.to_string()); | ||||
|     src.push("test/integration-ebpf/src/bpf"); | ||||
| 
 | ||||
|     let mut out_path = PathBuf::from(WORKSPACE_ROOT.to_string()); | ||||
|     out_path.push("target"); | ||||
|     out_path.push(opts.target.to_string()); | ||||
|     out_path.push(if opts.release { "release " } else { "debug" }); | ||||
| 
 | ||||
|     let include_path = out_path.join("include"); | ||||
|     get_libbpf_headers(&opts.libbpf_dir, &include_path)?; | ||||
|     let files = fs::read_dir(&src).unwrap(); | ||||
|     for file in files { | ||||
|         let p = file.unwrap().path(); | ||||
|         if let Some(ext) = p.extension() { | ||||
|             if ext == "c" { | ||||
|                 let mut out = PathBuf::from(&out_path); | ||||
|                 out.push(p.file_name().unwrap()); | ||||
|                 out.set_extension("o"); | ||||
|                 compile_with_clang(&p, &out, &include_path)?; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| /// Build eBPF programs with clang and libbpf headers.
 | ||||
| fn compile_with_clang<P: Clone + AsRef<Path>>( | ||||
|     src: P, | ||||
|     out: P, | ||||
|     include_path: P, | ||||
| ) -> anyhow::Result<()> { | ||||
|     let clang = match env::var("CLANG") { | ||||
|         Ok(val) => val, | ||||
|         Err(_) => String::from("/usr/bin/clang"), | ||||
|     }; | ||||
|     let arch = match std::env::consts::ARCH { | ||||
|         "x86_64" => "x86", | ||||
|         "aarch64" => "arm64", | ||||
|         _ => std::env::consts::ARCH, | ||||
|     }; | ||||
|     let mut cmd = Command::new(clang); | ||||
|     cmd.arg(format!("-I{}", include_path.as_ref().to_string_lossy())) | ||||
|         .arg("-g") | ||||
|         .arg("-O2") | ||||
|         .arg("-target") | ||||
|         .arg("bpf") | ||||
|         .arg("-c") | ||||
|         .arg(format!("-D__TARGET_ARCH_{}", arch)) | ||||
|         .arg(src.as_ref().as_os_str()) | ||||
|         .arg("-o") | ||||
|         .arg(out.as_ref().as_os_str()); | ||||
| 
 | ||||
|     let output = cmd.output().context("Failed to execute clang")?; | ||||
|     if !output.status.success() { | ||||
|         bail!( | ||||
|             "Failed to compile eBPF programs\n \ | ||||
|             stdout=\n \ | ||||
|             {}\n \ | ||||
|             stderr=\n \ | ||||
|             {}\n", | ||||
|             String::from_utf8(output.stdout).unwrap(), | ||||
|             String::from_utf8(output.stderr).unwrap() | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     Ok(()) | ||||
| } | ||||
| @ -0,0 +1,29 @@ | ||||
| use clap::Parser; | ||||
| use std::process::Command; | ||||
| 
 | ||||
| use crate::build_ebpf; | ||||
| 
 | ||||
| #[derive(Parser)] | ||||
| pub struct Options { | ||||
|     /// Whether to compile for the musl libc target
 | ||||
|     #[clap(short, long)] | ||||
|     pub musl: bool, | ||||
| 
 | ||||
|     #[clap(flatten)] | ||||
|     pub ebpf_options: build_ebpf::Options, | ||||
| } | ||||
| 
 | ||||
| pub fn build_test(opts: Options) -> anyhow::Result<()> { | ||||
|     build_ebpf::build_ebpf(opts.ebpf_options)?; | ||||
| 
 | ||||
|     let mut args = vec!["build", "-p", "integration-test", "--verbose"]; | ||||
|     if opts.musl { | ||||
|         args.push("--target=x86_64-unknown-linux-musl"); | ||||
|     } | ||||
|     let status = Command::new("cargo") | ||||
|         .args(&args) | ||||
|         .status() | ||||
|         .expect("failed to build bpf program"); | ||||
|     assert!(status.success()); | ||||
|     Ok(()) | ||||
| } | ||||
| @ -0,0 +1,72 @@ | ||||
| use std::{os::unix::process::CommandExt, path::PathBuf, 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, | ||||
|     /// libbpf directory
 | ||||
|     #[clap(long, action)] | ||||
|     pub libbpf_dir: String, | ||||
|     /// Arguments to pass to your application
 | ||||
|     #[clap(name = "args", last = true)] | ||||
|     pub run_args: Vec<String>, | ||||
| } | ||||
| 
 | ||||
| /// Build the project
 | ||||
| fn build(opts: &Options) -> Result<(), anyhow::Error> { | ||||
|     let mut args = vec!["build"]; | ||||
|     if opts.release { | ||||
|         args.push("--release") | ||||
|     } | ||||
|     args.push("--workspace"); | ||||
|     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, | ||||
|         libbpf_dir: PathBuf::from(&opts.libbpf_dir), | ||||
|     }) | ||||
|     .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/{}/integration-test", profile); | ||||
| 
 | ||||
|     // 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(" ")))) | ||||
| } | ||||
| @ -0,0 +1,17 @@ | ||||
| use lazy_static::lazy_static; | ||||
| use serde_json::Value; | ||||
| use std::process::Command; | ||||
| 
 | ||||
| lazy_static! { | ||||
|     pub static ref WORKSPACE_ROOT: String = workspace_root(); | ||||
| } | ||||
| 
 | ||||
| fn workspace_root() -> String { | ||||
|     let output = Command::new("cargo").arg("metadata").output().unwrap(); | ||||
|     if !output.status.success() { | ||||
|         panic!("unable to run cargo metadata") | ||||
|     } | ||||
|     let stdout = String::from_utf8(output.stdout).unwrap(); | ||||
|     let v: Value = serde_json::from_str(&stdout).unwrap(); | ||||
|     v["workspace_root"].as_str().unwrap().to_string() | ||||
| } | ||||
					Loading…
					
					
				
		Reference in New Issue