函数 pcap_open_live() 的调用形式是 pcap_t * pcap_open_live(const char *device, int snaplen, int promisc, int to_ms, char *ebuf),其中如果 device 为 NULL 或"any",则对所有接口捕获,snaplen 代表用户期望的捕获数据包最大长度,promisc 代表设置接口为混杂模式(捕获所有到达接口的数据包,但只有在设备给定的情况下有意义),to_ms 代表函数超时返回的时间。本函数的代码比较简单,其执行步骤如下:
- 为结构 pcap_t 分配空间并根据函数入参对其部分属性进行初试化。
- 分别利用函数 live_open_new() 或 live_open_old() 尝试创建 PF_PACKET 方式或 SOCK_PACKET 方式的 socket,注意函数名中一个为"new",另一个为"old"。
- 根据 socket 的方式,设置捕获句柄的读缓冲区长度,并分配空间。
- 为捕获句柄 pcap_t 设置 linux 系统下的特定函数,其中最重要的是读数据包函数和设置过滤器函数。(注意到这种从抽象模式到具体模式的设计思想在 linux 源代码中也多次出现,如 VFS 文件系统)
handle->read_op = pcap_read_linux; handle->setfilter_op = pcap_setfilter_linux;
下面我们依次分析 2.2 和 2.0 内核版本下的 socket 创建函数。
|
比较上面两个函数的代码,还有两个细节上的区别。首先是 socket 与接口绑定所使用的结构:老式的绑定使用了结构 sockaddr,而新式的则使用了 2.2 内核中定义的通用链路头部层结构 sockaddr_ll。
|
第二个是在 2.2 版本中设置设备为混杂模式时,使用了函数 setsockopt(),以及新的标志 PACKET_ADD_MEMBERSHIP 和结构 packet_mreq。我估计这种方式主要是希望提供一个统一的调用接口,以代替传统的(混乱的)ioctl 调用。
|
用户应用程序接口
Libpcap 提供的用户程序接口比较简单,通过反复调用函数pcap_next()[pcap.c] 则可获得捕获到的数据包。下面是一些使用到的数据结构:
|
pcap_dispatch() 简单的调用捕获句柄 pcap_t 中定义的特定操作系统的读数据函数:return p->read_op(p, cnt, callback, user)。在 linux 系统下,对应的读函数为 pcap_read_linux()(在创建捕获句柄时已定义 [pcap-linux.c]),而pcap_read_linux() 则是直接调用 pcap_read_packet()([pcap-linux.c])。
pcap_read_packet() 的中心任务是利用了 recvfrom() 从已创建的 socket 上读数据包数据,但是考虑到 socket 可能为前面讨论到的三种方式中的某一种,因此对数据缓冲区的结构有相应的处理,主要表现在加工模式下对伪链路层头部的合成。具体代码分析如下:
|