mirror of
https://github.com/eunomia-bpf/bpf-developer-tutorial.git
synced 2026-10-28 19:24:04 +08:00
fix: capture the actual TCP header length
This commit is contained in:
@@ -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 <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_endian.h>
|
||||
#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 <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <net/if.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <bpf/libbpf.h>
|
||||
#include <bpf/bpf.h>
|
||||
|
||||
#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)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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 <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_endian.h>
|
||||
#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 <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <net/if.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <bpf/libbpf.h>
|
||||
#include <bpf/bpf.h>
|
||||
|
||||
#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 的内容,请访问我们的教程代码仓库 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 或我们的网站 <https://eunomia.dev/tutorials/>
|
||||
|
||||
编程愉快!
|
||||
编程愉快!
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_endian.h>
|
||||
#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);
|
||||
|
||||
@@ -10,12 +10,19 @@
|
||||
#include <bpf/bpf.h>
|
||||
|
||||
#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);
|
||||
|
||||
11
src/41-xdp-tcpdump/xdp-tcpdump.h
Normal file
11
src/41-xdp-tcpdump/xdp-tcpdump.h
Normal 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 */
|
||||
Reference in New Issue
Block a user