From 7d8b706d780beb60d120f247bc3350fb9c0a1f2e Mon Sep 17 00:00:00 2001 From: LinuxDev9002 Date: Tue, 14 Jul 2026 04:04:57 -0700 Subject: [PATCH] fix: capture the actual TCP header length --- src/41-xdp-tcpdump/README.md | 109 +++++++++++++++++--------- src/41-xdp-tcpdump/README.zh.md | 111 ++++++++++++++++++--------- src/41-xdp-tcpdump/xdp-tcpdump.bpf.c | 34 ++++---- src/41-xdp-tcpdump/xdp-tcpdump.c | 18 +++-- src/41-xdp-tcpdump/xdp-tcpdump.h | 11 +++ 5 files changed, 191 insertions(+), 92 deletions(-) create mode 100644 src/41-xdp-tcpdump/xdp-tcpdump.h diff --git a/src/41-xdp-tcpdump/README.md b/src/41-xdp-tcpdump/README.md index 0a3896d..0deffc5 100644 --- a/src/41-xdp-tcpdump/README.md +++ b/src/41-xdp-tcpdump/README.md @@ -18,12 +18,24 @@ XDP is a high-performance data path within the Linux kernel that allows for prog Let's dive into the kernel-space eBPF code that captures TCP header information. +The kernel and user-space programs share the event definition in `xdp-tcpdump.h`. A TCP header can contain 20 to 60 bytes, and `header_len` records the exact size derived from the packet's data-offset field. + +```c +#define MAX_TCP_HEADER_BYTES 60 + +struct tcp_event { + unsigned int header_len; + unsigned char header[MAX_TCP_HEADER_BYTES]; +}; +``` + ### Full Kernel Code ```c #include "vmlinux.h" #include #include +#include "xdp-tcpdump.h" #define ETH_P_IP 0x0800 @@ -94,29 +106,38 @@ int xdp_pass(struct xdp_md *ctx) return XDP_PASS; } - // Define the number of bytes you want to capture from the TCP header - const int tcp_header_bytes = 32; - - // Ensure that the desired number of bytes does not exceed packet bounds - if ((void *)tcp + tcp_header_bytes > data_end) { + // Derive the TCP header length from data offset (doff), measured in 32-bit words + __u32 tcp_header_bytes = tcp->doff * 4; + if (tcp_header_bytes < sizeof(*tcp) || tcp_header_bytes > MAX_TCP_HEADER_BYTES) { return XDP_PASS; } - // Reserve space in the ring buffer - void *ringbuf_space = bpf_ringbuf_reserve(&rb, tcp_header_bytes, 0); - if (!ringbuf_space) { + // Reserve a fixed-size event because bpf_ringbuf_reserve requires a constant size + struct tcp_event *event = bpf_ringbuf_reserve(&rb, sizeof(*event), 0); + if (!event) { return XDP_PASS; // If reservation fails, skip processing } + event->header_len = tcp_header_bytes; + __builtin_memset(event->header, 0, sizeof(event->header)); + // Copy the TCP header bytes into the ring buffer // Using a loop to ensure compliance with eBPF verifier - for (int i = 0; i < tcp_header_bytes; i++) { + for (int i = 0; i < MAX_TCP_HEADER_BYTES; i++) { + if (i >= tcp_header_bytes) + break; + + if ((void *)tcp + i + 1 > data_end) { + bpf_ringbuf_discard(event, 0); + return XDP_PASS; + } + unsigned char byte = *((unsigned char *)tcp + i); - ((unsigned char *)ringbuf_space)[i] = byte; + event->header[i] = byte; } // Submit the data to the ring buffer - bpf_ringbuf_submit(ringbuf_space, 0); + bpf_ringbuf_submit(event, 0); // Optional: Print a debug message bpf_printk("Captured TCP header (%d bytes)", tcp_header_bytes); @@ -156,26 +177,37 @@ static bool is_tcp(struct ethhdr *eth, void *data_end) In the `xdp_pass` function, we: 1. Parse the Ethernet, IP, and TCP headers. -2. Ensure all headers are within the packet bounds to prevent invalid memory access. -3. Reserve space in the ring buffer to store the TCP header. -4. Copy the TCP header bytes into the ring buffer. +2. Read and validate the TCP data-offset field, which gives the actual header length. +3. Reserve a fixed-size event in the ring buffer because the helper requires a compile-time constant size. +4. Copy only the validated header bytes into the event while checking each packet access. 5. Submit the data to the ring buffer for user-space consumption. ```c -// Reserve space in the ring buffer -void *ringbuf_space = bpf_ringbuf_reserve(&rb, tcp_header_bytes, 0); -if (!ringbuf_space) { +__u32 tcp_header_bytes = tcp->doff * 4; +if (tcp_header_bytes < sizeof(*tcp) || tcp_header_bytes > MAX_TCP_HEADER_BYTES) { return XDP_PASS; } -// Copy the TCP header bytes -for (int i = 0; i < tcp_header_bytes; i++) { - unsigned char byte = *((unsigned char *)tcp + i); - ((unsigned char *)ringbuf_space)[i] = byte; +struct tcp_event *event = bpf_ringbuf_reserve(&rb, sizeof(*event), 0); +if (!event) { + return XDP_PASS; } -// Submit to ring buffer -bpf_ringbuf_submit(ringbuf_space, 0); +event->header_len = tcp_header_bytes; +__builtin_memset(event->header, 0, sizeof(event->header)); + +for (int i = 0; i < MAX_TCP_HEADER_BYTES; i++) { + if (i >= tcp_header_bytes) + break; + if ((void *)tcp + i + 1 > data_end) { + bpf_ringbuf_discard(event, 0); + return XDP_PASS; + } + unsigned char byte = *((unsigned char *)tcp + i); + event->header[i] = byte; +} + +bpf_ringbuf_submit(event, 0); ``` #### Using bpf_printk for Debugging @@ -199,17 +231,25 @@ Let's examine the user-space program that reads the captured TCP headers from th #include #include #include +#include #include #include #include "xdp-tcpdump.skel.h" // Generated skeleton header +#include "xdp-tcpdump.h" // Callback function to handle events from the ring buffer static int handle_event(void *ctx, void *data, size_t data_sz) { - if (data_sz < 20) { // Minimum TCP header size - fprintf(stderr, "Received incomplete TCP header\n"); + if (data_sz < sizeof(struct tcp_event)) { + fprintf(stderr, "Received incomplete TCP event\n"); + return 0; + } + + struct tcp_event *event = data; + if (event->header_len < 20 || event->header_len > MAX_TCP_HEADER_BYTES) { + fprintf(stderr, "Invalid TCP header length: %u\n", event->header_len); return 0; } @@ -235,12 +275,7 @@ static int handle_event(void *ctx, void *data, size_t data_sz) // Options and padding may follow } __attribute__((packed)); - if (data_sz < sizeof(struct tcphdr)) { - fprintf(stderr, "Data size (%zu) less than TCP header size\n", data_sz); - return 0; - } - - struct tcphdr *tcp = (struct tcphdr *)data; + struct tcphdr *tcp = (struct tcphdr *)event->header; // Convert fields from network byte order to host byte order uint16_t source_port = ntohs(tcp->source); @@ -369,13 +404,17 @@ The `handle_event` function processes TCP header data received from the ring buf ```c static int handle_event(void *ctx, void *data, size_t data_sz) { - // Validate data size - if (data_sz < 20) { - fprintf(stderr, "Received incomplete TCP header\n"); + if (data_sz < sizeof(struct tcp_event)) { + fprintf(stderr, "Received incomplete TCP event\n"); + return 0; + } + + struct tcp_event *event = data; + if (event->header_len < 20 || event->header_len > MAX_TCP_HEADER_BYTES) { + fprintf(stderr, "Invalid TCP header length: %u\n", event->header_len); return 0; } - // Parse the TCP header // ... (parsing code) } ``` diff --git a/src/41-xdp-tcpdump/README.zh.md b/src/41-xdp-tcpdump/README.zh.md index fc76a84..59e2f89 100644 --- a/src/41-xdp-tcpdump/README.zh.md +++ b/src/41-xdp-tcpdump/README.zh.md @@ -16,12 +16,24 @@ XDP 是 Linux 内核中一个高性能的数据路径,允许在网络栈的最 让我们深入了解捕获 TCP 头信息的内核空间 eBPF 代码。 +内核程序与用户空间程序通过 `xdp-tcpdump.h` 共享事件定义。TCP 头的长度可以是 20 到 60 字节,`header_len` 保存根据数据偏移字段计算出的准确长度。 + +```c +#define MAX_TCP_HEADER_BYTES 60 + +struct tcp_event { + unsigned int header_len; + unsigned char header[MAX_TCP_HEADER_BYTES]; +}; +``` + ### 完整的内核代码 ```c #include "vmlinux.h" #include #include +#include "xdp-tcpdump.h" #define ETH_P_IP 0x0800 @@ -92,29 +104,38 @@ int xdp_pass(struct xdp_md *ctx) return XDP_PASS; } - // 定义要捕获的 TCP 头字节数 - const int tcp_header_bytes = 32; - - // 确保所需字节数不超过数据包边界 - if ((void *)tcp + tcp_header_bytes > data_end) { + // 根据以 32 位字为单位的数据偏移字段 doff 计算 TCP 头长度 + __u32 tcp_header_bytes = tcp->doff * 4; + if (tcp_header_bytes < sizeof(*tcp) || tcp_header_bytes > MAX_TCP_HEADER_BYTES) { return XDP_PASS; } - // 在环形缓冲区中预留空间 - void *ringbuf_space = bpf_ringbuf_reserve(&rb, tcp_header_bytes, 0); - if (!ringbuf_space) { + // bpf_ringbuf_reserve 要求大小为常量,因此预留固定大小的事件 + struct tcp_event *event = bpf_ringbuf_reserve(&rb, sizeof(*event), 0); + if (!event) { return XDP_PASS; // 如果预留失败,跳过处理 } + event->header_len = tcp_header_bytes; + __builtin_memset(event->header, 0, sizeof(event->header)); + // 将 TCP 头字节复制到环形缓冲区 // 使用循环以确保符合 eBPF 验证器要求 - for (int i = 0; i < tcp_header_bytes; i++) { + for (int i = 0; i < MAX_TCP_HEADER_BYTES; i++) { + if (i >= tcp_header_bytes) + break; + + if ((void *)tcp + i + 1 > data_end) { + bpf_ringbuf_discard(event, 0); + return XDP_PASS; + } + unsigned char byte = *((unsigned char *)tcp + i); - ((unsigned char *)ringbuf_space)[i] = byte; + event->header[i] = byte; } // 将数据提交到环形缓冲区 - bpf_ringbuf_submit(ringbuf_space, 0); + bpf_ringbuf_submit(event, 0); // 可选:打印调试信息 bpf_printk("Captured TCP header (%d bytes)", tcp_header_bytes); @@ -154,26 +175,37 @@ static bool is_tcp(struct ethhdr *eth, void *data_end) 在 `xdp_pass` 函数中,我们: 1. 解析以太网、IP 和 TCP 头。 -2. 确保所有头信息在数据包边界内,以防止无效内存访问。 -3. 在环形缓冲区中预留空间以存储 TCP 头。 -4. 将 TCP 头字节复制到环形缓冲区。 +2. 读取并校验 TCP 数据偏移字段,得到实际的头长度。 +3. 由于辅助函数要求使用编译期常量大小,在环形缓冲区中预留固定大小的事件。 +4. 逐次检查数据包边界,只把经过校验的头字节复制到事件中。 5. 提交数据到环形缓冲区,供用户空间使用。 ```c -// 在环形缓冲区中预留空间 -void *ringbuf_space = bpf_ringbuf_reserve(&rb, tcp_header_bytes, 0); -if (!ringbuf_space) { +__u32 tcp_header_bytes = tcp->doff * 4; +if (tcp_header_bytes < sizeof(*tcp) || tcp_header_bytes > MAX_TCP_HEADER_BYTES) { return XDP_PASS; } -// 复制 TCP 头字节 -for (int i = 0; i < tcp_header_bytes; i++) { - unsigned char byte = *((unsigned char *)tcp + i); - ((unsigned char *)ringbuf_space)[i] = byte; +struct tcp_event *event = bpf_ringbuf_reserve(&rb, sizeof(*event), 0); +if (!event) { + return XDP_PASS; } -// 提交到环形缓冲区 -bpf_ringbuf_submit(ringbuf_space, 0); +event->header_len = tcp_header_bytes; +__builtin_memset(event->header, 0, sizeof(event->header)); + +for (int i = 0; i < MAX_TCP_HEADER_BYTES; i++) { + if (i >= tcp_header_bytes) + break; + if ((void *)tcp + i + 1 > data_end) { + bpf_ringbuf_discard(event, 0); + return XDP_PASS; + } + unsigned char byte = *((unsigned char *)tcp + i); + event->header[i] = byte; +} + +bpf_ringbuf_submit(event, 0); ``` #### 使用 bpf_printk 进行调试 @@ -197,17 +229,25 @@ bpf_printk("Captured TCP header (%d bytes)", tcp_header_bytes); #include #include #include +#include #include #include #include "xdp-tcpdump.skel.h" // 生成的骨架头文件 +#include "xdp-tcpdump.h" // 处理环形缓冲区事件的回调函数 static int handle_event(void *ctx, void *data, size_t data_sz) { - if (data_sz < 20) { // 最小 TCP 头大小 - fprintf(stderr, "Received incomplete TCP header\n"); + if (data_sz < sizeof(struct tcp_event)) { + fprintf(stderr, "Received incomplete TCP event\n"); + return 0; + } + + struct tcp_event *event = data; + if (event->header_len < 20 || event->header_len > MAX_TCP_HEADER_BYTES) { + fprintf(stderr, "Invalid TCP header length: %u\n", event->header_len); return 0; } @@ -233,12 +273,7 @@ static int handle_event(void *ctx, void *data, size_t data_sz) // 可能还有选项和填充 } __attribute__((packed)); - if (data_sz < sizeof(struct tcphdr)) { - fprintf(stderr, "Data size (%zu) less than TCP header size\n", data_sz); - return 0; - } - - struct tcphdr *tcp = (struct tcphdr *)data; + struct tcphdr *tcp = (struct tcphdr *)event->header; // 将字段从网络字节序转换为主机字节序 uint16_t source_port = ntohs(tcp->source); @@ -367,13 +402,17 @@ cleanup: ```c static int handle_event(void *ctx, void *data, size_t data_sz) { - // 验证数据大小 - if (data_sz < 20) { - fprintf(stderr, "Received incomplete TCP header\n"); + if (data_sz < sizeof(struct tcp_event)) { + fprintf(stderr, "Received incomplete TCP event\n"); + return 0; + } + + struct tcp_event *event = data; + if (event->header_len < 20 || event->header_len > MAX_TCP_HEADER_BYTES) { + fprintf(stderr, "Invalid TCP header length: %u\n", event->header_len); return 0; } - // 解析 TCP 头 // ...(解析代码) } ``` @@ -506,4 +545,4 @@ Captured TCP Header: 如果您想了解更多关于 eBPF 的内容,请访问我们的教程代码仓库 或我们的网站 -编程愉快! \ No newline at end of file +编程愉快! diff --git a/src/41-xdp-tcpdump/xdp-tcpdump.bpf.c b/src/41-xdp-tcpdump/xdp-tcpdump.bpf.c index 658a418..edebbfc 100644 --- a/src/41-xdp-tcpdump/xdp-tcpdump.bpf.c +++ b/src/41-xdp-tcpdump/xdp-tcpdump.bpf.c @@ -1,6 +1,7 @@ #include "vmlinux.h" #include #include +#include "xdp-tcpdump.h" #define ETH_P_IP 0x0800 @@ -71,32 +72,39 @@ int xdp_pass(struct xdp_md *ctx) return XDP_PASS; } - // Define the number of bytes you want to capture from the TCP header - // Typically, the TCP header is 20 bytes, but with options, it can be longer - // Here, we'll capture the first 32 bytes to include possible options - const int tcp_header_bytes = 32; - - // Ensure that the desired number of bytes does not exceed packet bounds - if ((void *)tcp + tcp_header_bytes > data_end) { + // Derive the TCP header length from data offset (doff), measured in 32-bit words + __u32 tcp_header_bytes = tcp->doff * 4; + if (tcp_header_bytes < sizeof(*tcp) || tcp_header_bytes > MAX_TCP_HEADER_BYTES) { return XDP_PASS; } - // Reserve space in the ring buffer - void *ringbuf_space = bpf_ringbuf_reserve(&rb, tcp_header_bytes, 0); - if (!ringbuf_space) { + // Reserve a fixed-size event because bpf_ringbuf_reserve requires a constant size + struct tcp_event *event = bpf_ringbuf_reserve(&rb, sizeof(*event), 0); + if (!event) { return XDP_PASS; // If reservation fails, skip processing } + event->header_len = tcp_header_bytes; + __builtin_memset(event->header, 0, sizeof(event->header)); + // Copy the TCP header bytes into the ring buffer // Using a loop to ensure compliance with eBPF verifier - for (int i = 0; i < tcp_header_bytes; i++) { + for (int i = 0; i < MAX_TCP_HEADER_BYTES; i++) { + if (i >= tcp_header_bytes) + break; + + if ((void *)tcp + i + 1 > data_end) { + bpf_ringbuf_discard(event, 0); + return XDP_PASS; + } + // Accessing each byte safely within bounds unsigned char byte = *((unsigned char *)tcp + i); - ((unsigned char *)ringbuf_space)[i] = byte; + event->header[i] = byte; } // Submit the data to the ring buffer - bpf_ringbuf_submit(ringbuf_space, 0); + bpf_ringbuf_submit(event, 0); // Optional: Print a debug message (will appear in kernel logs) bpf_printk("Captured TCP header (%d bytes)", tcp_header_bytes); diff --git a/src/41-xdp-tcpdump/xdp-tcpdump.c b/src/41-xdp-tcpdump/xdp-tcpdump.c index 51ca3d5..0913924 100644 --- a/src/41-xdp-tcpdump/xdp-tcpdump.c +++ b/src/41-xdp-tcpdump/xdp-tcpdump.c @@ -10,12 +10,19 @@ #include #include "xdp-tcpdump.skel.h" // Generated skeleton header +#include "xdp-tcpdump.h" // Callback function to handle events from the ring buffer static int handle_event(void *ctx, void *data, size_t data_sz) { - if (data_sz < 20) { // Minimum TCP header size - fprintf(stderr, "Received incomplete TCP header\n"); + if (data_sz < sizeof(struct tcp_event)) { + fprintf(stderr, "Received incomplete TCP event\n"); + return 0; + } + + struct tcp_event *event = data; + if (event->header_len < 20 || event->header_len > MAX_TCP_HEADER_BYTES) { + fprintf(stderr, "Invalid TCP header length: %u\n", event->header_len); return 0; } @@ -41,12 +48,7 @@ static int handle_event(void *ctx, void *data, size_t data_sz) // Options and padding may follow } __attribute__((packed)); - if (data_sz < sizeof(struct tcphdr)) { - fprintf(stderr, "Data size (%zu) less than TCP header size\n", data_sz); - return 0; - } - - struct tcphdr *tcp = (struct tcphdr *)data; + struct tcphdr *tcp = (struct tcphdr *)event->header; // Convert fields from network byte order to host byte order uint16_t source_port = ntohs(tcp->source); diff --git a/src/41-xdp-tcpdump/xdp-tcpdump.h b/src/41-xdp-tcpdump/xdp-tcpdump.h new file mode 100644 index 0000000..71c1d66 --- /dev/null +++ b/src/41-xdp-tcpdump/xdp-tcpdump.h @@ -0,0 +1,11 @@ +#ifndef __XDP_TCPDUMP_H +#define __XDP_TCPDUMP_H + +#define MAX_TCP_HEADER_BYTES 60 + +struct tcp_event { + unsigned int header_len; + unsigned char header[MAX_TCP_HEADER_BYTES]; +}; + +#endif /* __XDP_TCPDUMP_H */