土法炼钢兴趣小组的算法知识备份

【网络工程】BPF 网络诊断:bpftrace 与 bcc 工具实战

文章导航

分类入口
network
标签入口
#ebpf#bpf#bpftrace#bcc#diagnostics#linux

目录

传统网络诊断工具(tcpdump、ss、netstat)工作在用户态,只能看到内核暴露的接口信息。当问题藏在内核协议栈内部——softirq 延迟、丢包发生在哪个函数、TCP 重传的精确原因——传统工具就无能为力了。

eBPF(extended Berkeley Packet Filter)改变了这个局面。它允许在内核中安全地运行自定义程序,无需修改内核源码或加载内核模块。bcc(BPF Compiler Collection)提供了一组开箱即用的网络诊断工具,bpftrace 让你用几行脚本就能写出自定义探针。

本文聚焦 eBPF 在网络诊断中的工程应用——不讲 eBPF 的底层架构(那是另一篇文章的内容),只讲”怎么用它解决网络问题”。

一、bcc 网络工具集

1.1 工具全景

bcc 提供了十多个网络相关的诊断工具,每个工具解决一个特定问题:

工具 功能 解决什么问题
tcplife TCP 连接生命周期 谁在建立短连接?连接持续多久?
tcpretrans TCP 重传追踪 重传发生在哪个连接?什么状态下?
tcpdrop TCP 丢包追踪 内核在哪个函数丢弃了 TCP 包?
tcpconnect TCP 主动连接追踪 哪个进程在连哪个地址?
tcpaccept TCP 被动连接追踪 哪个进程接受了来自哪里的连接?
tcpconnlat TCP 连接延迟 建立连接花了多长时间?
tcpstates TCP 状态变化追踪 连接经历了哪些状态转换?
tcpsynbl SYN backlog 监控 SYN 队列和 accept 队列是否溢出?
tcprtt TCP RTT 分布 RTT 的分布是什么样的?
tcptop TCP 流量 Top N 哪些连接在消耗最多带宽?
solisten Socket 监听事件 什么时候开始/停止监听?

1.2 tcplife:连接生命周期分析

tcplife 在 TCP 连接关闭时输出其生命周期信息,包括持续时间、收发字节数:

sudo tcplife

# 输出示例
# PID    COMM       LADDR           LPORT RADDR           RPORT TX_KB  RX_KB  MS
# 12345  nginx      10.0.1.50       443   10.0.3.20       54321    2     15    45
# 12345  nginx      10.0.1.50       443   10.0.3.21       54322    1      8    32
# 23456  python     10.0.1.50       54000 10.0.2.100      3306    0      0     2
# 23456  python     10.0.1.50       54001 10.0.2.100      3306    0      0     1
# 23456  python     10.0.1.50       54002 10.0.2.100      3306    0      0     2

工程场景

  1. 检测短连接问题:上面的 python 进程到 MySQL 的连接持续时间只有 1–2 ms,说明每次查询都新建连接,没有使用连接池。

  2. 统计连接持续时间分布

sudo tcplife -t | awk 'NR>1 {print $NF}' | \
  awk '{
    if ($1 < 10) b["<10ms"]++;
    else if ($1 < 100) b["10-100ms"]++;
    else if ($1 < 1000) b["100ms-1s"]++;
    else if ($1 < 10000) b["1-10s"]++;
    else b[">10s"]++;
  } END {
    for (k in b) printf "%-12s %d\n", k, b[k]
  }' | sort
  1. 找出发送/接收最多数据的连接
# 按 TX 排序,找出发送最多的连接
sudo tcplife -t | sort -k7 -rn | head -10

# 按 RX 排序,找出接收最多的连接
sudo tcplife -t | sort -k8 -rn | head -10

1.3 tcpretrans:重传追踪

tcpretrans 实时报告 TCP 重传事件,比 netstat -s 的全局计数精确得多——它告诉你哪个连接在重传:

sudo tcpretrans

