最近要对tcpdump抓到的报文进行分析,开始的时候用wireshark的命令行工具tshark把分析的结果保存成文本文件然后再用正则表达式匹配需要的字段,这样好处是不用自己分析协议,只要抓取需要的字段就行了,缺点是相当地慢,330M的tcpdump文件经过tshark处理后得到5G+的文本文件,还要从这5G+文本中做字符串匹配……
于是往下走一层,发现wireshark和tcpdump都使用了同样的库--libpcap,来实现抓包操作。对于抓包需要用到的libpcap函数并不多,主要是自己分析协议部分比较麻烦。网上找了些libpcap的资料,在这里总结一下。
简介(参考资料[1])
libpcap是一个C语言库,libpcap的英文意思是 Packet Capture library,即数据包捕获函数库,其功能是通过网卡抓取网络以太网中的数据包。这个库为不同的平台提供了一致的c函数编程接口,在安装了 libpcap 的平台上,以 libpcap 为接口写的程序、应用,能够自由地跨平台使用。它支持多种操作系统。libpcap 结构简单,使用方便;它提供了20多个api封装函数,我们利用这些api函数即可完成本网络探测器所需的网络数据包监听功能。
第一个pcap程序:
#include <stdio.h>
#include <pcap.h>
int main()
{
pcap_t* pd;
char ebuf[PCAP_ERRBUF_SIZE], *dev;
const u_char* pkt;
struct pcap_pkthdr ph;
dev = pcap_lookupdev(ebuf);
if (!dev) {
fprintf(stderr, "%s\n", ebuf);
return -1;
}
printf("get net device -> %s\n", dev);
pd = pcap_open_live(dev, 65535, 0, 0, ebuf);
if (!pd) {
fprintf(stderr, "%s\n", ebuf);
return -1;
}
pkt = pcap_next(pd, &ph);
printf("A packet is captured.\n");
pcap_close(pd);
return 0;
使用libpcap的函数要包含头文件pcap.h,编译时加上链接选项-lpcap。函数
char *pcap_lookupdev(char *errbuf);
用来获取当前机器上可用的网络接口名称,如果找到则返回名称,找不到则返回NULL,失败原因保存在errbuf中。errbuf的长度至少为PCAP_ERRBUF_SIZE。这个函数好像只查找以太网卡,并没有把我机器上的无线网卡也列出来,不知道如果有多块以太网卡的时候会怎样。
找到设备之后就可以进行监听了。函数
pcap_t *pcap_open_live(const char *device, int snaplen,
int promisc, int to_ms, char *errbuf);
如果要打开tcpdump或wireshark保存的数据文件可以使用函数
pcap_t *pcap_open_offline(const char *fname, char *errbuf);
接下来开始捕获报文。函数
const u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h);
+---------------------+-------------------+-----+ | struct ether_header | ip/arp/... header | ... | +---------------------+-------------------+-----+
pcap_next()会把pcap加上的信息填充到参数h指向的结构体中,并且返回指向实际数据包的指针(也就是struct ether_header的起始位置)。函数的返回时间取决于在pcap_open_live中的to_ms参数值,对于直接打开文件来说则不需要阻塞。
当pcap_next()返回后打印信息”A packet is captured.”(因为在pcap_open_live()中设置超时间隔为0,也就是说pcap_next()会一直阻塞直到有数据包到达)。
当分析完成后使用函数
void pcap_close(pcap_t *p);
另一个pcap程序:
#include <stdio.h>
#include <pcap.h>
static void printer(u_char* arg, const struct pcap_pkthdr* ph,
const u_char* packet)
{
printf("A packet is captured.\n");
}
int main()
{
pcap_t* pd;
char ebuf[PCAP_ERRBUF_SIZE], *dev;
dev = pcap_lookupdev(ebuf);
if (!dev) {
fprintf(stderr, "%s\n", ebuf);
return -1;
}
printf("get net device -> %s\n", dev);
pd = pcap_open_live(dev, 65535, 0, 0, ebuf);
if (!pd) {
fprintf(stderr, "%s\n", ebuf);
return -1;
}
pcap_dispatch(pd, 0, printer, NULL);
pcap_close(pd);
return 0;
前面打开设备的部分与第一个程序相同,不同的是捕获的部分使用了另一个函数
int pcap_dispatch(pcap_t *p, int cnt, pcap_handler callback, u_char *user);
typedef void (*pcap_handler)(u_char *user, const struct pcap_pkthdr *h,
const u_char *bytes);
第一个参数user就是传递给pcap_dispatch()的最后一个参数;第二个参数是指向pcap数据包起始位置的struct pcap_pkthdr的指针(见前面的图),bytes(或者叫packet比较符合实际)指向实际的网络报文起始位置(在前面的图中就是struct ether_header的位置),其中整个报文的长度(从bytes指向的位置开始到整个报文的结束)由struct pcap_pkthdr中的caplen指定。struct pcap_pkthdr的定义为:
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) */
};
第一个参数ts为捕获报文的时间,第三个参数len没搞明白,但是从实际输出来看和caplen的值一样。
从实际输出看到,每次捕获到一个报文都会调用回调函数printer()打印消息。
另外还有一个和pcap_dispatch()功能相似的函数
int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user);
这个函数在读取超时的时候不会返回,只在捕获了cnt个数据包或读取出错时才返回。
另外可以使用函数pcap_dump_open(),pcap_dump()和pcap_dump_close()把报文保存在文件中。tcpdump和wireshark保存的报文格式都遵循pcap格式,所以它们可以打开对方的数据文件,不过直接使用pcap函数保存的文件会在文件开头加上一些pcap的信息,这个文件不能直接用tcpdump或wireshark打开,要把开头的pcap内容删掉才行。
好了,目前用到的函数主要就是这些,更多函数可以参考[1]和pcap.h的内容。
参考资料
[1] libpcap函数库
[2] The Sniffer’s Guide to Raw Traffic (a libpcap tutorial)