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

【Linux 网络子系统深度拆解】网络丢包定位:从 drop_monitor 到 kfree_skb 追踪

文章导航

分类入口
linuxnetworking
标签入口
#kernel#kfree_skb#drop_monitor#drop_reason#dropwatch#perf#bpftrace#packet-loss#tracepoint

目录

“丢包”是网络故障排查中最常见也最棘手的问题。一个包可能在内核的几十个位置被丢弃——网卡 ring buffer 满、校验和错误、Netfilter 规则拒绝、socket 缓冲区溢出、TCP 窗口外、XDP 程序丢弃、qdisc 队列满……传统的 ifconfig/proc/net/dev 只能看到笼统的 rx_dropped / tx_dropped 计数器,无法回答”在哪个内核函数、因为什么原因丢的”这个关键问题。

Linux 内核从 2.6 时代就引入了 drop_monitor 子系统,而 5.17+ 进一步增强了 kfree_skb tracepoint——现在每次丢包都携带精确的 drop_reason 枚举值。本文从内核源码出发,完整拆解丢包追踪的工具链和实战方法。

Linux 丢包追踪体系全景

一、kfree_skb:丢包追踪的基石

1.1 kfree_skb vs consume_skb

内核中释放 sk_buff 有两个路径:

两者的区别是语义上的——kfree_skb 意味着”这个包没有到达它的预期目的地”,是丢包追踪的核心。

1.2 kfree_skb tracepoint 结构

/* 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)    /* 丢包的内核函数地址 */
        __field(unsigned short,     protocol)    /* 协议号(ETH_P_IP 等) */
        __field(enum skb_drop_reason, reason)    /* 丢弃原因枚举 */
    ),
    ...
);

三个关键字段:

1.3 consume_skb tracepoint

/* include/trace/events/skb.h:54 */
TRACE_EVENT(consume_skb,
    TP_PROTO(struct sk_buff *skb, void *location),
    TP_ARGS(skb, location),
    TP_STRUCT__entry(
        __field(void *, skbaddr)
        __field(void *, location)
    ),
    ...
);

consume_skb 没有 reason 字段——正常消费不需要理由。在追踪中,可以通过同时监听两个 tracepoint 来区分正常消费和异常丢弃。

二、drop_reason:80+ 种丢包原因全解

2.1 核心丢包原因枚举

enum skb_drop_reason 定义在 include/net/dropreason-core.h,通过 DEFINE_DROP_REASON 宏展开。以下按网络栈层次分类:

L2/驱动层丢包:

reason 含义 常见触发场景
FULL_RING 网卡 ring buffer 满 网卡接收速率超过 CPU 处理速率
CPU_BACKLOG per-CPU backlog 队列满 RPS/RFS 分发后目标 CPU 处理不过来
DEV_HDR 设备头部错误 VLAN 头、PPP 头异常
DEV_READY 设备未就绪 接口 down 或正在初始化
OTHERHOST 包不属于本机 混杂模式下收到他人的包

L3(IP)层丢包:

reason 含义 常见触发场景
IP_CSUM IP 校验和错误 硬件卸载故障或链路噪声
IP_INHDR IP 头部异常 头部长度、版本号错误
IP_RPFILTER 反向路径过滤失败 非对称路由且 rp_filter=1
IP_NOPROTO 不支持的 IP 协议 未加载对应协议模块
IP_INADDRERRORS 目标地址错误 非本机地址且未开启转发
IP_INNOROUTES 无路由 路由表中无匹配项
IP_OUTNOROUTES 出口无路由 发送时找不到出口路由
PKT_TOO_BIG 包太大(超过 MTU) MTU 不匹配且 DF 位置位
PKT_TOO_SMALL 包太小 小于最小合法长度

L4(TCP)层丢包:

