BruceFan's Blog

Stay hungry, stay foolish

0%

eBPF XDP捕获TCP Header

捕获网络数据包对于监控、调试和保护网络通信至关重要。传统工具如tcpdump在用户空间运行,可能会带来显著的开销。通过利用eBPF和XDP,我们可以在内核中直接捕获TCP头信息,最小化开销并提高性能。

eBPF代码

eBPF部分的代码相对简单,在上一篇的基础上增加环形缓冲区,将内核中的数据传递到用户空间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>

#define ETH_P_IP 0x0800

struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 1 << 24); // 16 MB buffer
} rb SEC(".maps");

SEC("xdp")
int xdp_packet_parser(struct xdp_md *ctx) {
// get packet start and end addr
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;

// parse Ethernet header
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end) {
return XDP_ABORTED;
}
// check eth proto type
if (eth->h_proto == bpf_htons(ETH_P_IP)) { // IPv4
struct iphdr *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end) {
return XDP_ABORTED;
}
// check ip proto type
if (ip->protocol == IPPROTO_TCP) { // TCP
struct tcphdr *tcp = (void *)((void *)ip + (ip->ihl * 4));
if ((void *)(tcp + 1) > data_end) {
return XDP_ABORTED;
}
const int tcp_header_bytes = 20;
if ((void *)tcp + tcp_header_bytes > data_end) {
return XDP_PASS;
}
void *tcp_headers = bpf_ringbuf_reserve(&rb, tcp_header_bytes, 0);
if (!tcp_headers) {
return XDP_PASS;
}
for (int i = 0; i < tcp_header_bytes; i++) {
unsigned char byte = *((unsigned char *)tcp + i);
((unsigned char *)tcp_headers)[i] = byte;
}
bpf_ringbuf_submit(tcp_headers, 0);
}
}
return XDP_PASS;
}

char LICENSE[] SEC("license") = "GPL";

用户空间代码

用户空间中增加了TCP头解析的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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");
return 0;
}

// Parse the raw TCP header bytes
struct tcphdr {
uint16_t source;
uint16_t dest;
uint32_t seq;
uint32_t ack_seq;
uint16_t res1:4,
doff:4,
fin:1,
syn:1,
rst:1,
psh:1,
ack:1,
urg:1,
ece:1,
cwr:1;
uint16_t window;
uint16_t check;
uint16_t urg_ptr;
// 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;

// Convert fields from network byte order to host byte order
uint16_t source_port = ntohs(tcp->source);
uint16_t dest_port = ntohs(tcp->dest);
uint32_t seq = ntohl(tcp->seq);
uint32_t ack_seq = ntohl(tcp->ack_seq);
uint16_t window = ntohs(tcp->window);

// Extract flags
uint8_t flags = 0;
flags |= (tcp->fin) ? 0x01 : 0x00;
flags |= (tcp->syn) ? 0x02 : 0x00;
flags |= (tcp->rst) ? 0x04 : 0x00;
flags |= (tcp->psh) ? 0x08 : 0x00;
flags |= (tcp->ack) ? 0x10 : 0x00;
flags |= (tcp->urg) ? 0x20 : 0x00;
flags |= (tcp->ece) ? 0x40 : 0x00;
flags |= (tcp->cwr) ? 0x80 : 0x00;

printf("Captured TCP Header:\n");
printf(" Source Port: %u\n", source_port);
printf(" Destination Port: %u\n", dest_port);
printf(" Sequence Number: %u\n", seq);
printf(" Acknowledgment Number: %u\n", ack_seq);
printf(" Data Offset: %u\n", tcp->doff);
printf(" Flags: 0x%02x\n", flags);
printf(" Window Size: %u\n", window);
printf("\n");

return 0;
}

在main函数中增加了环形缓冲区读取的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
rb = ring_buffer__new(bpf_map__fd(skel->maps.rb), handle_event, NULL, NULL);
if (!rb) {
fprintf(stderr, "Failed to create ring buffer\n");
err = -1;
goto cleanup;
}

while (!exiting) {
err = ring_buffer__poll(rb, -1);
if (err == -EINTR)
continue;
if (err < 0)
{
fprintf(stderr, "Error polling ring buffer: %d\n", err);
break;
}
}

编译运行

编译方法还是把代码丢到libbpf-bootstrap里,运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ sudo ./xdp_dump eno1
Captured TCP Header:
Source Port: 49830
Destination Port: 22
Sequence Number: 3010216684
Acknowledgment Number: 2728834088
Data Offset: 8
Flags: 0x10
Window Size: 2048

Captured TCP Header:
Source Port: 49830
Destination Port: 22
Sequence Number: 3010216684
Acknowledgment Number: 2728834316
Data Offset: 8
Flags: 0x10
Window Size: 2048

reference
https://eunomia.dev/zh/tutorials/41-xdp-tcpdump/