记一次有惊无险的丢包调试经历
source link: https://blog.huoding.com/2020/04/27/814
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
某个项目把服务器从 CentOS 操作系统从 5 升级到了 7(Linux 内核:3.10.0-693),一切都很顺利,直到我在服务器上闲逛的时候,无意间发现了一个「大问题」:网卡 eth0 在 RX 上存在丢包(dropped)现象,每隔一秒丢一个包!
watch -d -n1 ‘ifconfig’
第一感觉怀疑是不是网卡的 ring buffer 太小了,通过「ethtool」确认:
shell> ethtool -g eth0 Ring parameters for eth0: Pre-set maximums: RX: 256 RX Mini: 0 RX Jumbo: 0 TX: 256 Current hardware settings: RX: 256 RX Mini: 0 RX Jumbo: 0 TX: 256
看上去确实不大,可惜 Current hardware settings 已经达到 Pre-set maximums 最大值,没法加大了。为了确认网卡是否真的存在丢包,继续通过「ethtool」确认:
shell> ethtool -S eth0 no stats available shell> ethtool -i eth0 driver: virtio_net version: 1.0.0 firmware-version: expansion-rom-version: bus-info: 0000:00:04.0 supports-statistics: no supports-test: no supports-eeprom-access: no supports-register-dump: no supports-priv-flags: no
看上去是 kvm 的 virtio_net 不支持 statistics,好在还有别的方法:
shell> find /sys -name eth0 /sys/devices/pci0000:00/0000:00:04.0/virtio1/net/eth0 /sys/class/net/eth0 shell> cd /sys/devices/pci0000:00/0000:00:04.0/virtio1/net/eth0 shell> cd statistics shell> grep . * | grep rx rx_bytes:633037730314 rx_compressed:0 rx_crc_errors:0 rx_dropped:206975 rx_errors:0 rx_fifo_errors:0 rx_frame_errors:0 rx_length_errors:0 rx_missed_errors:0 rx_nohandler:0 rx_over_errors:0 rx_packets:4717658080
虽然 rx_dropped 不为零,但是 rx_errors 等却都为零,这说明 ring buffer 并没有出现溢出的情况,否则 rx_fifo_errors 之类的错误不可能为零,由此可以推断:网卡已经把数据完整交给了操作系统,其本身并没有丢包,真正丢包的是操作系统。
如何判断操作系统在哪里丢包的呢?是时候让 dropwatch 出场了:
shell> dropwatch -l kas Initalizing kallsyms db dropwatch> start Enabling monitoring... Kernel monitoring activated. Issue Ctrl-C to stop monitoring 6 drops at ip_rcv+cf (0xffffffff815ca47f) 11 drops at ipv6_rcv+3ad (0xffffffff81643d7d) 75 drops at tcp_v4_rcv+87 (0xffffffff815f0197) 426 drops at sk_stream_kill_queues+50 (0xffffffff8157a740) 235 drops at tcp_rcv_state_process+1b0 (0xffffffff815e4fb0) 137 drops at tcp_v4_rcv+87 (0xffffffff815f0197) 11 drops at ipv6_rcv+3ad (0xffffffff81643d7d) 1 drops at __netif_receive_skb_core+3d2 (0xffffffff81586d82) shell> grep -w -A 10 __netif_receive_skb_core /proc/kallsyms ffffffff815869b0 t __netif_receive_skb_core ffffffff81587170 t __netif_receive_skb ffffffff815871d0 t netif_receive_skb_internal ffffffff81587290 T netif_receive_skb ffffffff81587300 t napi_gro_complete ffffffff81587400 T napi_gro_flush ffffffff81587490 T napi_complete_done ffffffff81587550 T napi_complete ffffffff81587570 T sk_busy_loop ffffffff81587830 t net_rx_action ffffffff81587bb0 t dev_gro_receive
关于 dropwatch 的原理,它是通过监控 kfree_skb 的调用来监控操作系统可能的丢包行为,有的丢包可能是正常行为,有的丢包可能是异常行为。如此说来,我们遇到的丢包会是上面哪个函数引起的呢?是正常的还是异常的呢?运气不好的话,可能得一个一个挨个分析,好在我们运气不错,记得文章开头我们提到过,在我们的问题中,每隔一秒丢一个包,于是自然而然的将目光锁定在 __netif_receive_skb_core 之上(丢包地址 0xffffffff81586d82 介于 ffffffff815869b0 和 ffffffff81587170 之间)。
查询一下 Linux 源代码 中 __netif_receive_skb_core 函数的定义来确认一下丢包原因:
static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc) { ... if (pfmemalloc && !skb_pfmemalloc_protocol(skb)) goto drop; ... drop: atomic_long_inc(&skb->dev->rx_dropped); kfree_skb(skb); /* Jamal, now you will not able to escape explaining * me how you were going to use this. :-) */ ret = NET_RX_DROP; ... } static bool skb_pfmemalloc_protocol(struct sk_buff *skb) { switch (skb->protocol) { case __constant_htons(ETH_P_ARP): case __constant_htons(ETH_P_IP): case __constant_htons(ETH_P_IPV6): case __constant_htons(ETH_P_8021Q): case __constant_htons(ETH_P_8021AD): return true; default: return false; } }
当 pfmemalloc 为真,且 skb_pfmemalloc_protocol 中判断不支持包协议的时候,就会丢包。此外,代码里能看到调用了 kfree_skb,侧面验证了 dropwatch 的工作原理。
如何判断我们问题中丢的包协议是什么呢,是时候让 systemtap 出场了:
#! /usr/bin/env stap probe kernel.function("__netif_receive_skb_core").label("drop") { printf("protocol: %x\n", $skb->protocol) }
从前面我们对 Linux 源代码的分析,Linux 内核支持的包 protocol 如下:
#define ETH_P_ARP 0x0806 /* Address Resolution packet */ #define ETH_P_IP 0x0800 /* Internet Protocol packet */ #define ETH_P_IPV6 0x86DD /* IPv6 over bluebook */ #define ETH_P_8021Q 0x8100 /* 802.1Q VLAN Extended Header */ #define ETH_P_8021AD 0x88A8 /* 802.1ad Service VLAN */
而 systemtap 检测到的 包 protocol 是 400,并不在 Linux 支持的范畴,所以被丢掉了。
至于丢掉的包到底是什么,我也不知道,不过我在一些系统的 源代码 中找到一些端倪:
#define ETHERTYPE_SPRITE 0x0500 /* ??? */ /* 0x0400 Nixdorf */
我们不防推测一下:在机房的某个角落里躺着一个老款电子设备,它是一个名为 Nixdorf 的公司生产的,具体功能不详,但是每秒中会在网络中广播一个 EtherType 为 400 的包,不过我们的操作系统并不支持此 protocol。于是就丢弃了它。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK