一个险恶bug的深入分析(3)

发表于:2012-06-06来源:伯乐在线作者:不详点击数: 标签:bug
这段代码对存储了所有pcap条目信息的列表ptype_all进行遍历, 判断pcap条目的设备结构能否匹配skb所属设备。 循环中对设备的检查非常有意思: 1 if (!ptype-d

  这段代码对存储了所有pcap条目信息的列表ptype_all进行遍历, 判断pcap条目的设备结构能否匹配skb所属设备。

  循环中对设备的检查非常有意思:

1
if (!ptype->dev || ptype->dev == skb->dev)

  如果你试图嗅探来自eth0的数据包,但是由于eth0已经是bond的一部分,因而这个检查一定会失败。原因是skb->dev已经被重写为bond设备的dev结构了。

  这就是为什么tcpdump和其他的测量工具嗅探bond相关的物理设备时看不到发送的数据包了!

  只要简单地把if语句修改成:

1
if (!ptype->dev || ptype->dev == skb->dev || ptype->dev == orig_dev) {

  因为加入了orig_dev检查,这下pcap就能够处理被dev指针被修改过的skb了。

  让我们来测试一下这个修改。

  有意思的现象,第二轮观察

  接下来,重新构建并安装修改后的内核(顺便说一下,这里有一份非常有用的文档),重新ping目标机器并开始嗅探发向物理设备的数据包:

1
2
3
4
5
% 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

  什么情况?

  为什么修改以后还是没有看到发送的数据包?

  libpcap

  让我们快速检查一下内核中负责处理AF_PACKET地址家族的libpcap接口。

  AF_PACKET 在内核中是单独实现的一个地址家族,相关的代码位于net/packet/af_packet.c。libpcap通过调用socket系统调用建立一个socket,调用的第一个参数被设置为PACKET。libpcap接下来会使用bind系统调用把这个socket绑定想要嗅探的设备上。

  现在有两种方法可以从内核中拿到数据包:

  • “以前的方法”: 对每个数据包的文件描述符调用recvfrom函数。在老版本的内核上,只有这一个函数可用。

  • “新方法”:调用poll函数会通知libpcap有一组数据包到达,在内核与libpcap的共享内存中等待读取。比起“老办法”这种方法效率更高(使用的系统调用更少),在最近大多数的内核包括Debian Lenny都支持这种办法。

  结果是,尽管Debian Lenny的内核支持“新方法”实现的AF_PACKET,但是相应的libpcap却不支持。这就意味着tcpdump(依赖于libpcap)只能逐次逐个地从内核中取得数据包。

  更新版本的libpcap默认使用“新方法”从内核读取数据包。因为Lenny支持这种用法,我试着构建了一个更新版本的libpcap并修改了tcpdump。在修改过的Lenny内核上测试这个修改,我看到当我在bond上的物理设备进行嗅探时,数据包从RX路径流出。如果我把新的libpcap修改成使用“以前的方法”搜集数据包,没有数据包从RX路径流出。

  这意味着在使用“以前的方法”时,要么AF_PACKET有bug,要么多版本的libpcap有bug。

  if语句

  经过数小时痛苦地阅读代码,我找了一条if语句可以控制libpcap使用“以前的方法”读取数据包。

1
2
3
4
5
// From pcap_read_packet in pcap-linux.c:
 
if (handle->md.ifindex != -1 &&
    from.sll_ifindex != handle->md.ifindex)
  return 0;

原文转自:http://www.ltesting.net