fix: capture the actual TCP header length

This commit is contained in:
LinuxDev9002
2026-07-14 04:04:57 -07:00
parent 9da978cc1d
commit 7d8b706d78
5 changed files with 191 additions and 92 deletions

View File

@@ -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. 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 ### Full Kernel Code
```c ```c
#include "vmlinux.h" #include "vmlinux.h"
#include <bpf/bpf_helpers.h> #include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h> #include <bpf/bpf_endian.h>
#include "xdp-tcpdump.h"
#define ETH_P_IP 0x0800 #define ETH_P_IP 0x0800
@@ -94,29 +106,38 @@ int xdp_pass(struct xdp_md *ctx)
return XDP_PASS; return XDP_PASS;
} }
// Define the number of bytes you want to capture from the TCP header // Derive the TCP header length from data offset (doff), measured in 32-bit words
const int tcp_header_bytes = 32; __u32 tcp_header_bytes = tcp->doff * 4;
if (tcp_header_bytes < sizeof(*tcp) || tcp_header_bytes > MAX_TCP_HEADER_BYTES) {
// Ensure that the desired number of bytes does not exceed packet bounds
if ((void *)tcp + tcp_header_bytes > data_end) {
return XDP_PASS; return XDP_PASS;
} }
// Reserve space in the ring buffer // Reserve a fixed-size event because bpf_ringbuf_reserve requires a constant size
void *ringbuf_space = bpf_ringbuf_reserve(&rb, tcp_header_bytes, 0); struct tcp_event *event = bpf_ringbuf_reserve(&rb, sizeof(*event), 0);
if (!ringbuf_space) { if (!event) {
return XDP_PASS; // If reservation fails, skip processing 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 // Copy the TCP header bytes into the ring buffer
// Using a loop to ensure compliance with eBPF verifier // 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 byte = *((unsigned char *)tcp + i);
((unsigned char *)ringbuf_space)[i] = byte; event->header[i] = byte;
} }
// Submit the data to the ring buffer // Submit the data to the ring buffer
bpf_ringbuf_submit(ringbuf_space, 0); bpf_ringbuf_submit(event, 0);
// Optional: Print a debug message // Optional: Print a debug message
bpf_printk("Captured TCP header (%d bytes)", tcp_header_bytes); 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: In the `xdp_pass` function, we:
1. Parse the Ethernet, IP, and TCP headers. 1. Parse the Ethernet, IP, and TCP headers.
2. Ensure all headers are within the packet bounds to prevent invalid memory access. 2. Read and validate the TCP data-offset field, which gives the actual header length.
3. Reserve space in the ring buffer to store the TCP header. 3. Reserve a fixed-size event in the ring buffer because the helper requires a compile-time constant size.
4. Copy the TCP header bytes into the ring buffer. 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. 5. Submit the data to the ring buffer for user-space consumption.
```c ```c
// Reserve space in the ring buffer __u32 tcp_header_bytes = tcp->doff * 4;
void *ringbuf_space = bpf_ringbuf_reserve(&rb, tcp_header_bytes, 0); if (tcp_header_bytes < sizeof(*tcp) || tcp_header_bytes > MAX_TCP_HEADER_BYTES) {
if (!ringbuf_space) {
return XDP_PASS; return XDP_PASS;
} }
// Copy the TCP header bytes struct tcp_event *event = bpf_ringbuf_reserve(&rb, sizeof(*event), 0);
for (int i = 0; i < tcp_header_bytes; i++) { if (!event) {
unsigned char byte = *((unsigned char *)tcp + i); return XDP_PASS;
((unsigned char *)ringbuf_space)[i] = byte;
} }
// Submit to ring buffer event->header_len = tcp_header_bytes;
bpf_ringbuf_submit(ringbuf_space, 0); __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 #### 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 <errno.h> #include <errno.h>
#include <unistd.h> #include <unistd.h>
#include <net/if.h> #include <net/if.h>
#include <arpa/inet.h>
#include <bpf/libbpf.h> #include <bpf/libbpf.h>
#include <bpf/bpf.h> #include <bpf/bpf.h>
#include "xdp-tcpdump.skel.h" // Generated skeleton header #include "xdp-tcpdump.skel.h" // Generated skeleton header
#include "xdp-tcpdump.h"
// Callback function to handle events from the ring buffer // Callback function to handle events from the ring buffer
static int handle_event(void *ctx, void *data, size_t data_sz) static int handle_event(void *ctx, void *data, size_t data_sz)
{ {
if (data_sz < 20) { // Minimum TCP header size if (data_sz < sizeof(struct tcp_event)) {
fprintf(stderr, "Received incomplete TCP header\n"); 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; return 0;
} }
@@ -235,12 +275,7 @@ static int handle_event(void *ctx, void *data, size_t data_sz)
// Options and padding may follow // Options and padding may follow
} __attribute__((packed)); } __attribute__((packed));
if (data_sz < sizeof(struct tcphdr)) { struct tcphdr *tcp = (struct tcphdr *)event->header;
fprintf(stderr, "Data size (%zu) less than TCP header size\n", data_sz);
return 0;
}
struct tcphdr *tcp = (struct tcphdr *)data;
// Convert fields from network byte order to host byte order // Convert fields from network byte order to host byte order
uint16_t source_port = ntohs(tcp->source); 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 ```c
static int handle_event(void *ctx, void *data, size_t data_sz) static int handle_event(void *ctx, void *data, size_t data_sz)
{ {
// Validate data size if (data_sz < sizeof(struct tcp_event)) {
if (data_sz < 20) { fprintf(stderr, "Received incomplete TCP event\n");
fprintf(stderr, "Received incomplete TCP header\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; return 0;
} }
// Parse the TCP header
// ... (parsing code) // ... (parsing code)
} }
``` ```

View File

@@ -16,12 +16,24 @@ XDP 是 Linux 内核中一个高性能的数据路径,允许在网络栈的最
让我们深入了解捕获 TCP 头信息的内核空间 eBPF 代码。 让我们深入了解捕获 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 ```c
#include "vmlinux.h" #include "vmlinux.h"
#include <bpf/bpf_helpers.h> #include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h> #include <bpf/bpf_endian.h>
#include "xdp-tcpdump.h"
#define ETH_P_IP 0x0800 #define ETH_P_IP 0x0800
@@ -92,29 +104,38 @@ int xdp_pass(struct xdp_md *ctx)
return XDP_PASS; return XDP_PASS;
} }
// 定义要捕获的 TCP 头字节数 // 根据以 32 位字为单位的数据偏移字段 doff 计算 TCP 头长度
const int tcp_header_bytes = 32; __u32 tcp_header_bytes = tcp->doff * 4;
if (tcp_header_bytes < sizeof(*tcp) || tcp_header_bytes > MAX_TCP_HEADER_BYTES) {
// 确保所需字节数不超过数据包边界
if ((void *)tcp + tcp_header_bytes > data_end) {
return XDP_PASS; return XDP_PASS;
} }
// 在环形缓冲区中预留空间 // bpf_ringbuf_reserve 要求大小为常量,因此预留固定大小的事件
void *ringbuf_space = bpf_ringbuf_reserve(&rb, tcp_header_bytes, 0); struct tcp_event *event = bpf_ringbuf_reserve(&rb, sizeof(*event), 0);
if (!ringbuf_space) { if (!event) {
return XDP_PASS; // 如果预留失败,跳过处理 return XDP_PASS; // 如果预留失败,跳过处理
} }
event->header_len = tcp_header_bytes;
__builtin_memset(event->header, 0, sizeof(event->header));
// 将 TCP 头字节复制到环形缓冲区 // 将 TCP 头字节复制到环形缓冲区
// 使用循环以确保符合 eBPF 验证器要求 // 使用循环以确保符合 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 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); 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` 函数中,我们: 在 `xdp_pass` 函数中,我们:
1. 解析以太网、IP 和 TCP 头。 1. 解析以太网、IP 和 TCP 头。
2. 确保所有头信息在数据包边界内,以防止无效内存访问。 2. 读取并校验 TCP 数据偏移字段,得到实际的头长度。
3. 在环形缓冲区中预留空间以存储 TCP 头。 3. 由于辅助函数要求使用编译期常量大小,在环形缓冲区中预留固定大小的事件。
4. 将 TCP 头字节复制到环形缓冲区。 4. 逐次检查数据包边界,只把经过校验的头字节复制到事件中。
5. 提交数据到环形缓冲区,供用户空间使用。 5. 提交数据到环形缓冲区,供用户空间使用。
```c ```c
// 在环形缓冲区中预留空间 __u32 tcp_header_bytes = tcp->doff * 4;
void *ringbuf_space = bpf_ringbuf_reserve(&rb, tcp_header_bytes, 0); if (tcp_header_bytes < sizeof(*tcp) || tcp_header_bytes > MAX_TCP_HEADER_BYTES) {
if (!ringbuf_space) {
return XDP_PASS; return XDP_PASS;
} }
// 复制 TCP 头字节 struct tcp_event *event = bpf_ringbuf_reserve(&rb, sizeof(*event), 0);
for (int i = 0; i < tcp_header_bytes; i++) { if (!event) {
unsigned char byte = *((unsigned char *)tcp + i); return XDP_PASS;
((unsigned char *)ringbuf_space)[i] = byte;
} }
// 提交到环形缓冲区 event->header_len = tcp_header_bytes;
bpf_ringbuf_submit(ringbuf_space, 0); __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 进行调试 #### 使用 bpf_printk 进行调试
@@ -197,17 +229,25 @@ bpf_printk("Captured TCP header (%d bytes)", tcp_header_bytes);
#include <errno.h> #include <errno.h>
#include <unistd.h> #include <unistd.h>
#include <net/if.h> #include <net/if.h>
#include <arpa/inet.h>
#include <bpf/libbpf.h> #include <bpf/libbpf.h>
#include <bpf/bpf.h> #include <bpf/bpf.h>
#include "xdp-tcpdump.skel.h" // 生成的骨架头文件 #include "xdp-tcpdump.skel.h" // 生成的骨架头文件
#include "xdp-tcpdump.h"
// 处理环形缓冲区事件的回调函数 // 处理环形缓冲区事件的回调函数
static int handle_event(void *ctx, void *data, size_t data_sz) static int handle_event(void *ctx, void *data, size_t data_sz)
{ {
if (data_sz < 20) { // 最小 TCP 头大小 if (data_sz < sizeof(struct tcp_event)) {
fprintf(stderr, "Received incomplete TCP header\n"); 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; return 0;
} }
@@ -233,12 +273,7 @@ static int handle_event(void *ctx, void *data, size_t data_sz)
// 可能还有选项和填充 // 可能还有选项和填充
} __attribute__((packed)); } __attribute__((packed));
if (data_sz < sizeof(struct tcphdr)) { struct tcphdr *tcp = (struct tcphdr *)event->header;
fprintf(stderr, "Data size (%zu) less than TCP header size\n", data_sz);
return 0;
}
struct tcphdr *tcp = (struct tcphdr *)data;
// 将字段从网络字节序转换为主机字节序 // 将字段从网络字节序转换为主机字节序
uint16_t source_port = ntohs(tcp->source); uint16_t source_port = ntohs(tcp->source);
@@ -367,13 +402,17 @@ cleanup:
```c ```c
static int handle_event(void *ctx, void *data, size_t data_sz) static int handle_event(void *ctx, void *data, size_t data_sz)
{ {
// 验证数据大小 if (data_sz < sizeof(struct tcp_event)) {
if (data_sz < 20) { fprintf(stderr, "Received incomplete TCP event\n");
fprintf(stderr, "Received incomplete TCP header\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; return 0;
} }
// 解析 TCP 头
// ...(解析代码) // ...(解析代码)
} }
``` ```