reason 含义 常见触发场景
TCP_CSUM TCP 校验和错误 链路损坏或中间设备修改
TCP_FLAGS TCP 标志位异常 无效的标志组合
TCP_ZEROWINDOW 零窗口 接收端窗口为 0
TCP_OLD_DATA 旧数据 已确认的数据重复到达
TCP_OVERWINDOW 超出接收窗口 序列号超出接收窗口范围
TCP_OFOMERGE OFO 队列合并丢弃 乱序队列中的重叠段
TCP_RFC7323_PAWS PAWS 时间戳检查失败 时间戳回退
TCP_OLD_SEQUENCE 旧序列号 重传的旧包
TCP_INVALID_SEQUENCE 无效序列号 不在预期范围内
TCP_RESET RST 包 连接被重置
TCP_INVALID_SYN 无效 SYN SYN 到非 LISTEN socket
TCP_CLOSE 连接已关闭 包到达已关闭的 socket
TCP_FASTOPEN TFO 失败 Fast Open cookie 不匹配
TCP_OLD_ACK 旧 ACK ACK 号低于 snd_una
TCP_TOO_OLD_ACK 过旧 ACK ACK 严重滞后
TCP_ACK_UNSENT_DATA ACK 未发送数据 ACK 号超过 snd_nxt
TCP_OFO_QUEUE_PRUNE OFO 队列修剪 内存压力下清理乱序队列
TCP_OFO_DROP OFO 队列丢弃 乱序队列满
TCP_MINTTL TTL 太小 TTL 低于 min_ttl(BGP 安全)

L4(UDP)层丢包:

reason 含义 常见触发场景
UDP_CSUM UDP 校验和错误 数据损坏
NO_SOCKET 找不到目标 socket 无进程监听该端口

Socket/缓冲区丢包:

reason 含义 常见触发场景
SOCKET_RCVBUFF socket 接收缓冲区满 应用读取太慢,SO_RCVBUF 太小
PROTO_MEM 协议内存限制 net.ipv4.udp_mem 等达到上限
SOCKET_BACKLOG backlog 队列满 net.core.somaxconnlisten() backlog 不足
SOCKET_FILTER socket filter 丢弃 BPF socket filter 返回 0

防火墙/策略丢包:

reason 含义 常见触发场景
NETFILTER_DROP Netfilter 丢弃 iptables/nftables DROP 规则
XDP XDP 程序丢弃 XDP 返回 XDP_DROP
TC_INGRESS TC ingress 丢弃 TC BPF 返回 TC_ACT_SHOT
TC_EGRESS TC egress 丢弃 TC egress filter 丢弃
BPF_CGROUP_EGRESS cgroup BPF 丢弃 cgroup egress 策略拒绝
XFRM_POLICY IPsec 策略失败 XFRM 策略检查不通过

队列/调度丢包:

reason 含义 常见触发场景
QDISC_DROP qdisc 丢弃 队列满(pfifo_fast/fq_codel/HTB)
QUEUE_PURGE 队列清除 qdisc 被删除或重置

2.2 子系统扩展机制

Linux 6.x 引入了 enum skb_drop_reason_subsys 允许各子系统注册自己的丢包原因:

/* include/net/dropreason.h */
enum skb_drop_reason_subsys {
    SKB_DROP_REASON_SUBSYS_CORE,              /* 核心丢包原因 */
    SKB_DROP_REASON_SUBSYS_MAC80211_UNUSABLE, /* Wi-Fi 不可用帧 */
    SKB_DROP_REASON_SUBSYS_MAC80211_MONITOR,  /* Wi-Fi 监控帧 */
    SKB_DROP_REASON_SUBSYS_OPENVSWITCH,       /* Open vSwitch */
    SKB_DROP_REASON_SUBSYS_NUM,
};

void drop_reasons_register_subsys(enum skb_drop_reason_subsys subsys,
                                  const struct drop_reason_list *list);
void drop_reasons_unregister_subsys(enum skb_drop_reason_subsys subsys);

