众所周知,libpcap是一个可移植的网络数据包捕获库,使用C/C++编写的程序都可以用它,当然,它还有很多其他语言的Wrapper。libpcap可以从tcpdump的网站下载到。
这里我主要介绍一下它的C语言API。在文章的最后,我将介绍一下winpcap(libpcap的Windows实现版)。
Prefix
首先,我们需要了解C语言的基本知识。其次,最好对计算机网络有一定的了解。
Knowledge
我们可以分5个步骤来描述使用libpcap编写程序的步骤:
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 Header,TCP 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文件,那么需要注意所有的变量定义都要放在函数的开始。