# 输出示例
# TIME     PID    IP LADDR:LPORT       T> RADDR:RPORT       STATE
# 14:23:01 12345  4  10.0.1.50:443     R> 10.0.3.20:54321   ESTABLISHED
# 14:23:01 12345  4  10.0.1.50:443     R> 10.0.3.20:54321   ESTABLISHED
# 14:23:05 0      4  10.0.1.50:443     R> 10.0.3.22:54400   SYN_RECV
# 14:23:12 23456  4  10.0.1.50:54000   R> 10.0.2.100:3306   ESTABLISHED

输出中 T> 表示重传方向,STATE 是重传时的 TCP 状态。

关键分析

# 统计每个目标 IP 的重传次数
sudo tcpretrans -c | head -20

# 带计数模式,每秒汇总
sudo tcpretrans -l | awk '{print $5}' | awk -F: '{print $1}' | \
  sort | uniq -c | sort -rn | head -10

1.4 tcpdrop:内核丢包追踪

tcpdrop 是最强大的丢包诊断工具——它告诉你内核在哪个函数丢弃了 TCP 包,并输出内核调用栈:

sudo tcpdrop

# 输出示例
# TIME     PID    IP LADDR:LPORT       T> RADDR:RPORT       STATE    (REASON)
# 14:30:01 0      4  10.0.1.50:443     R> 10.0.3.20:54321   CLOSE    (NOT_SPECIFIED)
#         tcp_v4_rcv+0x1234
#         ip_local_deliver_finish+0x56
#         ip_local_deliver+0x78
#         ...

常见丢包原因和内核函数

内核函数 丢包原因 解决方案
tcp_v4_rcv__inet_lookup 目标端口无进程监听 检查服务状态
tcp_rcv_state_process 在非预期状态收到包 检查 TCP 状态机异常
tcp_v4_syn_recv_sock SYN Cookie 验证失败或内存不足 检查 SYN 队列和内存
tcp_drop_reasonSKB_DROP_REASON_TCP_OLD_ACK 收到了过时的 ACK 通常无害,忽略
nf_hook_slow netfilter/iptables 规则丢弃 检查防火墙规则

1.5 tcpconnlat:连接建立延迟

tcpconnlat 测量 TCP 连接建立的延迟(从 SYN 到 ESTABLISHED):

# 显示所有连接建立延迟
sudo tcpconnlat

# 只显示延迟超过 100ms 的连接
sudo tcpconnlat 100

# 输出示例
# PID    COMM       IP SADDR           DADDR           DPORT  LAT(ms)
# 23456  python     4  10.0.1.50       10.0.2.100      3306     1.23
# 23456  python     4  10.0.1.50       10.0.2.100      3306     1.18
# 34567  curl       4  10.0.1.50       93.184.216.34   443    145.67  ← 跨洋连接
# 12345  nginx      4  10.0.1.50       10.0.5.200      8080   502.34  ← 异常!

502 ms 的内网连接建立延迟明显异常,可能原因: - SYN 包被丢弃后重传(RTO 最小 200 ms) - 服务端 SYN backlog 满导致 SYN 被丢弃

1.6 tcpstates:状态转换追踪

tcpstates 追踪 TCP 连接的每一次状态变化,对于诊断连接泄漏和异常关闭特别有用:

sudo tcpstates

# 输出示例
# SKADDR           C-PID C-COMM  LADDR         LPORT RADDR         RPORT OLDSTATE    -> NEWSTATE    MS
# ffff888012345678 23456 python  10.0.1.50     54000 10.0.2.100    3306  CLOSE       -> SYN_SENT     0.00
# ffff888012345678 23456 python  10.0.1.50     54000 10.0.2.100    3306  SYN_SENT    -> ESTABLISHED  1.23
# ffff888012345678 23456 python  10.0.1.50     54000 10.0.2.100    3306  ESTABLISHED -> FIN_WAIT1    2.45
# ffff888012345678 23456 python  10.0.1.50     54000 10.0.2.100    3306  FIN_WAIT1   -> FIN_WAIT2    3.67
# ffff888012345678 23456 python  10.0.1.50     54000 10.0.2.100    3306  FIN_WAIT2   -> CLOSE        4.12

工程场景

排查 CLOSE_WAIT 泄漏时,用 tcpstates 可以看到连接进入 CLOSE_WAIT 后是否还会转换到 CLOSE。如果卡在 CLOSE_WAIT 不动,说明应用没有调用 close()

