eBPF 入門開發實踐教程十二:使用 eBPF 獲取 TCP 連接的往返時間 tcprtt
背景
互聯網對網絡的要求越來越高,網絡質量經常被提到一個非常高的地位。導致網絡質量差的因素有很多,可能是硬件因素導致,也可能是程序 寫的不好導致。爲了能更好地定位網絡問題,tcprtt
工具被提出。它可以監測 TCP 鏈接的往返時間,從而分析 網絡質量,幫助用戶定位問題來源。
當有 tcp 鏈接建立時,該工具會自動根據當前系統的支持情況,選擇合適的執行函數。在執行函數中,tcprtt
會收集 tcp 鏈接的各項基本信息,包括地址,源端口,目標端口,耗時 等等,並將其更新到直方圖的 map 中。運行結束後通過在用戶態添加代碼,把往返時間展現給用戶。
編寫 eBPF 程序
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2021 Wenbo Zhang
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_endian.h>
#include "tcprtt.h"
#include "bits.bpf.h"
#include "maps.bpf.h"
char LICENSE[] SEC("license") = "Dual BSD/GPL";
const volatile bool targ_laddr_hist = false;
const volatile bool targ_raddr_hist = false;
const volatile bool targ_show_ext = false;
const volatile __u16 targ_sport = 0;
const volatile __u16 targ_dport = 0;
const volatile __u32 targ_saddr = 0;
const volatile __u32 targ_daddr = 0;
const volatile bool targ_ms = false;
#define MAX_ENTRIES 10240
/// @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;
}
這段代碼是基於 eBPF 的網絡延遲分析工具,它通過 hooking TCP 協議棧中的 tcp_rcv_established 函數來統計 TCP 連接的 RTT 分佈。下面是這段代碼的主要工作原理:
-
首先定義了一個名爲 "hists" 的 eBPF 哈希表,用於保存 RTT 直方圖數據。
-
當 tcp_rcv_established 函數被調用時,它首先從傳入的 socket 結構體中獲取 TCP 相關信息,包括本地 / 遠程 IP 地址、本地 / 遠程端口號以及 TCP 狀態信息等。
-
接下來,代碼會檢查用戶指定的條件是否匹配當前 TCP 連接。如果匹配失敗,則直接返回。
-
如果匹配成功,則從 "hists" 哈希表中查找與本地 / 遠程 IP 地址匹配的直方圖數據。如果該 IP 地址的直方圖不存在,則創建一個新的直方圖並插入哈希表中。
-
接下來,代碼會從 socket 結構體中獲取當前 TCP 連接的 RTT(srtt),並根據用戶設置的選項來將 srtt 值進行處理。如果用戶設置了 "-ms" 選項,則將 srtt 值除以 1000。
-
接着,代碼會將 srtt 值轉換爲直方圖的槽位 (slot),並將該槽位的計數器 + 1。
-
如果用戶設置了 "-show-ext" 選項,則還會累加直方圖的總延遲 (latency) 和計數(cnt)。
編譯運行
eunomia-bpf 是一個結合 Wasm 的開源 eBPF 動態加載運行時和開發工具鏈,它的目的是簡化 eBPF 程序的開發、構建、分發、運行。可以參考 https://github.com/eunomia-bpf/eunomia-bpf 下載和安裝 ecc 編譯工具鏈和 ecli 運行時。我們使用 eunomia-bpf 編譯運行這個例子。
Compile:
docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest
或者
$ ecc runqlat.bpf.c runqlat.h
Compiling bpf object...
Generating export types...
Packing ebpf object and config into package.json...
Run:
$ 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 |********** |
總結
tcprtt 是一個基於 eBPF 的 TCP 延遲分析工具。通過 hooking TCP 協議棧中的 tcp_rcv_established 函數來統計 TCP 連接的 RTT 分佈,可以對指定的 TCP 連接進行 RTT 分佈統計,並將結果保存到 eBPF 哈希表中。同時,這個工具支持多種條件過濾和 RTT 分佈數據擴展功能,以便用戶可以更好地進行網絡性能分析和調優。
更多的例子和詳細的開發指南,請參考 eunomia-bpf 的官方文檔:https://github.com/eunomia-bpf/eunomia-bpf
完整的教程和源代碼已經全部開源,可以在 https://github.com/eunomia-bpf/bpf-developer-tutorial 中查看。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Um8egpkF9DdtjdnQV12B8g