mirror of
https://github.com/eunomia-bpf/bpf-developer-tutorial.git
synced 2026-02-03 18:24:27 +08:00
update the tcprtt and summary
This commit is contained in:
@@ -1,20 +1,37 @@
|
||||
# eBPF入门实践教程:使用 libbpf-bootstrap 开发程序统计 TCP 连接延时
|
||||
# eBPF入门实践教程十四:记录 TCP 连接状态与 TCP RTT
|
||||
|
||||
## 内核态代码
|
||||
eBPF (扩展的伯克利数据包过滤器) 是一项强大的网络和性能分析工具,被广泛应用在 Linux 内核上。eBPF 使得开发者能够动态地加载、更新和运行用户定义的代码,而无需重启内核或更改内核源代码。
|
||||
|
||||
在我们的 eBPF 入门实践教程系列的这一篇,我们将介绍两个示例程序:`tcpstates` 和 `tcprtt`。`tcpstates` 用于记录 TCP 连接的状态变化,而 `tcprtt` 则用于记录 TCP 的往返时间 (RTT, Round-Trip Time)。
|
||||
|
||||
## `tcprtt` 与 `tcpstates`
|
||||
|
||||
网络质量在当前的互联网环境中至关重要。影响网络质量的因素有许多,包括硬件、网络环境、软件编程的质量等。为了帮助用户更好地定位网络问题,我们引入了 `tcprtt` 这个工具。`tcprtt` 可以监控 TCP 链接的往返时间,从而评估网络质量,帮助用户找出可能的问题所在。
|
||||
|
||||
当 TCP 链接建立时,`tcprtt` 会自动根据当前系统的状况,选择合适的执行函数。在执行函数中,`tcprtt` 会收集 TCP 链接的各项基本信息,如源地址、目标地址、源端口、目标端口、耗时等,并将这些信息更新到直方图型的 BPF map 中。运行结束后,`tcprtt` 会通过用户态代码,将收集的信息以图形化的方式展示给用户。
|
||||
|
||||
`tcpstates` 则是一个专门用来追踪和打印 TCP 连接状态变化的工具。它可以显示 TCP 连接在每个状态中的停留时长,单位为毫秒。例如,对于一个单独的 TCP 会话,`tcpstates` 可以打印出类似以下的输出:
|
||||
|
||||
```sh
|
||||
SKADDR C-PID C-COMM LADDR LPORT RADDR RPORT OLDSTATE -> NEWSTATE MS
|
||||
ffff9fd7e8192000 22384 curl 100.66.100.185 0 52.33.159.26 80 CLOSE -> SYN_SENT 0.000
|
||||
ffff9fd7e8192000 0 swapper/5 100.66.100.185 63446 52.33.159.26 80 SYN_SENT -> ESTABLISHED 1.373
|
||||
ffff9fd7e8192000 22384 curl 100.66.100.185 63446 52.33.159.26 80 ESTABLISHED -> FIN_WAIT1 176.042
|
||||
ffff9fd7e819
|
||||
|
||||
2000 0 swapper/5 100.66.100.185 63446 52.33.159.26 80 FIN_WAIT1 -> FIN_WAIT2 0.536
|
||||
ffff9fd7e8192000 0 swapper/5 100.66.100.185 63446 52.33.159.26 80 FIN_WAIT2 -> CLOSE 0.006
|
||||
```
|
||||
|
||||
以上输出中,最多的时间被花在了 ESTABLISHED 状态,也就是连接已经建立并在传输数据的状态,这个状态到 FIN_WAIT1 状态(开始关闭连接的状态)的转变过程中耗费了 176.042 毫秒。
|
||||
|
||||
在我们接下来的教程中,我们会更深入地探讨这两个工具,解释它们的实现原理,希望这些内容对你在使用 eBPF 进行网络和性能分析方面的工作有所帮助。
|
||||
|
||||
## tcpstate
|
||||
|
||||
由于篇幅所限,这里我们主要讨论和分析对应的 eBPF 内核态代码实现。以下是 tcpstate 的 eBPF 代码:
|
||||
|
||||
```c
|
||||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
/* Copyright (c) 2021 Hengqi Chen */
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include "tcpstates.h"
|
||||
|
||||
#define MAX_ENTRIES 10240
|
||||
#define AF_INET 2
|
||||
#define AF_INET6 10
|
||||
|
||||
const volatile bool filter_by_sport = false;
|
||||
const volatile bool filter_by_dport = false;
|
||||
const volatile short target_family = 0;
|
||||
@@ -103,97 +120,25 @@ int handle_set_state(struct trace_event_raw_inet_sock_set_state *ctx)
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
```
|
||||
|
||||
```tcpstates``` 是一个追踪当前系统上的TCP套接字的TCP状态的程序,主要通过跟踪内核跟踪点 ```inet_sock_set_state``` 来实现。统计数据通过 ```perf_event```向用户态传输。
|
||||
`tcpstates`主要依赖于 eBPF 的 Tracepoints 来捕获 TCP 连接的状态变化,从而跟踪 TCP 连接在每个状态下的停留时间。
|
||||
|
||||
```c
|
||||
SEC("tracepoint/sock/inet_sock_set_state")
|
||||
int handle_set_state(struct trace_event_raw_inet_sock_set_state *ctx)
|
||||
```
|
||||
### 定义 BPF Maps
|
||||
|
||||
在套接字改变状态处附加一个eBPF跟踪函数。
|
||||
在`tcpstates`程序中,首先定义了几个 BPF Maps,它们是 eBPF 程序和用户态程序之间交互的主要方式。`sports`和`dports`分别用于存储源端口和目标端口,用于过滤 TCP 连接;`timestamps`用于存储每个 TCP 连接的时间戳,以计算每个状态的停留时间;`events`则是一个 perf_event 类型的 map,用于将事件数据发送到用户态。
|
||||
|
||||
```c
|
||||
if (ctx->protocol != IPPROTO_TCP)
|
||||
return 0;
|
||||
### 追踪 TCP 连接状态变化
|
||||
|
||||
if (target_family && target_family != family)
|
||||
return 0;
|
||||
程序定义了一个名为`handle_set_state`的函数,该函数是一个 tracepoint 类型的程序,它将被挂载到`sock/inet_sock_set_state`这个内核 tracepoint 上。每当 TCP 连接状态发生变化时,这个 tracepoint 就会被触发,然后执行`handle_set_state`函数。
|
||||
|
||||
if (filter_by_sport && !bpf_map_lookup_elem(&sports, &sport))
|
||||
return 0;
|
||||
在`handle_set_state`函数中,首先通过一系列条件判断确定是否需要处理当前的 TCP 连接,然后从`timestamps`map 中获取当前连接的上一个时间戳,然后计算出停留在当前状态的时间。接着,程序将收集到的数据放入一个 event 结构体中,并通过`bpf_perf_event_output`函数将该 event 发送到用户态。
|
||||
|
||||
if (filter_by_dport && !bpf_map_lookup_elem(&dports, &dport))
|
||||
return 0;
|
||||
```
|
||||
### 更新时间戳
|
||||
|
||||
跟踪函数被调用后,先判断当前改变状态的套接字是否满足我们需要的过滤条件,如果不满足则不进行记录。
|
||||
最后,根据 TCP 连接的新状态,程序将进行不同的操作:如果新状态为 TCP_CLOSE,表示连接已关闭,程序将从`timestamps`map 中删除该连接的时间戳;否则,程序将更新该连接的时间戳。
|
||||
|
||||
```c
|
||||
tsp = bpf_map_lookup_elem(×tamps, &sk);
|
||||
ts = bpf_ktime_get_ns();
|
||||
if (!tsp)
|
||||
delta_us = 0;
|
||||
else
|
||||
delta_us = (ts - *tsp) / 1000;
|
||||
|
||||
event.skaddr = (__u64)sk;
|
||||
event.ts_us = ts / 1000;
|
||||
event.delta_us = delta_us;
|
||||
event.pid = bpf_get_current_pid_tgid() >> 32;
|
||||
event.oldstate = ctx->oldstate;
|
||||
event.newstate = ctx->newstate;
|
||||
event.family = family;
|
||||
event.sport = sport;
|
||||
event.dport = dport;
|
||||
bpf_get_current_comm(&event.task, sizeof(event.task));
|
||||
|
||||
if (family == AF_INET) {
|
||||
bpf_probe_read_kernel(&event.saddr, sizeof(event.saddr), &sk->__sk_common.skc_rcv_saddr);
|
||||
bpf_probe_read_kernel(&event.daddr, sizeof(event.daddr), &sk->__sk_common.skc_daddr);
|
||||
} else { /* family == AF_INET6 */
|
||||
bpf_probe_read_kernel(&event.saddr, sizeof(event.saddr), &sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32);
|
||||
bpf_probe_read_kernel(&event.daddr, sizeof(event.daddr), &sk->__sk_common.skc_v6_daddr.in6_u.u6_addr32);
|
||||
}
|
||||
```
|
||||
|
||||
使用状态改变相关填充event结构体。
|
||||
|
||||
- 此处使用了```libbpf``` 的 CO-RE 支持。
|
||||
|
||||
```c
|
||||
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event));
|
||||
```
|
||||
|
||||
将事件结构体发送至用户态程序。
|
||||
|
||||
```c
|
||||
if (ctx->newstate == TCP_CLOSE)
|
||||
bpf_map_delete_elem(×tamps, &sk);
|
||||
else
|
||||
bpf_map_update_elem(×tamps, &sk, &ts, BPF_ANY);
|
||||
```
|
||||
|
||||
根据这个TCP链接的新状态,决定是更新下时间戳记录还是不再记录它的时间戳。
|
||||
|
||||
## 用户态程序
|
||||
|
||||
```c
|
||||
while (!exiting) {
|
||||
err = perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS);
|
||||
if (err < 0 && err != -EINTR) {
|
||||
warn("error polling perf buffer: %s\n", strerror(-err));
|
||||
goto cleanup;
|
||||
}
|
||||
/* reset err to return 0 if exiting */
|
||||
err = 0;
|
||||
}
|
||||
```
|
||||
|
||||
不停轮询内核程序所发过来的 ```perf event```。
|
||||
用户态的部分主要是通过 libbpf 来加载 eBPF 程序,然后通过 perf_event 来接收内核中的事件数据:
|
||||
|
||||
```c
|
||||
static void handle_event(void* ctx, int cpu, void* data, __u32 data_sz) {
|
||||
@@ -228,16 +173,102 @@ static void handle_event(void* ctx, int cpu, void* data, __u32 data_sz) {
|
||||
(double)e->delta_us / 1000);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
static void handle_lost_events(void* ctx, int cpu, __u64 lost_cnt) {
|
||||
warn("lost %llu events on CPU #%d\n", lost_cnt, cpu);
|
||||
`handle_event`就是这样一个回调函数,它会被 perf_event 调用,每当内核有新的事件到达时,它就会处理这些事件。
|
||||
|
||||
在`handle_event`函数中,我们首先通过`inet_ntop`函数将二进制的 IP 地址转换成人类可读的格式,然后根据是否需要输出宽格式,分别打印不同的信息。这些信息包括了事件的时间戳、源 IP 地址、源端口、目标 IP 地址、目标端口、旧状态、新状态以及在旧状态停留的时间。
|
||||
|
||||
这样,用户就可以清晰地看到 TCP 连接状态的变化,以及每个状态的停留时间,从而帮助他们诊断网络问题。
|
||||
|
||||
总结起来,用户态部分的处理主要涉及到了以下几个步骤:
|
||||
|
||||
1. 使用 libbpf 加载并运行 eBPF 程序。
|
||||
2. 设置回调函数来接收内核发送的事件。
|
||||
3. 处理接收到的事件,将其转换成人类可读的格式并打印。
|
||||
|
||||
以上就是`tcpstates`程序用户态部分的主要实现逻辑。通过这一章的学习,你应该已经对如何在用户态处理内核事件有了更深入的理解。在下一章中,我们将介绍更多关于如何使用 eBPF 进行网络监控的知识。
|
||||
|
||||
### tcprtt
|
||||
|
||||
在本章节中,我们将分析`tcprtt` eBPF 程序的内核态代码。`tcprtt`是一个用于测量 TCP 往返时间(Round Trip Time, RTT)的程序,它将 RTT 的信息统计到一个 histogram 中。
|
||||
|
||||
```c
|
||||
|
||||
/// @sample {"interval": 1000, "type" : "log2_hist"}
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, MAX_ENTRIES);
|
||||
__type(key, u64);
|
||||
__type(value, struct hist);
|
||||
} hists SEC(".maps");
|
||||
|
||||
static struct hist zero;
|
||||
|
||||
SEC("fentry/tcp_rcv_established")
|
||||
int BPF_PROG(tcp_rcv, struct sock *sk)
|
||||
{
|
||||
const struct inet_sock *inet = (struct inet_sock *)(sk);
|
||||
struct tcp_sock *ts;
|
||||
struct hist *histp;
|
||||
u64 key, slot;
|
||||
u32 srtt;
|
||||
|
||||
if (targ_sport && targ_sport != inet->inet_sport)
|
||||
return 0;
|
||||
if (targ_dport && targ_dport != sk->__sk_common.skc_dport)
|
||||
return 0;
|
||||
if (targ_saddr && targ_saddr != inet->inet_saddr)
|
||||
return 0;
|
||||
if (targ_daddr && targ_daddr != sk->__sk_common.skc_daddr)
|
||||
return 0;
|
||||
|
||||
if (targ_laddr_hist)
|
||||
key = inet->inet_saddr;
|
||||
else if (targ_raddr_hist)
|
||||
key = inet->sk.__sk_common.skc_daddr;
|
||||
else
|
||||
key = 0;
|
||||
histp = bpf_map_lookup_or_try_init(&hists, &key, &zero);
|
||||
if (!histp)
|
||||
return 0;
|
||||
ts = (struct tcp_sock *)(sk);
|
||||
srtt = BPF_CORE_READ(ts, srtt_us) >> 3;
|
||||
if (targ_ms)
|
||||
srtt /= 1000U;
|
||||
slot = log2l(srtt);
|
||||
if (slot >= MAX_SLOTS)
|
||||
slot = MAX_SLOTS - 1;
|
||||
__sync_fetch_and_add(&histp->slots[slot], 1);
|
||||
if (targ_show_ext) {
|
||||
__sync_fetch_and_add(&histp->latency, srtt);
|
||||
__sync_fetch_and_add(&histp->cnt, 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
收到事件后所调用对应的处理函数并进行输出打印。
|
||||
首先,我们定义了一个 hash 类型的 eBPF map,名为`hists`,它用来存储 RTT 的统计信息。在这个 map 中,键是 64 位整数,值是一个`hist`结构,这个结构包含了一个数组,用来存储不同 RTT 区间的数量。
|
||||
|
||||
接着,我们定义了一个 eBPF 程序,名为`tcp_rcv`,这个程序会在每次内核中处理 TCP 收包的时候被调用。在这个程序中,我们首先根据过滤条件(源/目标 IP 地址和端口)对 TCP 连接进行过滤。如果满足条件,我们会根据设置的参数选择相应的 key(源 IP 或者目标 IP 或者 0),然后在`hists` map 中查找或者初始化对应的 histogram。
|
||||
|
||||
接下来,我们读取 TCP 连接的`srtt_us`字段,这个字段表示了平滑的 RTT 值,单位是微秒。然后我们将这个 RTT 值转换为对数形式,并将其作为 slot 存储到 histogram 中。
|
||||
|
||||
如果设置了`show_ext`参数,我们还会将 RTT 值和计数器累加到 histogram 的`latency`和`cnt`字段中。
|
||||
|
||||
通过以上的处理,我们可以对每个 TCP 连接的 RTT 进行统计和分析,从而更好地理解网络的性能状况。
|
||||
|
||||
总结起来,`tcprtt` eBPF 程序的主要逻辑包括以下几个步骤:
|
||||
|
||||
1. 根据过滤条件对 TCP 连接进行过滤。
|
||||
2. 在`hists` map 中查找或者初始化对应的 histogram。
|
||||
3. 读取 TCP 连接的`srtt_us`字段,并将其转换为对数形式,存储到 histogram 中。
|
||||
4. 如果设置了`show_ext`参数,将 RTT 值和计数器累加到 histogram 的`latency`和`cnt`字段中。
|
||||
|
||||
## 编译运行
|
||||
|
||||
对于 tcpstates,可以通过以下命令编译和运行 libbpf 应用:
|
||||
|
||||
```console
|
||||
$ make
|
||||
...
|
||||
@@ -258,6 +289,99 @@ ffff9bf6d8ee88c0 229832 redis-serv 0.0.0.0 6379 0.0.0.0 0
|
||||
ffff9bf7109d6900 88750 node 127.0.0.1 39755 127.0.0.1 50966 ESTABLISHED -> FIN_WAIT1 0.000
|
||||
```
|
||||
|
||||
对于 tcprtt,我们可以使用 eunomia-bpf 编译运行这个例子:
|
||||
|
||||
Compile:
|
||||
|
||||
```shell
|
||||
docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest
|
||||
```
|
||||
|
||||
或者
|
||||
|
||||
```console
|
||||
$ ecc runqlat.bpf.c runqlat.h
|
||||
Compiling bpf object...
|
||||
Generating export types...
|
||||
Packing ebpf object and config into package.json...
|
||||
```
|
||||
|
||||
运行:
|
||||
|
||||
```console
|
||||
$ sudo ecli run package.json -h
|
||||
A simple eBPF program
|
||||
|
||||
|
||||
Usage: package.json [OPTIONS]
|
||||
|
||||
Options:
|
||||
--verbose Whether to show libbpf debug information
|
||||
--targ_laddr_hist Set value of `bool` variable targ_laddr_hist
|
||||
--targ_raddr_hist Set value of `bool` variable targ_raddr_hist
|
||||
--targ_show_ext Set value of `bool` variable targ_show_ext
|
||||
--targ_sport <targ_sport> Set value of `__u16` variable targ_sport
|
||||
--targ_dport <targ_dport> Set value of `__u16` variable targ_dport
|
||||
--targ_saddr <targ_saddr> Set value of `__u32` variable targ_saddr
|
||||
--targ_daddr <targ_daddr> Set value of `__u32` variable targ_daddr
|
||||
--targ_ms Set value of `bool` variable targ_ms
|
||||
-h, --help Print help
|
||||
-V, --version Print version
|
||||
|
||||
Built with eunomia-bpf framework.
|
||||
See https://github.com/eunomia-bpf/eunomia-bpf for more information.
|
||||
|
||||
$ sudo ecli run package.json
|
||||
key = 0
|
||||
latency = 0
|
||||
cnt = 0
|
||||
|
||||
(unit) : count distribution
|
||||
0 -> 1 : 0 | |
|
||||
2 -> 3 : 0 | |
|
||||
4 -> 7 : 0 | |
|
||||
8 -> 15 : 0 | |
|
||||
16 -> 31 : 0 | |
|
||||
32 -> 63 : 0 | |
|
||||
64 -> 127 : 0 | |
|
||||
128 -> 255 : 0 | |
|
||||
256 -> 511 : 0 | |
|
||||
512 -> 1023 : 4 |******************** |
|
||||
1024 -> 2047 : 1 |***** |
|
||||
2048 -> 4095 : 0 | |
|
||||
4096 -> 8191 : 8 |****************************************|
|
||||
|
||||
key = 0
|
||||
latency = 0
|
||||
cnt = 0
|
||||
|
||||
(unit) : count distribution
|
||||
0 -> 1 : 0 | |
|
||||
2 -> 3 : 0 | |
|
||||
4 -> 7 : 0 | |
|
||||
8 -> 15 : 0 | |
|
||||
16 -> 31 : 0 | |
|
||||
32 -> 63 : 0 | |
|
||||
64 -> 127 : 0 | |
|
||||
128 -> 255 : 0 | |
|
||||
256 -> 511 : 0 | |
|
||||
512 -> 1023 : 11 |*************************** |
|
||||
1024 -> 2047 : 1 |** |
|
||||
2048 -> 4095 : 0 | |
|
||||
4096 -> 8191 : 16 |****************************************|
|
||||
8192 -> 16383 : 4 |********** |
|
||||
```
|
||||
|
||||
参考资料:
|
||||
|
||||
- [tcpstates](https://github.com/iovisor/bcc/blob/master/tools/tcpstates_example.txt)
|
||||
- [tcprtt](https://github.com/iovisor/bcc/blob/master/tools/tcprtt.py)
|
||||
- <https://github.com/iovisor/bcc/blob/master/libbpf-tools/tcpstates.bpf.c>
|
||||
|
||||
## 总结
|
||||
|
||||
这里的代码修改自 <https://github.com/iovisor/bcc/blob/master/libbpf-tools/tcpstates.bpf.c>
|
||||
通过本篇 eBPF 入门实践教程,我们学习了如何使用tcpstates和tcprtt这两个 eBPF 示例程序,监控和分析 TCP 的连接状态和往返时间。我们了解了tcpstates和tcprtt的工作原理和实现方式,包括如何使用 BPF map 存储数据,如何在 eBPF 程序中获取和处理 TCP 连接信息,以及如何在用户态应用程序中解析和显示 eBPF 程序收集的数据。这些知识和技巧对于使用 eBPF 进行网络和性能分析是非常重要的。
|
||||
|
||||
如果您希望学习更多关于 eBPF 的知识和实践,请查阅 eunomia-bpf 的官方文档:<https://github.com/eunomia-bpf/eunomia-bpf> 。您还可以访问我们的教程代码仓库 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 以获取更多示例和完整的教程。
|
||||
|
||||
接下来的教程将进一步探讨 eBPF 的高级特性,我们会继续分享更多有关 eBPF 开发实践的内容。
|
||||
|
||||
@@ -183,257 +183,6 @@ static void handle_lost_events(void* ctx, int cpu, __u64 lost_cnt) {
|
||||
warn("lost %llu events on CPU #%d\n", lost_cnt, cpu);
|
||||
}
|
||||
|
||||
extern unsigned char _binary_min_core_btfs_tar_gz_start[] __attribute__((weak));
|
||||
extern unsigned char _binary_min_core_btfs_tar_gz_end[] __attribute__((weak));
|
||||
|
||||
|
||||
/* tar header from
|
||||
* https://github.com/tklauser/libtar/blob/v1.2.20/lib/libtar.h#L39-L60 */
|
||||
struct tar_header {
|
||||
char name[100];
|
||||
char mode[8];
|
||||
char uid[8];
|
||||
char gid[8];
|
||||
char size[12];
|
||||
char mtime[12];
|
||||
char chksum[8];
|
||||
char typeflag;
|
||||
char linkname[100];
|
||||
char magic[6];
|
||||
char version[2];
|
||||
char uname[32];
|
||||
char gname[32];
|
||||
char devmajor[8];
|
||||
char devminor[8];
|
||||
char prefix[155];
|
||||
char padding[12];
|
||||
};
|
||||
|
||||
static char* tar_file_start(struct tar_header* tar,
|
||||
const char* name,
|
||||
int* length) {
|
||||
while (tar->name[0]) {
|
||||
sscanf(tar->size, "%o", length);
|
||||
if (!strcmp(tar->name, name))
|
||||
return (char*)(tar + 1);
|
||||
tar += 1 + (*length + 511) / 512;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
#define FIELD_LEN 65
|
||||
#define ID_FMT "ID=%64s"
|
||||
#define VERSION_FMT "VERSION_ID=\"%64s"
|
||||
|
||||
struct os_info {
|
||||
char id[FIELD_LEN];
|
||||
char version[FIELD_LEN];
|
||||
char arch[FIELD_LEN];
|
||||
char kernel_release[FIELD_LEN];
|
||||
};
|
||||
|
||||
static struct os_info* get_os_info() {
|
||||
struct os_info* info = NULL;
|
||||
struct utsname u;
|
||||
size_t len = 0;
|
||||
ssize_t read;
|
||||
char* line = NULL;
|
||||
FILE* f;
|
||||
|
||||
if (uname(&u) == -1)
|
||||
return NULL;
|
||||
|
||||
f = fopen("/etc/os-release", "r");
|
||||
if (!f)
|
||||
return NULL;
|
||||
|
||||
info = calloc(1, sizeof(*info));
|
||||
if (!info)
|
||||
goto out;
|
||||
|
||||
strncpy(info->kernel_release, u.release, FIELD_LEN);
|
||||
strncpy(info->arch, u.machine, FIELD_LEN);
|
||||
|
||||
while ((read = getline(&line, &len, f)) != -1) {
|
||||
if (sscanf(line, ID_FMT, info->id) == 1)
|
||||
continue;
|
||||
|
||||
if (sscanf(line, VERSION_FMT, info->version) == 1) {
|
||||
/* remove '"' suffix */
|
||||
info->version[strlen(info->version) - 1] = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
free(line);
|
||||
fclose(f);
|
||||
|
||||
return info;
|
||||
}
|
||||
#define INITIAL_BUF_SIZE (1024 * 1024 * 4) /* 4MB */
|
||||
|
||||
/* adapted from https://zlib.net/zlib_how.html */
|
||||
static int inflate_gz(unsigned char* src,
|
||||
int src_size,
|
||||
unsigned char** dst,
|
||||
int* dst_size) {
|
||||
size_t size = INITIAL_BUF_SIZE;
|
||||
size_t next_size = size;
|
||||
z_stream strm;
|
||||
void* tmp;
|
||||
int ret;
|
||||
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
strm.avail_in = 0;
|
||||
strm.next_in = Z_NULL;
|
||||
|
||||
ret = inflateInit2(&strm, 16 + MAX_WBITS);
|
||||
if (ret != Z_OK)
|
||||
return -EINVAL;
|
||||
|
||||
*dst = malloc(size);
|
||||
if (!*dst)
|
||||
return -ENOMEM;
|
||||
|
||||
strm.next_in = src;
|
||||
strm.avail_in = src_size;
|
||||
|
||||
/* run inflate() on input until it returns Z_STREAM_END */
|
||||
do {
|
||||
strm.next_out = *dst + strm.total_out;
|
||||
strm.avail_out = next_size;
|
||||
ret = inflate(&strm, Z_NO_FLUSH);
|
||||
if (ret != Z_OK && ret != Z_STREAM_END)
|
||||
goto out_err;
|
||||
/* we need more space */
|
||||
if (strm.avail_out == 0) {
|
||||
next_size = size;
|
||||
size *= 2;
|
||||
tmp = realloc(*dst, size);
|
||||
if (!tmp) {
|
||||
ret = -ENOMEM;
|
||||
goto out_err;
|
||||
}
|
||||
*dst = tmp;
|
||||
}
|
||||
} while (ret != Z_STREAM_END);
|
||||
|
||||
*dst_size = strm.total_out;
|
||||
|
||||
/* clean up and return */
|
||||
ret = inflateEnd(&strm);
|
||||
if (ret != Z_OK) {
|
||||
ret = -EINVAL;
|
||||
goto out_err;
|
||||
}
|
||||
return 0;
|
||||
|
||||
out_err:
|
||||
free(*dst);
|
||||
*dst = NULL;
|
||||
return ret;
|
||||
}
|
||||
struct btf *btf__load_vmlinux_btf(void);
|
||||
void btf__free(struct btf *btf);
|
||||
static bool vmlinux_btf_exists(void) {
|
||||
struct btf* btf;
|
||||
int err;
|
||||
|
||||
btf = btf__load_vmlinux_btf();
|
||||
err = libbpf_get_error(btf);
|
||||
if (err)
|
||||
return false;
|
||||
|
||||
btf__free(btf);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int ensure_core_btf(struct bpf_object_open_opts* opts) {
|
||||
char name_fmt[] = "./%s/%s/%s/%s.btf";
|
||||
char btf_path[] = "/tmp/bcc-libbpf-tools.btf.XXXXXX";
|
||||
struct os_info* info = NULL;
|
||||
unsigned char* dst_buf = NULL;
|
||||
char* file_start;
|
||||
int dst_size = 0;
|
||||
char name[100];
|
||||
FILE* dst = NULL;
|
||||
int ret;
|
||||
|
||||
/* do nothing if the system provides BTF */
|
||||
if (vmlinux_btf_exists())
|
||||
return 0;
|
||||
|
||||
/* compiled without min core btfs */
|
||||
if (!_binary_min_core_btfs_tar_gz_start)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
info = get_os_info();
|
||||
if (!info)
|
||||
return -errno;
|
||||
|
||||
ret = mkstemp(btf_path);
|
||||
if (ret < 0) {
|
||||
ret = -errno;
|
||||
goto out;
|
||||
}
|
||||
|
||||
dst = fdopen(ret, "wb");
|
||||
if (!dst) {
|
||||
ret = -errno;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = snprintf(name, sizeof(name), name_fmt, info->id, info->version,
|
||||
info->arch, info->kernel_release);
|
||||
if (ret < 0 || ret == sizeof(name)) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = inflate_gz(
|
||||
_binary_min_core_btfs_tar_gz_start,
|
||||
_binary_min_core_btfs_tar_gz_end - _binary_min_core_btfs_tar_gz_start,
|
||||
&dst_buf, &dst_size);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = 0;
|
||||
file_start = tar_file_start((struct tar_header*)dst_buf, name, &dst_size);
|
||||
if (!file_start) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (fwrite(file_start, 1, dst_size, dst) != dst_size) {
|
||||
ret = -ferror(dst);
|
||||
goto out;
|
||||
}
|
||||
|
||||
opts->btf_custom_path = strdup(btf_path);
|
||||
if (!opts->btf_custom_path)
|
||||
ret = -ENOMEM;
|
||||
|
||||
out:
|
||||
free(info);
|
||||
fclose(dst);
|
||||
free(dst_buf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void cleanup_core_btf(struct bpf_object_open_opts* opts) {
|
||||
if (!opts)
|
||||
return;
|
||||
|
||||
if (!opts->btf_custom_path)
|
||||
return;
|
||||
|
||||
unlink(opts->btf_custom_path);
|
||||
free((void*)opts->btf_custom_path);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
LIBBPF_OPTS(bpf_object_open_opts, open_opts);
|
||||
static const struct argp argp = {
|
||||
@@ -454,12 +203,6 @@ int main(int argc, char** argv) {
|
||||
libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
|
||||
libbpf_set_print(libbpf_print_fn);
|
||||
|
||||
err = ensure_core_btf(&open_opts);
|
||||
if (err) {
|
||||
warn("failed to fetch necessary BTF for CO-RE: %s\n", strerror(-err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
obj = tcpstates_bpf__open_opts(&open_opts);
|
||||
if (!obj) {
|
||||
warn("failed to open BPF object\n");
|
||||
@@ -540,7 +283,6 @@ int main(int argc, char** argv) {
|
||||
cleanup:
|
||||
perf_buffer__free(pb);
|
||||
tcpstates_bpf__destroy(obj);
|
||||
cleanup_core_btf(&open_opts);
|
||||
|
||||
return err != 0;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,17 @@
|
||||
- [eBPF 入门实践教程:使用 LSM 进行安全检测防御](19-lsm-connect/README.md)
|
||||
- [eBPF 入门实践教程:使用 eBPF 进行 tc 流量控制](20-tc/README.md)
|
||||
|
||||
# eBPF 高级特性与进阶主题
|
||||
|
||||
- [在 Android 上使用 eBPF 程序](22-android/README.md)
|
||||
- [使用 eBPF 追踪 HTTP 请求或其他七层协议](23-http/README.md)
|
||||
- [使用 sockops 加速网络请求转发](29-sockops/README.md)
|
||||
- [使用 eBPF 隐藏进程或文件信息](24-hide/README.md)
|
||||
- [使用 bpf_send_signal 发送信号终止进程](25-signal/README.md)
|
||||
- [使用 eBPF 添加 sudo 用户](26-sudo/README.md)
|
||||
- [使用 eBPF 替换任意程序读取或写入的文本](27-replace/README.md)
|
||||
- [BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序](28-detach/README.md)
|
||||
|
||||
# bcc 开发者教程
|
||||
|
||||
- [BPF Features by Linux Kernel Version](bcc-documents/kernel-versions.md)
|
||||
|
||||
Reference in New Issue
Block a user