Skip to content

Commit 37d54f9

Browse files
bpf: test: introduce test infra for bpf
Signed-off-by: saiaunghlyanhtet <saiaunghlyanhtet2003@gmail.com>
1 parent e05e538 commit 37d54f9

7 files changed

Lines changed: 958 additions & 1 deletion

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,4 @@ jobs:
142142
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
143143

144144
- name: Run tests
145-
run: cargo test --verbose
145+
run: cargo test --bins --verbose

Makefile

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Firebee Makefile
2+
3+
.PHONY: all build clean test run_bpf_tests help
4+
5+
# Default target
6+
all: build
7+
8+
# Build the project
9+
build:
10+
@echo "Building Firebee..."
11+
cargo build --release
12+
cargo libbpf build
13+
14+
# Clean build artifacts
15+
clean:
16+
@echo "Cleaning build artifacts..."
17+
cargo clean
18+
rm -f src/bpf/*.skel.rs
19+
20+
# Run userspace tests
21+
test:
22+
@echo "Running userspace tests..."
23+
cargo test --bins
24+
25+
# Run BPF helper function tests (kernel-side, Cilium-style)
26+
run_bpf_tests:
27+
@echo "Firebee BPF Kernel-Side Test Suite"
28+
@echo "========================================================================"
29+
@echo "Test framework: Cilium-style CHECK/TEST macros"
30+
@echo "Location: src/bpf/firebee_test.bpf.c"
31+
@echo ""
32+
@echo "Executing kernel-side BPF tests (requires root/CAP_BPF)..."
33+
@echo ""
34+
@sudo -E cargo test --test bpf_tests -- --ignored --nocapture || \
35+
(echo "" && \
36+
echo "Note: Tests require root privileges to load BPF programs" && \
37+
echo "Run with: sudo make run_bpf_tests" && \
38+
exit 1)
39+
@echo ""
40+
@echo "========================================================================"
41+
42+
# Run all tests (userspace + BPF helper tests)
43+
test_all:
44+
@echo "========================================================================"
45+
@echo "Running all tests (userspace + BPF kernel tests)"
46+
@echo "========================================================================"
47+
@echo ""
48+
@echo "[1/2] Running userspace tests..."
49+
@cargo test
50+
@echo ""
51+
@echo "========================================================================"
52+
@echo "[2/2] Running BPF kernel-side tests..."
53+
@echo "========================================================================"
54+
@echo ""
55+
@sudo -E cargo test --test bpf_tests -- --ignored --nocapture || \
56+
(echo "" && \
57+
echo "Note: BPF tests require root privileges to load BPF programs" && \
58+
echo "Run with: sudo make test_all" && \
59+
exit 1)
60+
@echo ""
61+
@echo "========================================================================"
62+
@echo "All tests completed successfully!"
63+
@echo "========================================================================"
64+
65+
# Check code formatting
66+
fmt:
67+
@echo "Checking code formatting..."
68+
cargo fmt -- --check
69+
70+
# Format code
71+
fmt_fix:
72+
@echo "Formatting code..."
73+
cargo fmt
74+
75+
# Run clippy lints
76+
clippy:
77+
@echo "Running clippy..."
78+
cargo clippy -- -D warnings
79+
80+
81+
# Display help
82+
help:
83+
@echo "Firebee Makefile targets:"
84+
@echo ""
85+
@echo " make build - Build the project in release mode"
86+
@echo " make clean - Clean build artifacts"
87+
@echo " make test - Run userspace tests (lib + bins)"
88+
@echo " make run_bpf_tests - Run BPF helper function tests (requires sudo)"
89+
@echo " make test_all - Run all tests: userspace + BPF (requires sudo)"
90+
@echo " make fmt - Check code formatting"
91+
@echo " make fmt_fix - Format code"
92+
@echo " make clippy - Run clippy lints"
93+
@echo " make help - Display this help message"
94+
@echo ""
95+
@echo "Test categories:"
96+
@echo " - userspace tests: Regular Rust library and binary tests"
97+
@echo " - BPF tests: Tests for BPF helper functions (IP/port matching, etc.)"

build.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use libbpf_cargo::SkeletonBuilder;
1010

1111
const SRC_INGRESS: &str = "src/bpf/firebee.bpf.c";
1212
const SRC_EGRESS: &str = "src/bpf/firebee_egress.bpf.c";
13+
const SRC_TEST: &str = "src/bpf/firebee_test.bpf.c";
1314

1415
fn main() {
1516
let manifest_dir = PathBuf::from(
@@ -20,6 +21,7 @@ fn main() {
2021

2122
let out_ingress = bpf_dir.join("firebee.skel.rs");
2223
let out_egress = bpf_dir.join("firebee_egress.skel.rs");
24+
let out_test = bpf_dir.join("firebee_test.skel.rs");
2325

2426
let arch = env::var("CARGO_CFG_TARGET_ARCH")
2527
.expect("CARGO_CFG_TARGET_ARCH must be set in build script");
@@ -43,6 +45,14 @@ fn main() {
4345
.unwrap();
4446
println!("cargo:rerun-if-changed={SRC_EGRESS}");
4547

48+
// Build test program (for BPF unit tests)
49+
SkeletonBuilder::new()
50+
.source(SRC_TEST)
51+
.clang_args(&clang_args)
52+
.build_and_generate(&out_test)
53+
.unwrap();
54+
println!("cargo:rerun-if-changed={SRC_TEST}");
55+
4656
// Rerun if common headers change
4757
println!("cargo:rerun-if-changed=src/bpf/firebee_common.h");
4858
println!("cargo:rerun-if-changed=src/bpf/firebee_helpers.h");

src/bpf/firebee_test.bpf.c

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
/* Firebee BPF Helper Function Tests */
2+
3+
#include <linux/bpf.h>
4+
#include <linux/if_ether.h>
5+
#include <linux/ip.h>
6+
#include <linux/ipv6.h>
7+
#include <linux/tcp.h>
8+
#include <linux/udp.h>
9+
#include <bpf/bpf_helpers.h>
10+
#include <bpf/bpf_endian.h>
11+
12+
#include "firebee_common.h"
13+
#include "firebee_helpers.h"
14+
#include "test_common.h"
15+
16+
char __license[] SEC("license") = "GPL";
17+
18+
/* IPv4 Matching Tests */
19+
CHECK("ip_matches")
20+
int test_ip_matches(struct xdp_md *ctx)
21+
{
22+
test_init();
23+
24+
TEST("exact_match", {
25+
__u32 packet_ip = 0xC0A80101; /* 192.168.1.1 */
26+
__u32 rule_ip = 0xC0A80101;
27+
__u32 subnet_mask = 0xFFFFFFFF; /* /32 */
28+
29+
int result = ip_matches(packet_ip, rule_ip, subnet_mask);
30+
if (!result)
31+
test_fatal("Expected exact IP match for 192.168.1.1/32");
32+
});
33+
34+
TEST("exact_mismatch", {
35+
__u32 packet_ip = 0xC0A80101; /* 192.168.1.1 */
36+
__u32 rule_ip = 0xC0A80102; /* 192.168.1.2 */
37+
__u32 subnet_mask = 0xFFFFFFFF;
38+
39+
int result = ip_matches(packet_ip, rule_ip, subnet_mask);
40+
if (result)
41+
test_fatal("Expected IP mismatch for different IPs");
42+
});
43+
44+
TEST("cidr_24_match", {
45+
__u32 packet_ip = 0xC0A80101; /* 192.168.1.1 */
46+
__u32 rule_ip = 0xC0A80100; /* 192.168.1.0 */
47+
__u32 subnet_mask = 0xFFFFFF00; /* /24 */
48+
49+
int result = ip_matches(packet_ip, rule_ip, subnet_mask);
50+
if (!result)
51+
test_fatal("Expected /24 CIDR match");
52+
});
53+
54+
TEST("cidr_24_mismatch", {
55+
__u32 packet_ip = 0xC0A80201; /* 192.168.2.1 */
56+
__u32 rule_ip = 0xC0A80100; /* 192.168.1.0 */
57+
__u32 subnet_mask = 0xFFFFFF00; /* /24 */
58+
59+
int result = ip_matches(packet_ip, rule_ip, subnet_mask);
60+
if (result)
61+
test_fatal("Expected /24 CIDR mismatch for different subnets");
62+
});
63+
64+
TEST("cidr_16_match", {
65+
__u32 packet_ip = 0xC0A80101; /* 192.168.1.1 */
66+
__u32 rule_ip = 0xC0A80000; /* 192.168.0.0 */
67+
__u32 subnet_mask = 0xFFFF0000; /* /16 */
68+
69+
int result = ip_matches(packet_ip, rule_ip, subnet_mask);
70+
if (!result)
71+
test_fatal("Expected /16 CIDR match");
72+
});
73+
74+
TEST("any_ip_match", {
75+
__u32 packet_ip = 0xC0A80101; /* 192.168.1.1 */
76+
__u32 rule_ip = 0x00000000; /* 0.0.0.0 */
77+
__u32 subnet_mask = 0x00000000; /* /0 - any */
78+
79+
int result = ip_matches(packet_ip, rule_ip, subnet_mask);
80+
if (!result)
81+
test_fatal("Expected any IP (0.0.0.0/0) to match");
82+
});
83+
84+
test_finish();
85+
}
86+
87+
/* Port Matching Tests */
88+
CHECK("port_matches")
89+
int test_port_matches(struct xdp_md *ctx)
90+
{
91+
test_init();
92+
93+
TEST("exact_match", {
94+
__u16 packet_port = 80;
95+
__u16 rule_port = 80;
96+
97+
int result = port_matches(packet_port, rule_port);
98+
if (!result)
99+
test_fatal("Expected exact port match for port 80");
100+
});
101+
102+
TEST("exact_mismatch", {
103+
__u16 packet_port = 80;
104+
__u16 rule_port = 443;
105+
106+
int result = port_matches(packet_port, rule_port);
107+
if (result)
108+
test_fatal("Expected port mismatch for 80 vs 443");
109+
});
110+
111+
TEST("any_port_match", {
112+
__u16 packet_port = 12345;
113+
__u16 rule_port = PORT_ANY;
114+
115+
int result = port_matches(packet_port, rule_port);
116+
if (!result)
117+
test_fatal("Expected PORT_ANY (0) to match any port");
118+
});
119+
120+
test_finish();
121+
}
122+
123+
/* Protocol Matching Tests */
124+
CHECK("protocol_matches")
125+
int test_protocol_matches(struct xdp_md *ctx)
126+
{
127+
test_init();
128+
129+
TEST("tcp_match", {
130+
__u8 packet_proto = IPPROTO_TCP;
131+
__u8 rule_proto = IPPROTO_TCP;
132+
133+
int result = protocol_matches(packet_proto, rule_proto);
134+
if (!result)
135+
test_fatal("Expected TCP protocol match");
136+
});
137+
138+
TEST("tcp_udp_mismatch", {
139+
__u8 packet_proto = IPPROTO_TCP;
140+
__u8 rule_proto = IPPROTO_UDP;
141+
142+
int result = protocol_matches(packet_proto, rule_proto);
143+
if (result)
144+
test_fatal("Expected TCP/UDP protocol mismatch");
145+
});
146+
147+
TEST("any_protocol_match", {
148+
__u8 packet_proto = IPPROTO_ICMP;
149+
__u8 rule_proto = IPPROTO_ANY;
150+
151+
int result = protocol_matches(packet_proto, rule_proto);
152+
if (!result)
153+
test_fatal("Expected IPPROTO_ANY to match ICMP");
154+
});
155+
156+
test_finish();
157+
}
158+
159+
/* IPv6 Matching Tests */
160+
CHECK("ipv6_matches")
161+
int test_ipv6_matches(struct xdp_md *ctx)
162+
{
163+
test_init();
164+
165+
TEST("exact_match", {
166+
/* 2001:db8::1 */
167+
__u32 packet_ip[4];
168+
__u32 rule_ip[4];
169+
packet_ip[0] = bpf_htonl(0x20010db8);
170+
packet_ip[1] = 0;
171+
packet_ip[2] = 0;
172+
packet_ip[3] = bpf_htonl(0x1);
173+
rule_ip[0] = bpf_htonl(0x20010db8);
174+
rule_ip[1] = 0;
175+
rule_ip[2] = 0;
176+
rule_ip[3] = bpf_htonl(0x1);
177+
__u8 prefix_len = 128; /* /128 - exact match */
178+
179+
int result = ipv6_matches(packet_ip, rule_ip, prefix_len);
180+
if (!result)
181+
test_fatal("Expected exact IPv6 match for 2001:db8::1/128");
182+
});
183+
184+
TEST("exact_mismatch", {
185+
/* 2001:db8::1 vs 2001:db8::2 */
186+
__u32 packet_ip[4];
187+
__u32 rule_ip[4];
188+
packet_ip[0] = bpf_htonl(0x20010db8);
189+
packet_ip[1] = 0;
190+
packet_ip[2] = 0;
191+
packet_ip[3] = bpf_htonl(0x1);
192+
rule_ip[0] = bpf_htonl(0x20010db8);
193+
rule_ip[1] = 0;
194+
rule_ip[2] = 0;
195+
rule_ip[3] = bpf_htonl(0x2);
196+
__u8 prefix_len = 128;
197+
198+
int result = ipv6_matches(packet_ip, rule_ip, prefix_len);
199+
if (result)
200+
test_fatal("Expected IPv6 mismatch for different addresses");
201+
});
202+
203+
TEST("prefix_64_match", {
204+
/* 2001:db8:1234:5678::1 matches 2001:db8:1234:5678::/64 */
205+
__u32 packet_ip[4];
206+
__u32 rule_ip[4];
207+
packet_ip[0] = bpf_htonl(0x20010db8);
208+
packet_ip[1] = bpf_htonl(0x12345678);
209+
packet_ip[2] = 0;
210+
packet_ip[3] = bpf_htonl(0x1);
211+
rule_ip[0] = bpf_htonl(0x20010db8);
212+
rule_ip[1] = bpf_htonl(0x12345678);
213+
rule_ip[2] = 0;
214+
rule_ip[3] = 0;
215+
__u8 prefix_len = 64;
216+
217+
int result = ipv6_matches(packet_ip, rule_ip, prefix_len);
218+
if (!result)
219+
test_fatal("Expected /64 prefix match");
220+
});
221+
222+
TEST("prefix_32_match", {
223+
/* 2001:db8:1234:5678::1 matches 2001:db8::/32 */
224+
__u32 packet_ip[4];
225+
__u32 rule_ip[4];
226+
packet_ip[0] = bpf_htonl(0x20010db8);
227+
packet_ip[1] = bpf_htonl(0x12345678);
228+
packet_ip[2] = 0;
229+
packet_ip[3] = bpf_htonl(0x1);
230+
rule_ip[0] = bpf_htonl(0x20010db8);
231+
rule_ip[1] = 0;
232+
rule_ip[2] = 0;
233+
rule_ip[3] = 0;
234+
__u8 prefix_len = 32;
235+
236+
int result = ipv6_matches(packet_ip, rule_ip, prefix_len);
237+
if (!result)
238+
test_fatal("Expected /32 prefix match for 2001:db8::/32");
239+
});
240+
241+
TEST("any_ipv6_match", {
242+
/* Any IPv6 address matches ::/0 */
243+
__u32 packet_ip[4];
244+
__u32 rule_ip[4];
245+
packet_ip[0] = bpf_htonl(0x20010db8);
246+
packet_ip[1] = bpf_htonl(0x12345678);
247+
packet_ip[2] = 0;
248+
packet_ip[3] = bpf_htonl(0x1);
249+
rule_ip[0] = 0;
250+
rule_ip[1] = 0;
251+
rule_ip[2] = 0;
252+
rule_ip[3] = 0;
253+
__u8 prefix_len = 0;
254+
255+
int result = ipv6_matches(packet_ip, rule_ip, prefix_len);
256+
if (!result)
257+
test_fatal("Expected any IPv6 (::/0) to match");
258+
});
259+
260+
test_finish();
261+
}

0 commit comments

Comments
 (0)