引子

  本文将要描述一个我在某些linux版本以及libpcap(unix/linux平台下网络数据捕获包)中发现的一个非常险恶的bug。

  一个有意思的现象

  一位客户向我们报告,在一些安装了Debian Lenny的机器上,处于主动备份模式下的网卡不能检测到发送的数据包,边界流量检测器没有任何图形显示。我在公司里找了几台与客户硬件配置一样的机器,开始对问题展开调查。

  首先,我从自己的笔记本ping目标机器。接着,我在目标机器上使用tcpdump嗅探(sniff 窃听网络上流经的数据包)绑定接口收到的ICMP包。

% sudo tcpdump -i bond0 dst 172.16.209.136 and proto 1
12:57:26.275660 IP 172.16.209.1 > 172.16.209.136: ICMP echo request, id 62831, seq 54, length 64
12:57:27.275731 IP 172.16.209.1 > 172.16.209.136: ICMP echo request, id 62831, seq 55, length 64
^C
2 packets captured
2 packets received by filter
0 packets dropped by kernel

  看来一切正常,是时候开始监听eth0了。eth0绑定的是活跃物理网卡:

% sudo tcpdump -i eth0 dst 172.16.209.136 and proto 1
^C
0 packets captured
2 packets received by filter
0 packets dropped by kernel

  结果,bond0嗅探的结果显示有ICMP输入,但是eth0绑定的物理网卡没有任何数据包输入。这难怪我们的监测工具没有输入流量显示,因为测量仪没有检测到数据包!

  这是什么原因呢?

  设备无关层

  为了调试这个问题,我首先开始检查网络协议栈的设备无关层,跟踪pcap负责处理输入数据包的相关代码。设备驱动通过调用设备无关层的netif_receive_skb函数处理从网络捕获的一组数据。

  查看位于net/core/dev.c文件中的netif_receive_skb函数(简洁起见这里只摘取重要部分):

 int netif_receive_skb(struct sk_buff *skb)
{
  /* ... */
 
  orig_dev = skb_bond(skb);
 
  if (!orig_dev)
    return NET_RX_DROP;
 
  /* ...  */
  skb_bond函数会判断skb是否属于bond。如果是,那么函数要确保skb来自bond上的一个活跃设备。这个检查是为了防止高层协议为某个bond重复进行配置分配。如果skb通过了这些检查,skb的dev指针会被赋值为指向bond的设备指针,同时netif_receive_skb函数会返回原设备指针。

  从概念上理解,你可以把上述代码等价于如下伪代码:

  起始状态:
 orig_dev = NULL
skb->dev = "eth0"
 
orig_dev = skb->dev
if skb->dev 是bond的一部分:
  if skb->dev 是bond上的活跃设备
    skb->dev = bond

  结束状态:

 orig_dev = "eth0"
skb->dev = bond

  因此,看上去skb消息被当成发自一个bond中的设备而不是物理设备。

  如果我们继续跟踪netif_receive_skb函数,我们会看到代码将skb转给了pcap处理:

 list_for_each_entry_rcu(ptype, &ptype_all, list) {
  if (!ptype->dev || ptype->dev == skb->dev) {
    if (pt_prev)
      ret = deliver_skb(skb, pt_prev, orig_dev);
 
    pt_prev = ptype;
  }
}
  这段代码对存储了所有pcap条目信息的列表ptype_all进行遍历, 判断pcap条目的设备结构能否匹配skb所属设备。