mirror of
https://github.com/eunomia-bpf/bpf-developer-tutorial.git
synced 2026-10-28 19:24:04 +08:00
Fix verified eBPF tutorial examples and instructions (#211)
* docs: fix XDP stub compile command * docs: link hardirqs example headers * fix: capture the actual TCP header length * docs: refresh generated tutorial index * Revert "docs: refresh generated tutorial index" This reverts commit6a2418a277. * fix: print TCP header length as unsigned * docs: sync TCP header debug format * fix: address final tutorial portability review * docs: auto-generate documentation * Revert "docs: auto-generate documentation" This reverts commitbe93763602. --------- Co-authored-by: LinuxDev9002 <i@littlefisher.me> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
@@ -35,6 +35,8 @@ By following the above approach, we can use hardirqs and softirqs in eBPF to cap
|
||||
|
||||
The main purpose of the hardirqs program is to obtain the name, execution count, and execution time of interrupt handlers and display the distribution of execution time in the form of a histogram. Let's analyze this code step by step.
|
||||
|
||||
The custom headers used below are part of the complete example rather than system headers. Build from the `src/10-hardirqs` directory, which contains [`hardirqs.h`](https://github.com/eunomia-bpf/bpf-developer-tutorial/blob/main/src/10-hardirqs/hardirqs.h), [`bits.bpf.h`](https://github.com/eunomia-bpf/bpf-developer-tutorial/blob/main/src/10-hardirqs/bits.bpf.h), and [`maps.bpf.h`](https://github.com/eunomia-bpf/bpf-developer-tutorial/blob/main/src/10-hardirqs/maps.bpf.h) alongside the source file.
|
||||
|
||||
```c
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
// Copyright (c) 2020 Wenbo Zhang
|
||||
|
||||
@@ -26,6 +26,8 @@ softirqs 是软件中断处理程序。它们是内核中的一种底层异步
|
||||
|
||||
hardirqs 程序的主要目的是获取中断处理程序的名称、执行次数和执行时间,并以直方图的形式展示执行时间的分布。让我们一步步分析这段代码。
|
||||
|
||||
下面引用的自定义头文件属于完整示例,并不是系统头文件。请在 `src/10-hardirqs` 目录中编译,该目录在源码旁提供了 [`hardirqs.h`](https://github.com/eunomia-bpf/bpf-developer-tutorial/blob/main/src/10-hardirqs/hardirqs.h)、[`bits.bpf.h`](https://github.com/eunomia-bpf/bpf-developer-tutorial/blob/main/src/10-hardirqs/bits.bpf.h) 和 [`maps.bpf.h`](https://github.com/eunomia-bpf/bpf-developer-tutorial/blob/main/src/10-hardirqs/maps.bpf.h)。
|
||||
|
||||
```c
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
// Copyright (c) 2020 Wenbo Zhang
|
||||
|
||||
@@ -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,32 +106,41 @@ 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);
|
||||
bpf_printk("Captured TCP header (%u bytes)", tcp_header_bytes);
|
||||
|
||||
return XDP_PASS;
|
||||
}
|
||||
@@ -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
|
||||
@@ -183,7 +215,7 @@ bpf_ringbuf_submit(ringbuf_space, 0);
|
||||
The `bpf_printk` function logs messages to the kernel's trace pipe, which can be invaluable for debugging.
|
||||
|
||||
```c
|
||||
bpf_printk("Captured TCP header (%d bytes)", tcp_header_bytes);
|
||||
bpf_printk("Captured TCP header (%u bytes)", tcp_header_bytes);
|
||||
```
|
||||
|
||||
## User-Space Code Analysis
|
||||
@@ -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,32 +104,41 @@ 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);
|
||||
bpf_printk("Captured TCP header (%u bytes)", tcp_header_bytes);
|
||||
|
||||
return XDP_PASS;
|
||||
}
|
||||
@@ -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 进行调试
|
||||
@@ -181,7 +213,7 @@ bpf_ringbuf_submit(ringbuf_space, 0);
|
||||
`bpf_printk` 函数将消息记录到内核的跟踪管道,对于调试非常有用。
|
||||
|
||||
```c
|
||||
bpf_printk("Captured TCP header (%d bytes)", tcp_header_bytes);
|
||||
bpf_printk("Captured TCP header (%u bytes)", tcp_header_bytes);
|
||||
```
|
||||
|
||||
## 用户空间代码分析
|
||||
@@ -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,35 +72,42 @@ 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);
|
||||
bpf_printk("Captured TCP header (%u bytes)", tcp_header_bytes);
|
||||
|
||||
return XDP_PASS;
|
||||
}
|
||||
|
||||
@@ -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_ */
|
||||
@@ -450,7 +450,7 @@ To run the XDP load balancer, execute the following command, specifying the inte
|
||||
To make XDP work with veth interfaces we need to add a stub eBPF program to the other end of the pair.
|
||||
|
||||
```console
|
||||
clang -target bpf -o stub.bpf.o -c stub.bpf.c
|
||||
clang -target bpf -I/usr/include/$(gcc -print-multiarch) -o stub.bpf.o -c stub.bpf.c
|
||||
sudo ip l set dev veth7 xdp obj ./stub.bpf.o sec .xdp # stub XDP_PASS on the receiving interface
|
||||
sudo ip netns exec lb ./xdp_lb veth6 10.0.0.2 de:ad:be:ef:00:02 10.0.0.3 de:ad:be:ef:00:03
|
||||
```
|
||||
|
||||
@@ -446,9 +446,11 @@ sudo ./teardown.sh
|
||||
|
||||
### 运行负载均衡器
|
||||
|
||||
要运行 XDP 负载均衡器,执行以下命令,指定接口和后端服务器的 IP 和 MAC 地址:
|
||||
要运行 XDP 负载均衡器,执行以下命令,指定接口和后端服务器的 IP 和 MAC 地址。为了让 XDP 在 veth 接口上工作,还需要在 veth 对的另一端挂载一个返回 `XDP_PASS` 的桩程序。
|
||||
|
||||
```console
|
||||
clang -target bpf -I/usr/include/$(gcc -print-multiarch) -o stub.bpf.o -c stub.bpf.c
|
||||
sudo ip l set dev veth7 xdp obj ./stub.bpf.o sec .xdp
|
||||
sudo ip netns exec lb ./xdp_lb veth6 10.0.0.2 de:ad:be:ef:00:02 10.0.0.3 de:ad:be:ef:00:03
|
||||
```
|
||||
|
||||
@@ -556,4 +558,4 @@ sudo bpftrace -e 'tracepoint:xdp:xdp_bulk_tx{@redir_errno[-args->err] = count();
|
||||
|
||||
### 参考文献
|
||||
|
||||
- [XDP 编程实践教程](https://github.com/xdp-project/xdp-tutorial)
|
||||
- [XDP 编程实践教程](https://github.com/xdp-project/xdp-tutorial)
|
||||
|
||||
Reference in New Issue
Block a user