From 63c8be04c3bbecfe954370ce0144bac63c0d34a0 Mon Sep 17 00:00:00 2001 From: yunwei37 <1067852565@qq.com> Date: Fri, 2 Jun 2023 20:43:22 +0800 Subject: [PATCH] update the tcprtt and summary --- src/14-tcpstates/README.md | 322 ++++++++++++++++++++++++----------- src/14-tcpstates/tcpstates.c | 258 ---------------------------- src/SUMMARY.md | 11 ++ 3 files changed, 234 insertions(+), 357 deletions(-) diff --git a/src/14-tcpstates/README.md b/src/14-tcpstates/README.md index 700cdf7..09a9d5e 100644 --- a/src/14-tcpstates/README.md +++ b/src/14-tcpstates/README.md @@ -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 -#include -#include -#include -#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 Set value of `__u16` variable targ_sport + --targ_dport Set value of `__u16` variable targ_dport + --targ_saddr Set value of `__u32` variable targ_saddr + --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) +- + ## 总结 -这里的代码修改自 +通过本篇 eBPF 入门实践教程,我们学习了如何使用tcpstates和tcprtt这两个 eBPF 示例程序,监控和分析 TCP 的连接状态和往返时间。我们了解了tcpstates和tcprtt的工作原理和实现方式,包括如何使用 BPF map 存储数据,如何在 eBPF 程序中获取和处理 TCP 连接信息,以及如何在用户态应用程序中解析和显示 eBPF 程序收集的数据。这些知识和技巧对于使用 eBPF 进行网络和性能分析是非常重要的。 + +如果您希望学习更多关于 eBPF 的知识和实践,请查阅 eunomia-bpf 的官方文档: 。您还可以访问我们的教程代码仓库 以获取更多示例和完整的教程。 + +接下来的教程将进一步探讨 eBPF 的高级特性,我们会继续分享更多有关 eBPF 开发实践的内容。 diff --git a/src/14-tcpstates/tcpstates.c b/src/14-tcpstates/tcpstates.c index ecdeb67..6f9d8c5 100644 --- a/src/14-tcpstates/tcpstates.c +++ b/src/14-tcpstates/tcpstates.c @@ -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; } diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 43bae71..98edf52 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -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)