我有一个 Linux 机器,当从外部接收(下载)文件时,我们在 tcpdump 中看到大量(30%)TCP 重传。使用 dropwatch 实用程序,我们在内核函数 net_receive_skb() 中看到许多数据包丢失。这意味着 NIC 已接收到数据,但随后在处理数据包时,其中一些数据会被丢弃在内核中。许多丢弃的数据包可以解释重传的必要性。
dropwatch 输出如下:
dropwatch -l kas
Initalizing kallsyms db
dropwatch> start
Enabling monitoring...
Kernel monitoring activated.
Issue Ctrl-C to stop monitoring
1 drops at tcp_rcv_established+906 (0xffffffff814d0a66)
6 drops at unix_dgram_connect+4ac (0xffffffff8151890c)
6 drops at unix_dgram_connect+4ac (0xffffffff8151890c)
19 drops at __netif_receive_skb+49f (0xffffffff8147b4ef)
5 drops at __netif_receive_skb+49f (0xffffffff8147b4ef)
9 drops at __netif_receive_skb+49f (0xffffffff8147b4ef)
7 drops at __netif_receive_skb+49f (0xffffffff8147b4ef)
6 drops at __netif_receive_skb+49f (0xffffffff8147b4ef)
14 drops at __netif_receive_skb+49f (0xffffffff8147b4ef)
15 drops at __netif_receive_skb+49f (0xffffffff8147b4ef)
2 drops at __netif_receive_skb+49f (0xffffffff8147b4ef)
2 drops at inet_csk_reset_xmit_timer.clone.1+265 (0xffffffff814d9cb5) ^CGot a stop message
dropwatch> exit
Shutting down ...
该系统是CentOS 6.2,内核为2.6.32(centOS软件包名称2.6.32-696.el6.x86_64)。因此我查看了内核源代码中netif_receive_skb的版本,试图找到丢包的原因。我发现只有一个地方调用 kfree_skb (靠近函数末尾)会在丢弃的数据包上留下痕迹。代码是:
int netif_receive_skb(struct sk_buff *skb)
{
struct packet_type *ptype, *pt_prev;
struct net_device *orig_dev;
struct net_device *null_or_orig;
int ret = NET_RX_DROP;
__be16 type;
if (!skb->tstamp.tv64)
net_timestamp(skb);
if (skb->vlan_tci && vlan_hwaccel_do_receive(skb))
return NET_RX_SUCCESS;
/* if we've gotten here through NAPI, check netpoll */
if (netpoll_receive_skb(skb))
return NET_RX_DROP;
if (!skb->iif)
skb->iif = skb->dev->ifindex;
null_or_orig = NULL;
orig_dev = skb->dev;
if (orig_dev->master) {
if (skb_bond_should_drop(skb))
null_or_orig = orig_dev; /* deliver only exact match */
else
skb->dev = orig_dev->master;
}
__get_cpu_var(netdev_rx_stat).total++;
skb_reset_network_header(skb);
skb_reset_transport_header(skb);
skb->mac_len = skb->network_header - skb->mac_header;
pt_prev = NULL;
rcu_read_lock();
#ifdef CONFIG_NET_CLS_ACT
if (skb->tc_verd & TC_NCLS) {
skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
goto ncls;
}
#endif
list_for_each_entry_rcu(ptype, &ptype_all, list) {
if (ptype->dev == null_or_orig || ptype->dev == skb->dev ||
ptype->dev == orig_dev) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}
#ifdef CONFIG_NET_CLS_ACT
skb = handle_ing(skb, &pt_prev, &ret, orig_dev);
if (!skb)
goto out;
ncls:
#endif
skb = handle_bridge(skb, &pt_prev, &ret, orig_dev);
if (!skb)
goto out;
skb = handle_macvlan(skb, &pt_prev, &ret, orig_dev);
if (!skb)
goto out;
type = skb->protocol;
list_for_each_entry_rcu(ptype,
&ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
if (ptype->type == type &&
(ptype->dev == null_or_orig || ptype->dev == skb->dev ||
ptype->dev == orig_dev)) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}
if (pt_prev) {
ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
} else {
kfree_skb(skb);
/* Jamal, now you will not able to escape explaining
* me how you were going to use this. :-)
*/
ret = NET_RX_DROP;
}
out:
rcu_read_unlock();
return ret;
}
看来只有当 skb->dev 未在任何协议的 ptype 列表中注册时,才会发生对 kfree_skb 的调用,因此在针对 ptype 列表进行 2 次循环后 pt_prev 保持为 NULL。这是没有意义的,因为系统只删除所有包的一小部分 - 这意味着该设备“大部分时间在协议 ptype 列表中注册,但有时不在那里”。
所以,问题是 - 我在理解 dropwatch 结果和 netif_receive_skb 代码时犯了什么错误?对于此功能中报告的数据包丢失,更合理的解释是什么?
答案1
也许这摘录自https://access.redhat.com/solutions/657483有助于解释掉在那里的原因:
RT 和 RHEL7 内核包含针对其他非错误情况更新 rx_dropped 计数器的代码。
- 软网积压已满
- 错误的 VLAN 标记
- 使用未知或未注册协议接收的数据包
- 当服务器仅配置为 IPv4 时的 IPv6 帧