二、bpftrace 自定义网络探针

2.1 bpftrace 基础语法

bpftrace 是 eBPF 的高级追踪语言,语法类似 awk。核心结构:

probe /filter/ {
    action
}

常用探针类型:

探针 格式 示例
内核函数入口 kprobe:func kprobe:tcp_sendmsg
内核函数返回 kretprobe:func kretprobe:tcp_sendmsg
Tracepoint tracepoint:category:event tracepoint:tcp:tcp_retransmit_skb
USDT usdt:path:probe 用户态静态探针

2.2 TCP 重传原因分析

#!/usr/bin/env bpftrace
// tcp-retrans-reason.bt — 分析 TCP 重传的原因和模式

tracepoint:tcp:tcp_retransmit_skb
{
    $sk = (struct sock *)args->skaddr;
    $inet_sport = $sk->__sk_common.skc_num;
    $inet_dport = $sk->__sk_common.skc_dport;
    $dport = ($inet_dport >> 8) | (($inet_dport & 0xFF) << 8);

    $saddr = ntop(af_inet, $sk->__sk_common.skc_rcv_saddr);
    $daddr = ntop(af_inet, $sk->__sk_common.skc_daddr);
    $state = $sk->__sk_common.skc_state;

    @retrans_by_dest[$daddr, $dport] = count();
    @retrans_by_state[$state] = count();

    printf("retrans: %s:%d -> %s:%d state=%d\n",
        $saddr, $inet_sport, $daddr, $dport, $state);
}

interval:s:10
{
    printf("\n--- 10s Summary ---\n");
    printf("By destination:\n");
    print(@retrans_by_dest);
    printf("By state:\n");
    print(@retrans_by_state);
    clear(@retrans_by_dest);
    clear(@retrans_by_state);
}

2.3 TCP 连接延迟直方图

#!/usr/bin/env bpftrace
// tcp-connect-latency.bt — TCP 连接建立延迟直方图

kprobe:tcp_v4_connect
{
    @start[tid] = nsecs;
}

kretprobe:tcp_v4_connect
/@start[tid]/
{
    @connect_ns[tid] = nsecs - @start[tid];
    delete(@start[tid]);
}

kprobe:tcp_finish_connect
{
    if (@connect_ns[tid]) {
        $latency_us = @connect_ns[tid] / 1000;
        @latency_hist = hist($latency_us);

        if ($latency_us > 100000) {
            printf("SLOW CONNECT: %d us, pid=%d comm=%s\n",
                $latency_us, pid, comm);
        }
        delete(@connect_ns[tid]);
    }
}

END
{
    printf("\nTCP Connect Latency (us):\n");
    print(@latency_hist);
}

运行后输出类似:

TCP Connect Latency (us):
@latency_hist:
[64, 128)          234 |@@@@@@@@@@@@                                    |
[128, 256)          89 |@@@@                                            |
[256, 512)          45 |@@                                              |
[512, 1K)           23 |@                                               |
[1K, 2K)            12 |                                                |
[2K, 4K)             5 |                                                |
[4K, 8K)             2 |                                                |
[128K, 256K)         3 |                                                |  ← 异常慢连接

2.4 Socket 缓冲区监控

#!/usr/bin/env bpftrace
// socket-buffer.bt — 监控 socket 缓冲区使用

kprobe:tcp_sendmsg
{
    $sk = (struct sock *)arg0;
    $wmem = $sk->sk_wmem_queued;
    $sndbuf = $sk->sk_sndbuf;

    if ($wmem * 100 / $sndbuf > 80) {
        printf("SEND BUFFER HIGH: pid=%d comm=%s wmem=%d sndbuf=%d (%d%%)\n",
            pid, comm, $wmem, $sndbuf, $wmem * 100 / $sndbuf);
    }
    @send_usage = hist($wmem * 100 / $sndbuf);
}

kprobe:tcp_recvmsg
{
    $sk = (struct sock *)arg0;
    $rmem = $sk->sk_backlog.len + $sk->sk_receive_queue.qlen;
    @recv_queue = hist($rmem);
}

