Linux内核常见的丢包场景有哪些

目录

摘要 

1 收发包处理流程

2 硬件网卡相关

2.1 ring buffer满

2.2 利用 ntuple 保证关键业务

3 arp丢包

3.1 neighbor table overflow

3.2 unresolved drops

4 conntrack丢包:nf_conntrack: table full

5 udp接收buffer满

6 丢包定位

6.1 dropwatch 查看丢包

6.2 利用 iptables LOG 跟踪报文流程

6.3 利用 iptables 规则跟踪丢包

7 总结


摘要 

      一个数据包在网络中传输的过程中,是没法保证一定能被目的机接收到的。其中有各种各样的丢包原因,今天来学习一下数据包经过 linux 内核时常见的丢包场景。

1 收发包处理流程

        有必要再回顾下 linux 内核的收发包处理流程,才能更好的认清楚数据包所经过的环节。

        当网卡接收到报文时,将数据包 DMA 拷贝到 RingBuf 并触发一个硬中断。继而 cpu 开始执行对应的(硬)中断处理例程。在硬中断处理例程中,将数据包 list 放在了每 cpu 变量 poll_list 中,紧接着触发了一个收包软中断。对应 cpu 的软中断线程 ksoftirqd 处理网络包接收软中断( net_rx_action() ),将数据包从 RingBuf 中取出,协议栈层层处理,经网络层到传输层,数据包被放到 socket 的接收队列中。随后被应用层收取数据。

        同样的,在发送数据时,skb 也是经过协议栈层层处理。在网络层对 skb 克隆,填充路由项,依次流经 netfilter 框架的 local_out 、post_routing 点。在邻居子系统填充了 mac 地址,在网络设备子系统中将 skb 放到了发送队列 RingBuf 中。在网卡发送完成后,又会通过硬中断触发软中断,在软中断处理函数中进行 RingBuf 的清理。

        接下来进入丢包正题。

2 硬件网卡相关

2.1 ring buffer满

        一般物理网卡丢包时,可以通过 ethtool -S 看到相应的统计信息:

[root@centos ~]# ethtool -s eht0
NIC statistics:  
     rx_packets: 123456  
     tx_packets: 789012  
     rx_bytes: 123456789  
     tx_bytes: 987654321  
     rx_errors: 0  
     tx_errors: 0  
     tx_dropped: 0  
     ...
     rx_no_buffer_count : 0  

        如果发现 rx_no_buffer_count 一直在增长,基本上是因为接收的 RingBuf 满导致的丢包。当然问题的核心原因还是接收流量太大& cpu 处理慢,导致数据包积压造成了丢包。具体的,可能有以下几种场景:

a 硬中断分发不均

        通过 cat /proc/interrupts 来查看网卡的硬中断是否均衡,如果报文处理都集中在一个核上,那么很大可能会导致 cpu 处理不过来导致丢包。这种情况可以考虑关闭 irqbalance,使用 set_irq_affinity.sh 来进行手动绑核,使硬中断的分发更均匀。

[root@centos ~]# cat /proc/interrupts
           CPU0       CPU1       
 ...
 27:    6547137          0   PCI-MSI-edge      virtio0-input.0
 28:          1          0   PCI-MSI-edge      virtio0-output.0
 29:          1    5938591   PCI-MSI-edge      virtio0-input.1
 30:          1          0   PCI-MSI-edge      virtio0-output.1

b 会话分发不均

        有时候发现硬中断已经是均衡的了,但网络流量触发的中断量级更大,并且集中在某几个核上面,这也是可能导致丢包的原因之一。对于支持多接收队列的网卡,通过 RSS 功能,根据数据包的某些字段(如源ip、目的ip、源端口、目的端口)将数据包哈希到不同的接收队列中,进而分发给不同的 cpu 进行处理。

        查看网卡是否支持多队列:

[root@centos ~]# ethtool -l eth0
Channel parameters for eth0:
Pre-set maximums:
RX:		0
TX:		0
Other:		0
Combined:	2  # 支持2个组合队列:既可作接收也可作发送
Current hardware settings:
RX:		0
TX:		0
Other:		0
Combined:	2
# 查看 udp 会话分发哈希关键字
[root@centos ~]# ethtool --show-ntuple eth0 rx-flow-hash udp4
UDP over IPV4 flows use these fields for computing Hash flow key:
IP SA
IP DA

通过 ethtool --config-ntuple 可以修改分发使用的哈希关键字。

c rps

        如果网卡不支持多队列,或者支持的多队列远远小于CPU核数(当然这两种情况都比较少见了)。linux内核提供了 rps 机制来进行软中断的负载均衡。比如可以通过 echo ff > /sys/class/net/eth0/queues/rx-0/rps_cpus 来指定某个接收队列负载均衡到哪几个核处理。当然由于通过软件进行模拟,相比硬中断均衡,性能会下降很多,一般没有特殊原因,不建议开启。

d 突发流量

        如果是间接性的突发流量导致丢包,可以通过 ethtool -g 来查看当前的 RingBuf 配置,并通过ethtool -G 命令加大 RingBuf 的 size,减少突发流量导致的丢包。

[root@centos ~]# ethtool -g eth0
Ring parameters for eth0:
Pre-set maximums:
RX:		1024
RX Mini:	0
RX Jumbo:	0
TX:		1024
Current hardware settings:
RX:		1024
RX Mini:	0
RX Jumbo:	0
TX:		1024