这意味着 Open vSwitch、mac80211 等子系统可以定义自己的丢包原因,通过同一个 kfree_skb tracepoint 报告。

三、drop_monitor 子系统

3.1 工作原理

drop_monitor 是内核的 netlink 子系统,专门用于监控丢包事件。它注册了 kfree_skb tracepoint 的回调,将丢包事件通过 netlink socket 发送到用户态。

相比直接使用 bpftrace 追踪 kfree_skb tracepoint,drop_monitor 的优势在于:

  1. 聚合:在内核态聚合相同位置的丢包事件,减少 netlink 消息量
  2. 硬件丢包:可以通过 devlink trap 接收网卡硬件报告的丢包
  3. 标准接口:通过 netlink 提供标准化的丢包监控 API

3.2 使用 dropwatch 工具

dropwatchdrop_monitor 的用户态前端:

# 安装
apt install dropwatch    # Debian/Ubuntu
yum install dropwatch    # RHEL/CentOS

# 启动交互模式
dropwatch -l kas

# 在交互模式中
> start
# 输出示例:
# Initalizing kallsyms db
# 1 drops at tcp_v4_rcv+0x3c (sobject: netfilter)
# 5 drops at __udp4_lib_rcv+0x2a1 (sobject: NO_SOCKET)
# 2 drops at nf_hook_slow+0x4b (sobject: netfilter)
> stop
> exit

dropwatch 输出每个丢包位置的函数名和聚合计数,快速定位丢包热点。

现代智能网卡可以通过 devlink trap 机制报告硬件级丢包:

# 列出网卡支持的 trap 类型
devlink trap list pci/0000:03:00.0

# 启用特定 trap
devlink trap set pci/0000:03:00.0 trap source_mac_is_multicast action trap

# 查看 trap 统计
devlink trap show pci/0000:03:00.0

四、实战:多工具丢包定位

4.1 bpftrace:按原因聚合丢包

最常用的丢包追踪脚本——按 drop_reasonlocation 聚合:

bpftrace -e '
tracepoint:skb:kfree_skb {
    @by_reason[args->reason] = count();
    @by_location[ksym(args->location)] = count();
}
interval:s:10 {
    printf("\n--- Drop reasons ---\n");
    print(@by_reason);
    printf("\n--- Drop locations ---\n");
    print(@by_location);
    clear(@by_reason);
    clear(@by_location);
}
'

按网络设备过滤:

bpftrace -e '
#include <linux/skbuff.h>
tracepoint:skb:kfree_skb {
    $skb = (struct sk_buff *)args->skbaddr;
    if ($skb->dev != 0) {
        $dev = $skb->dev;
        @by_dev[str($dev->name)] = count();
    }
    @by_reason[args->reason] = count();
}
'

4.2 bpftrace:追踪特定原因的丢包调用栈

当发现某个 drop_reason 异常增长时,追踪完整内核调用栈定位根因:

# 追踪 SOCKET_RCVBUFF 丢包的调用栈
bpftrace -e '
tracepoint:skb:kfree_skb
/args->reason == 14/    /* SKB_DROP_REASON_SOCKET_RCVBUFF */
{
    printf("SOCKET_RCVBUFF drop at %s protocol=0x%x\n",
        ksym(args->location), args->protocol);
    print(kstack);
}
'

追踪 Netfilter 丢包并显示五元组:

bpftrace -e '
#include <linux/skbuff.h>
#include <linux/ip.h>
tracepoint:skb:kfree_skb
/args->reason == 6/    /* SKB_DROP_REASON_NETFILTER_DROP */
{
    $skb = (struct sk_buff *)args->skbaddr;
    $iph = (struct iphdr *)($skb->head + $skb->network_header);
    printf("NF_DROP: %s -> %s proto=%d\n",
        ntop(AF_INET, &$iph->saddr),
        ntop(AF_INET, &$iph->daddr),
        $iph->protocol);
}
'

4.3 perf:记录丢包事件