interval:s:30
{
    printf("\n--- Send Buffer Usage (%%) ---\n");
    print(@send_usage);
    clear(@send_usage);
}

2.5 网络包延迟追踪

追踪数据包从网卡到应用层的完整延迟:

#!/usr/bin/env bpftrace
// pkt-latency.bt — 追踪包处理延迟

tracepoint:net:netif_receive_skb
{
    @pkt_start[args->skbaddr] = nsecs;
}

kprobe:tcp_queue_rcv
{
    $skb = arg1;
    if (@pkt_start[$skb]) {
        $latency_us = (nsecs - @pkt_start[$skb]) / 1000;
        @kernel_latency = hist($latency_us);

        if ($latency_us > 1000) {
            printf("HIGH KERNEL LATENCY: %d us for TCP packet\n", $latency_us);
        }
        delete(@pkt_start[$skb]);
    }
}

interval:s:10
{
    printf("\n--- Kernel packet processing latency (us) ---\n");
    print(@kernel_latency);
    clear(@kernel_latency);
}

三、XDP 丢包分析

3.1 安装 bcc 和 bpftrace

在开始使用 eBPF 工具之前,需要确保环境就绪:

# Ubuntu/Debian
sudo apt-get install -y bpfcc-tools linux-headers-$(uname -r) bpftrace

# CentOS/RHEL 8+
sudo dnf install -y bcc-tools bpftrace kernel-devel-$(uname -r)

# 验证安装
sudo tcplife --help
sudo bpftrace --version

# 检查内核 BTF 支持(bpftrace CO-RE 需要)
ls /sys/kernel/btf/vmlinux
# 如果文件存在,说明支持 BTF,bpftrace 可以直接访问内核结构体

安装完成后,bcc 工具通常在 /usr/share/bcc/tools/ 或直接可用(如 tcplifetcpretrans)。

3.2 XDP 程序类型

XDP(eXpress Data Path)在网卡驱动层处理包,是 Linux 中最早的包处理点。XDP 程序的返回值决定包的命运:

返回值 含义 用途
XDP_PASS 正常传递给内核协议栈 默认行为
XDP_DROP 在驱动层丢弃 DDoS 防御、ACL
XDP_TX 从接收端口原路发回 反射式负载均衡
XDP_REDIRECT 重定向到另一个接口或 CPU 转发、AF_XDP
XDP_ABORTED 错误,丢弃并记录 调试

3.3 用 bpftrace 追踪 XDP 丢包

#!/usr/bin/env bpftrace
// xdp-drop-trace.bt — 追踪 XDP 层的丢包

tracepoint:xdp:xdp_bulk_tx
{
    if (args->action == 1) {  // XDP_DROP
        @xdp_drops = count();
        @xdp_drop_by_ifindex[args->ifindex] = count();
    }
}

tracepoint:xdp:xdp_exception
{
    printf("XDP exception: ifindex=%d action=%d\n",
        args->ifindex, args->action);
    @xdp_exceptions = count();
}

interval:s:5
{
    printf("\n--- XDP Stats (5s) ---\n");
    printf("Total drops: ");
    print(@xdp_drops);
    printf("Drops by interface:\n");
    print(@xdp_drop_by_ifindex);
    clear(@xdp_drops);
    clear(@xdp_drop_by_ifindex);
}

3.4 编写 XDP 丢包统计程序

用 C 编写一个简单的 XDP 程序,统计各协议的包计数:

// xdp_stats.c — XDP 包统计
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <bpf/bpf_helpers.h>

struct {
    __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
    __uint(max_entries, 256);
    __type(key, __u32);
    __type(value, __u64);
} pkt_count SEC(".maps");

struct {
    __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
    __uint(max_entries, 256);
    __type(key, __u32);
    __type(value, __u64);
} byte_count SEC(".maps");

