在 Linux 网络的复杂脉络中,数据丢包就像隐匿的幽灵,悄无声息地破坏着网络的顺畅运行。你是否曾困惑,为何关键数据在传输途中突然消失,而排查起来却如同大海捞针?别担心,今天我们将深入 Linux 网络丢包场景,掌握精准 “捕捉” 丢包踪迹的秘诀,让这些隐匿的问题无所遁形。
一、Linux网络丢包概述
在当今数字化时代,网络如同空气般无处不在,支撑着我们生活、工作、娱乐的方方面面。对于使用 Linux 系统的用户来说,网络丢包却犹如一场 “噩梦”,时常搅扰着系统的平稳运行。想象一下,当你兴致勃勃地加载网页,却只见进度条蜗牛般缓慢挪动,图片半天出不来,文字也断断续续;又或是在关键时刻传输重要数据,结果文件传输中断,进度归零,一切只能从头再来。这些让人抓狂的场景,往往就是网络丢包在背后 “捣鬼”。
丢包不仅会导致网络延迟飙升,让实时通信变得卡顿无比,还可能使数据传输的准确性大打折扣,引发各种错误,严重影响用户体验和工作效率。对于企业级应用,如线上交易、视频会议、云计算等,丢包更是可能带来直接的经济损失。所以,搞清楚 Linux 内核常见的网络丢包场景,就如同给网络 “把脉问诊”,找到病因,才能 “对症下药”,让网络恢复畅通无阻。
在开始之前,我们先用一张图解释 linux 系统接收网络报文的过程:
-
首先网络报文通过物理网线发送到网卡
-
网络驱动程序会把网络中的报文读出来放到 ring buffer 中,这个过程使用 DMA(Direct Memory Access),不需要 CPU 参与
-
内核从 ring buffer 中读取报文进行处理,执行 IP 和 TCP/UDP 层的逻辑,最后把报文放到应用程序的 socket buffer 中
-
应用程序从 socket buffer 中读取报文进行处理
二、探寻Linux内核收发包的 “幕后流程”
2.1 收包流程:数据包的 “入境之路”
当网卡接收到报文时,这场 “入境之旅” 就开启了。首先,网卡通过 DMA(直接内存访问)技术,以极高的效率将数据包拷贝到 RingBuf(环形缓冲区)中,就好比货物被快速卸到了一个临时仓库。紧接着,网卡向 CPU 发起一个硬中断,就像吹响了紧急集合哨,通知 CPU 有数据抵达 “国门”。
CPU 迅速响应,开始执行对应的硬中断处理例程,在这个例程里,它会将数据包的相关信息放入每 CPU 变量 poll_list 中,随后触发一个收包软中断,把后续的精细活儿交给软中断去处理。对应 CPU 的软中断线程 ksoftirqd 就登场了,它负责处理网络包接收软中断,具体来说,就是执行 net_rx_action () 函数。
在这个函数的 “指挥” 下,数据包从 RingBuf 中被小心翼翼地取出,然后进入协议栈,开启层层闯关。从链路层开始,检查报文合法性,剥去帧头、帧尾,接着进入网络层,判断包的走向,若是发往本机,再传递到传输层。最终,数据包被妥妥地放到 socket 的接收队列中,等待应用层随时来收取,至此,数据包算是顺利 “入境”,完成了它的收包流程。
2.2 发包流程:数据包的 “出境之旅”
应用程序要发送数据时,数据包的 “出境之旅” 便启程了。首先,应用程序调用 Socket API(比如 sendmsg)发送网络包,这一操作触发系统调用,使得数据从用户空间拷贝到内核空间,同时,内核会为其分配一个 skb(sk_buff 结构体,它可是数据包在内核中的 “代言人”,承载着各种关键信息),并将数据封装其中。接着,skb 进入协议栈,开始自上而下的 “闯关升级”。
在传输层,会为数据添加 TCP 头或 UDP 头,进行拥塞控制、滑动窗口等一系列精细操作;到了网络层,依据目标 IP 地址查找路由表,确定下一跳,填充 IP 头中的源和目标 IP 地址、TTL 等关键信息,还可能进行 skb 切分,同时要经过 netfilter 框架的 “安检”,判断是否符合过滤规则。
之后,在邻居子系统填充目的 MAC 地址,再进入网络设备子系统,skb 被放入发送队列 RingBuf 中,等待网卡发送。网卡发送完成后,会向 CPU 发出一个硬中断,告知 “任务完成”,这个硬中断又会触发软中断,在软中断处理函数中,对 RingBuf 进行清理,把已经发送成功的数据包残留信息清除掉,就像清理运输后的车厢,为下一次运输做好准备,至此,数据包顺利 “出境”,完成了它的发包流程。
三、硬件网卡相关丢包场景
3.1 Ring Buffer 满:“拥堵” 的数据包入口
当网卡接收流量过大,而 CPU 处理速度又跟不上时,就好比高速公路入口车流量爆棚,可收费站的处理效率却很低,数据包就会在 RingBuf 这个 “入口” 处积压,最终导致 RingBuf 满而丢包。这时候,通过 ethtool -S 查看网卡统计信息,如果 rx_no_buffer_count 一直在增长,那基本就能断定是接收 RingBuf 满导致的丢包 “惨案”。
那是什么造成了这种拥堵局面呢?一方面,硬中断分发不均是个 “罪魁祸首”。就像快递包裹集中涌向一个快递员,处理不过来一样,如果网卡的硬中断都扎堆在一个 CPU 核上处理,那这个核肯定不堪重负。咱们可以通过 cat /proc/interrupts 查看网卡硬中断的分配情况,要是发现报文处理都集中在某一个核,那就得考虑 “分流” 了。此时,关闭 irqbalance,再使用 set_irq_affinity.sh 脚本手动把硬中断均匀地绑到多个 CPU 核上,让各个核各司其职,协同处理,就能缓解拥堵。
另一方面,会话分发不均也会添乱。有时候硬中断看似均衡,但某些网络流量触发的中断量级特别大,还都集中在几个核上,这也会导致处理不过来丢包。对于支持多接收队列的网卡,它有个 RSS(Receive Side Scaling)功能,就像智能分拣机,能根据数据包的源 IP、目的 IP、源端口、目的端口等字段,把数据包哈希到不同的接收队列,再分发给不同 CPU 处理。咱们可以用 ethtool -l eth0 查看网卡是否支持多队列,如果支持,还能通过 ethtool --show-ntuple eth0 rx-flow-hash udp4 查看 udp 会话分发哈希关键字,要是觉得不合理,就用 ethtool --config-ntuple 修改分发使用的哈希关键字,让数据包的分发更科学。
要是网卡不支持多队列,或者支持的队列数远少于 CPU 核数,别担心,Linux 内核还有个 rps(Receive Packet Steering)机制,它能进行软中断的负载均衡,就像是软件模拟的 “交通协管员”。比如通过 echo ff > /sys/class/net/eth0/queues/rx-0/rps_cpus 来指定某个接收队列由哪几个核处理。不过要注意,这毕竟是软件模拟,和硬中断均衡比起来,性能稍逊一筹,一般没啥特殊原因,不建议开启。
还有一种情况,就是间接性的突发流量来袭。这就好比节假日的景区,突然涌入大量游客,景区设施一下子应付不来。遇到这种情况,可以通过 ethtool -g 查看当前 RingBuf 配置,再用 ethtool -G 命令加大 RingBuf 的 size,给数据包准备个更大的 “临时仓库”,减少突发流量导致的丢包。
3.2 利用 ntuple 保证关键业务:流量中的 “优先级通道”
在网络的 “洪流” 中,有些关键业务的数据包就像着急赶路的 VIP 旅客,需要特殊照顾,优先通行。默认情况下,网卡一视同仁,通过 rss 技术选择队列处理数据包,可分不清哪些是控制报文等关键业务。这时候,开启网卡的 ntuple 功能就像是给关键业务开辟了一条 “优先级通道”。比如说,将 tcp 端口号为 23 的报文(通常是 telnet 控制报文),定向到 queue 9,让它享受 “专车服务”,与此同时,其他普通报文仍按 rss 技术,使用另外 8 个队列正常排队。
[root@centos ~]# ethtool -K eth0 ntuple on
[root@centos ~]# ethtool -U eth0 flow-type tcp4 dst-port 23 action 9
[root@centos ~]# ethtool -X eth0 equal 8
具体操作就是,先执行 ethtool -K eth0 ntuple on 开启网卡 ntuple 功能,接着 ethtool -U eth0 flow-type tcp4 dst-port 23 action 9 设置规则,把目标端口为 23 的 tcp 报文导向 queue 9,最后 ethtool -X eth0 equal 8 设置使用 8 个队列处理普通报文。不过得注意,这个方案得底层网卡支持才行,就像不是所有车站都有 VIP 通道,得看硬件设施是否到位。
四、ARP丢包隐患
4.1 Neighbor table overflow:ARP 表的 “容量危机”
ARP(Address Resolution Protocol),它负责将 IP 地址转换为 MAC 地址,就像是网络世界里的 “翻译官”,让数据包能准确找到目标设备。但有时候,这个 “翻译官” 也会遇到麻烦,比如邻居表(neighbor table)满了,也就是 ARP 表溢出。这时候,内核会像个焦急的 “传令兵”,大量打印 “neighbour: arp_cache: neighbor table overflow!” 这样的报错信息,仿佛在大声呼喊:“出事啦,ARP 表装不下啦!” 咱们可以通过查看 /proc/net/stat/arp_cache 文件,要是发现里面的 table_fulls 统计值蹭蹭往上涨,那毫无疑问,ARP 表已经陷入了 “容量危机”。内核会大量打印如下的消息:
[22181.923055] neighbour: arp_cache: neighbor table overflow!
[22181.923080] neighbour: arp_cache: neighbor table overflow!
[22181.923128] neighbour: arp_cache: neighbor table overflow!
cat /proc/net/stat/arp_cache也能看到大量的 table_fulls 统计:
[root@centos ~]# cat /proc/net/stat/arp_cache
entries allocs destroys hash_grows lookups hits res_failed rcv_probes_mcast rcv_probes_ucast periodic_gc_runs forced_gc_runs unresolved_discards table_fulls
00000002 0000000d 0000000d 00000000 00000515 00000089 00000015 00000000 00000000 00000267 000003a1 00000023 0000039c
00000002 0000001c 0000001a 00000000 00001675 00000374 00000093 00000000 00000000 000003bf 00000252 00000046 00000240
为啥 ARP 表会满呢?
这通常是因为短时间内大量新的 IP-MAC 映射关系涌入,可旧的条目又没及时清理出去,垃圾回收机制没跟上节奏。想象一下,一个小仓库,不停地进货,却很少出货,空间不就越来越小嘛。
怎么解决呢?
调整内核参数是个 “妙招”。在 /proc/sys/net/ipv4/neigh/default/ 目录下,有几个关键的参数:gc_thresh1、gc_thresh2、gc_thresh3,它们就像是仓库的 “库存管理员”,掌控着 ARP 表的大小。gc_thresh1 是最小条目数,当缓存中的条目数少于它,垃圾回收器就 “睡大觉”,不干活;gc_thresh2 是软最大条目数,要是实际条目数超过它 5 秒,垃圾回收器就会被 “叫醒”,赶紧清理;gc_thresh3 是硬最大条目数,一旦缓存中的条目数越过这条 “红线”,垃圾回收器就得马不停蹄地一直工作。
一般来说,咱们可以适当提高这些阈值,给 ARP 表 “扩容”。比如,执行 “echo 1024 > /proc/sys/net/ipv4/neigh/default/gc_thresh1”,这就相当于跟系统说:“嘿,把最小库存标准提高到 1024 吧。” 要是想让设置永久生效,还得编辑 /etc/sysctl.conf 文件,把 “net.ipv4.neigh.default.gc_thresh1 = 1024” 之类的配置项加进去,保存后执行 sysctl -p,就大功告成啦。不过得注意,调整参数要结合实际网络环境,别调得太大,不然占用过多内存,反而可能引发其他问题,得拿捏好这个 “度”。
4.2 unresolved drops
当发送报文时,如果还没解析到 arp,就会发送 arp 请求,并缓存相应的报文。当然这个缓存是有限制的,默认为 SK_WMEM_MAX(即与 net.core.wmem_default 相同)。当大量发送报文并且 arp 没解析到时,就可能超过 queue_len 导致丢包,cat /proc/net/stat/arp_cache可以看到unresolved_discards 的统计:
[root@centos ~]# cat /proc/net/stat/arp_cache
entries allocs destroys hash_grows lookups hits res_failed rcv_probes_mcast rcv_probes_ucast periodic_gc_runs forced_gc_runs unresolved_discards table_fulls
00000004 00000006 00000003 00000000 0000008f 00000018 00000000 00000000 00000000 00000031 00000000 00000000 00000000
00000004 00000005 00000004 00000000 00000184 0000003b 00000000 00000000 00000000 00000053 00000000 00000005 00000000
可以通过调整 /proc/sys/net/ipv4/neigh/eth0/unres_qlen_bytes 参数来缓解该问题。
五、Conntrack丢包困境
5.1 nf_conntrack: table full:连接跟踪表的 “超载难题”
nf_conntrack 模块就像是网络连接的 “管家”,负责跟踪每个网络连接的状态,像连接何时建立、何时传输数据、何时结束,它都记得清清楚楚。可有时候,这个 “管家” 也会遇到大麻烦,那就是连接跟踪表满了,内核会频繁打印 “nf_conntrack: table full, dropping packet” 的报错信息,就像管家着急地大喊:“不行啦,装不下啦,要丢包啦!” 这时候,通过 cat /proc/sys/net/netfilter/nf_conntrack_count 查看当前连接数,再和 cat /proc/sys/net/netfilter/nf_conntrack_max 显示的最大连接数对比,如果发现当前连接数飙升,直逼甚至超过最大连接数,那基本就能断定是连接跟踪表满导致的丢包。
为啥连接跟踪表会满呢?
当服务器遭遇突发的高并发连接,像电商大促、热门游戏开服,大量新连接瞬间涌入,可旧连接又没及时清理,就像高峰期的火车站,人只进不出,候车大厅不就爆满了嘛。还有一种情况,某些应用程序存在连接泄漏问题,不停地创建新连接,却忘记关闭旧连接,时间一长,连接跟踪表也吃不消了。
那怎么解决呢?
调整内核参数是个关键招法。首先,加大 nf_conntrack_max 参数,就像给连接跟踪表 “扩容”。理论上,它的最大值可以按照 CONNTRACK_MAX = RAMSIZE (in bytes) / 16384 / (ARCH / 32) 这个公式来计算,比如 32G 内存的服务器,大概可以设置为 1048576。咱们可以通过 sysctl -w net.netfilter.nf_conntrack_max = 1048576 即时生效,要是想让设置永久生效,就得编辑 /etc/sysctl.conf 文件,把 “net.netfilter.nf_conntrack_max = 1048576” 写进去,保存后执行 sysctl -p。不过要注意,不能盲目调得太大,毕竟内存资源有限,得根据服务器实际业务量、内存大小等因素综合考量,找到一个平衡点,不然可能会引发系统内存不足,导致其他更严重的问题。
5.2 UDP 接收 buffer 满:UDP 数据的 “接收瓶颈”
UDP(User Datagram Protocol)作为一种无连接的传输协议,虽然传输高效,但也伴随着丢包的风险。当 UDP 接收 buffer 满时,那些后续抵达的 UDP 数据包就像迷失方向的 “孤雁”,找不到落脚之地,只能无奈被丢弃。
怎么判断是不是 UDP 接收 buffer 满导致的丢包呢?
咱们可以使用 netstat -su 这个命令,它就像是一个 “数据包侦探”,能帮咱们查看 UDP 的错包信息。要是发现 “packet receive errors” 这一项的数值在不断攀升,那就得当心了,很可能是 UDP 接收 buffer 满引发的丢包 “警报”。
通过netstat查看 receive buffer errors 的统计就可以获知是否存在这种情况:
[root@centos ~]# netstat -su
...
Udp:
690124 packets received
3919 packets to unknown port received.
0 packet receive errors
694556 packets sent
0 receive buffer errors
0 send buffer errors
UdpLite:
IpExt:
InNoRoutes: 2
InMcastPkts: 37301
InOctets: 2711899731
OutOctets: 2207144577
InMcastOctets: 1342836
InNoECTPkts: 14891803
InECT1Pkts: 2339
一般通过修改 /proc/sys/net/core/rmem_max,并且设置 SO_RCVBUF 选项来增加 socket buffer 可以缓解该问题。
为啥 UDP 接收 buffer 会满呢?
一方面,可能是应用程序处理 UDP 数据包的速度太慢,就像快递员送货,仓库收货速度远远赶不上送货速度,货物自然就堆积如山了。比如说,某个应用程序忙于复杂的数据处理,无暇及时从接收 buffer 中取出 UDP 数据包,导致 buffer 被填满。另一方面,UDP 接收 buffer 的大小设置不合理,默认的 buffer 空间太小,面对大量涌入的 UDP 数据包,很快就不堪重负。
那怎么解决这个问题呢?
调整内核参数是关键一招。咱们可以通过 sysctl 命令来增大 UDP 接收 buffer 的大小。比如,执行 sysctl -w net.core.rmem_max=8388608
,这就相当于给 UDP 数据包的 “仓库” 扩容,让它能容纳更多的数据。同时,还可以调整 net.core.rmem_default 参数,像执行 sysctl -w net.core.rmem_default=8388608
,让默认的接收 buffer 也变大,双管齐下,为 UDP 数据包提供更充足的 “栖息之所”,减少丢包的发生。
六、丢包排查 “实战攻略”
6.1 dropwatch 查看丢包:精准 “捕捉” 丢包踪迹
当遭遇网络丢包时,dropwatch 就像是一位 “侦探”,能帮咱们快速锁定 “嫌疑人”。它基于 kfree_skb 事件,能实时监控内核丢包情况,让那些 “逃逸” 的数据包无处遁形。使用时,先安装相关依赖,像 sudo apt-get install -y libnl-3-dev libnl-genl-3-dev binutils-dev libreadline6-dev,接着 git clone https://github.com/pavel-odintsov/drop_watch.git
下载工具,进入 /src 目录执行 make 编译。
运行时,执行 sudo./dropwatch -l kas 启动,再输入 start 开始监控,它就会像个尽职的 “卫士”,实时显示丢包的数量、发生位置等关键信息。比如说,看到 “1 drops at icmp_rcv+11c (0xffffffff8193bb1c) [software]”,就知道有一个数据包在 icmp_rcv 函数偏移 11c 处被丢弃,咱们顺着这条线索,深挖这个函数相关的代码逻辑,就能找出丢包原因,让网络恢复顺畅。
6.2 利用 iptables LOG 跟踪报文流程:追踪丢包的 “蛛丝马迹”
iptables LOG 功能就如同在网络路径上撒下的 “荧光粉”,能记录报文流经的 “脚印”。当怀疑丢包与网络配置、防火墙规则有关时,它就能派上大用场。咱们可以通过 iptables -A INPUT -j LOG –log-prefix=”iptables-”
这样的命令,在 INPUT 链末尾添加 LOG 规则,记录流入数据包信息。
之后查看 syslog,就能看到类似 “Apr 10 17:45:54 S109 kernel: iptables-IN=eth1 OUT= MAC=ff:ff:ff:ff:ff:ff:52:54:00:02:12:a8:08:00 SRC=0.0.0.0 DST=255.255.255.255 LEN=330 TOS=0x00 PREC=0x00 TTL=128 ID=4225 PROTO=UDP SPT=68 DPT=67 LEN=310” 的日志,里面详细记录了源 MAC、目的 MAC、源 IP、目的 IP 等关键信息,沿着这些 “蛛丝马迹”,就能排查出数据包是不是在防火墙处 “碰壁” 而丢包。
如下可以看到 ping 包是通过eth0 接口出去的:
[root@centos ~]# iptables -I OUTPUT -m limit --limit 1/s -p icmp -j LOG
[root@centos ~]# ping www.baidu.com
PING www.a.shifen.com (183.2.172.185) 56(84) bytes of data.
64 bytes from 183.2.172.185 (183.2.172.185): icmp_seq=1 ttl=52 time=5.87 ms
64 bytes from 183.2.172.185 (183.2.172.185): icmp_seq=2 ttl=52 time=5.03 ms
64 bytes from 183.2.172.185 (183.2.172.185): icmp_seq=3 ttl=52 time=5.17 ms
^C
--- www.a.shifen.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 5.039/5.362/5.875/0.371 ms
[root@centos ~]#
[root@centos ~]# dmesg
...
[2648940.712804] IN= OUT=eth0 SRC=10.0.24.9 DST=169.254.128.3 LEN=28 TOS=0x00 PREC=0x00 TTL=64 ID=23349 PROTO=ICMP TYPE=0 CODE=0 ID=50280 SEQ=34390
[2648943.189182] IN= OUT=eth0 SRC=10.0.24.9 DST=169.254.128.15 LEN=28 TOS=0x00 PREC=0x00 TTL=64 ID=62301 PROTO=ICMP TYPE=0 CODE=0 ID=36154 SEQ=45797
[2648943.730672] IN= OUT=eth0 SRC=10.0.24.9 DST=169.254.128.3 LEN=28 TOS=0x00 PREC=0x00 TTL=64 ID=23451 PROTO=ICMP TYPE=0 CODE=0 ID=50284 SEQ=34394
6.3 利用 iptables 规则跟踪丢包:定制化丢包 “侦查”
有时候,咱们只想关注特定源 IP、目的 IP 或端口的丢包情况,这时候定制化的 iptables 规则就像一把 “精准手术刀”,能帮咱们深入剖析。
比如,怀疑来自某个 IP 段的数据包丢包严重,就可以执行 iptables -A INPUT -s 192.168.1.0/24 -j LOG –log-prefix=”suspect_src_ip-”
,
把这个 IP 段流入的数据包记录下来;要是关注某个目的端口,就用 iptables -A INPUT -d 192.168.1.100 -p tcp --dport 80 -j LOG –log-prefix=”suspect_dst_port80-”
,精准追踪去往特定端口的数据包踪迹。通过这些定制规则,深入分析 syslog 日志,就能快速定位问题根源,解决丢包烦恼。
1、设置丢包模拟的 iptables 规则
假设要模拟丢弃来自特定 IP 地址(例如 192.168.1.100)的 UDP 数据包,可以使用以下命令:“iptables -A INPUT -s 192.168.1.100 -p udp -j DROP
”。
如果要模拟在特定网络接口(如 eth0)上丢弃所有的 TCP 数据包,可以使用:“iptables -A INPUT -i eth0 -p tcp -j DROP
”。
2、观察网络应用反应定位丢包
案例分析:在一个多媒体播放系统中,客户端通过 UDP 协议从 Linux 服务器获取音频和视频流。用户报告说音频流偶尔会中断,怀疑是网络丢包。管理员在服务器上设置了上述的 iptables 规则,模拟丢弃来自客户端 IP 地址的 UDP 数据包。发现当设置这个规则时,音频流中断的情况与用户报告的现象一致。进一步检查发现,是因为网络中的某个中间设备(如路由器)对 UDP 流量有特殊的限制或者错误配置,导致 UDP 数据包在传输过程中被丢弃。
3、调整规则排查问题:在确定了可能的丢包环节后,可以调整 iptables 规则进行进一步的排查。例如,可以修改规则的条件,如将源地址范围扩大或者缩小,或者改变协议类型等,以更精确地定位丢包的原因。例如,如果怀疑是某个 IP 地址段的问题,可以将规则中的源地址修改为该 IP 地址段,如 “iptables -A INPUT -s 192.168.1.0/24 -p udp -j DROP
”,然后观察网络应用的反应。
最后想补充一点,很多网工用Ping命令来检测丢包情况,但其实除了Ping,常用的tracert,nslookup 都可以用来判断主机的网络连通性。
而且 Linux 下有一个更好用的网络联通性判断工具,它可以结合ping nslookup traceroute 来判断网络的相关特性,这个命令就是 mtr。
mtr 全称 my traceroute,是一个把 ping 和 traceroute 合并到一个程序的网络诊断工具。traceroute 默认使用 UDP 数据包探测,而 mtr 默认使用 ICMP 报文探测,ICMP 在某些路由节点的优先级要比其他数据包低,所以测试得到的数据可能低于实际情况。
标签:丢包,UDP,报文,网络,网卡,Linux,net,数据包 From: https://www.cnblogs.com/o-O-oO/p/18666758原创 往事敬秋风 深度Linux