# 记录 60 秒的丢包事件及调用栈
perf record -a -g --call-graph dwarf \
    -e 'skb:kfree_skb' -- sleep 60

# 查看丢包栈
perf script | head -200

# 按丢包函数统计
perf report --sort comm,dso,symbol

# 按 drop_reason 统计
perf script -F event,trace | grep 'reason:' | \
    awk -F'reason: ' '{print $2}' | sort | uniq -c | sort -rn

4.4 ftrace:最轻量追踪

在无法安装 bpftrace 的环境中(如最小化容器):

# 启用 kfree_skb tracepoint
echo 1 > /sys/kernel/debug/tracing/events/skb/kfree_skb/enable

# 实时查看
cat /sys/kernel/debug/tracing/trace_pipe
# 输出示例:
# <idle>-0  [003] ..s1 12345.678: kfree_skb: skbaddr=0xffff... protocol=2048
#     location=tcp_v4_rcv+0x1a3 reason: TCP_INVALID_SEQUENCE

# 过滤特定原因(通过 filter 文件)
echo 'reason == 2' > /sys/kernel/debug/tracing/events/skb/kfree_skb/filter

# 关闭
echo 0 > /sys/kernel/debug/tracing/events/skb/kfree_skb/enable

4.5 nstat 和 /proc/net/snmp:统计计数器

在深入追踪之前,先通过统计计数器确认丢包方向:

# TCP 相关统计
nstat -az | grep -i 'drop\|error\|fail\|overflow\|prune\|retrans'

# 关键计数器解读
# TcpExtListenDrops     - SYN 队列满丢弃
# TcpExtListenOverflows - Accept 队列满溢出
# TcpExtTCPBacklogDrop  - socket backlog 满丢弃
# TcpExtTCPMinTTLDrop   - TTL 太小丢弃
# TcpExtTCPRcvQDrop     - 接收队列满丢弃
# TcpExtTCPOFODrop      - OFO 队列丢弃
# TcpExtTCPRetransFail  - 重传失败
# TcpExtPFMemallocDrop  - 内存压力丢弃

# UDP 丢包
cat /proc/net/snmp | grep Udp
# Udp: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors
# InErrors = 校验和错误等
# RcvbufErrors = 接收缓冲区满丢弃
# NoPorts = 无监听端口

# 网卡级丢包
cat /proc/net/dev | awk 'NR>2{print $1, "rx_drop="$5, "tx_drop="$13}'

# ethtool 统计
ethtool -S eth0 | grep -i 'drop\|error\|discard\|miss\|overflow'

五、常见丢包场景诊断手册

5.1 网卡 ring buffer 满(FULL_RING)

症状ethtool -S 显示 rx_missed_errorsrx_no_buffer_count 增长。

诊断

bpftrace -e '
tracepoint:skb:kfree_skb /args->reason == 67/ {  /* FULL_RING */
    @by_cpu[cpu] = count();
}
interval:s:5 { print(@by_cpu); clear(@by_cpu); }
'

解决

# 增大 ring buffer
ethtool -G eth0 rx 4096

# 检查当前 ring buffer 大小
ethtool -g eth0

5.2 socket 接收缓冲区满(SOCKET_RCVBUFF)

症状nstat 显示 TcpExtTCPRcvQDropUdpRcvbufErrors 增长。

诊断

bpftrace -e '
tracepoint:skb:kfree_skb /args->reason == 14/ {  /* SOCKET_RCVBUFF */
    printf("RCVBUF full at %s\n", ksym(args->location));
    @stacks = count();
}
'

解决

# 增大默认接收缓冲区
sysctl -w net.core.rmem_default=262144
sysctl -w net.core.rmem_max=16777216

# 或应用层设置 SO_RCVBUF

5.3 Netfilter/iptables 丢弃(NETFILTER_DROP)

症状iptables -L -v -n 显示 DROP 规则匹配计数增长。