SEC("xdp")
int xdp_stats_prog(struct xdp_md *ctx)
{
    void *data_end = (void *)(long)ctx->data_end;
    void *data = (void *)(long)ctx->data;

    struct ethhdr *eth = data;
    if ((void *)(eth + 1) > data_end)
        return XDP_PASS;

    __u32 proto = 0;
    if (eth->h_proto == __constant_htons(ETH_P_IP)) {
        struct iphdr *ip = (void *)(eth + 1);
        if ((void *)(ip + 1) > data_end)
            return XDP_PASS;
        proto = ip->protocol;
    }

    __u64 *count = bpf_map_lookup_elem(&pkt_count, &proto);
    if (count)
        __sync_fetch_and_add(count, 1);

    __u32 pkt_size = data_end - data;
    __u64 *bytes = bpf_map_lookup_elem(&byte_count, &proto);
    if (bytes)
        __sync_fetch_and_add(bytes, pkt_size);

    return XDP_PASS;
}

char LICENSE[] SEC("license") = "GPL";

编译和加载:

# 编译
clang -O2 -g -target bpf -c xdp_stats.c -o xdp_stats.o

# 加载到 eth0
ip link set dev eth0 xdpgeneric obj xdp_stats.o sec xdp

# 查看统计(用 bpftool)
bpftool map dump name pkt_count

# 卸载
ip link set dev eth0 xdpgeneric off

四、实战诊断场景

4.1 场景一:定位间歇性丢包的内核路径

问题:服务每隔几分钟出现少量 TCP 重传,但 tcpdump 看不到明显的网络层问题。

# 1. 用 tcpdrop 追踪内核丢包点
sudo tcpdrop -4

# 等待一段时间,捕获到丢包:
# TIME     PID  IP LADDR:LPORT  T> RADDR:RPORT  STATE  (REASON)
# 14:30:01 0    4  10.0.1.50:443  10.0.3.20:54321  ESTABLISHED  (TCP_OLD_DATA)
#   tcp_validate_incoming+0x234
#   tcp_rcv_established+0x156
#   tcp_v4_do_rcv+0x89
#   ...

# 2. TCP_OLD_DATA 表示收到了序列号过旧的数据段
# 这通常发生在网络路径上有包重排序(reordering)时

# 3. 进一步确认重排序
sudo bpftrace -e '
kprobe:tcp_dsack_set {
    @dsack = count();
}
kprobe:tcp_mark_head_lost {
    @head_lost = count();
}
interval:s:10 {
    print(@dsack);
    print(@head_lost);
    clear(@dsack);
    clear(@head_lost);
}'

4.2 场景二:追踪 SYN 队列溢出

问题:在高并发场景下,部分新连接建立失败。

# 1. 先用传统工具确认
netstat -s | grep -E "overflow|drop|SYN"
# 1234 times the listen queue of a socket overflowed
# 5678 SYNs to LISTEN sockets dropped

# 2. 用 bpftrace 追踪实时溢出
sudo bpftrace -e '
tracepoint:sock:inet_sock_set_state
/args->newstate == 3/  // TCP_SYN_RECV
{
    @syn_recv = count();
}

kprobe:tcp_req_err
{
    @syn_drop = count();
    printf("SYN dropped: pid=%d comm=%s\n", pid, comm);
}

interval:s:5 {
    printf("SYN_RECV: "); print(@syn_recv);
    printf("SYN drops: "); print(@syn_drop);
    clear(@syn_recv);
    clear(@syn_drop);
}'

# 3. 监控 backlog 使用率
sudo bpftrace -e '
kprobe:tcp_v4_syn_recv_sock
{
    $sk = (struct sock *)arg0;
    $max = $sk->sk_max_ack_backlog;
    $cur = $sk->sk_ack_backlog;

    if ($cur * 100 / $max > 80) {
        printf("BACKLOG HIGH: %d/%d (%d%%) port=%d\n",
            $cur, $max, $cur * 100 / $max,
            $sk->__sk_common.skc_num);
    }
}'

修复

# 增大 SYN backlog
sysctl -w net.ipv4.tcp_max_syn_backlog=65536

# 增大 accept 队列
sysctl -w net.core.somaxconn=65536

# 应用层也需要调整 listen backlog
# Go: net.Listen("tcp", ":8080") 默认用 somaxconn
# Nginx: listen 80 backlog=65536;

4.3 场景三:诊断 softirq 延迟

问题:网络延迟偶发飙高,怀疑是内核处理瓶颈。

