diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6b8710a --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +.git diff --git a/.github/workflows/default.yml b/.github/workflows/default.yml index 6235d39..a7209c0 100644 --- a/.github/workflows/default.yml +++ b/.github/workflows/default.yml @@ -15,12 +15,12 @@ jobs: # This workflow contains a single job called "build" build: # The type of runner that the job will run on - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install dependencies run: make deps diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..70f39b0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +# Reproducible build/test environment for ebpf-kill-example. +# +# Build: docker build -t ebpf-kill-example . +# Test: docker run --rm --privileged ebpf-kill-example +# +# The eBPF program is loaded into the kernel and attached to a tracepoint at +# test time, so running the tests requires --privileged (or CAP_BPF + +# CAP_PERFMON + CAP_SYS_ADMIN) and a kernel that exposes the kill tracepoint. +FROM ubuntu:24.04 + +# Toolchain: clang/llvm emit the BPF bytecode, libelf + zlib are libbpf's +# link-time dependencies, and build-essential/make build libbpf and the loader. +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + clang \ + llvm \ + libelf-dev \ + zlib1g-dev \ + make \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app +COPY . . + +# Build the vendored libbpf submodule and the example. +RUN make + +# The entrypoint mounts tracefs so the tracepoint is reachable at test time. +RUN chmod +x docker-entrypoint.sh +ENTRYPOINT ["./docker-entrypoint.sh"] +CMD ["make", "test"] diff --git a/Makefile b/Makefile index 0fb38bf..818dd15 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ deps: sudo apt update - sudo apt install -y build-essential git make gcc clang llvm libelf-dev gcc-multilib + sudo apt install -y build-essential git make gcc clang llvm libelf-dev zlib1g-dev gcc-multilib git submodule update --init libbpf: diff --git a/README.md b/README.md index 1654e28..34399ed 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,14 @@ To run this example, the following software is required. - **Linux kernel v4.19+** - **LLVM 10+** - libelf-dev (Installed via *make deps*) +- zlib1g-dev (Installed via *make deps*) - gcc-multilib (Installed via *make deps*) +Alternatively, you can build and run everything with [Docker](#docker), which +needs only Docker installed — no local toolchain. This is the easiest way to +try the example on non-Linux hosts (e.g. macOS) or on architectures where +*gcc-multilib* is unavailable, such as arm64. + ## Installation To install ebpf-kill-example, first clone this repository. @@ -57,6 +63,29 @@ nhed@nhed-1:~/Development/ebpf-kill-example$ make test [ OK ] -- eBPF program ran as expected. ``` +## Docker + +A `Dockerfile` is provided for a reproducible build and test environment that +does not depend on your host setup. This is handy on non-Linux hosts or when +you would rather not install the toolchain locally. + +Build the image: + +``` +docker build -t ebpf-kill-example . +``` + +Build and run the test. The eBPF program is loaded into the kernel and attached +to a tracepoint, so `--privileged` is required (the container mounts tracefs so +the tracepoint is reachable): + +``` +docker run --rm --privileged ebpf-kill-example +``` + +On Docker Desktop (macOS/Windows) the container runs against the Docker VM's +Linux kernel, so a Linux host is not required. + ## Example ![Example](/img/example.png?raw=true) diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100755 index 0000000..c7cfe09 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# The eBPF loader attaches to a kernel tracepoint, which is exposed through +# tracefs. Containers do not mount tracefs by default, so mount it here (this +# requires --privileged). Failure is ignored so non-test uses of the image +# still work without extra privileges. +if [ ! -e /sys/kernel/tracing/events ]; then + mount -t tracefs nodev /sys/kernel/tracing 2>/dev/null || true +fi + +exec "$@" diff --git a/libbpf b/libbpf index fbd60db..f5dcbae 160000 --- a/libbpf +++ b/libbpf @@ -1 +1 @@ -Subproject commit fbd60dbff51c870f5e80a17c4f2fd639eb80af90 +Subproject commit f5dcbae736e5d7f83a35718e01be1a8e3010fa39 diff --git a/src/Makefile b/src/Makefile index 8cb281b..83b37a9 100644 --- a/src/Makefile +++ b/src/Makefile @@ -7,6 +7,12 @@ LDFLAGS ?= -L$(LIBBPF_DIR) LIBS = -lbpf -lelf +# clang's `bpf` target does not search the host's multiarch include directory, +# so kernel/libc headers such as are not found. Point it there +# explicitly. Using $(uname -m) keeps this portable across architectures +# (e.g. x86_64-linux-gnu, aarch64-linux-gnu). +ARCH_INCLUDES = -I/usr/include/$(shell uname -m)-linux-gnu + all: $(TARGET) kern.o .PHONY: clean @@ -14,17 +20,16 @@ all: $(TARGET) kern.o clean: rm -f $(TARGET) rm -f kern.o - rm -f kern.ll $(TARGET): %: user.c Makefile gcc $(CFLAGS) $(LDFLAGS) -o $(TARGET) user.c -Wl,-rpath=$(LIBBPF_DIR) $(LIBS) kern.o: kern.c - clang -S \ + clang \ -target bpf \ -D __BPF_TRACING__ \ $(CFLAGS) \ + $(ARCH_INCLUDES) \ -Wall \ -Werror \ - -O2 -emit-llvm -c -g kern.c - llc -march=bpf -filetype=obj -o kern.o kern.ll + -O2 -g -c kern.c -o kern.o diff --git a/src/user.c b/src/user.c index 7915b03..0335aaf 100644 --- a/src/user.c +++ b/src/user.c @@ -26,7 +26,7 @@ int main(int argc, char **argv) { char path[PATH_MAX]; - sprintf(path, "%s/kern.o", dirname(argv[0])); + snprintf(path, sizeof(path), "%s/kern.o", dirname(argv[0])); struct bpf_object *obj; struct bpf_link *link = NULL; diff --git a/test/test.sh b/test/test.sh index 00f5007..8739550 100755 --- a/test/test.sh +++ b/test/test.sh @@ -14,7 +14,10 @@ set -e [ "$UID" -eq 0 ] || exec sudo bash "$0" "$@" echo "-- Loading eBPF program." -sudo ./src/ebpf-kill-example > /tmp/ebpf-kill.log & +# Already running as root (line 14 re-execs via sudo when needed), so invoke +# the loader directly. This keeps the test working in environments without +# sudo installed, such as containers. +./src/ebpf-kill-example > /tmp/ebpf-kill.log & sleep 5