libpcap库使用指南

By | 2010-11-04

众所周知,libpcap是一个可移植的网络数据包捕获库,使用C/C++编写的程序都可以用它,当然,它还有很多其他语言的Wrapper。libpcap可以从tcpdump的网站下载到。

这里我主要介绍一下它的C语言API。在文章的最后,我将介绍一下winpcap(libpcap的Windows实现版)。

Prefix

首先,我们需要了解C语言的基本知识。其次,最好对计算机网络有一定的了解。

Knowledge

我们可以分5个步骤来描述使用libpcap编写程序的步骤:

  1. 我们首先要决定监听的设备接口,这个可以用一个string来表示,也可以由pcap提供给我们。
  2. 初始化pcap。我们要告诉pcap需要监听的是哪一个设备。pcap对于不同的设备,使用session来区分它们,一个设备就是一个“session”。
  3. 我们需要创建一个监听的原则(rule),然后编译它,提供给pcap作为一个session的filter。
  4. 接下来,我们进入真正的监听循环中。我们告诉pcap监听多少packets;每当一个packet到来的时候,都会调用我们已经定义好的一个函数,在这个函数中,我们处理监听到的数据。
  5. 最后,我们关闭监听的session。

Functions

在源代码中,#include <pcap.h>是必须的。

char* pcap_lookupdev(char* errbuf)

返回一个设备名,Linux可能是eth1,Mac OS X可能是en0,错误时返回NULL。参数errbuf是遇到错误时对错误的描述字符串,我们一般这么定义:
char errbuf[PCAP_ERRBUF_SIZE]。

pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf)

这个函数会打开device所表示的设备,返回一个“session”,错误时返回NULL。snaplen是pcap能捕获的最大字节数,promisc为true(非0值,一般是1)的时候,这个设备在监听的时候使用的是promiscuous mode(即使有时候设置成了false,在某些特定情况下也会强制变为true)。to_ms是超时毫秒数,0表示没有超时。如果没有超时,一般来说,读取snaplen字节之后才会返回,除非发生错误。errbuf和前面提到的是一个意思。

这里解释一下promisc模式,中文一般翻译为混杂模式。非混杂模式只监听所选择设备的数据包,包括接收到的,发送到的,以及路由的。而混杂模式则会监听线路上所有的数据包,工作在non-switched environment下(比如说hub),但是,混杂模式是可以被检测到的,而且在高速的网络环境下,会给系统造成很大的负担。

int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask)

这个函数编译str,其中str是一种常规字符串,在tcpdump的man page中有详细的说明,这里就不再赘述了。所得结果存在fp中,可以作为

int pcap_setfilter(pcap_t *p, struct bpf_program *fp)

的参数,给session设置filter。

p就是在前面pcap_open_live打开的session。optimize是决定常规字符串是否优化的的选项,1表示true,0表示false。netmask是子网掩码。

通过

int pcap_lookupnet(const char *device, bpf_u_int32 *netp, bpf_u_int32 *maskp, char *errbuf)

可以得到device的网络地址和子网掩码。

上面三个函数错误时返回-1,成功时返回其他值。

u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)

这个函数获得监听到的数据包,h是一个存储抓取的数据包信息的结构体。

struct pcap_pkthdr {
        struct timeval ts; /* time stamp */
        bpf_u_int32 caplen; /* length of portion present */
        bpf_u_int32 len; /* length this packet (off wire) */
};

int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)


int pcap_dispatch(pcap_t *p, int cnt, pcap_handler callback, u_char *user)

则分别是循环监听数据包cnt次,如果cnt为负数,那么表示一直监听,直到发生错误。callback是一个回调函数,它的格式是这样的:

void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet)

其中,user作为用户自定义的数据,传入给callback的args参数,callback将监听到的数据和信息分别存入packet和header。

pcap_loop和pcap_dispatch的区别在于,前者会一直读取数据,直到读取了cnt个数据包,后者会在pcap_open_live设置的timeout时间到了之后返回(尽管是不能保证的)。细节的区别请参看pcap的man page

pcap_dispatch返回读取的packets数目,0表示没有读到packets,-1表示发生了错误,可以使用pcap_perror()或者pcap_geterr()得到错误信息。-2表示调用了pcap_breakloop(pcap_t *)。

void pcap_perror(pcap_t *p, char *prefix)

打印出p的错误信息,prefix作为错误信息的前缀。

char *pcap_geterr(pcap_t *p)

得到错误信息,在p关闭后,其返回值将不会指向正确的错误信息。

pcap_loop返回0表示读取了cnt个packets,返回-1表示发生了错误,返回-2表示调用了pcap_breakloop(pcap_t *)。

void pcap_freecode(struct bpf_program *)

释放掉编译后的bpf_program资源。

void pcap_close(pcap_t *p)

一般会在最后关闭p,释放所有资源。

Extras

int pcap_datalink(pcap_t *p)
int pcap_list_datalinks(pcap_t *p, int **dlt_buf);
int pcap_set_datalink(pcap_t *p, int dlt);

这三个函数操作设备的datalink,pcap_datalink会返回设备的链路层类型,例如:

DLT_EN10MB是Ethernet (10Mb, 100Mb, 1000Mb, and up)类型。
DLT_IEEE802_11是IEEE 802.11 wireless LAN类型。

pcap_list_datalinks将会列出设备支持的链路层类型,返回类型个数,错误则返回-1。

而pcap_set_datalink设置设备的链路层类型,错误返回-1。

Network

注意,我们抓到的数据包有包头,例如对于TCP/IP的数据包,包括Ethernet Header,IP HeaderTCP Header

所以,在这一个小节里,我们将简单讲一下以太网(Ethernet)的数据包结构。

这幅图显示了以太网+IP+TCP的结构。

Ethernet Header长度总是14字节,分别包括6字节的目标MAC地址,6字节的源MAC地址,2字节的以太网类型。这里是Ethernet Header的type字段含义。

而IP和TCP的头定义都在上面两个链接中说的很清楚了,这里就不再赘述了。

Winpcap

Winpcap是libpcap在Windows下的实现版本,编程的流程和libpcap没有什么区别,值得注意的是,微软的C编译器支持的C标准很老,如果你编译的是C文件,那么需要注意所有的变量定义都要放在函数的开始。

发表评论

电子邮件地址不会被公开。 必填项已用*标注