# 1. 追踪 NET_RX softirq 处理时间
sudo bpftrace -e '
tracepoint:irq:softirq_entry
/args->vec == 3/  // NET_RX
{
    @start[tid] = nsecs;
}

tracepoint:irq:softirq_exit
/args->vec == 3 && @start[tid]/
{
    $duration_us = (nsecs - @start[tid]) / 1000;
    @softirq_duration = hist($duration_us);

    if ($duration_us > 1000) {
        printf("LONG softirq: %d us on CPU %d\n", $duration_us, cpu);
    }
    delete(@start[tid]);
}

interval:s:10 {
    printf("\n--- NET_RX softirq duration (us) ---\n");
    print(@softirq_duration);
    clear(@softirq_duration);
}'

# 输出示例:
# NET_RX softirq duration (us):
# @softirq_duration:
# [1, 2)            12345 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
# [2, 4)             5678 |@@@@@@@@@@@@@@@@@@@@@@                         |
# [4, 8)             2345 |@@@@@@@@@                                      |
# [8, 16)             890 |@@@                                            |
# [16, 32)            234 |                                               |
# [32, 64)             56 |                                               |
# [64, 128)            12 |                                               |
# [1K, 2K)              3 |                                               |  ← 异常长

# 2. 如果 softirq 集中在某个 CPU
sudo bpftrace -e '
tracepoint:irq:softirq_entry
/args->vec == 3/
{
    @by_cpu[cpu] = count();
}
interval:s:5 {
    print(@by_cpu);
    clear(@by_cpu);
}'

# 如果所有处理集中在 CPU 0 → 需要配置 RPS

五、eBPF 网络诊断方法论

5.1 分层诊断思路

eBPF 工具在网络诊断中的使用应该遵循分层思路:

应用层问题
  └── tcplife: 连接模式分析(短连接/长连接)
  └── tcptop: 流量分布分析

传输层问题
  └── tcpretrans: 重传定位
  └── tcpdrop: 内核丢包分析
  └── tcpconnlat: 连接延迟分析
  └── tcpstates: 状态机异常

内核协议栈问题
  └── bpftrace softirq 探针: 处理延迟
  └── bpftrace socket buffer 探针: 缓冲区压力
  └── /proc/net/softnet_stat: 包处理统计

驱动/硬件层问题
  └── XDP 统计: 驱动层包计数
  └── ethtool -S: 网卡硬件统计

5.2 eBPF 工具的性能开销

eBPF 工具虽然高效,但不是零开销:

工具类型 典型开销 适合场景
Tracepoint < 1% CPU 生产环境持续运行
kprobe(低频函数) 1–3% CPU 生产环境短期诊断
kprobe(高频函数如 tcp_sendmsg) 3–10% CPU 仅在排查时启用
XDP 程序 接近零(在驱动层) 生产环境持续运行

最佳实践

  1. 优先使用 tracepoint,性能稳定且接口不会跨内核版本变化
  2. kprobe 作为 tracepoint 不够用时的补充
  3. 在生产环境使用时设置超时:timeout 300 bpftrace script.bt
  4. 避免在高频路径上使用 printf,改用 map 聚合

5.3 内核版本兼容性

功能 最低内核版本 说明
基本 BPF 3.15 cBPF
eBPF 3.18 扩展 BPF
kprobe 4.1 内核函数探针
Tracepoint 4.7 静态追踪点
XDP 4.8 native 模式
bpftrace 4.9+ 推荐 5.0+
BTF(无需头文件) 5.2+ CO-RE 支持
fentry/fexit 5.5+ 比 kprobe 更快

六、常用 bpftrace 单行命令速查

在排查网络问题时,以下单行命令可以快速获取关键信息,无需编写完整脚本:

# 统计每秒新建 TCP 连接数
sudo bpftrace -e 'kprobe:tcp_v4_connect { @connects = count(); }
  interval:s:1 { printf("connects/s: %d\n", @connects); clear(@connects); }'

# TCP 接收队列深度直方图
sudo bpftrace -e 'kprobe:tcp_recvmsg {
  $sk = (struct sock *)arg0;
  @recv_q = hist($sk->sk_receive_queue.qlen); }'

