日期:2014-05-16  浏览次数:20682 次

IEEE802.11数据帧在Linux上的抓取
终于得到了梦寐的《802.11无线网络权威指南》,虽然是复印版本,看起来也一样舒服,光看书是不行的,关键还是自己练习,这就需要搭建一个舒服的实验环境,抓包是必不可少的了,因为只有详细分析802.11数据帧,才能深入理解协议的细节。软件上就是这个理,手上没设备还是不行,这可是搭建实验环境的第一步,巧妇难为无米之炊。设备问题很好解决,买一个就行了,最好买适合DIY的那种,既便宜又不怕折腾坏了,因此淘宝是一个好去处。我搞到了一个ralink的802.11bgn的无线网卡,USB2.0的,除了有点发热之外别的都很好,软AP信号足,功率够,速度快。就是驱动不太给力啊。

        硬件有了,先驱起来再说,完事之后就要想办法抓包了,千万别以为抓包很容易,一个软件就搞定,想抓取802.11的数据包,还真得下一番功夫啊,本文接下来的部分就谈谈我的802.11抓包经历。

        Linux内核的抓包机制软件上全在ptype_all这个链表,而不存在所谓“网卡混杂模式”,混杂模式主要是硬件上的概念,有些网卡会在芯片内部完成MAC地址的过滤,因此必须让芯片“知道”不要过滤任何地址这一件事,因此就有了混杂模式的概念,对于无线网络,混杂模式在理解上更加复杂,因此“不要过滤任何数据帧”和802.11规定的AP地址过滤某些行为上是矛盾的。举个例子,一个没有和此AP建立关联的移动节点发来的包,AP按照802.11规范是要丢掉它的,然而混杂模式又要求接受它,这就是矛盾。矛盾的本质原因在于无线链路的“无边界”特征,这是电磁波的物理特性导致的。对于此谁也无能改变,因此ESS无线网络就不能设计成一个完全的广播网络,否则一个三维空间的广播造成的冲突要浪费多少资源啊--我们知道有线局域网以太网最初是一个一维线缆上的广播而已,现在有了交换机,CSMA/CD基本已经不再被使用了。只能由AP接入点来负责在移动节点之间转发数据帧,因为只有它知道谁跟自己建立了关联。这种区别造成了无线网络抓包的尴尬。

        在设计上,操作系统完全避免了这个尴尬,要么它根本不允许在802.11这个层次上进行抓包,要么由驱动决定如何实现抓包。对于Linux,高版本内核实现了统一的802.11适配层框架,驱动的实现可选使用,低版本内核完全由驱动来决定能否实现抓包;对于Windows,如果你使用WireShark工具在无线网卡上进行抓包,会得到以下错误:


在Wireshark的网站上,也有类似的说法:


这个may not未免太无耻了。然而tcpdump的手册上却说可以在monitor mode下抓取802.11帧,然而这却需要驱动来支持。对Linux而言,虽然较高版本的内核支持了802.11适配层,然而却不是每个驱动的实现都遵循了这个框架,比如我手上的ralink的驱动就很扯,完全按照windows的那一套来写的。因此依靠monitor mode来抓取802.11帧这完全靠不住!接下来分析Linux的抓包机制和802.11适配层的关系以及衔接。

        对于进入的包,包在netif_receive_skb中被截取,对于出去的包,则在dev_queue_xmit,而这两个地方全部都在802.11逻辑的上面,这就涉及到了802.11的设计。
1.作为有线局域网的扩展,要可以和有线局域网无缝桥接,也就是对arp协议透明;
2.由于无线网络物理链路的特殊性,需要设计特殊的帧格式以满足需要。

这两点看上去是矛盾的,其实不然。有两种方案可实现,其一就是将以太帧封装在802.11帧里面,类似隧道那样,其二就是直接通过以太帧构造802.11帧,两种帧只要能保证可以互相生成即可。802.11采用的正是这种方式,数据帧发往无线链路时,由以太帧生成802.11帧,反过来由802.11帧生成以太帧。

        这就需要做一个适配层,用来转换两种帧,在linux中,这种适配层表述的很清晰。基于2.6.32的内核,$SRC/driver/net/mac80211目录下面的代码就是适配层的实现。值得注意的是,旧一点的内核版本虽然没有这么清晰的目录结构,代码也是很明白的,总的实现如下图所示:


注意,适配器适配的两种帧并不是平行的,在协议栈上是上下的关系,这是因为我们已经习惯了以太网,因此需要将802.11适配到以太网,而不需要反过来适配,因此对外只需要呈现一个以太网卡接口即可,没有必要再显示一个“无线网卡设备”接口。最终的结果就是802.11和802.3更像是协议栈上的上下层关系,而不是两种平行的协议。

        由于802.11的处理逻辑完全在“网卡设备”接口以下,而抓包逻辑则在“网卡设备”接口以上(netif_XX/dev_YY),而Linux内核看到实现了802.11的网卡设备时,所有路径均已经经过了适配层,因此抓包逻辑看到的“无线网卡”上的流量就都成了以太帧了。另外对于实现了基础结构BSS无线AP的无线网卡而言,有时它转发的是两个无线站点之间的流量,为了使“网卡设备”接口这种流量,需要将所有的802.11数据帧在抉择是发往有线网还是发给另一个无线站点之前全部进行适配,适配成以太帧,如下图所示:


这样,我们知道了为何无法使用tcpdump抓取802.11帧了,接下来就是想办法抓取到这种帧。最关键的事情就是找到在哪里802.11帧转化为了以太帧以及相反的转化,而这很容易,在2.6.32内核中,ieee80211_invoke_rx_handlers函数里面取到的就是802.11帧,不过注意,一定要在__ieee80211_data_to_8023调用之前,对于其它的内核版本,也是类似的。找到了这个之后,接下来需要将以下的代码添加到你找到的位置:

list_for_each_entry_rcu(ptype, &ptype_all, list) {
    if (ptype->dev == null_or_orig || ptype->dev == skb->dev ||
        ptype->dev == orig_dev)
        ret = deliver_skb(skb, ptype, skb->dev);
    }
}

ptype_all并没有导出,那么可以通过/boot/System.map-2.6.32-5-amd64这个文件中取得,这样就可以使得tcpdump抓取到802.11的数据帧,原汁原味的。同样的方法也可以用于抓取发出的802.11数据帧。        到此为止,不得不插一句,来看看依靠802.11适配层也就是wireless框架如何来实现抓包,正如tcpdump的手册上写的那样,注意这只能在高版本的内核上才行得通,我手上的2.6.37则刚好。在框架内部,实现了ieee80211_rx函数,该函数被驱动调用,接收来自驱动的802.11数据帧,其中调用了ieee80211_rx_monitor来实现802.11数据帧直接上传到“网卡设备接口”,从而越过了适配层将802.11帧适配到以太帧这一步,ieee80211_rx_monitor中式这么实现的:
list_for_each_entry_rcu(sdata, &local->interfaces, list) {
    if (sdata->vif.type != NL80211_IFTYPE_MONITOR)
        continue;
    if (sdata->u.mntr_f