View File

@@ -1,6 +1,7 @@
#include "vmlinux.h" #include "vmlinux.h"
#include <bpf/bpf_helpers.h> #include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h> #include <bpf/bpf_endian.h>
#include "xdp-tcpdump.h"
#define ETH_P_IP 0x0800 #define ETH_P_IP 0x0800
@@ -71,32 +72,39 @@ int xdp_pass(struct xdp_md *ctx)
return XDP_PASS; return XDP_PASS;
} }
// Define the number of bytes you want to capture from the TCP header // Derive the TCP header length from data offset (doff), measured in 32-bit words
// Typically, the TCP header is 20 bytes, but with options, it can be longer __u32 tcp_header_bytes = tcp->doff * 4;
// Here, we'll capture the first 32 bytes to include possible options if (tcp_header_bytes < sizeof(*tcp) || tcp_header_bytes > MAX_TCP_HEADER_BYTES) {
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) {
return XDP_PASS; return XDP_PASS;
} }
// Reserve space in the ring buffer // Reserve a fixed-size event because bpf_ringbuf_reserve requires a constant size
void *ringbuf_space = bpf_ringbuf_reserve(&rb, tcp_header_bytes, 0); struct tcp_event *event = bpf_ringbuf_reserve(&rb, sizeof(*event), 0);
if (!ringbuf_space) { if (!event) {
return XDP_PASS; // If reservation fails, skip processing 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 // Copy the TCP header bytes into the ring buffer
// Using a loop to ensure compliance with eBPF verifier // 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 // Accessing each byte safely within bounds
unsigned char byte = *((unsigned char *)tcp + i); unsigned char byte = *((unsigned char *)tcp + i);
((unsigned char *)ringbuf_space)[i] = byte; event->header[i] = byte;
} }
// Submit the data to the ring buffer // 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) // Optional: Print a debug message (will appear in kernel logs)
bpf_printk("Captured TCP header (%d bytes)", tcp_header_bytes); bpf_printk("Captured TCP header (%d bytes)", tcp_header_bytes);