# 追踪 TCP RST 发送
sudo bpftrace -e 'kprobe:tcp_send_active_reset {
  printf("RST sent: pid=%d comm=%s\n", pid, comm); @rst = count(); }'

# 每秒 TCP 重传率
sudo bpftrace -e '
  tracepoint:tcp:tcp_retransmit_skb { @retrans++; }
  kprobe:tcp_sendmsg { @sent++; }
  interval:s:5 {
    printf("retrans=%d sent=%d rate=%.4f%%\n",
      @retrans, @sent,
      @sent > 0 ? @retrans * 100.0 / @sent : 0);
    @retrans = 0; @sent = 0;
  }'

# 追踪 DNS 查询延迟(通过 getaddrinfo)
sudo bpftrace -e 'uprobe:/lib/x86_64-linux-gnu/libc.so.6:getaddrinfo {
  @start[tid] = nsecs; }
  uretprobe:/lib/x86_64-linux-gnu/libc.so.6:getaddrinfo /@start[tid]/ {
  $ms = (nsecs - @start[tid]) / 1000000;
  if ($ms > 10) { printf("slow DNS: %d ms pid=%d comm=%s\n", $ms, pid, comm); }
  @dns_latency = hist($ms);
  delete(@start[tid]); }'

# 监控 listen overflow(accept 队列满)
sudo bpftrace -e 'tracepoint:tcp:tcp_listen_overflow {
  printf("OVERFLOW: port=%d backlog=%d\n",
    args->sport, args->sk_max_ack_backlog);
  @overflow[args->sport] = count(); }'

使用技巧

  1. 所有命令都需要 sudo,eBPF 程序需要 CAP_BPF 或 root 权限
  2. timeout 60 bpftrace -e '...' 限制运行时间,防止忘记清理
  3. 单行命令适合快速诊断,复杂分析请写成脚本文件
  4. 如果 kprobe 不可用(内核函数名变了),优先尝试 tracepoint

七、总结:eBPF 网络诊断的核心价值

  1. 精确定位传统工具看不到的问题tcpdump 只能看到线上的包,看不到内核在哪里丢弃了包。tcpdrop + 内核调用栈让你精确知道丢包原因。这是 eBPF 最大的价值——把内核黑盒变成白盒。

  2. 从全局统计到精确归因netstat -s 告诉你”有 1000 次重传”,tcpretrans 告诉你”这 1000 次重传中,800 次是到 10.0.2.100:3306 的连接”。从”有问题”到”哪里有问题”,这一步传统工具做不到。

  3. bcc 工具开箱即用。大多数场景不需要写自定义 bpftrace 脚本。tcplifetcpretranstcpdroptcpconnlat 四个工具覆盖了 80% 的 TCP 诊断需求。先用这四个工具,不够再写脚本。

  4. 注意性能开销。eBPF 高效但不是免费的。在高频路径上挂探针(如 tcp_sendmsg)会有可观的 CPU 开销。生产环境使用时设置超时,诊断完毕及时清理。

  5. 遵循诊断升级路径。排查网络问题时的工具升级路径应该是:ss/nstattcpdump/tsharkbcc 工具 → 自定义 bpftrace 脚本 → XDP 程序。从简单到复杂,从用户态到内核态。大多数问题在前两步就能解决,只有确实需要内核级可见性时才上 eBPF。

  6. 投资学习 bpftrace 语法。bpftrace 的学习曲线比直接用 C 写 eBPF 程序低得多。掌握 kprobetracepointhist()count() 四个核心概念,就能应对大部分诊断场景。把常用的单行命令保存为 alias 或脚本,排查时直接调用。


参考文献


上一篇:ss/netstat/ip 工具链:Socket 状态分析与连接审计

下一篇:L4 负载均衡:IPVS、LVS 与连接级调度

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2025-08-05 · network

【网络工程】eBPF 可编程网络:从包过滤到流量工程

eBPF 正在重新定义网络工程——从传统的 iptables/netfilter 规则堆砌,到可编程、可观测、高性能的网络数据平面。本文系统讲解 eBPF 网络程序类型(XDP/TC/Socket)、Map 数据结构、Cilium 的 eBPF 数据平面实现,以及 eBPF 在负载均衡、可观测性和网络安全中的工程实践。


By .