diff --git a/docs/ebpf_unit_test_zh.md b/docs/ebpf_unit_test_zh.md new file mode 100644 index 000000000..5358f4c4a --- /dev/null +++ b/docs/ebpf_unit_test_zh.md @@ -0,0 +1,240 @@ +# Kmesh eBPF 单元测试框架文档 + +# 1. 框架概述 + +Kmesh eBPF 单元测试框架是一个用于测试 eBPF 内核态程序的工具,支持多种 eBPF 程序类型的单元测试。该框架基于 Go 语言的单元测试框架,能够独立运行单个 eBPF 程序的测试,而无需加载整个 Kmesh 系统,从而提高测试效率和覆盖率。 + +# 2. 目录结构 + +测试框架的目录结构如下: + +``` +test/bpf_ut/ +├── bpftest/ # Go 语言实现的单元测试框架 +│ ├── bpf_test.go # 测试框架核心逻辑以及辅助函数 +│ ├── trf.pb.go # 由 trf.proto 生成的 Go 代码 +│ ├── trf.proto # 测试结果格式定义 +│ ├── general_test.go # bpf/kmesh/general相关测试用例以及辅助函数 +│ └── workload_test.go # bpf/kmesh/workload相关测试用例以及辅助函数 +├── include/ # 测试相关头文件 +│ ├── ut_common.h # 通用测试宏和函数 +│ ├── xdp_common.h # XDP 测试相关函数和宏定义 +│ └── tc_common.h # TC 测试相关函数和宏定义 +├── Makefile # 构建和运行测试的脚本 +└── *_test.c # 测试文件,如 xdp_authz_offload_test.c +``` + +# 3. 核心组件 + +## 3.1 Go 语言实现的单元测试框架 (`bpftest/`) + +Go 语言实现的单元测试框架负责加载和执行 eBPF 程序,捕获测试结果并生成报告: + +- `bpf_test.go`:框架的核心组件,包含两种主要测试类型: + - **`unitTest_BPF_PROG_TEST_RUN`**:基于 `BPF_PROG_TEST_RUN` 机制,适用于 [BPF_PROG_TEST_RUN 文档](https://docs.ebpf.io/linux/syscall/BPF_PROG_TEST_RUN/)列出的支持测试 eBPF 程序类型。 + - **`unitTest_BUILD_CONTEXT`**:针对 `BPF_PROG_TEST_RUN` 不支持的程序类型,需要在 Go 代码中构造自定义上下文,通过 `cilium/ebpf` 加载、执行并验证 eBPF 程序的行为。 + - **`unitTests_*`**:针对每一个 `*_test.c` 测试文件,维护多个具体的测试项。 +- `trf.proto`:定义测试结果和测试过程日志格式的 Protocol Buffers 文件。 + +## 3.2 测试头文件 (`include/`) + +- `ut_common.h`:提供测试宏和辅助函数,支持测试初始化、断言、日志记录等功能。 +- `xdp_common.h`:提供 XDP 程序测试相关的辅助函数,如构建和验证 XDP 数据包。 +- `tc_common.h`:提供 TC 程序测试相关的辅助函数,如构建和验证 TC 数据包。 + +## 3.3 测试文件 (`*_test.c`) + +- 测试文件的核心在于引入单元测试相关的头文件(如 `ut_common.h`、`xdp_common.h` 和 `tc_common.h`);mock 需要 mock 的下游函数;直接 include 需要测试的 eBPF 程序;编写内核态的测试逻辑。 +- 对于 `unittest_BPF_PROG_TEST_RUN` 类型的测试,一个测试项通常包含以下部分: + - mock:mock 需要 mock 的下游函数。 + - include:直接 include 需要测试的 eBPF 程序。 + - tail_call:使用 `tail_call` 机制调用被测试的 eBPF 程序。 + - PKTGEN:生成测试数据包的函数,使用 `PKTGEN` 宏定义。 + - JUMP:调用被测试的 eBPF 程序的函数,使用 `JUMP` 宏定义。 + - CHECK:验证测试结果的函数,使用 `CHECK` 宏定义。 +- 对于 `unitTest_BUILD_CONTEXT` 类型的测试,测试项通常包含以下部分: + - mock:mock 需要 mock 的下游函数。 + - include:直接 include 需要测试的 eBPF 程序。 + +## 3.4 Makefile + +`Makefile` 负责使用 clang 编译 `*_test.c` 文件并生成相应的 `*_test.o` 文件,随后在执行时通过 Go 测试框架加载这些对象文件来进行测试。 + +# 4. 测试框架工作原理 + +## 4.1 `unitTest_BPF_PROG_TEST_RUN` + +该测试类型基于内核提供的 `BPF_PROG_TEST_RUN` 机制,用于直接验证支持 `BPF_PROG_TEST_RUN` 的 eBPF 程序。通过内核态快速调用,可方便地测试针对 XDP、TC 等常见程序类型的逻辑。 +对应的用户态 Go 测试代码会加载生成的 `.o` 文件,遍历被测程序并执行测试。对于每个测试项,还可在 `setupInUserSpace` 函数中做额外的初始化(如设置全局配置或更新 eBPF Map)。 + +## 4.2 `unitTest_BUILD_CONTEXT` + +当需要测试不支持 `BPF_PROG_TEST_RUN` 的 eBPF 程序类型时,可使用 `unitTest_BUILD_CONTEXT`。这种测试模式需要在用户态自行构造上下文并载入 eBPF 对象文件,完成特殊场景下的验证。 +对应的用户态 Go 测试代码在 `workFunc` 中执行实际测试逻辑,如挂载 cgroup、加载并附加 sockops 程序,然后通过各种方式(如向 TCP 服务器发起连接)触发 eBPF 程序运行并进行验证。 + +## 4.3 用户态 Go 测试代码 + +无论采用哪种测试类型,都会在用户态利用 Go 测试框架对编译得到的 eBPF 程序进行加载、执行与结果校验。`bpf_test.go` 中定义了各类工具函数,如: +- `loadAndRunSpec`:载入并初始化 `.o` 文件中的程序、Map。 +- `startLogReader`:读取 eBPF Map 中的 ringbuf 或日志输出。 +- `registerTailCall`:为特定测试场景注册 tail call。 +这样可以结合内核测试程序与用户态测试逻辑,为 eBPF 程序提供更完善的验证能力。 + +# 5. 编写测试 + +## 5.1 `unitTest_BPF_PROG_TEST_RUN` + +对于使用 `BPF_PROG_TEST_RUN` 机制的 eBPF 程序,测试文件通常包含 mock、include、tail_call、PKTGEN、JUMP、CHECK 等部分。 + +eBPF 测试文件通常遵循以下结构: + +```c +// sample_test.c +#include "ut_common.h" +#include "xdp_common.h" + +// 1. 定义必要的 eBPF 映射和常量 + +// 2. 实现 PKTGEN 函数(生成测试数据包) +PKTGEN("program_type", "test_name") +int test_pktgen(struct xdp_md *ctx) +{ + // 设置测试数据 + return build_xdp_packet(...); +} + +// 3. 实现 JUMP 函数(调用被测试的 eBPF 程序) +JUMP("program_type", "test_name") +int test_jump(struct xdp_md *ctx) +{ + // 调用被测试的 eBPF 程序 + bpf_tail_call(...); + return TEST_ERROR; +} + +// 4. 实现 CHECK 函数(验证测试结果) +CHECK("program_type", "test_name") +int test_check(const struct xdp_md *ctx) +{ + // 验证测试结果 + test_init(); + check_xdp_packet(...); + test_finish(); +} +``` + +用户态 Go 代码需要在 `bpf_test.go` 中实现对应的测试逻辑。以下是一个简单的示例: + +```go +func test(t *testing.T) { + tests := []unitTests_BPF_PROG_TEST_RUN{ + { + objFilename: "sample_test.o", + uts: []unitTest_BPF_PROG_TEST_RUN{ + { + name: "test_name", // 需要与 C 代码中的测试名称一致 + setupInUserSpace: func(t *testing.T, coll *ebpf.Collection) {}, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.objFilename, tt.run()) + } +} +``` + +## 5.2 `unitTest_BUILD_CONTEXT` + +当需要测试不支持 `BPF_PROG_TEST_RUN` 的 eBPF 程序时,可使用 `unitTest_BUILD_CONTEXT`。在这种模式下,用户态测试会主动在 `workFunc` 中挂载 cgroup 并加载 eBPF 对象进行验证。例如: + +```c +// workload_sockops_test.c +#include +#include +#include "bpf_log.h" +#include "bpf_common.h" + +// mock bpf_sk_storage_get +struct sock_storage_data mock_storage = { + .via_waypoint = 1, +}; + +static void *mock_bpf_sk_storage_get(void *map, void *sk, void *value, __u64 flags) +{ + void *storage = NULL; + storage = bpf_sk_storage_get(map, sk, value, flags); + if (!storage && map == &map_of_sock_storage) { + storage = &mock_storage; + } + return storage; +} + +#define bpf_sk_storage_get mock_bpf_sk_storage_get + +// 直接 include 需要测试的 eBPF 程序 +#include "workload/sockops.c" +``` + +对应的 Go 测试逻辑可能在 `workload_test.go` 里,通过附加到 cgroup 并建立 TCP 连接来触发 sockops: + +```go +func TestWorkloadSockOps(t *testing.T) { + tests := []unitTests_BUILD_CONTEXT{ + { + objFilename: "workload_sockops_test.o", + uts: []unitTest_BUILD_CONTEXT{ + { + name: "sample_test", + workFunc: func(t *testing.T, cgroupPath, objFilePath string) { + // 加载 ebpf 内核态程序 + coll, lk := load_bpf_2_cgroup(t, objFilePath, cgroupPath) + defer coll.Close() + defer lk.Close() + + // 触发连接操作,检查 bpf_map 中记录结果 + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.objFilename, tt.run()) + } +} +``` + +## 5.3 测试宏 + +框架提供了多种测试宏简化测试编写: + +- `test_log(fmt, ...)`:记录测试日志。 +- `assert(cond)`:断言条件为真,否则测试失败。 +- `test_fail() / test_fail_now()`:标记测试失败。 +- `test_skip() / test_skip_now()`:跳过当前测试。 + +## 5.4 测试辅助宏 + +对于 XDP 程序测试,框架提供了专门的辅助宏: + +- `build_xdp_packet`:构建 XDP 测试数据包。 +- `check_xdp_packet`:验证 XDP 数据包处理结果。 + +对于 TC 程序测试,框架提供了专门的辅助宏: + +- `build_tc_packet`:构建 TC 测试数据包。 +- `check_tc_packet`:验证 TC 数据包处理结果。 + +# 6. 运行测试 + +测试可以通过 `Makefile` 中定义的命令运行: + +```bash +cd kmesh +make ebpf_unit_test +``` + +可以使用以下参数控制测试执行: + +- `V=1`:启用详细测试输出 \ No newline at end of file diff --git a/test/unittest/workload/Makefile b/test/unittest/workload/Makefile deleted file mode 100644 index 1b1595df7..000000000 --- a/test/unittest/workload/Makefile +++ /dev/null @@ -1,27 +0,0 @@ -CFLAGS += -g3 -Wall -fPIC -I. -D__TARGET_ARCH_x86_64 -D__x86_64__ -D_GNU_SOURCE -CFLAGS += -isystem /usr/include -LIBS = bpf - -WORKLOAD_DIR = ../../../bpf/kmesh/workload -TEST_DIR = . -INCLUDE_DIR = $(WORKLOAD_DIR)/../../include - -FLAGS += -I$(WORKLOAD_DIR) -I../../../bpf/kmesh/workload/include/ctx -I../../../bpf/kmesh/workload/include/ -I../../../bpf/probes/ -I$(INCLUDE_DIR) - -all: xdp_test - -xdp_test: $(TEST_DIR)/xdp.bpf.o $(TEST_DIR)/xdp_test.skel.h - gcc $(CFLAGS) $(FLAGS) -include /usr/include/errno.h -o $(TEST_DIR)/xdp_test $(TEST_DIR)/xdp_test.c -l$(LIBS) - -$(TEST_DIR)/xdp.bpf.o: $(WORKLOAD_DIR)/xdp.c - clang -target bpf $(CFLAGS) $(FLAGS) -O2 -g -c $< -o $@ - -$(TEST_DIR)/xdp_test.skel.h: $(TEST_DIR)/xdp.bpf.o - bpftool gen skeleton $< > $@ - -.PHONY: clean -clean: - rm -rf $(TEST_DIR)/xdp.bpf.o - rm -rf $(TEST_DIR)/xdp_test.skel.h - rm -rf $(TEST_DIR)/xdp_test - rm -rf preprocessed_output.c \ No newline at end of file diff --git a/test/unittest/workload/common.h b/test/unittest/workload/common.h deleted file mode 100644 index 83385a5ac..000000000 --- a/test/unittest/workload/common.h +++ /dev/null @@ -1,160 +0,0 @@ -#ifndef __TEST_COMMON_H -#define __TEST_COMMON_H - -#include -#include -#include -#include -#include -#include -// Test result codes -#define TEST_PASS 0 -#define TEST_FAIL 1 -#define TEST_SKIP 2 -#define TEST_ERROR 3 - -// Coverage related paths -#define COVERAGE_PROG_PIN_DIR "/sys/fs/bpf/prog" -#define COVERAGE_MAP_PIN_DIR "/sys/fs/bpf/map" -#define COVERAGE_BLOCK_LIST "/tmp/block.list" -#define COVERAGE_OUTPUT "/tmp/coverage.html" - -#define MAX_SUBTESTS 64 - -typedef enum { - TEST_STATUS_NONE = 0, - TEST_STATUS_RUNNING, - TEST_STATUS_COMPLETED, - TEST_STATUS_FAILED, - TEST_STATUS_SKIPPED -} test_status_t; - -typedef struct { - const char *name; - test_status_t status; - int result; - const char *message; - double duration; // Test execution time in seconds -} test_context_t; - -// Global test state -typedef struct { - const char *suite_name; - test_context_t subtests[MAX_SUBTESTS]; - int subtest_count; - int passed_count; - int failed_count; - int skipped_count; -} test_suite_t; - -static test_suite_t current_suite = {0}; -static test_context_t *current_test_ctx = NULL; - -static inline void test_init(const char *test_name) -{ - printf("\n=== Starting test suite: %s ===\n", test_name); - current_suite.suite_name = test_name; - current_suite.subtest_count = 0; - current_suite.passed_count = 0; - current_suite.failed_count = 0; - current_suite.skipped_count = 0; -} - -static inline void test_finish(void) -{ - printf("\n=== Test suite summary: %s ===\n", current_suite.suite_name); - printf("Total tests: %d\n", current_suite.subtest_count); - printf(" Passed: %d\n", current_suite.passed_count); - printf(" Failed: %d\n", current_suite.failed_count); - printf(" Skipped: %d\n", current_suite.skipped_count); - - if (current_suite.subtest_count > 0) { - printf("\nDetailed results:\n"); - for (int i = 0; i < current_suite.subtest_count; i++) { - test_context_t *test = ¤t_suite.subtests[i]; - const char *status_str = test->result == TEST_PASS ? "PASS" : - test->result == TEST_SKIP ? "SKIP" : - test->result == TEST_FAIL ? "FAIL" : - "ERROR"; - - printf(" %s: %s (%.3fs)", test->name, status_str, test->duration); - if (test->message) { - printf(" - %s", test->message); - } - printf("\n"); - } - } - - printf("\n=== Final result: %s ===\n\n", current_suite.failed_count > 0 ? "FAILED" : "PASSED"); -} - -#define TEST(test_name, fn) \ - do { \ - const char *_test_name = test_name; \ - if (current_suite.subtest_count >= MAX_SUBTESTS) { \ - printf("ERROR: Too many subtests\n"); \ - break; \ - } \ - test_context_t *_test_ctx = ¤t_suite.subtests[current_suite.subtest_count++]; \ - _test_ctx->name = _test_name; \ - _test_ctx->status = TEST_STATUS_RUNNING; \ - _test_ctx->result = TEST_PASS; \ - _test_ctx->message = NULL; \ - current_test_ctx = _test_ctx; /* Set current test context */ \ - struct timespec _start_time, _end_time; \ - clock_gettime(CLOCK_MONOTONIC, &_start_time); \ - test_log("\n--- Starting test: %s ---", _test_name); \ - fn(); \ - clock_gettime(CLOCK_MONOTONIC, &_end_time); \ - _test_ctx->duration = \ - (_end_time.tv_sec - _start_time.tv_sec) + (_end_time.tv_nsec - _start_time.tv_nsec) / 1e9; \ - _test_ctx->status = TEST_STATUS_COMPLETED; \ - switch (_test_ctx->result) { \ - case TEST_PASS: \ - current_suite.passed_count++; \ - break; \ - case TEST_FAIL: \ - current_suite.failed_count++; \ - break; \ - case TEST_SKIP: \ - current_suite.skipped_count++; \ - break; \ - } \ - test_log( \ - "--- Test %s: %s ---\n", \ - _test_ctx->name, \ - _test_ctx->result == TEST_PASS ? "PASSED" : \ - _test_ctx->result == TEST_SKIP ? "SKIPPED" : \ - "FAILED"); \ - current_test_ctx = NULL; /* Clear current test context */ \ - } while (0) - -#define SKIP_SUB_TEST(msg) \ - do { \ - test_log("Skipping test: %s", msg); \ - current_test_ctx->result = TEST_SKIP; \ - current_test_ctx->message = msg; \ - break; \ - } while (0) - -#define test_assert(cond, msg) \ - do { \ - if (!(cond)) { \ - test_log("Assert failed: %s", msg); \ - test_log("At %s:%d", __FILE__, __LINE__); \ - if (current_test_ctx) { \ - current_test_ctx->result = TEST_FAIL; \ - current_test_ctx->message = msg; \ - } \ - return; \ - } \ - } while (0) - -// Test logging -#define test_log(fmt, ...) printf(fmt "\n", ##__VA_ARGS__) - -// Test setup/teardown helpers -typedef void (*test_setup_fn)(void); -typedef void (*test_teardown_fn)(void); - -#endif /* __TEST_COMMON_H */ \ No newline at end of file diff --git a/test/unittest/workload/run_tests.sh b/test/unittest/workload/run_tests.sh deleted file mode 100644 index 62de9d6cf..000000000 --- a/test/unittest/workload/run_tests.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/bin/bash - -# Exit on error -set -e - -# Ensure running as root -if [ "$EUID" -ne 0 ]; then - echo "Please run as root" - exit 1 -fi - -# Set memory limits -ulimit -l unlimited - -# Create necessary directories -mkdir -p /sys/fs/bpf/prog -mkdir -p /sys/fs/bpf/map -mkdir -p /tmp/coverage - -# Define paths -ELF_FILE="xdp.bpf.o" -PROG_PIN_DIR="/sys/fs/bpf/prog" -MAP_PIN_DIR="/sys/fs/bpf/map" -BLOCK_LIST="/tmp/coverage/block.list" -COVERAGE_OUTPUT="/tmp/coverage/coverage.html" - -# Clean up old pins -rm -rf ${PROG_PIN_DIR}/* ${MAP_PIN_DIR}/* || true - -# Clean and rebuild -echo "Cleaning and rebuilding..." -make clean -make - -# 1. Load and instrument BPF program -echo "Loading and instrumenting BPF program..." -coverbee load \ - --elf=${ELF_FILE} \ - --prog-pin-dir=${PROG_PIN_DIR} \ - --map-pin-dir=${MAP_PIN_DIR} \ - --block-list=${BLOCK_LIST} \ - --log=/tmp/coverage/coverbee.log - -# 2. Run tests -echo "Running tests..." -./xdp_test - -# 3. Collect coverage data -echo "Collecting coverage data..." -coverbee cover \ - --map-pin-dir=${MAP_PIN_DIR} \ - --block-list=${BLOCK_LIST} \ - --output=${COVERAGE_OUTPUT} \ - --format=html - -# 4. Cleanup -echo "Cleaning up..." -rm -rf ${PROG_PIN_DIR}/* -rm -rf ${MAP_PIN_DIR}/* - -echo "Coverage report generated at: ${COVERAGE_OUTPUT}" diff --git a/test/unittest/workload/xdp_test.c b/test/unittest/workload/xdp_test.c deleted file mode 100644 index 104d4cd6d..000000000 --- a/test/unittest/workload/xdp_test.c +++ /dev/null @@ -1,310 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "xdp_test.skel.h" -#include - -#include "common.h" - -// Add missing includes and definitions -#include -#include -#include -#include - -#define XDP_PACKET_HEADROOM 256 -#define SKB_DATA_ALIGN(len) (((len) + (64 - 1)) & ~(64 - 1)) -#define AUTH_PASS 0 -#define AUTH_FORBID 1 - -// Define skb_shared_info structure (simplified version) -struct skb_shared_info { - unsigned char pad[256]; // Simplified version -}; - -// Define xdp_buff structure -struct xdp_buff { - void *data; - void *data_end; - void *data_meta; - void *data_hard_start; - unsigned int frame_sz; -}; - -#define PACKET_SIZE 100 - -struct xdp_bpf *skel; -int prog_fd; - -static int run_xdp_test(void *packet, size_t size) -{ - // Constants from kernel implementation - unsigned int headroom = XDP_PACKET_HEADROOM; // 256 - unsigned int tailroom = SKB_DATA_ALIGN(sizeof(struct skb_shared_info)); - unsigned int max_data_sz = 4096 - headroom - tailroom; - - if (size > max_data_sz) { - test_log("Packet size too large: %zu > %u", size, max_data_sz); - return -1; - } - - // Allocate memory for the full frame - void *data; - size_t frame_size = headroom + size + tailroom; - if (posix_memalign(&data, getpagesize(), frame_size) != 0) { - test_log("Failed to allocate memory for test data"); - return -1; - } - - // Initialize the memory to zero - memset(data, 0, frame_size); - - // Copy packet data to the correct location (after headroom) - memcpy(data + headroom, packet, size); - struct xdp_buff xdp = { - .data_hard_start = data, - .data = data + headroom, - .data_meta = data + headroom, - .data_end = data + headroom + size, - .frame_sz = frame_size, - }; - - test_log("Debug info:"); - test_log(" frame_size: %zu", frame_size); - test_log(" headroom: %u", headroom); - test_log(" data_size: %zu", size); - test_log(" tailroom: %u", tailroom); - test_log(" hard_start: %p", xdp.data_hard_start); - test_log(" data: %p", xdp.data); - test_log(" data_end: %p", xdp.data_end); - test_log(" frame_sz: %u", xdp.frame_sz); - - struct bpf_test_run_opts opts = { - .sz = sizeof(struct bpf_test_run_opts), - .data_in = data + headroom, - .data_out = data + headroom, - .data_size_in = size, - .data_size_out = size, - // XDP programs don't use ctx_in/ctx_out according to kernel code - .ctx_in = NULL, - .ctx_out = NULL, - .ctx_size_in = 0, - .ctx_size_out = 0, - .repeat = 1, - }; - - int ret = bpf_prog_test_run_opts(prog_fd, &opts); - if (ret != 0) { - test_log("bpf_prog_test_run_opts failed: %d (errno: %d - %s)", ret, errno, strerror(errno)); - test_log("Data size: %u, Frame size: %zu", opts.data_size_in, frame_size); - test_log("Prog FD: %d", prog_fd); - test_log("Retval: %d", opts.retval); - } else { - test_log("Test run successful, retval: %d", opts.retval); - } - - free(data); - memcpy(packet, opts.data_out, opts.data_size_out); - return ret; -} - -void bpf_offload() -{ - xdp_bpf__destroy(skel); -} - -void test_packet_parsing() -{ - unsigned char packet[PACKET_SIZE] = {0}; - struct ethhdr *eth = (struct ethhdr *)packet; - struct iphdr *ip = (struct iphdr *)(packet + sizeof(struct ethhdr)); - struct tcphdr *tcp = (struct tcphdr *)(packet + sizeof(struct ethhdr) + sizeof(struct iphdr)); - - // Set minimum packet size - size_t min_size = sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct tcphdr); - test_assert(PACKET_SIZE >= min_size, "PACKET_SIZE too small"); - - // Fill in packet headers - eth->h_proto = htons(ETH_P_IP); - memset(eth->h_source, 0x12, ETH_ALEN); - memset(eth->h_dest, 0x34, ETH_ALEN); - - ip->version = 4; - ip->ihl = 5; - ip->protocol = IPPROTO_TCP; - ip->tot_len = htons(sizeof(struct iphdr) + sizeof(struct tcphdr)); - ip->daddr = htonl(0x08080808); // 8.8.8.8 - ip->saddr = htonl(0x0A000001); // 10.0.0.1 - - tcp->source = htons(12345); - tcp->dest = htons(80); - tcp->doff = 5; // 20 bytes TCP header - - test_log("Packet size: %u", PACKET_SIZE); - test_log("Headers size: %u", min_size); - - int err = run_xdp_test(packet, PACKET_SIZE); - test_log("err = %d", err); - test_assert(err == 0, "run_xdp_test failed"); -} - -void bpf_load() -{ - int err; - - skel = xdp_bpf__open(); - test_assert(skel != NULL, "Failed to open BPF skeleton"); - - // Set XDP program type - bpf_program__set_type(skel->progs.xdp_shutdown, BPF_PROG_TYPE_XDP); - - err = xdp_bpf__load(skel); - test_assert(err == 0, "Failed to load BPF skeleton"); - - prog_fd = bpf_program__fd(skel->progs.xdp_shutdown); - test_assert(prog_fd >= 0, "Failed to get program FD"); - - // Get program type and expected test parameters - struct bpf_prog_info info = {}; - __u32 info_len = sizeof(info); - err = bpf_obj_get_info_by_fd(prog_fd, &info, &info_len); - test_assert(err == 0, "Failed to get program info"); - - test_log("Successfully loaded XDP program, prog_fd = %d, type = %u", prog_fd, info.type); -} - -void test_ip_version_check() -{ - unsigned char packet[PACKET_SIZE] = {0}; - struct ethhdr *eth = (struct ethhdr *)packet; - struct iphdr *ip = (struct iphdr *)(packet + sizeof(struct ethhdr)); - - eth->h_proto = htons(ETH_P_IP); - ip->version = 5; // Invalid IP version - - int err = run_xdp_test(packet, PACKET_SIZE); - test_assert(err == 0, "run_xdp_test failed"); -} - -void test_tuple_extraction() -{ - unsigned char packet[PACKET_SIZE] = {0}; - struct ethhdr *eth = (struct ethhdr *)packet; - struct iphdr *ip = (struct iphdr *)(packet + sizeof(struct ethhdr)); - struct tcphdr *tcp = (struct tcphdr *)(packet + sizeof(struct ethhdr) + sizeof(struct iphdr)); - - eth->h_proto = htons(ETH_P_IP); - ip->version = 4; - ip->ihl = 5; - ip->protocol = IPPROTO_TCP; - ip->saddr = inet_addr("192.168.1.1"); - ip->daddr = inet_addr("192.168.1.2"); - tcp->source = htons(12345); - tcp->dest = htons(80); - - int err = run_xdp_test(packet, PACKET_SIZE); - test_assert(err == 0, "run_xdp_test failed"); -} - -void test_connection_shutdown() -{ - unsigned char packet[PACKET_SIZE] = {0}; - struct ethhdr *eth = (struct ethhdr *)packet; - struct iphdr *ip = (struct iphdr *)(packet + sizeof(struct ethhdr)); - struct tcphdr *tcp = (struct tcphdr *)(packet + sizeof(struct ethhdr) + sizeof(struct iphdr)); - - // Set minimum packet size - size_t min_size = sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct tcphdr); - test_assert(PACKET_SIZE >= min_size, "PACKET_SIZE too small"); - - // Fill in ethernet header - eth->h_proto = htons(ETH_P_IP); - memset(eth->h_source, 0x12, ETH_ALEN); - memset(eth->h_dest, 0x34, ETH_ALEN); - - // Fill in IP header - ip->version = 4; - ip->ihl = 5; - ip->protocol = IPPROTO_TCP; - ip->tot_len = htons(sizeof(struct iphdr) + sizeof(struct tcphdr)); - ip->saddr = inet_addr("192.168.1.1"); - ip->daddr = inet_addr("192.168.1.2"); - - // Fill in TCP header - tcp->source = htons(12345); - tcp->dest = htons(80); - tcp->doff = 5; // 20 bytes TCP header - tcp->syn = 1; // SYN packet - tcp->rst = 0; // Make sure RST is not set initially - - // Add the connection to the auth map to simulate a connection that should be shut down - struct bpf_sock_tuple tuple = { - .ipv4 = - { - .saddr = ip->saddr, - .daddr = ip->daddr, - .sport = tcp->source, - .dport = tcp->dest, - }, - }; - __u32 value = AUTH_FORBID; // Use AUTH_FORBID value - - // Update the map - int err = bpf_map_update_elem(bpf_map__fd(skel->maps.map_of_auth_result), &tuple, &value, BPF_ANY); - test_assert(err == 0, "Failed to update map"); - - // Log test details - test_log("Testing connection shutdown:"); - test_log(" Source IP: %s", inet_ntoa((struct in_addr){.s_addr = ip->saddr})); - test_log(" Dest IP: %s", inet_ntoa((struct in_addr){.s_addr = ip->daddr})); - test_log(" Source Port: %d", ntohs(tcp->source)); - test_log(" Dest Port: %d", ntohs(tcp->dest)); - test_log(" Map value: %u", value); - - // Run the XDP program - int err1 = run_xdp_test(packet, PACKET_SIZE); - test_assert(err1 == 0, "XDP test failed"); - - // Check if the packet was modified correctly - struct tcphdr *modified_tcp = (struct tcphdr *)(packet + sizeof(struct ethhdr) + sizeof(struct iphdr)); - test_log("TCP flags after XDP:"); - test_log(" RST: %d", modified_tcp->rst); - test_log(" SYN: %d", modified_tcp->syn); - test_log(" FIN: %d", modified_tcp->fin); - test_log(" PSH: %d", modified_tcp->psh); - test_log(" ACK: %d", modified_tcp->ack); - - // Verify that RST flag was set - test_assert(modified_tcp->rst == 1, "RST flag not set"); - test_assert(modified_tcp->syn == 0, "SYN flag not cleared"); // SYN should be cleared - test_assert(modified_tcp->fin == 0, "FIN flag not cleared"); // FIN should be cleared - test_assert(modified_tcp->psh == 0, "PSH flag not cleared"); // PSH should be cleared - test_assert(modified_tcp->ack == 0, "ACK flag not cleared"); // ACK should be cleared - - // Clean up the map entry - bpf_map_delete_elem(bpf_map__fd(skel->maps.map_of_auth_result), &tuple); -} - -int main() -{ - test_init("xdp_test"); - - TEST("BPF Program Load", bpf_load); - TEST("Packet Parsing", test_packet_parsing); - TEST("IP Version Check", test_ip_version_check); - TEST("Tuple Extraction", test_tuple_extraction); - TEST("Connection Shutdown", test_connection_shutdown); - TEST("BPF Program Cleanup", bpf_offload); - - test_finish(); - return current_suite.failed_count > 0 ? 1 : 0; -} \ No newline at end of file