线上网络故障排查中,最常见的困境是”知道有问题,但不知道问题在哪一层”。一个
TCP 连接延迟 200ms,可能是应用层阻塞、socket
缓冲区满、softirq
调度延迟、拥塞控制退避、丢包重传、网卡队列溢出中的任意一个。传统的
netstat、ss、tcpdump
只能看到结果(重传了多少包),而看不到原因(在哪个内核函数、因为什么条件触发了重传)。
内核提供了三层可观测基础设施——tracepoint(静态插桩)、kprobe/fentry(动态插桩)和 PMC(硬件性能计数器)。bpftrace、perf 和 ftrace 是操作这三层基础设施的三大工具。本文从网络诊断实战出发,逐个展示它们在不同场景下的用法和选择策略。
下图展示了三大追踪工具与内核网络 tracepoint 的关系:
一、内核网络 tracepoint 全景
内核在 include/trace/events/
目录下为网络子系统定义了一组静态
tracepoint,覆盖了收发包、TCP 状态、丢包、NAPI
等关键路径。
1.1 网络相关 tracepoint 分类
| trace 子系统 | 头文件 | 关键 tracepoint | 用途 |
|---|---|---|---|
skb |
trace/events/skb.h |
kfree_skb、consume_skb、skb_copy_datagram_iovec |
丢包追踪、正常消费追踪 |
tcp |
trace/events/tcp.h |
tcp_retransmit_skb、tcp_send_reset、tcp_receive_reset、tcp_destroy_sock、tcp_rcv_space_adjust、tcp_retransmit_synack、tcp_probe、tcp_bad_csum、tcp_cong_state_set |
TCP 重传、RST、拥塞状态 |
net |
trace/events/net.h |
net_dev_start_xmit、net_dev_xmit、netif_receive_skb、netif_rx |
收发包路径追踪 |
napi |
trace/events/napi.h |
napi_poll |
NAPI 轮询效率 |
sock |
trace/events/sock.h |
inet_sock_set_state、sock_exceed_buf_limit、sock_rcvqueue_full |
socket 状态变化、缓冲区溢出 |
neigh |
trace/events/neigh.h |
neigh_create、neigh_update、neigh_event_send |
ARP/NDP 邻居子系统 |
1.2 kfree_skb:最重要的丢包 tracepoint
kfree_skb tracepoint 在每次内核因错误丢弃
sk_buff 时被触发,携带丢弃原因:
/* include/trace/events/skb.h:24 */
TRACE_EVENT(kfree_skb,
TP_PROTO(struct sk_buff *skb, void *location,
enum skb_drop_reason reason),
TP_ARGS(skb, location, reason),
TP_STRUCT__entry(
__field(void *, skbaddr)
__field(void *, location) /* 调用 kfree_skb 的内核函数地址 */
__field(unsigned short, protocol)
__field(enum skb_drop_reason, reason) /* 丢弃原因枚举 */
),
...
);location 字段是调用
kfree_skb_reason() 的内核函数地址,可以通过
ksym() 解析为函数名。reason 字段是
enum skb_drop_reason,在
include/net/dropreason-core.h 中定义了 80+
种丢弃原因:
/* include/net/dropreason-core.h - 部分常见原因 */
SKB_DROP_REASON_NOT_SPECIFIED /* 未指定 */
SKB_DROP_REASON_NO_SOCKET /* 找不到目标 socket */
SKB_DROP_REASON_TCP_CSUM /* TCP 校验和错误 */
SKB_DROP_REASON_SOCKET_FILTER /* socket filter 丢弃 */
SKB_DROP_REASON_UDP_CSUM /* UDP 校验和错误 */
SKB_DROP_REASON_NETFILTER_DROP /* Netfilter 丢弃 */
SKB_DROP_REASON_SOCKET_RCVBUFF /* socket 接收缓冲区满 */
SKB_DROP_REASON_PROTO_MEM /* 协议内存压力 */
SKB_DROP_REASON_TCP_FLAGS /* TCP 标志位异常 */
SKB_DROP_REASON_TCP_ZEROWINDOW /* TCP 零窗口 */
SKB_DROP_REASON_TCP_OLD_DATA /* TCP 旧数据 */
SKB_DROP_REASON_TCP_OVERWINDOW /* TCP 超窗口 */
SKB_DROP_REASON_XDP /* XDP 程序丢弃 */
SKB_DROP_REASON_TC_INGRESS /* TC ingress 丢弃 */
SKB_DROP_REASON_TC_EGRESS /* TC egress 丢弃 */
SKB_DROP_REASON_QDISC_DROP /* qdisc 队列满丢弃 */
SKB_DROP_REASON_CPU_BACKLOG /* per-CPU backlog 满 */
SKB_DROP_REASON_FULL_RING /* 网卡 ring buffer 满 */1.3 tcp_probe:TCP 拥塞状态快照
tcp_probe tracepoint 是 TCP
诊断的核心工具,在每次收到数据包时记录完整的 TCP
拥塞状态快照:
/* include/trace/events/tcp.h:238 */
TRACE_EVENT(tcp_probe,
TP_PROTO(struct sock *sk, struct sk_buff *skb),
TP_STRUCT__entry(
__array(__u8, saddr, sizeof(struct sockaddr_in6))
__array(__u8, daddr, sizeof(struct sockaddr_in6))
__field(__u16, sport)
__field(__u16, dport)
__field(__u32, mark)
__field(__u16, data_len)
__field(__u32, snd_nxt) /* 下一个发送序列号 */
__field(__u32, snd_una) /* 最小未确认序列号 */
__field(__u32, snd_cwnd) /* 拥塞窗口 */
__field(__u32, ssthresh) /* 慢启动阈值 */
__field(__u32, snd_wnd) /* 发送窗口 */
__field(__u32, srtt) /* 平滑 RTT(微秒) */
__field(__u32, rcv_wnd) /* 接收窗口 */
__field(__u64, sock_cookie) /* socket 唯一标识 */
),
...
);这个 tracepoint 提供了诊断 TCP 性能问题所需的所有关键指标——cwnd 变化、RTT 波动、窗口大小,且开销极低。
二、bpftrace:网络诊断的瑞士军刀
bpftrace 是基于 eBPF 的高级追踪语言,支持 tracepoint、kprobe、kretprobe、fentry/fexit 等多种挂载点。它的优势在于一行命令就能完成复杂的内核追踪。
2.1 TCP 重传根因分析
TCP 重传是最常见的网络性能问题。以下脚本同时追踪重传事件和拥塞状态:
bpftrace -e '
tracepoint:tcp:tcp_retransmit_skb {
$sk = (struct sock *)args->skaddr;
$inet = (struct inet_sock *)$sk;
printf("RETRANSMIT %s:%d -> %s:%d state=%d\n",
ntop(args->saddr), args->sport,
ntop(args->daddr), args->dport,
args->state);
@retrans_by_state[args->state] = count();
@retrans_by_dst[ntop(args->daddr)] = count();
}
'进阶:结合 tcp_probe 追踪重传前后的 cwnd
变化:
bpftrace -e '
tracepoint:tcp:tcp_retransmit_skb {
@retrans[args->sport, args->dport] = count();
}
tracepoint:tcp:tcp_probe {
if (@retrans[args->sport, args->dport] > 0) {
printf("POST_RETRANS %s:%d cwnd=%u ssthresh=%u srtt=%u\n",
ntop(args->daddr), args->dport,
args->snd_cwnd, args->ssthresh, args->srtt);
}
}
'2.2 收包路径延迟剖析
追踪包从网卡到 socket 的完整路径延迟:
bpftrace -e '
kprobe:__netif_receive_skb_core {
@start[arg0] = nsecs;
}
kprobe:tcp_v4_rcv {
$skb = arg0;
if (@start[$skb]) {
@rx_to_tcp_ns = hist(nsecs - @start[$skb]);
delete(@start[$skb]);
}
}
'追踪 NAPI poll 效率——每次 poll 处理的包数量分布:
bpftrace -e '
tracepoint:napi:napi_poll {
@napi_work = hist(args->work);
@napi_budget[args->budget] = count();
if (args->work == args->budget) {
@napi_exhausted = count();
}
}
'2.3 softirq 延迟定位
网络包处理在 softirq 上下文中执行。当 softirq 被延迟(被其他高优先级任务抢占),网络延迟会显著增加:
bpftrace -e '
tracepoint:irq:softirq_entry /args->vec == 3/ {
/* NET_RX_SOFTIRQ = 3 */
@softirq_start[tid] = nsecs;
}
tracepoint:irq:softirq_exit /args->vec == 3/ {
if (@softirq_start[tid]) {
$dur = nsecs - @softirq_start[tid];
@net_rx_softirq_dur_us = hist($dur / 1000);
if ($dur > 1000000) { /* > 1ms */
printf("LONG NET_RX_SOFTIRQ: %d us on CPU %d\n",
$dur / 1000, cpu);
}
delete(@softirq_start[tid]);
}
}
'追踪 softirq 被阻塞的时间——从 raise 到实际执行的延迟:
bpftrace -e '
tracepoint:irq:softirq_raise /args->vec == 3/ {
@raise[cpu] = nsecs;
}
tracepoint:irq:softirq_entry /args->vec == 3 && @raise[cpu]/ {
$delay = nsecs - @raise[cpu];
@softirq_delay_us = hist($delay / 1000);
delete(@raise[cpu]);
}
'2.4 socket 缓冲区压力监控
bpftrace -e '
tracepoint:sock:sock_rcvqueue_full {
@rcvq_full_by_proto[args->protocol] = count();
}
tracepoint:sock:sock_exceed_buf_limit {
@buf_limit[args->protocol, str(args->kind)] = count();
}
'2.5 conntrack 表满监控
bpftrace -e '
kprobe:nf_conntrack_tuple_taken {
@ct_lookups = count();
}
kretprobe:nf_conntrack_alloc /retval == 0/ {
@ct_alloc_fail = count();
}
kprobe:nf_ct_resolve_clash {
@ct_clash = count();
}
interval:s:5 {
printf("--- conntrack stats ---\n");
print(@ct_lookups);
print(@ct_alloc_fail);
print(@ct_clash);
clear(@ct_lookups);
clear(@ct_alloc_fail);
clear(@ct_clash);
}
'三、perf:统计采样与火焰图
perf 的优势在于基于 PMC 的统计采样和硬件事件追踪,适合全局性能分析而非单点追踪。
3.1 网络包处理 CPU 火焰图
# 采样 30 秒的 CPU 栈,聚焦网络相关函数
perf record -a -g --call-graph dwarf -F 99 -- sleep 30
# 生成火焰图(需要 FlameGraph 工具)
perf script | stackcollapse-perf.pl | flamegraph.pl > net-cpu.svg更精准地过滤网络相关栈:
# 只采样 ksoftirqd 线程
perf record -g --call-graph dwarf -F 99 \
-p $(pgrep -d, ksoftirqd) -- sleep 30
# 只采样 NET_RX_SOFTIRQ
perf record -a -g --call-graph dwarf -F 99 \
-e 'irq:softirq_entry' --filter 'vec == 3' -- sleep 303.2 perf 追踪丢包事件
# 记录所有 kfree_skb 事件
perf record -a -g --call-graph dwarf \
-e 'skb:kfree_skb' -- sleep 60
# 查看丢包栈
perf script | head -100
# 统计丢包原因分布
perf script | grep 'reason:' | awk '{print $NF}' | sort | uniq -c | sort -rn3.3 perf stat 网络计数器
# 快速统计网络相关内核函数调用次数
perf stat -a -e 'skb:kfree_skb,tcp:tcp_retransmit_skb,napi:napi_poll,net:netif_receive_skb' -- sleep 10
# 示例输出
# 12,345 skb:kfree_skb
# 456 tcp:tcp_retransmit_skb
# 567,890 napi:napi_poll
# 1,234,567 net:netif_receive_skb3.4 TCP 重传热图
# 记录 tcp_retransmit_skb 事件及其调用栈
perf record -a -g -e 'tcp:tcp_retransmit_skb' -- sleep 300
# 提取重传的时间分布
perf script -F time,event | awk '{print int($1)}' | uniq -c | \
awk '{printf "%d %d\n", $2, $1}'四、ftrace:最轻量的内核追踪
ftrace 是内核内建的追踪框架,无需安装额外工具,通过 debugfs/tracefs 文件系统操作。适合在最小化环境(容器、嵌入式)中使用。
4.1 function_graph:可视化调用链
# 追踪收包路径的完整函数调用链
cd /sys/kernel/debug/tracing
# 设置过滤函数
echo '__netif_receive_skb_core' > set_graph_function
echo function_graph > current_tracer
echo 1 > tracing_on
# 查看结果
cat trace | head -100
# 关闭
echo 0 > tracing_on
echo nop > current_tracerfunction_graph 输出示例:
3) | __netif_receive_skb_core() {
3) | __netif_receive_skb_one_core() {
3) 0.156 us | skb_pfmemalloc_protocol();
3) | ip_rcv() {
3) | ip_rcv_core() {
3) 0.094 us | ip_compute_csum();
3) 0.732 us | }
3) | nf_hook_slow() {
3) 0.246 us | iptable_filter_hook();
3) 0.518 us | }
3) | ip_local_deliver() {
3) | ip_local_deliver_finish() {
3) | tcp_v4_rcv() {
3) ...
4.2 event tracing:轻量级事件追踪
# 启用 TCP 重传 tracepoint
echo 1 > /sys/kernel/debug/tracing/events/tcp/tcp_retransmit_skb/enable
# 启用丢包 tracepoint
echo 1 > /sys/kernel/debug/tracing/events/skb/kfree_skb/enable
# 读取事件
cat /sys/kernel/debug/tracing/trace_pipe
# 关闭
echo 0 > /sys/kernel/debug/tracing/events/tcp/tcp_retransmit_skb/enable
echo 0 > /sys/kernel/debug/tracing/events/skb/kfree_skb/enable4.3 kprobe event:动态追踪任意内核函数
# 创建 kprobe 追踪 tcp_v4_connect 的返回值
echo 'r:tcp_connect_ret tcp_v4_connect ret=$retval' > \
/sys/kernel/debug/tracing/kprobe_events
# 启用
echo 1 > /sys/kernel/debug/tracing/events/kprobes/tcp_connect_ret/enable
# 读取
cat /sys/kernel/debug/tracing/trace_pipe
# 清理
echo 0 > /sys/kernel/debug/tracing/events/kprobes/tcp_connect_ret/enable
echo '-:tcp_connect_ret' >> /sys/kernel/debug/tracing/kprobe_events4.4 trace-cmd:ftrace 的友好前端
# 记录 TCP 和丢包事件
trace-cmd record -e tcp -e skb:kfree_skb -e napi:napi_poll sleep 30
# 分析
trace-cmd report | head -200
# 只查看重传
trace-cmd report | grep tcp_retransmit_skb
# 统计事件分布
trace-cmd report --stat五、工具选择决策指南
5.1 三大工具对比
| 维度 | bpftrace | perf | ftrace |
|---|---|---|---|
| 安装需求 | bpftrace 包 + kernel headers | perf-tools 包 | 内核内建(无需安装) |
| 编程模型 | AWK-like 脚本语言 | 命令行 + perf script | debugfs 文件操作 |
| 聚合能力 | 内核态聚合(map、hist) | 用户态后处理 | 无(需外部工具) |
| CPU 开销 | 低(eBPF JIT) | 极低(采样)/ 中(tracepoint) | 低(function tracer)/ 极低(event) |
| 适合场景 | 条件过滤、聚合统计、实时告警 | 全局采样、火焰图、硬件计数器 | 最小化环境、函数调用链 |
| 输出格式 | 终端直出、直方图 | perf.data 二进制 + perf script | 文本 trace |
5.2 场景选择决策树
需要追踪什么?
├── 全局 CPU 热点 → perf record + 火焰图
├── 特定事件计数 → perf stat -e 'tracepoint:...'
├── 条件过滤 + 聚合 → bpftrace
│ ├── "只看目标 IP 为 10.0.0.1 的重传" → bpftrace filter
│ ├── "统计每个 drop_reason 的直方图" → bpftrace hist
│ └── "实时打印 cwnd < 10 的连接" → bpftrace printf
├── 函数调用链可视化 → ftrace function_graph
├── 最小化环境(无法安装工具) → ftrace event tracing
└── 长期监控 + 告警 → bpftrace 脚本 + 定时输出
5.3 性能开销参考
| 追踪方式 | 每事件开销 | 适合频率 |
|---|---|---|
| tracepoint(无 BPF) | ~50-100 ns | 高频(100K+/s) |
| tracepoint + bpftrace | ~100-200 ns | 高频(100K+/s) |
| kprobe + bpftrace | ~200-500 ns | 中频(10K-100K/s) |
| fentry + bpftrace | ~100-200 ns | 高频(100K+/s) |
| perf 采样(99 Hz) | 几乎为零 | 不限 |
| ftrace function_graph | ~200-500 ns/函数 | 低频/短时间 |
关键原则:tracepoint 优于 kprobe(稳定 ABI、更低开销);fentry 优于 kprobe(更低开销、可用 BTF);采样优于追踪(开销固定,不随事件频率增长)。
六、实战案例:TCP 连接延迟 200ms 的根因分析
以一个真实案例演示三大工具的协同使用。
第一步:perf stat 快速定界
perf stat -a -e 'tcp:tcp_retransmit_skb,skb:kfree_skb,napi:napi_poll' -- sleep 10如果 tcp_retransmit_skb
计数远高于预期,说明问题在 TCP 重传。
第二步:bpftrace 定位重传连接
bpftrace -e '
tracepoint:tcp:tcp_retransmit_skb {
printf("%s:%d -> %s:%d state=%d\n",
ntop(args->saddr), args->sport,
ntop(args->daddr), args->dport, args->state);
@by_dst[ntop(args->daddr)] = count();
}
interval:s:5 { print(@by_dst); clear(@by_dst); }
'发现集中在某个目标 IP,说明是到特定目的地的链路问题。
第三步:tcp_probe 追踪 cwnd 变化
bpftrace -e '
tracepoint:tcp:tcp_probe
/args->dport == 8080/ {
printf("cwnd=%u ssthresh=%u srtt=%u snd_wnd=%u\n",
args->snd_cwnd, args->ssthresh, args->srtt, args->snd_wnd);
@cwnd = hist(args->snd_cwnd);
@srtt = hist(args->srtt);
}
'如果看到 cwnd 频繁从高值骤降,且 srtt 波动剧烈,说明链路存在间歇性丢包。
第四步:kfree_skb 确认丢包位置
bpftrace -e '
tracepoint:skb:kfree_skb {
@drop_reason[args->reason] = count();
@drop_location[ksym(args->location)] = count();
}
interval:s:10 { print(@drop_reason); print(@drop_location); exit(); }
'如果 reason 集中在 QDISC_DROP
或
FULL_RING,说明是出口队列或网卡缓冲区溢出。
第五步:perf 火焰图确认 CPU 瓶颈
perf record -a -g --call-graph dwarf -F 99 -- sleep 30
perf script | stackcollapse-perf.pl | flamegraph.pl > net-flame.svg在火焰图中查找
sch_direct_xmit、dev_hard_start_xmit
等函数占比,确认是否 CPU 在发包路径上成为瓶颈。
七、常用 bpftrace 单行脚本速查表
| 场景 | bpftrace 命令 |
|---|---|
| TCP 重传统计 | bpftrace -e 'tracepoint:tcp:tcp_retransmit_skb { @[ntop(args->daddr)] = count(); }' |
| RST 包追踪 | bpftrace -e 'tracepoint:tcp:tcp_send_reset { printf("%s:%d\n", ntop(args->daddr), args->dport); }' |
| 丢包原因分布 | bpftrace -e 'tracepoint:skb:kfree_skb { @[args->reason] = count(); }' |
| socket 状态变化 | bpftrace -e 'tracepoint:sock:inet_sock_set_state { printf("%d->%d\n", args->oldstate, args->newstate); }' |
| NAPI poll 分布 | bpftrace -e 'tracepoint:napi:napi_poll { @work = hist(args->work); }' |
| 发包延迟 | bpftrace -e 'kprobe:dev_queue_xmit { @s[arg0]=nsecs; } kretprobe:dev_queue_xmit /@s[arg0]/ { @ns=hist(nsecs-@s[arg0]); delete(@s[arg0]); }' |
| softirq 延迟 | bpftrace -e 'tracepoint:irq:softirq_entry /args->vec==3/ { @s[tid]=nsecs; } tracepoint:irq:softirq_exit /args->vec==3 && @s[tid]/ { @us=hist((nsecs-@s[tid])/1000); delete(@s[tid]); }' |
| TCP cwnd 分布 | bpftrace -e 'tracepoint:tcp:tcp_probe { @cwnd = hist(args->snd_cwnd); }' |
| 拥塞状态变化 | bpftrace -e 'tracepoint:tcp:tcp_cong_state_set { @[args->cong_state] = count(); }' |
八、注意事项与最佳实践
8.1 生产环境安全准则
- 先在测试环境验证:任何追踪脚本都应先在非生产环境测试
- 限制追踪时间:使用
timeout或 bpftrace 的interval:s:N { exit(); }防止遗忘关闭 - 避免高频
printf:每秒百万级事件场景下,
printf会导致 perf buffer 溢出。改用@map聚合 - 注意 kprobe 稳定性:kprobe 挂载的函数可能在内核升级后改名。优先使用 tracepoint(稳定 ABI)
- 内存限制:bpftrace 的 map
有内存上限(默认 4096 entries),高基数场景需
-DBPF_MAX_ENTRIES=65536调整
8.2 容器环境特殊考虑
在容器中追踪网络需要注意:
- bpftrace 需要
CAP_BPF+CAP_PERFMON权限(或privileged模式) - 容器内看到的是全局 tracepoint,需要通过 cgroup ID 或 netns ID 过滤
- 使用
bpftrace -e 'tracepoint:... /cgroup == cgroupid("/sys/fs/cgroup/...")/ { ... }'过滤
8.3 与上一篇的联系
本文介绍的追踪工具是上一篇 eBPF 网络钩子的”只读”版本——钩子用于修改数据面行为,追踪工具用于观察数据面行为。两者使用相同的 eBPF 基础设施(verifier、JIT、map),但追踪程序不修改包内容,只读取和记录。
参考文献
- Linux 内核源码
include/trace/events/tcp.h:TCP tracepoint 定义 - Linux 内核源码
include/trace/events/skb.h:kfree_skbtracepoint 定义 - Linux 内核源码
include/trace/events/net.h:收发包 tracepoint 定义 - Linux 内核源码
include/trace/events/napi.h:NAPI poll tracepoint 定义 - Linux 内核源码
include/trace/events/sock.h:socket 状态 tracepoint 定义 - Linux 内核源码
include/net/dropreason-core.h:enum skb_drop_reason完整定义
上一篇:eBPF 网络钩子全景:TC/XDP/socket/cgroup
下一篇:网络丢包定位:从 drop_monitor 到 kfree_skb 追踪
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【Linux 网络子系统深度拆解】网络丢包定位:从 drop_monitor 到 kfree_skb 追踪
从内核源码拆解 Linux 网络丢包追踪的完整体系:kfree_skb tracepoint 与 80+ 种 drop_reason 枚举、drop_monitor netlink 子系统、dropwatch 工具、perf 丢包记录、bpftrace 丢包聚合脚本,以及生产环境常见丢包点速查表。
【Kubernetes 网络深度系列】Linux 网络栈全景:一个包从网卡到用户态的完整旅程
从 NIC 驱动到用户态 read(),一个网络包在 Linux 内核中到底经历了什么?本文拆解 sk_buff、NAPI、softirq、netfilter 的完整收包路径,并用 bpftrace 实测追踪每一跳的延迟。
【Linux 网络子系统深度拆解】eBPF 网络钩子全景:TC/XDP/socket/cgroup
从内核源码全面拆解 eBPF 在网络子系统中的所有挂载点:TC BPF direct-action 模式与 bpf_mprog 多程序链、XDP 驱动级钩子回顾、socket ops 回调与 TCP 生命周期事件、cgroup BPF 策略控制、sk_msg/sk_skb 的 sockmap 重定向引擎、struct_ops 实现自定义拥塞控制,以及 bpftrace 可观测实战。
【Linux 网络子系统深度拆解】XDP 内核实现:在驱动层重编程网络栈
从内核源码拆解 XDP 的完整实现:xdp_buff 数据结构、驱动级钩子、五种动作路径、AF_XDP 零拷贝通道、devmap/cpumap/xskmap 重定向机制、多缓冲区支持,以及 bpftrace 可观测实战。