2.2 利用 ntuple 保证关键业务

        有些情况下,需要保证控制报文高优先级进行处理,默认情况下,网卡无差别对待所有报文,通过 rss 技术来选择队列,无法区分控制报文。可以开启网卡的 ntuple 功能来实现,比如将 tcp 端口号为 23 的报文,定向到 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

        当对相应的业务进行绑核处理后,tcp 的 23 端口即可以跟其他业务不冲突,保证其高优先级进行处理。当然这个方案依赖于底层网卡的实现,并不是所有网卡都支持的。

3 arp丢包

3.1 neighbor table overflow

        arp 比较常见的一个问题是 neighbor 表满了,内核会大量打印如下的消息:

[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的参数如下几个参数相关,将其改大到合适的值即可解决该问题:

# 保存在于ARP高速缓存中的最少层数,如果少于这个数,垃圾收集器将不会运行
[root@centos ~]# echo 1024 > /proc/sys/net/ipv4/neigh/default/gc_thresh1 
# 保存在 ARP 高速缓存中的最多的记录软限制。垃圾收集器在开始收集前,允许记录数超过这个数字 5 秒
[root@centos ~]# echo 2048 > /proc/sys/net/ipv4/neigh/default/gc_thresh2 
# 保存在 ARP 高速缓存中的最多记录的硬限制,一旦高速缓存中的数目高于此,垃圾收集器将马上运行
[root@centos ~]# echo 4096 > /proc/sys/net/ipv4/neigh/default/gc_thresh3

3.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 参数来缓解该问题。

4 conntrack丢包:nf_conntrack: table full

        当连接数比较多的时候,可能遇到这个连接跟踪表满的错误。

[ 2306.529864] nf_conntrack: nf_conntrack: table full, dropping packet
[ 2370.529866] nf_conntrack: nf_conntrack: table full, dropping packet
[ 2383.059264] nf_conntrack: nf_conntrack: table full, dropping packet

        conntrack 有个最大表项数(/proc/sys/net/netfilter/nf_conntrack_max)限制来控制。当 conntrack 数目达到 nf_conntrack_max 后,就会尝试将一些未 assured 状态的 conntrack 提前老化掉,尝试8次,未找到合适的可以提前老化的表项,就直接进行丢包处理。

        出于性能的考虑,调大 nf_conntrack_max 的同时,一般也需要调整 nf_conntrack_buckets,建议 max/buckets 小于8 比较合适,这样即使被攻击了,也能快速的进行 early_drop。比如调整 max 到300万,buckets 最好也做相应的调整:

[root@centos ~]# echo 3000000 > /proc/sys/net/netfilter/nf_conntrack_max
[root@centos ~]# echo $((65536*)) > /proc/sys/net/netfilter/nf_conntrack_buckets

5 udp接收buffer满

        当一个快的 udp sender,会导致一个较慢的 udp receiver socket recv 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 可以缓解该问题。

6 丢包定位

6.1 dropwatch 查看丢包

        除了 tcpdump 抓包进行分析外,dropwatch 也是网络协议栈丢包检查利器。实现原理也比较清晰,在 net/core/drop_monitor.c 文件中,当 kfree_skb 被调用时,内核就会记录下来,并通过 netlink 来通知用户态的 dropwatch 来记录。使用起来也非常简单,可以看到所有调用 kfree_skb() 的地方都会被输出:

[root@centos ~]# dropwatch -l kas
Initalizing kallsyms db
dropwatch> start
Enabling monitoring...
Kernel monitoring activated.
Issue Ctrl-C to stop monitoring
1 drops at skb_queue_purge+18 (0xffffffffb4467eb8)
1 drops at icmp_rcv+125 (0xffffffffb4505a35)
1 drops at icmp_rcv+125 (0xffffffffb4505a35)
1 drops at icmp_rcv+125 (0xffffffffb4505a35)
^CGot a stop message
dropwatch> exit
Shutting down ...

6.2 利用 iptables LOG 跟踪报文流程

        有时候,可能会有多个路由出口,为了知道特定的报文是如何路由出去的,可以借助 iptables LOG 来进行跟踪(需要结合limit模块来做限速,避免输出过多)。如下可以看到 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 规则跟踪丢包

        还记得 netfilter 框架的的 5 个hook点吗?

        从图中可以看到,接收到报文,路由前,路由后,发送报文,都可以在配置 iptables 规则来进行跟踪。先梳理清楚数据包经过的 hook 点,接着配置 iptables 规则,使得协议命中但不会产生具体的动作,比如对于 ipsec 协议,通过以下两条规则统计的报文数观察在 OUTPUT 之后到 POSTROUTING 之间的丢包个数:

[root@centos ~]# iptables -t mangle -I OUTPUT -p esp
[root@centos ~]# iptables -t mangle -I POSTROUTING -p esp

7 总结

        除了上面介绍的一些场景外,还有很多其他的场景,比如 ip 层分片重组导致的丢包、tc配置的丢包等等,这里不再一一举例来说明了。总的来说,多总结,多思考,善于使用 linux 内核提供给我们的工具,才能更好的排查问题。

  • 30
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fireplusplus

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值