目录
前置知识
代码分析
BPF部分
功能说明
struct __sk_buff 说明
bpf_htons & bpf_ntohs
为什么有l2 + 1、l3+1
data 数据的排布
用户部分
功能说明
DECLARE_LIBBPF_OPTS
执行效果
前置知识
IP数据包的总长度指的是整个IP数据包的长度,包括IP头部和数据部分的总和,以字节为单位。IP数据包在传输过程中可能会被分片,每个分片都会有自己的IP头部和数据部分,但是所有分片的IP头部的总长度应该是相同的。
生存时间(TTL)是一个8位字段,它表示IP数据包在网络中可以被路由器转发的最大次数。当一个路由器接收到一个IP数据包时,它会将TTL减1,然后将数据包转发到下一个路由器。当TTL的值减少到0时,路由器会丢弃该数据包,并向源主机发送ICMP“生存时间超时”消息。TTL的主要作用是防止IP数据包在网络中不断循环,因为如果数据包被无限转发,它可能会占用网络资源并导致网络拥塞。通过限制数据包的生存时间,TTL可以确保数据包在网络中得到及时处理并在合理的时间内达到目的地。
代码分析
BPF部分
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
/* Copyright (c) 2022 Hengqi Chen */
#include <vmlinux.h>
#include <bpf/bpf_endian.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#define TC_ACT_OK 0
#define ETH_P_IP 0x0800 /* Internet Protocol packet */
#define IPPROTO_TCP 6
SEC("tc")
int tc_ingress(struct __sk_buff *ctx)
{
void *data_end = (void *)(__u64)ctx->data_end;
void *data = (void *)(__u64)ctx->data;
struct ethhdr *l2;
struct iphdr *l3;
struct tcphdr *l4;
if (ctx->protocol != bpf_htons(ETH_P_IP))
return TC_ACT_OK;
l2 = data;
if ((void *)(l2 + 1) > data_end)
return TC_ACT_OK;
l3 = (struct iphdr *)(l2 + 1);
if ((void *)(l3 + 1) > data_end || l3->protocol != IPPROTO_TCP)
return TC_ACT_OK;
l4 = (struct tcphdr *)(l3 + 1);
if ((void *)(l4 + 1) > data_end)
return TC_ACT_OK;
bpf_printk("Got IP packet: src_ip: %u, dst_ip: %u, src_port: %u, dst_port: %u, tot_len: %d, ttl: %d",
bpf_ntohl(l3->saddr), bpf_ntohl(l3->daddr), bpf_ntohs(l4->source), bpf_ntohs(l4->dest), bpf_ntohs(l3->tot_len), l3->ttl);
return TC_ACT_OK;
}
char __license[] SEC("license") = "GPL";
功能说明
这是一个 Traffic Control (TC) 类型的 BPF 程序,用于在数据包进入或离开网络设备时进行处理。
这里只需要TCP的包,并且会读取tcp包的目标IP、本地IP、目标端口和本地端口,最后会打印这个包的长度以及存活时间(路由器跳转最大上限)
struct __sk_buff 说明
struct __sk_buff 是 Linux 内核网络开发中非常重要的数据结构,它代表了一个网络数据包的缓冲区。这个结构体定义在文件 include/uapi/linux/bpf.h 中,是 eBPF 开发中常用的数据类型之一。
__sk_buff 结构体包含了一个网络数据包的所有信息,包括数据包的协议头部、数据部分、网络接口、传输控制块等等。在 eBPF 开发中,通过操作这个结构体,可以实现对网络数据包的过滤、转发、修改等操作。
以下是 struct __sk_buff 结构体的一些重要成员:
- data:指向数据包的数据部分的指针
- data_end:指向数据部分的结尾的指针
- protocol:数据包的协议类型,例如 ETH_P_IP 表示 IPv4 协议,ETH_P_IPV6 表示 IPv6 协议等
- len:数据包的总长度,包括协议头部和数据部分
- ifindex:接收数据包的网络接口的索引
- cb[]:一个大小为 48 字节的预留空间,可以用来在 eBPF 程序中保存一些自定义的信息。
在x86 的体系中 该结构的详细信息如下:
bpf_htons & bpf_ntohs
bpf_htons 是一个 BPF 函数,用于将一个 16 位的整数从主机字节序(即当前机器的字节序)转换为网络字节序(大端字节序)。在网络通信中,各个计算机使用的字节序可能不同,因此需要统一使用网络字节序以确保数据的正确传输。
为什么有l2 + 1、l3+1
l2 + 1不是在对l2的值加一,而是在移动指针。由于l2是一个struct ethhdr类型的指针,通过l2 + 1我们实际上获取了紧随以太网头部之后的位置,也就是下一个协议头部的起始位置。
在C语言中,当你对一个指针加一个整数时,增加的实际字节数等于这个整数乘以这个指针指向的数据类型的大小。因此,l2 + 1实际上指向了l2后面的内存,这个位置恰好是以太网头部之后的位置。
现在,(void *)(l2 + 1) > data_end这个检查确保我们没有超出数据包的尾部。如果没有这个检查,我们可能会读取超出数据包尾部的内存,这可能会导致内存访问错误。
其他同理
data 数据的排布
data指针在struct __sk_buff中指向网络数据包的数据部分。这些数据的具体排布取决于数据包的协议类型。对于一个典型的以太网TCP的数据包,数据的排布可能如下:
- 以太网头部 (struct ethhdr)
- IP头部 (struct iphdr 或 struct ipv6hdr)
- 传输层头部(如 struct tcphdr 或 struct udphdr)
- 数据载荷
用户部分
功能说明
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
/* Copyright (c) 2022 Hengqi Chen */
#include <signal.h>
#include <unistd.h>
#include "tc.skel.h"
#define LO_IFINDEX 1
static volatile sig_atomic_t exiting = 0;
static void sig_int(int signo)
{
exiting = 1;
}
static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{
return vfprintf(stderr, format, args);
}
int main(int argc, char **argv)
{
DECLARE_LIBBPF_OPTS(bpf_tc_hook, tc_hook, .ifindex = LO_IFINDEX,
.attach_point = BPF_TC_INGRESS);
DECLARE_LIBBPF_OPTS(bpf_tc_opts, tc_opts, .handle = 1, .priority = 1);
bool hook_created = false;
struct tc_bpf *skel;
int err;
libbpf_set_print(libbpf_print_fn);
skel = tc_bpf__open_and_load();
if (!skel) {
fprintf(stderr, "Failed to open BPF skeleton\n");
return 1;
}
/* The hook (i.e. qdisc) may already exists because:
* 1. it is created by other processes or users
* 2. or since we are attaching to the TC ingress ONLY,
* bpf_tc_hook_destroy does NOT really remove the qdisc,
* there may be an egress filter on the qdisc
*/
err = bpf_tc_hook_create(&tc_hook);
if (!err)
hook_created = true;
if (err && err != -EEXIST) {
fprintf(stderr, "Failed to create TC hook: %d\n", err);
goto cleanup;
}
tc_opts.prog_fd = bpf_program__fd(skel->progs.tc_ingress);
err = bpf_tc_attach(&tc_hook, &tc_opts);
if (err) {
fprintf(stderr, "Failed to attach TC: %d\n", err);
goto cleanup;
}
if (signal(SIGINT, sig_int) == SIG_ERR) {
err = errno;
fprintf(stderr, "Can't set signal handler: %s\n", strerror(errno));
goto cleanup;
}
printf("Successfully started! Please run `sudo cat /sys/kernel/debug/tracing/trace_pipe` "
"to see output of the BPF program.\n");
while (!exiting) {
fprintf(stderr, ".");
sleep(1);
}
tc_opts.flags = tc_opts.prog_fd = tc_opts.prog_id = 0;
err = bpf_tc_detach(&tc_hook, &tc_opts);
if (err) {
fprintf(stderr, "Failed to detach TC: %d\n", err);
goto cleanup;
}
cleanup:
if (hook_created)
bpf_tc_hook_destroy(&tc_hook);
tc_bpf__destroy(skel);
return -err;
}
DECLARE_LIBBPF_OPTS
- DECLARE_LIBBPF_OPTS(bpf_tc_hook, tc_hook, .ifindex = LO_IFINDEX, .attach_point = BPF_TC_INGRESS);这个声明创建了一个bpf_tc_hook结构体的实例tc_hook。bpf_tc_hook是一个libbpf提供的结构体,用于描述一个TC(Traffic Control)挂钩点。在这个结构体中,.ifindex字段是网络接口的索引号,这里被设置为LO_IFINDEX,代表本地环回设备(loopback device)的接口索引号,通常为1。.attach_point字段是挂钩点的类型,这里被设置为BPF_TC_INGRESS,代表这个挂钩点是一个入口点。
- DECLARE_LIBBPF_OPTS(bpf_tc_opts, tc_opts, .handle = 1, .priority = 1);这个声明创建了一个bpf_tc_opts结构体的实例tc_opts。bpf_tc_opts是一个libbpf提供的结构体,用于描述一个TC挂钩点的选项。在这个结构体中,.handle字段是这个挂钩点的句柄,这里被设置为1;.priority字段是这个挂钩点的优先级,这里也被设置为1。
执行效果
Chrome_ChildIOT-6868 [019] d.s3. 93109.843628: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 52762, dst_port: 37351, tot_len: 463, ttl: 64
clash-linux-8128 [018] d.s3. 93109.843773: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 37351, dst_port: 52762, tot_len: 368, ttl: 64
clash-linux-8128 [018] d.s3. 93109.843780: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 52762, dst_port: 37351, tot_len: 52, ttl: 64
clash-linux-8128 [018] d.s3. 93110.380165: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 37351, dst_port: 57856, tot_len: 72, ttl: 64
clash-linux-8128 [018] d.s3. 93110.380178: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 57856, dst_port: 37351, tot_len: 52, ttl: 64
clash-linux-31621 [017] d.s3. 93111.374526: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 37351, dst_port: 57856, tot_len: 72, ttl: 64
clash-linux-31621 [017] d.s3. 93111.374539: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 57856, dst_port: 37351, tot_len: 52, ttl: 64
clash-linux-8128 [018] d.s3. 93112.368961: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 37351, dst_port: 57856, tot_len: 72, ttl: 64
clash-linux-8128 [018] d.s3. 93112.368974: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 57856, dst_port: 37351, tot_len: 52, ttl: 64
Chrome_ChildIOT-6868 [019] d.s3. 93112.826725: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 52762, dst_port: 37351, tot_len: 463, ttl: 64
clash-linux-31621 [017] d.s3. 93112.826876: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 37351, dst_port: 52762, tot_len: 368, ttl: 64
clash-linux-31621 [017] d.s3. 93112.826883: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 52762, dst_port: 37351, tot_len: 52, ttl: 64
Chrome_ChildIOT-6868 [019] d.s3. 93113.079085: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 52762, dst_port: 37351, tot_len: 463, ttl: 64
Chrome_ChildIOT-6868 [019] d.s3. 93113.079128: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 56738, dst_port: 37351, tot_len: 463, ttl: 64
Chrome_ChildIOT-6868 [019] d.s3. 93113.079161: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 52778, dst_port: 37351, tot_len: 473, ttl: 64
clash-linux-6910 [012] d.s3. 93113.079181: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 37351, dst_port: 52762, tot_len: 368, ttl: 64
clash-linux-6910 [012] d.s3. 93113.079191: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 52762, dst_port: 37351, tot_len: 52, ttl: 64
clash-linux-31621 [017] d.s3. 93113.079587: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 37351, dst_port: 56738, tot_len: 4148, ttl: 64
clash-linux-31621 [017] d.s3. 93113.079593: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 56738, dst_port: 37351, tot_len: 52, ttl: 64
clash-linux-31621 [017] d.s3. 93113.079600: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 37351, dst_port: 56738, tot_len: 9728, ttl: 64
clash-linux-31621 [017] d.s3. 93113.079605: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 37351, dst_port: 56738, tot_len: 59, ttl: 64
clash-linux-6910 [012] d.s3. 93113.079844: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 37351, dst_port: 52778, tot_len: 4148, ttl: 64
clash-linux-6910 [012] d.s3. 93113.079851: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 52778, dst_port: 37351, tot_len: 52, ttl: 64
clash-linux-6910 [012] d.s3. 93113.079858: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 37351, dst_port: 52778, tot_len: 32248, ttl: 64
clash-linux-6910 [012] d.s3. 93113.079862: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 37351, dst_port: 52778, tot_len: 59, ttl: 64
Chrome_ChildIOT-6868 [019] d.s3. 93113.081719: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 56738, dst_port: 37351, tot_len: 52, ttl: 64
Chrome_ChildIOT-6868 [019] d.s3. 93113.081748: bpf_trace_printk: Got IP packet: src_ip: 2130706433, dst_ip: 2130706433, src_port: 52778, dst_port: 37351, tot_len: 52, ttl: 64