View File

@@ -10,12 +10,19 @@
#include <bpf/bpf.h> #include <bpf/bpf.h>
#include "xdp-tcpdump.skel.h" // Generated skeleton header #include "xdp-tcpdump.skel.h" // Generated skeleton header
#include "xdp-tcpdump.h"
// Callback function to handle events from the ring buffer // Callback function to handle events from the ring buffer
static int handle_event(void *ctx, void *data, size_t data_sz) static int handle_event(void *ctx, void *data, size_t data_sz)
{ {
if (data_sz < 20) { // Minimum TCP header size if (data_sz < sizeof(struct tcp_event)) {
fprintf(stderr, "Received incomplete TCP header\n"); 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; return 0;
} }
@@ -41,12 +48,7 @@ static int handle_event(void *ctx, void *data, size_t data_sz)
// Options and padding may follow // Options and padding may follow
} __attribute__((packed)); } __attribute__((packed));
if (data_sz < sizeof(struct tcphdr)) { struct tcphdr *tcp = (struct tcphdr *)event->header;
fprintf(stderr, "Data size (%zu) less than TCP header size\n", data_sz);
return 0;
}
struct tcphdr *tcp = (struct tcphdr *)data;
// Convert fields from network byte order to host byte order // Convert fields from network byte order to host byte order
uint16_t source_port = ntohs(tcp->source); uint16_t source_port = ntohs(tcp->source);

View File

@@ -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 */