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 分佈。下面是這段代碼的主要工作原理:

  1. 首先定義了一個名爲 "hists" 的 eBPF 哈希表,用於保存 RTT 直方圖數據。

  2. 當 tcp_rcv_established 函數被調用時,它首先從傳入的 socket 結構體中獲取 TCP 相關信息,包括本地 / 遠程 IP 地址、本地 / 遠程端口號以及 TCP 狀態信息等。

  3. 接下來,代碼會檢查用戶指定的條件是否匹配當前 TCP 連接。如果匹配失敗,則直接返回。

  4. 如果匹配成功,則從 "hists" 哈希表中查找與本地 / 遠程 IP 地址匹配的直方圖數據。如果該 IP 地址的直方圖不存在,則創建一個新的直方圖並插入哈希表中。

  5. 接下來,代碼會從 socket 結構體中獲取當前 TCP 連接的 RTT(srtt),並根據用戶設置的選項來將 srtt 值進行處理。如果用戶設置了 "-ms" 選項,則將 srtt 值除以 1000。

  6. 接着,代碼會將 srtt 值轉換爲直方圖的槽位 (slot),並將該槽位的計數器 + 1。

  7. 如果用戶設置了 "-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