传统网络诊断工具(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工程场景:
检测短连接问题:上面的 python 进程到 MySQL 的连接持续时间只有 1–2 ms,说明每次查询都新建连接,没有使用连接池。
统计连接持续时间分布:
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- 找出发送/接收最多数据的连接:
# 按 TX 排序,找出发送最多的连接
sudo tcplife -t | sort -k7 -rn | head -10
# 按 RX 排序,找出接收最多的连接
sudo tcplife -t | sort -k8 -rn | head -101.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 状态。
关键分析:
SYN_RECV状态的重传:对方的 SYN-ACK 丢失,或者 SYN Flood 攻击ESTABLISHED状态的重传:链路丢包或对端处理太慢- 同一连接大量重传:特定路径有问题
# 统计每个目标 IP 的重传次数
sudo tcpretrans -c | head -20
# 带计数模式,每秒汇总
sudo tcpretrans -l | awk '{print $5}' | awk -F: '{print $1}' | \
sort | uniq -c | sort -rn | head -101.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_reason →
SKB_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/ 或直接可用(如
tcplife、tcpretrans)。
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 程序 | 接近零(在驱动层) | 生产环境持续运行 |
最佳实践:
- 优先使用 tracepoint,性能稳定且接口不会跨内核版本变化
- kprobe 作为 tracepoint 不够用时的补充
- 在生产环境使用时设置超时:
timeout 300 bpftrace script.bt - 避免在高频路径上使用
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(); }'使用技巧:
- 所有命令都需要
sudo,eBPF 程序需要CAP_BPF或 root 权限 - 用
timeout 60 bpftrace -e '...'限制运行时间,防止忘记清理 - 单行命令适合快速诊断,复杂分析请写成脚本文件
- 如果
kprobe不可用(内核函数名变了),优先尝试tracepoint
七、总结:eBPF 网络诊断的核心价值
精确定位传统工具看不到的问题。
tcpdump只能看到线上的包,看不到内核在哪里丢弃了包。tcpdrop+ 内核调用栈让你精确知道丢包原因。这是 eBPF 最大的价值——把内核黑盒变成白盒。从全局统计到精确归因。
netstat -s告诉你”有 1000 次重传”,tcpretrans告诉你”这 1000 次重传中,800 次是到 10.0.2.100:3306 的连接”。从”有问题”到”哪里有问题”,这一步传统工具做不到。bcc 工具开箱即用。大多数场景不需要写自定义 bpftrace 脚本。
tcplife、tcpretrans、tcpdrop、tcpconnlat四个工具覆盖了 80% 的 TCP 诊断需求。先用这四个工具,不够再写脚本。注意性能开销。eBPF 高效但不是免费的。在高频路径上挂探针(如
tcp_sendmsg)会有可观的 CPU 开销。生产环境使用时设置超时,诊断完毕及时清理。遵循诊断升级路径。排查网络问题时的工具升级路径应该是:
ss/nstat→tcpdump/tshark→bcc工具 → 自定义bpftrace脚本 → XDP 程序。从简单到复杂,从用户态到内核态。大多数问题在前两步就能解决,只有确实需要内核级可见性时才上 eBPF。投资学习 bpftrace 语法。bpftrace 的学习曲线比直接用 C 写 eBPF 程序低得多。掌握
kprobe、tracepoint、hist()、count()四个核心概念,就能应对大部分诊断场景。把常用的单行命令保存为 alias 或脚本,排查时直接调用。
参考文献
- Brendan Gregg (2019). BPF Performance Tools, Addison-Wesley
- bcc Reference Guide (github.com/iovisor/bcc/blob/master/docs/reference_guide.md)
- bpftrace Reference Guide (github.com/bpftrace/bpftrace/blob/master/docs/reference_guide.md)
- Linux kernel BPF documentation (kernel.org/doc/html/latest/bpf)
- XDP Tutorial (github.com/xdp-project/xdp-tutorial)
上一篇:ss/netstat/ip 工具链:Socket 状态分析与连接审计
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【网络工程】tcpdump 实战精通:BPF 过滤与捕获策略
系统讲解 tcpdump 的工程实战:BPF 过滤语法完整指南、滚动捕获策略、时间戳精度控制、容器/Pod 环境抓包、高级用法与性能优化,让抓包成为系统化的诊断方法。
【网络工程】eBPF 可编程网络:从包过滤到流量工程
eBPF 正在重新定义网络工程——从传统的 iptables/netfilter 规则堆砌,到可编程、可观测、高性能的网络数据平面。本文系统讲解 eBPF 网络程序类型(XDP/TC/Socket)、Map 数据结构、Cilium 的 eBPF 数据平面实现,以及 eBPF 在负载均衡、可观测性和网络安全中的工程实践。
【网络工程】网络故障排查系统化方法:从现象到根因
系统讲解网络故障排查的方法论:OSI 分层排查法、连通性/性能/间歇性三类故障的诊断路径、排查决策树、工具链选择、真实故障案例复盘,建立从'网络不通'到精确定位根因的工程能力。
【网络工程】ss/netstat/ip 工具链:Socket 状态分析与连接审计
系统讲解 Linux 网络诊断工具链:ss 的过滤语法与性能优势、ip 命令族全景、conntrack 连接追踪、/proc/net/ 文件解读,建立从 Socket 状态到连接审计的工程能力。