diff --git a/src/43-kfuncs/README.md b/src/43-kfuncs/README.md index a3ba587..5880fce 100644 --- a/src/43-kfuncs/README.md +++ b/src/43-kfuncs/README.md @@ -393,7 +393,7 @@ Assuming you have a user-space application or a tool to load and attach the eBPF **Sample Output:** ```bash -# sudo ./load_kfunc +# sudo ./kfunc BPF program loaded and attached successfully. Press Ctrl-C to exit. ``` diff --git a/src/43-kfuncs/README.zh.md b/src/43-kfuncs/README.zh.md index f80f394..938f2e5 100644 --- a/src/43-kfuncs/README.zh.md +++ b/src/43-kfuncs/README.zh.md @@ -393,7 +393,7 @@ int handle_kprobe(struct pt_regs *ctx) **示例输出:** ```bash -# sudo ./load_kfunc +# sudo ./kfunc BPF 程序已加载并成功附加。按 Ctrl-C 退出。 ``` diff --git a/src/46-xdp-test/.gitignore b/src/46-xdp-test/.gitignore new file mode 100644 index 0000000..1e2d41b --- /dev/null +++ b/src/46-xdp-test/.gitignore @@ -0,0 +1,3 @@ +/.output +/bootstrap +xdp-pktgen diff --git a/src/46-xdp-test/Makefile b/src/46-xdp-test/Makefile new file mode 100644 index 0000000..c10c169 --- /dev/null +++ b/src/46-xdp-test/Makefile @@ -0,0 +1,141 @@ +# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +OUTPUT := .output +CLANG ?= clang +LIBBPF_SRC := $(abspath ../third_party/libbpf/src) +BPFTOOL_SRC := $(abspath ../third_party/bpftool/src) +LIBBPF_OBJ := $(abspath $(OUTPUT)/libbpf.a) +BPFTOOL_OUTPUT ?= $(abspath $(OUTPUT)/bpftool) +BPFTOOL ?= $(BPFTOOL_OUTPUT)/bootstrap/bpftool +LIBBLAZESYM_SRC := $(abspath ../third_party/blazesym/) +LIBBLAZESYM_OBJ := $(abspath $(OUTPUT)/libblazesym.a) +LIBBLAZESYM_HEADER := $(abspath $(OUTPUT)/blazesym.h) +ARCH ?= $(shell uname -m | sed 's/x86_64/x86/' \ + | sed 's/arm.*/arm/' \ + | sed 's/aarch64/arm64/' \ + | sed 's/ppc64le/powerpc/' \ + | sed 's/mips.*/mips/' \ + | sed 's/riscv64/riscv/' \ + | sed 's/loongarch64/loongarch/') +VMLINUX := ../third_party/vmlinux/$(ARCH)/vmlinux.h +# Use our own libbpf API headers and Linux UAPI headers distributed with +# libbpf to avoid dependency on system-wide headers, which could be missing or +# outdated +INCLUDES := -I$(OUTPUT) -I../third_party/libbpf/include/uapi -I$(dir $(VMLINUX)) +CFLAGS := -g -Wall +ALL_LDFLAGS := $(LDFLAGS) $(EXTRA_LDFLAGS) + +APPS = xdp-pktgen # minimal minimal_legacy uprobe kprobe fentry usdt sockfilter tc ksyscall + +CARGO ?= $(shell which cargo) +ifeq ($(strip $(CARGO)),) +BZS_APPS := +else +BZS_APPS := # profile +APPS += $(BZS_APPS) +# Required by libblazesym +ALL_LDFLAGS += -lrt -ldl -lpthread -lm +endif + +# Get Clang's default includes on this system. We'll explicitly add these dirs +# to the includes list when compiling with `-target bpf` because otherwise some +# architecture-specific dirs will be "missing" on some architectures/distros - +# headers such as asm/types.h, asm/byteorder.h, asm/socket.h, asm/sockios.h, +# sys/cdefs.h etc. might be missing. +# +# Use '-idirafter': Don't interfere with include mechanics except where the +# build would have failed anyways. +CLANG_BPF_SYS_INCLUDES ?= $(shell $(CLANG) -v -E - &1 \ + | sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') + +ifeq ($(V),1) + Q = + msg = +else + Q = @ + msg = @printf ' %-8s %s%s\n' \ + "$(1)" \ + "$(patsubst $(abspath $(OUTPUT))/%,%,$(2))" \ + "$(if $(3), $(3))"; + MAKEFLAGS += --no-print-directory +endif + +define allow-override + $(if $(or $(findstring environment,$(origin $(1))),\ + $(findstring command line,$(origin $(1)))),,\ + $(eval $(1) = $(2))) +endef + +$(call allow-override,CC,$(CROSS_COMPILE)cc) +$(call allow-override,LD,$(CROSS_COMPILE)ld) + +.PHONY: all +all: $(APPS) + +.PHONY: clean +clean: + $(call msg,CLEAN) + $(Q)rm -rf $(OUTPUT) $(APPS) + +$(OUTPUT) $(OUTPUT)/libbpf $(BPFTOOL_OUTPUT): + $(call msg,MKDIR,$@) + $(Q)mkdir -p $@ + +# Build libbpf +$(LIBBPF_OBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(OUTPUT)/libbpf + $(call msg,LIB,$@) + $(Q)$(MAKE) -C $(LIBBPF_SRC) BUILD_STATIC_ONLY=1 \ + OBJDIR=$(dir $@)/libbpf DESTDIR=$(dir $@) \ + INCLUDEDIR= LIBDIR= UAPIDIR= \ + install + +# Build bpftool +$(BPFTOOL): | $(BPFTOOL_OUTPUT) + $(call msg,BPFTOOL,$@) + $(Q)$(MAKE) ARCH= CROSS_COMPILE= OUTPUT=$(BPFTOOL_OUTPUT)/ -C $(BPFTOOL_SRC) bootstrap + + +$(LIBBLAZESYM_SRC)/target/release/libblazesym.a:: + $(Q)cd $(LIBBLAZESYM_SRC) && $(CARGO) build --features=cheader,dont-generate-test-files --release + +$(LIBBLAZESYM_OBJ): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT) + $(call msg,LIB, $@) + $(Q)cp $(LIBBLAZESYM_SRC)/target/release/libblazesym.a $@ + +$(LIBBLAZESYM_HEADER): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT) + $(call msg,LIB,$@) + $(Q)cp $(LIBBLAZESYM_SRC)/target/release/blazesym.h $@ + +# Build BPF code +$(OUTPUT)/%.bpf.o: %.bpf.c $(LIBBPF_OBJ) $(wildcard %.h) $(VMLINUX) | $(OUTPUT) $(BPFTOOL) + $(call msg,BPF,$@) + $(Q)$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) \ + $(INCLUDES) $(CLANG_BPF_SYS_INCLUDES) \ + -c $(filter %.c,$^) -o $(patsubst %.bpf.o,%.tmp.bpf.o,$@) + $(Q)$(BPFTOOL) gen object $@ $(patsubst %.bpf.o,%.tmp.bpf.o,$@) + +# Generate BPF skeletons +$(OUTPUT)/%.skel.h: $(OUTPUT)/%.bpf.o | $(OUTPUT) $(BPFTOOL) + $(call msg,GEN-SKEL,$@) + $(Q)$(BPFTOOL) gen skeleton $< > $@ + +# Build user-space code +$(patsubst %,$(OUTPUT)/%.o,$(APPS)): %.o: %.skel.h + +$(OUTPUT)/%.o: %.c $(wildcard %.h) | $(OUTPUT) + $(call msg,CC,$@) + $(Q)$(CC) $(CFLAGS) $(INCLUDES) -c $(filter %.c,$^) -o $@ + +$(patsubst %,$(OUTPUT)/%.o,$(BZS_APPS)): $(LIBBLAZESYM_HEADER) + +$(BZS_APPS): $(LIBBLAZESYM_OBJ) + +# Build application binary +$(APPS): %: $(OUTPUT)/%.o $(LIBBPF_OBJ) | $(OUTPUT) + $(call msg,BINARY,$@) + $(Q)$(CC) $(CFLAGS) $^ $(ALL_LDFLAGS) -lelf -lz -o $@ + +# delete failed targets +.DELETE_ON_ERROR: + +# keep intermediate (.skel.h, .bpf.o, etc) targets +.SECONDARY: diff --git a/src/46-xdp-test/README.md b/src/46-xdp-test/README.md new file mode 100644 index 0000000..600a8a4 --- /dev/null +++ b/src/46-xdp-test/README.md @@ -0,0 +1,44 @@ +# xdp-pktgen: xdp based packet generator + +This is a simple xdp based packet generator. + +## **How to use** + +clone the repo, you can update the git submodule with following commands: + +```sh +git submodule update --init --recursive +``` + +### **3. Install dependencies** + +For dependencies, it varies from distribution to distribution. You can refer to shell.nix and dockerfile for installation. + +On Ubuntu, you may run `make install` or + +```sh +sudo apt-get install -y --no-install-recommends \ + libelf1 libelf-dev zlib1g-dev \ + make clang llvm +``` + +to install dependencies. + +### **4. Build the project** + +To build the project, run the following command: + +```sh +make build +``` + +This will compile your code and create the necessary binaries. You can you the `Github Code space` or `Github Action` to build the project as well. + +### ***Run the Project*** + +You can run the binary with: + +```console +sudo ./xdp-pktgen +``` + diff --git a/src/46-xdp-test/README.zh.md b/src/46-xdp-test/README.zh.md new file mode 100644 index 0000000..600a8a4 --- /dev/null +++ b/src/46-xdp-test/README.zh.md @@ -0,0 +1,44 @@ +# xdp-pktgen: xdp based packet generator + +This is a simple xdp based packet generator. + +## **How to use** + +clone the repo, you can update the git submodule with following commands: + +```sh +git submodule update --init --recursive +``` + +### **3. Install dependencies** + +For dependencies, it varies from distribution to distribution. You can refer to shell.nix and dockerfile for installation. + +On Ubuntu, you may run `make install` or + +```sh +sudo apt-get install -y --no-install-recommends \ + libelf1 libelf-dev zlib1g-dev \ + make clang llvm +``` + +to install dependencies. + +### **4. Build the project** + +To build the project, run the following command: + +```sh +make build +``` + +This will compile your code and create the necessary binaries. You can you the `Github Code space` or `Github Action` to build the project as well. + +### ***Run the Project*** + +You can run the binary with: + +```console +sudo ./xdp-pktgen +``` + diff --git a/src/46-xdp-test/test_udp_pkt.h b/src/46-xdp-test/test_udp_pkt.h new file mode 100644 index 0000000..f31c165 --- /dev/null +++ b/src/46-xdp-test/test_udp_pkt.h @@ -0,0 +1,114 @@ +#ifndef TEST_UDP_PKT_H +#define TEST_UDP_PKT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // For inet_addr() +#include // For memcpy, memset +#include +#include +#include + +#define PORT 9876 +#define SERVER_IP "127.0.0.1" + +// Define packet structures first +struct test_udp_packet { + struct ethhdr eth; + struct ipv6hdr iph; + struct udphdr udp; + __u8 payload[64 - sizeof(struct udphdr) - sizeof(struct ethhdr) - sizeof(struct ipv6hdr)]; +} __attribute__((packed)); + +struct test_udp_packet_v4 { + struct ethhdr eth; + struct iphdr iph; + struct udphdr udp; + uint8_t payload[64 - sizeof(struct udphdr) - sizeof(struct ethhdr) - sizeof(struct iphdr)]; +} __attribute__((packed)); + +// Helper function declarations +static uint16_t ip_checksum(void *vdata, size_t length); +static __be16 __calc_udp_cksum(const struct test_udp_packet *pkt); + +// Helper function implementations +static uint16_t ip_checksum(void *vdata, size_t length) { + char *data = vdata; + uint64_t acc = 0xffff; + + for (size_t i = 0; i + 1 < length; i += 2) { + uint16_t word; + memcpy(&word, data + i, 2); + acc += ntohs(word); + if (acc > 0xffff) { + acc -= 0xffff; + } + } + + if (length & 1) { + uint16_t word = 0; + memcpy(&word, data + length - 1, 1); + acc += ntohs(word); + if (acc > 0xffff) { + acc -= 0xffff; + } + } + + return htons(~acc); +} + +static __be16 __calc_udp_cksum(const struct test_udp_packet *pkt) { + __u32 chksum = pkt->iph.nexthdr + bpf_ntohs(pkt->iph.payload_len); + int i; + + for (i = 0; i < 8; i++) { + chksum += bpf_ntohs(pkt->iph.saddr.s6_addr16[i]); + chksum += bpf_ntohs(pkt->iph.daddr.s6_addr16[i]); + } + chksum += bpf_ntohs(pkt->udp.source); + chksum += bpf_ntohs(pkt->udp.dest); + chksum += bpf_ntohs(pkt->udp.len); + + while (chksum >> 16) + chksum = (chksum & 0xFFFF) + (chksum >> 16); + return bpf_htons(~chksum); +} + +static struct test_udp_packet_v4 create_test_udp_packet_v4(void) { + struct test_udp_packet_v4 pkt = {0}; + + // Ethernet header + pkt.eth.h_proto = htons(ETH_P_IP); + memcpy(pkt.eth.h_dest, (const unsigned char[]){0xb8, 0x3f, 0xd2, 0x2a, 0xe5, 0x11}, sizeof(pkt.eth.h_dest)); + memcpy(pkt.eth.h_source, (const unsigned char[]){0xb8, 0x3f, 0xd2, 0x2a, 0xe7, 0x69}, sizeof(pkt.eth.h_source)); + + // IPv4 header + pkt.iph.version = 4; + pkt.iph.ihl = 5; + pkt.iph.tot_len = htons(sizeof(struct test_udp_packet_v4) - sizeof(struct ethhdr)); + pkt.iph.ttl = 64; // default TTL + pkt.iph.protocol = IPPROTO_UDP; + pkt.iph.saddr = inet_addr(SERVER_IP); + pkt.iph.daddr = inet_addr(SERVER_IP); + pkt.iph.check = ip_checksum(&pkt.iph, sizeof(struct iphdr)); + + // UDP header + pkt.udp.source = htons(12345); + pkt.udp.dest = htons(PORT); + pkt.udp.len = htons(sizeof(struct udphdr) + sizeof(pkt.payload)); + pkt.udp.check = 0; // Optional for IPv4 + + // Payload + memset(pkt.payload, 0x42, sizeof(pkt.payload)); + + return pkt; +} + +#endif // TEST_UDP_PKT_H diff --git a/src/46-xdp-test/xdp-pktgen.bpf.c b/src/46-xdp-test/xdp-pktgen.bpf.c new file mode 100644 index 0000000..450627a --- /dev/null +++ b/src/46-xdp-test/xdp-pktgen.bpf.c @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: MIT */ +#include "vmlinux.h" +#include +#include + +char _license[] SEC("license") = "GPL"; + +SEC("xdp") +int xdp_redirect_notouch(struct xdp_md *ctx) +{ + return XDP_TX; +} diff --git a/src/46-xdp-test/xdp-pktgen.c b/src/46-xdp-test/xdp-pktgen.c new file mode 100644 index 0000000..103b9f3 --- /dev/null +++ b/src/46-xdp-test/xdp-pktgen.c @@ -0,0 +1,172 @@ +/* SPDX-License-Identifier: MIT */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xdp-pktgen.skel.h" +#include "test_udp_pkt.h" + +#ifndef BPF_F_TEST_XDP_LIVE_FRAMES +#define BPF_F_TEST_XDP_LIVE_FRAMES (1U << 1) +#endif + +static volatile bool exiting = false; + +static void sig_handler(int sig) +{ + exiting = true; +} + +struct config { + int ifindex; + int xdp_flags; + int repeat; + int batch_size; +}; + +struct config cfg = { + .ifindex = 6, + .repeat = 1 << 20, + .batch_size = 0, +}; + +static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args) +{ + return vfprintf(stderr, format, args); +} + +static int run_prog(int run_prog_fd, int count) +{ + // struct test_udp_packet pkt_udp = create_test_udp_packet_v6(); + char* pkt_file = NULL; + unsigned char pkt_file_buffer[1024]; + int size = 0; + if ((pkt_file = getenv("PKTGEN_FILE")) != NULL) { + FILE* file = fopen(pkt_file, "r"); + if (file == NULL) { + printf("Error opening file\n"); + return -1; + } + // read the the file length of data into the buffer + size = fread(pkt_file_buffer, 1, 1024, file); + fclose(file); + } else { + struct test_udp_packet_v4 pkt_udp = create_test_udp_packet_v4(); + size = sizeof(pkt_udp); + memcpy(pkt_file_buffer, &pkt_udp, size); + } + struct xdp_md ctx_in = { + .data_end = size, + .ingress_ifindex = cfg.ifindex + }; + // struct xdp_md ctx_in_array[64]; + // for (int i = 0; i < 64; i++) { + // ctx_in_array[i].data_end = sizeof(pkt_udp); + // ctx_in_array[i].ingress_ifindex = cfg.ifindex; + // } + + printf("pkt size: %ld\n", size); + DECLARE_LIBBPF_OPTS(bpf_test_run_opts, opts, + .data_in = pkt_file_buffer, + .data_size_in = size, + .ctx_in = &ctx_in, + .ctx_size_in = sizeof(ctx_in), + .repeat = cfg.repeat, + .flags = BPF_F_TEST_XDP_LIVE_FRAMES, + .batch_size = cfg.batch_size, + .cpu = 0, + ); + __u64 iterations = 0; + cpu_set_t cpu_cores; + int err; + + CPU_ZERO(&cpu_cores); + CPU_SET(0, &cpu_cores); + pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpu_cores); + do { + err = bpf_prog_test_run_opts(run_prog_fd, &opts); + if (err) + return -errno; + iterations += opts.repeat; + } while ((count == 0 || iterations < count) && !exiting); + return 0; +} + +static int probe_kernel_support(int run_prog_fd) +{ + int err = run_prog(run_prog_fd, 1); + if (err == -EOPNOTSUPP) { + printf("BPF_PROG_RUN with batch size support is missing from libbpf.\n"); + } else if (err == -EINVAL) { + err = -EOPNOTSUPP; + printf("Kernel doesn't support live packet mode for XDP BPF_PROG_RUN.\n"); + } else if (err) { + printf("Error probing kernel support: %s\n", strerror(-err)); + } else { + printf("Kernel supports live packet mode for XDP BPF_PROG_RUN.\n"); + } + return err; +} + +int main() +{ + struct xdp_pktgen_bpf *skel = NULL; + int err = 0; + __u32 key = 0; + + /* Set up libbpf errors and debug info callback */ + libbpf_set_print(libbpf_print_fn); + /* Cleaner handling of Ctrl-C */ + signal(SIGINT, sig_handler); + signal(SIGTERM, sig_handler); + + skel = xdp_pktgen_bpf__open(); + if (!skel) { + err = -errno; + printf("Couldn't open XDP program: %s\n", strerror(-err)); + goto out; + } + + err = xdp_pktgen_bpf__load(skel); + if (err) + goto out; + + int run_prog_fd = bpf_program__fd(skel->progs.xdp_redirect_notouch); + // probe kernel support for BPF_PROG_RUN + err = probe_kernel_support(run_prog_fd); + if (err) + return err; + + err = run_prog(run_prog_fd, 0); + if (err) { + printf("run xdp program error: %d\n", err); + } + +out: + xdp_pktgen_bpf__destroy(skel); + return err; +} diff --git a/src/scripts/generate_toc.py b/src/scripts/generate_toc.py index 53c5a4d..3580df2 100644 --- a/src/scripts/generate_toc.py +++ b/src/scripts/generate_toc.py @@ -211,8 +211,8 @@ def generate_toc_cn(base_dir, project_root): return toc # Example usage -base_directory = "/root/bpf-developer-tutorial/src/" # Replace with the actual base directory -project_root = "/root/bpf-developer-tutorial/" # The root of the project +base_directory = "src/" # Replace with the actual base directory +project_root = "./" # The root of the project toc_output = generate_toc(base_directory, project_root) # toc_output = generate_toc_cn(base_directory, project_root) # Output the TOC