随机丢包但 ping 延迟正常,说明网络层连通性无问题,丢包大概率发生在传输层或更高层,如 TCP 重传丢失、SYN 被静默丢弃、防火墙限速、conntrack 表溢出或应用接收队列满;应先用 tcpdump、ss、/proc/net/nf_conntrack 和 netstat -s 排查基础状态,再结合 perf(抓 kfree_skb、net_dev_queue 等事件)和 bpftrace(追踪 tcp_v4_do_rcv 等函数返回值与上下文)精确定位丢包路径,并关联应用日志与缓冲区配置综合分析。

随机丢包但 ping 延迟正常,说明 ICMP 报文能稳定往返,网络层连通性没问题,问题大概率出在传输层或更高层——比如 TCP 重传被丢、SYN 被静默丢弃、防火墙/中间设备策略性限速、连接跟踪表溢出、或应用层主动丢包(如 socket 接收队列满)。perf 和 bpftrace 是定位这类内核路径问题的利器,关键在于抓对位置、过滤干扰、比对上下文。
先确认丢包是否真发生在本机
别急着上 perf,先用基础命令排除误判:
- tcpdump -i any 'tcp[tcpflags] & (tcp-syn|tcp-rst|tcp-fin) != 0' -c 100:看是否有大量 SYN/RST/FIN 异常,尤其关注客户端发了 SYN 但本机没回 SYN-ACK;
- ss -s:检查 “TCP: inuse”、“orphan”、“mem” 等字段,orphan 过高可能表示 TIME-WAIT 或内存压力;
- cat /proc.net/nf_conntrack | wc -l 和 sysctl net.netfilter.nf_conntrack_max:对比 conntrack 表是否打满(常见于大量短连接);
- netstat -s | grep -A 5 "Tcp:":重点看 “retransmits”、“embryonic RSTs”、“pruned from receive queue” 等计数是否异常增长。
用 perf 抓内核协议栈关键丢包点
perf 更适合统计型采样,适合发现高频丢包路径。重点关注几个内核函数返回值为 -1(失败)或跳过后续处理的位置:
- perf record -e 'skb:kfree_skb' --filter 'protocol == 0x0800 && len > 64' -a sleep 30:捕获 IPv4 skb 释放事件,配合 --call-graph dwarf 查看调用栈,若大量来自 tcp_v4_do_rcv / tcp_rcv_established / ip_local_deliver_finish,说明丢包发生在 TCP 处理阶段;
- perf record -e 'net:net_dev_queue' -e 'net:netif_receive_skb' -a sleep 30:对比入向 skb 数量与实际进入协议栈数量,若 netif_receive_skb 明显少于网卡收包中断(可用 irqbalance 或 /proc/interrupts 验证),可能是驱动或硬件丢包;
- perf record -e 'kprobe:tcp_v4_rcv' -e 'kprobe:tcp_rcv_state_process' -e 'kprobe:tcp_send_loss_probe' -a sleep 30:手动埋点观察 TCP 状态机行为,结合 --call-graph 看是否卡在某个分支(如 sk->sk_receive_queue 满时直接 goto drop)。
用 bpftrace 实时追踪具体丢包原因
bpftrace 更灵活,可带条件过滤和上下文打印。下面几个 one-liner 直击常见丢包场景:
- bpftrace -e 'kprobe:tcp_v4_do_rcv { @drop_reason["sk_receive_queue full"] = count() if (args->sk->__sk_common.skc_state == 1 && args->skb->len > 0 && args->sk->sk_receive_queue.qlen >= args->sk->sk_rcvbuf); }':检测 ESTABLISHED 状态下因接收队列满丢包;
- bpftrace -e 'kprobe:ip_local_deliver_finish /args->skb->pkt_type == 0x00/ { @proto[args->skb->protocol] = count(); }':统计不同协议(IPv4/IPv6/TCP/UDP)在本地交付阶段的分布,突增某类 protocol 可能指向伪造包或畸形包被丢;
- bpftrace -e 'kretprobe:tcp_v4_do_rcv /retval == -1/ { printf("DROPPED TCP %s:%d -> %s:%d reason: %d\n", ntop(args->iph->saddr), ntohs(args->th->source), ntop(args->iph->daddr), ntohs(args->th->dest), retval); }':打印所有被 tcp_v4_do_rcv 主动拒绝的 TCP 包及返回码(-1 通常是 packet error 或 checksum fail)。
关联分析:把 perf/bpftrace 和应用日志串起来
单纯看内核丢包不够,要和业务行为对齐:
- 用 bpftrace -e 'uprobe:/path/to/app:handle_request { printf("[%d] req start %s\n", pid, strftime("%H:%M:%S", nsecs)); }' 打点关键业务入口;
- 在丢包高发时段,用 perf script 导出 perf.data,grep 关键时间戳附近内核栈,看是否集中在某类请求处理路径后;
- 如果应用用了 SO_RCVBUF/SO_SNDBUF,检查是否设得过小:ss -i 查看 Recv-Q/Send-Q 持续非零,且 qsize 小于应用预期吞吐,大概率是缓冲区瓶颈。
不复杂但容易忽略:很多“随机丢包”其实是连接跟踪老化超时后,返回包因无 conntrack entry 被 DROP;或者 iptables 的 raw 表里有 -j NOTRACK 但没配好,导致状态不一致。bpftrace 抓 nf_hook_slow 或 xt_nflog_trace 能快速验证。