诊断

bpftrace -e '
tracepoint:skb:kfree_skb /args->reason == 6/ {  /* NETFILTER_DROP */
    printf("NF drop at %s\n", ksym(args->location));
    print(kstack(5));
}
'

通过调用栈区分是 INPUT、FORWARD 还是 OUTPUT 链的规则。

5.4 TCP 序列号相关丢弃

症状nstat 显示 TcpExtTCPPAWSEstabTcpExtTCPSACKDiscard 增长。

诊断

bpftrace -e '
tracepoint:skb:kfree_skb
/args->reason >= 33 && args->reason <= 44/  /* TCP_OLD_DATA..TCP_OFO_DROP */
{
    @tcp_drop_reason[args->reason] = count();
}
interval:s:10 { print(@tcp_drop_reason); clear(@tcp_drop_reason); }
'

5.5 qdisc 队列满(QDISC_DROP)

症状tc -s qdisc show dev eth0 显示 dropped 计数增长。

诊断

bpftrace -e '
tracepoint:skb:kfree_skb /args->reason == 57/ {  /* QDISC_DROP */
    printf("qdisc drop at %s\n", ksym(args->location));
    @by_cpu[cpu] = count();
}
'

解决

# 增大 txqueuelen
ip link set dev eth0 txqueuelen 10000

# 或更换 qdisc
tc qdisc replace dev eth0 root fq_codel limit 10240

六、丢包追踪流程总结

6.1 系统化排查流程

第一步:nstat + /proc/net/snmp 快速定界
    ↓ 确认丢包方向(RX/TX)和协议(TCP/UDP)
第二步:ethtool -S 检查网卡级丢包
    ↓ 排除硬件/驱动层问题
第三步:bpftrace kfree_skb 按 reason 聚合
    ↓ 确定具体丢包原因
第四步:bpftrace 追踪该 reason 的调用栈
    ↓ 定位到具体内核函数
第五步:根据函数和原因调整配置
    → ring buffer / rcvbuf / iptables / qdisc / sysctl

6.2 drop_reason 快速查询

在生产环境中,可以使用以下脚本快速获取丢包原因的可读名称:

# 一行命令:统计 10 秒内的丢包原因
bpftrace -e '
tracepoint:skb:kfree_skb {
    @[args->reason] = count();
}
interval:s:10 { exit(); }
' 2>/dev/null

结合 dropreason-core.h 中的枚举定义,将数字映射为名称。或者使用 perf 自动解析:

perf stat -a -e 'skb:kfree_skb' -- sleep 10

6.3 性能开销

方法 开销 适合场景
nstat / /proc/net/snmp 始终开启,快速定界
ethtool -S 始终可查,网卡级
kfree_skb tracepoint + bpftrace ~100-200 ns/丢包 实时诊断
perf record -e skb:kfree_skb ~100 ns/丢包 录制分析
ftrace event ~50-100 ns/丢包 最小化环境
dropwatch ~100 ns/丢包 快速交互式诊断

由于丢包通常是低频事件(相对于正常包处理),追踪开销在绝大多数场景下可以忽略。

参考文献

  1. Linux 内核源码 include/trace/events/skb.hkfree_skbconsume_skb tracepoint 定义
  2. Linux 内核源码 include/net/dropreason-core.henum skb_drop_reason 完整定义(80+ 种原因)
  3. Linux 内核源码 include/net/dropreason.henum skb_drop_reason_subsys 子系统扩展机制
  4. Linux 内核文档 Documentation/networking/kfree_skb_reason.rst:drop reason 文档
  5. Linux 内核源码 net/core/drop_monitor.cdrop_monitor netlink 子系统实现

上一篇内核网络追踪工具箱:bpftrace/perf/ftrace 实战

下一篇内核网络调优方法论:从基准测试到生产验证

同主题继续阅读

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

2025-07-22 · linux / networking

【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 可观测实战。


By .