获取完适配器信息之后,我们就需要利用适配器来抓取流量包啦。
本次教程分为三部分,如下所示:
一、使用回调方法来抓取数据包
这次主要用到的函数是pcap_open(),它的API如下
pcap_t* pcap_open ( const char * source, int snaplen, int flags, int read_timeout, struct pcap_rmtauth * auth, char * errbuf )重要参数解释:
snaplen 制定要捕获数据包中的哪些部分。 在一些操作系统中 (比如 xBSD 和 Win32), 驱动可以被配置成只捕获数据包的初始化部分: 这样可以减少应用程序间复制数据的量,从而提高捕获效率。本例中,我们将值定为65535,它比我们能遇到的最大的MTU还要大。因此,我们确信我们总能收到完整的数据包。
flags: 最最重要的flag是用来指示适配器是否要被设置成混杂模式。 一般情况下,适配器只接收发给它自己的数据包, 而那些在其他机器之间通讯的数据包,将会被丢弃。 相反,如果适配器是混杂模式,那么不管这个数据包是不是发给我的,我都会去捕获。也就是说,我会去捕获所有的数据包。 这意味着在一个共享媒介(比如总线型以太网),WinPcap能捕获其他主机的所有的数据包。 大多数用于数据捕获的应用程序都会将适配器设置成混杂模式,所以,我们也会在下面的范例中,使用混杂模式。
to_ms 指定读取数据的超时时间,以毫秒计(1s=1000ms)。在适配器上进行读取操作(比如用 pcap_dispatch() 或 pcap_next_ex()) 都会在to_ms 毫秒时间内响应,即使在网络上没有可用的数据包。 在统计模式下,to_ms 还可以用来定义统计的时间间隔。 将to_ms 设置为0意味着没有超时,那么如果没有数据包到达的话,读操作将永远不会返回。 如果设置成-1,则情况恰好相反,无论有没有数据包到达,读操作都会立即返回。
pcap_open()函数的返回类型为 pcap_t * ,在引用时我们需要先声明一个pcap_t 类型的指针。
pcap_t *adhandle;在前面我们说了怎样获取适配器列表,获取之后它会保存到
pcap_if_t *alldevs;声明一个
pcap_if_t *d;首先指向 alldevs,可以通过d-next来获取下一个设备。
定位到某一个设备之后,我们就可以利用pcap_open函数来生成一个捕获对象。
返回类型为pcap_t
调用方法为:
adhandle= pcap_open(d->name, // 设备名 65536, // 65535保证能捕获到不同数据链路层上的每个数据包的全部内容 PCAP_OPENFLAG_PROMISCUOUS, // 混杂模式 1000, // 读取超时时间 NULL, // 远程机器验证 errbuf // 错误缓冲池 )捕获方法
pcap_loop(adhandle, 0, packet_handler, NULL);
其中第一个参数adhandle参数为 pcap_t 的捕获对象。
packet_handler为回调函数,当捕获到数据包时,会自动调用这个函数。
注意:当适配器被打开,捕获工作就可以用 pcap_dispatch() 或pcap_loop()进行。 这两个函数非常的相似,区别就是 pcap_ dispatch() 当超时时间到了(timeout expires)就返回 (尽管不能保证) ,而pcap_loop() 不会因此而返回,只有当 cnt 数据包被捕获,所以,pcap_loop()会在一小段时间内,阻塞网络的利用。pcap_loop()对于我们这个简单的范例来说,可以满足需求,不过,pcap_dispatch() 函数一般用于比较复杂的程序中。
这两个函数都有一个 回调 参数, packet_handler指向一个可以接收数据包的函数。 这个函数会在收到每个新的数据包并收到一个通用状态时被libpcap所调用 ( 与函数pcap_loop() 和pcap_dispatch() 中的user 参数相似),数据包的首部一般有一些诸如时间戳,数据包长度的信息,还有包含了协议首部的实际数据。
冗余校验码CRC不再支持,因为帧到达适配器,并经过校验确认以后,适配器就会将CRC删除,与此同时,大部分适配器会直接丢弃CRC错误的数据包,所以,WinPcap没法捕获到它们。
上面的程序将每一个数据包的时间戳和长度从 pcap_pkthdr 的首部解析出来,并打印在屏幕上。
DEMO:#include "pcap.h" /* packet handler 函数原型 */ void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data); int main() { pcap_if_t *alldevs; pcap_if_t *d; int inum; int i=0; pcap_t *adhandle; char errbuf[PCAP_ERRBUF_SIZE]; //解决eclipse c&&cpp scanf优先于printf的方法 setvbuf(stdout, NULL, _IONBF, 0); /* 获取本机设备列表 */ if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1) { fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf); exit(1); } /* 打印列表 */ for(d=alldevs; d; d=d->next) { printf("%d. %s", ++i, d->name); if (d->description) printf(" (%s)\n", d->description); else printf(" (No description available)\n"); } if(i==0) { printf("\nNo interfaces found! Make sure WinPcap is installed.\n"); return -1; } printf("Enter the interface number (1-%d):",i); scanf("%d", &inum); if(inum < 1 || inum > i) { printf("\nInterface number out of range.\n"); /* 释放设备列表 */ pcap_freealldevs(alldevs); return -1; } /* 跳转到选中的适配器 */ for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++); /* 打开设备 */ if ( (adhandle= pcap_open(d->name, // 设备名 65536, // 65535保证能捕获到不同数据链路层上的每个数据包的全部内容 PCAP_OPENFLAG_PROMISCUOUS, // 混杂模式 1000, // 读取超时时间 NULL, // 远程机器验证 errbuf // 错误缓冲池 ) ) == NULL) { fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n", d->name); /* 释放设备列表 */ pcap_freealldevs(alldevs); return -1; } printf("\nlistening on %s...\n", d->description); /* 释放设备列表 */ pcap_freealldevs(alldevs); /* 开始捕获 */ pcap_loop(adhandle, 0, packet_handler, NULL); return 0; }
packet_handler函数:
/* 每次捕获到数据包时,libpcap都会自动调用这个回调函数 */ void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data) { struct tm *ltime; char timestr[16]; time_t local_tv_sec; /* 将时间戳转换成可识别的格式 */ local_tv_sec = header->ts.tv_sec; ltime=localtime(&local_tv_sec); strftime( timestr, sizeof timestr, "%H:%M:%S", ltime); printf("%s,%.6d len:%d\n", timestr, header->ts.tv_usec, header->len); }
本次呢,将用 pcap_next_ex() 函数代替上面说的 pcap_loop()函数。
pcap_loop()函数是基于回调的原理来进行数据捕获,这是一种精妙的方法,并且在某些场合中,它是一种很好的选择。 然而,处理回调有时候并不实用 -- 它会增加程序的复杂度,特别是在拥有多线程的C++程序中。
可以通过直接调用pcap_next_ex() 函数来获得一个数据包 -- 只有当编程人员使用了pcap_next_ex() 函数才能收到数据包。
在大多数情况下,推荐大家用 pcap_next_ex() 函数来抓取数据包。pcap_next_ex()函数介绍
int pcap_next_ex ( pcap_t * p, struct pcap_pkthdr ** pkt_header, const u_char ** pkt_data)这个函数有三个参数,第一个参数就是用来捕获数据包的handler,为pcap_t类型,第二个pkt_header包含了抓取到的数据包的信息,比如时间等基本信息。
第三个pkt_data则是接收到的数据帧的全部内容,通过对它的解析可以得到一系列有用的内容。你通过解析这个就能得到流经你的网卡的数据包内的所有信息。
比如有人在上传一些文件,你若捕获到他的数据包,通过解析你便会发现他在发送什么。
以下是利用了pcap_next_ex 函数的一个小例子。
#include <stdio.h> #include <stdlib.h> #include <pcap.h> char *iptos(u_long in); //u_long即为 unsigned long void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data); //struct tm *ltime; //和时间处理有关的变量 int main(){ pcap_if_t * alldevs; //所有网络适配器 pcap_if_t *d; //选中的网络适配器 char errbuf[PCAP_ERRBUF_SIZE]; //错误缓冲区,大小为256 char source[PCAP_ERRBUF_SIZE]; pcap_t *adhandle; //捕捉实例,是pcap_open返回的对象 int i = 0; //适配器计数变量 struct pcap_pkthdr *header; //接收到的数据包的头部 const u_char *pkt_data; //接收到的数据包的内容 int res; //表示是否接收到了数据包 //time_t local_tv_sec; //和时间处理有关的变量 //char timestr[16]; //和时间处理有关的变量 /** int pcap_findalldevs_ex ( char * source, struct pcap_rmtauth * auth, pcap_if_t ** alldevs, char * errbuf ); PCAP_SRC_IF_STRING代表用户想从一个本地文件开始捕获内容; */ //获取本地适配器列表 if(pcap_findalldevs_ex(PCAP_SRC_IF_STRING,NULL,&alldevs,errbuf) == -1){ //结果为-1代表出现获取适配器列表失败 fprintf(stderr,"Error in pcap_findalldevs_ex:\n",errbuf); //exit(0)代表正常退出,exit(other)为非正常退出,这个值会传给操作系统 exit(1); } //打印设备列表信息 /** d = alldevs 代表赋值第一个设备,d = d->next代表切换到下一个设备 结构体 pcap_if_t: pcap_if * next 指向下一个pcap_if,pcap_if_t和pcap_if 结构是一样的 char * name 代表适配器的名字 char * description 对适配器的描述 pcap_addr * addresses 适配器存储的地址 u_int flags 适配器接口标识符,值为PCAP_IF_LOOPBACK */ for(d = alldevs;d !=NULL;d = d->next){ printf("-----------------------------------------------------------------\nnumber:%d\nname:%s\n",++i,d->name); if(d->description){ //打印适配器的描述信息 printf("description:%s\n",d->description); }else{ //适配器不存在描述信息 printf("description:%s","no description\n"); } //打印本地环回地址 printf("\tLoopback: %s\n",(d->flags & PCAP_IF_LOOPBACK)?"yes":"no"); /** pcap_addr * next 指向下一个地址的指针 sockaddr * addr IP地址 sockaddr * netmask 子网掩码 sockaddr * broadaddr 广播地址 sockaddr * dstaddr 目的地址 */ pcap_addr_t *a; //网络适配器的地址用来存储变量 for(a = d->addresses;a;a = a->next){ //sa_family代表了地址的类型,是IPV4地址类型还是IPV6地址类型 switch (a->addr->sa_family) { case AF_INET: //代表IPV4类型地址 printf("Address Family Name:AF_INET\n"); if(a->addr){ //->的优先级等同于括号,高于强制类型转换,因为addr为sockaddr类型,对其进行操作须转换为sockaddr_in类型 printf("Address:%s\n",iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr)); } if (a->netmask){ printf("\tNetmask: %s\n",iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr)); } if (a->broadaddr){ printf("\tBroadcast Address: %s\n",iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr)); } if (a->dstaddr){ printf("\tDestination Address: %s\n",iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr)); } break; case AF_INET6: //代表IPV6类型地址 printf("Address Family Name:AF_INET6\n"); printf("this is an IPV6 address\n"); break; default: break; } } } //i为0代表上述循环未进入,即没有找到适配器,可能的原因为Winpcap没有安装导致未扫描到 if(i == 0){ printf("interface not found,please check winpcap installation"); } int num; printf("Enter the interface number(1-%d):",i); //让用户选择选择哪个适配器进行抓包 scanf_s("%d",&num); printf("\n"); //用户输入的数字超出合理范围 if(num<1||num>i){ printf("number out of range\n"); pcap_freealldevs(alldevs); return -1; } //跳转到选中的适配器 for(d=alldevs, i=0; i< num-1 ; d=d->next, i++); //运行到此处说明用户的输入是合法的 if((adhandle = pcap_open(d->name, //设备名称 65535, //存放数据包的内容长度 PCAP_OPENFLAG_PROMISCUOUS, //混杂模式 1000, //超时时间 NULL, //远程验证 errbuf //错误缓冲 )) == NULL){ //打开适配器失败,打印错误并释放适配器列表 fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n", d->name); // 释放设备列表 pcap_freealldevs(alldevs); return -1; } //打印输出,正在监听中 printf("\nlistening on %s...\n", d->description); //利用pcap_next_ex来接受数据包 while((res = pcap_next_ex(adhandle,&header,&pkt_data))>=0) { if(res ==0){ //返回值为0代表接受数据包超时,重新循环继续接收 continue; }else{ //运行到此处代表接受到正常从数据包 // 将时间戳转换成可识别的格式 //local_tv_sec = header->ts.tv_sec; //localtime_s(ltime,&local_tv_sec); //strftime( timestr, sizeof timestr, "%H:%M:%S", ltime); printf("%.6ld len:%d\n", header->ts.tv_usec, header->len); } } //释放网络适配器列表 pcap_freealldevs(alldevs); /** int pcap_loop ( pcap_t * p, int cnt, pcap_handler callback, u_char * user ); typedef void (*pcap_handler)(u_char *, const struct pcap_pkthdr *, const u_char *); */ //开始捕获信息,当捕获到数据包时,会自动调用这个函数 //pcap_loop(adhandle,0,packet_handler,NULL); int inum; scanf_s("%d", &inum); return 0; } /* 每次捕获到数据包时,libpcap都会自动调用这个回调函数 */ /** pcap_loop()函数是基于回调的原理来进行数据捕获的,如技术文档所说,这是一种精妙的方法,并且在某些场合下, 它是一种很好的选择。但是在处理回调有时候会并不实用,它会增加程序的复杂度,特别是在多线程的C++程序中 */ /* void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data) { struct tm *ltime = NULL; char timestr[16]; time_t local_tv_sec; // 将时间戳转换成可识别的格式 local_tv_sec = header->ts.tv_sec; localtime_s(ltime,&local_tv_sec); strftime( timestr, sizeof timestr, "%H:%M:%S", ltime); printf("%s,%.6ld len:%d\n", timestr, header->ts.tv_usec, header->len); } */ /* 将数字类型的IP地址转换成字符串类型的 */ #define IPTOSBUFFERS 12 char *iptos(u_long in) { static char output[IPTOSBUFFERS][3*4+3+1]; static short which; u_char *p; p = (u_char *)∈ which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1); sprintf_s(output[which], "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); return output[which]; }
三、添加过滤器
WinPcap和Libpcap的最强大的特性之一,是拥有过滤数据包的引擎。 它提供了有效的方法去获取网络中的某些数据包,这也是WinPcap捕获机制中的一个组成部分。 用来过滤数据包的函数是pcap_compile() 和pcap_setfilter() 。
pcap_compile() 它将一个高层的布尔过滤表达式编译成一个能够被过滤引擎所解释的低层的字节码。有关布尔过滤表达式的语法可以参见Filtering expression syntax 这一节的内容。
pcap_setfilter() 将一个过滤器与内核捕获会话向关联。当pcap_setfilter() 被调用时,这个过滤器将被应用到来自网络的所有数据包,并且,所有的符合要求的数据包 (即那些经过过滤器以后,布尔表达式为真的包) ,将会立即复制给应用程序。
以下代码展示了如何编译并设置过滤器。 请注意,我们必须从 pcap_if 结构体中获得掩码,因为一些使用pcap_compile() 创建的过滤器需要它。
比如传递给 pcap_compile() 的过滤器是"ip and tcp",这说明我们只希望保留IPv4和TCP的数据包,并把他们发送给应用程序。
if (d->addresses != NULL) /* 获取接口第一个地址的掩码 */ netmask=((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr; else /* 如果这个接口没有地址,那么我们假设这个接口在C类网络中 */ netmask=0xffffff; compile the filter if (pcap_compile(adhandle, &fcode, "ip and tcp", 1, netmask) < 0) { fprintf(stderr,"\nUnable to compile the packet filter. Check the syntax.\n"); /* 释放设备列表 */ pcap_freealldevs(alldevs); return -1; } set the filter if (pcap_setfilter(adhandle, &fcode) < 0) { fprintf(stderr,"\nError setting the filter.\n"); /* 释放设备列表 */ pcap_freealldevs(alldevs); return -1; }
加入过滤之后的程序:
#include <stdio.h> #include <stdlib.h> #include <pcap.h> char *iptos(u_long in); //u_long即为 unsigned long void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data); //struct tm *ltime; //和时间处理有关的变量 /* 4字节的IP地址 */ typedef struct ip_address { u_char byte1; u_char byte2; u_char byte3; u_char byte4; } ip_address; /* IPv4 首部 */ typedef struct ip_header { u_char ver_ihl; // 版本 (4 bits) + 首部长度 (4 bits) u_char tos; // 服务类型(Type of service) u_short tlen; // 总长(Total length) u_short identification; // 标识(Identification) u_short flags_fo; // 标志位(Flags) (3 bits) + 段偏移量(Fragment offset) (13 bits) u_char ttl; // 存活时间(Time to live) u_char proto; // 协议(Protocol) u_short crc; // 首部校验和(Header checksum) ip_address saddr; // 源地址(Source address) ip_address daddr; // 目的地址(Destination address) u_int op_pad; // 选项与填充(Option + Padding) } ip_header; /* UDP 首部*/ typedef struct udp_header { u_short sport; // 源端口(Source port) u_short dport; // 目的端口(Destination port) u_short len; // UDP数据包长度(Datagram length) u_short crc; // 校验和(Checksum) } udp_header; int main(){ pcap_if_t * alldevs; //所有网络适配器 pcap_if_t *d; //选中的网络适配器 char errbuf[PCAP_ERRBUF_SIZE]; //错误缓冲区,大小为256 char source[PCAP_ERRBUF_SIZE]; pcap_t *adhandle; //捕捉实例,是pcap_open返回的对象 int i = 0; //适配器计数变量 struct pcap_pkthdr *header; //接收到的数据包的头部 const u_char *pkt_data; //接收到的数据包的内容 int res; //表示是否接收到了数据包 u_int netmask; //过滤时用的子网掩码 char packet_filter[] = "ip and udp"; //过滤字符 struct bpf_program fcode; //pcap_compile所调用的结构体 ip_header *ih; //ip头部 udp_header *uh; //udp头部 u_int ip_len; //ip地址有效长度 u_short sport,dport; //主机字节序列 //time_t local_tv_sec; //和时间处理有关的变量 //char timestr[16]; //和时间处理有关的变量 /** int pcap_findalldevs_ex ( char * source, struct pcap_rmtauth * auth, pcap_if_t ** alldevs, char * errbuf ); PCAP_SRC_IF_STRING代表用户想从一个本地文件开始捕获内容; */ //获取本地适配器列表 if(pcap_findalldevs_ex(PCAP_SRC_IF_STRING,NULL,&alldevs,errbuf) == -1){ //结果为-1代表出现获取适配器列表失败 fprintf(stderr,"Error in pcap_findalldevs_ex:\n",errbuf); //exit(0)代表正常退出,exit(other)为非正常退出,这个值会传给操作系统 exit(1); } //打印设备列表信息 /** d = alldevs 代表赋值第一个设备,d = d->next代表切换到下一个设备 结构体 pcap_if_t: pcap_if * next 指向下一个pcap_if,pcap_if_t和pcap_if 结构是一样的 char * name 代表适配器的名字 char * description 对适配器的描述 pcap_addr * addresses 适配器存储的地址 u_int flags 适配器接口标识符,值为PCAP_IF_LOOPBACK */ for(d = alldevs;d !=NULL;d = d->next){ printf("-----------------------------------------------------------------\nnumber:%d\nname:%s\n",++i,d->name); if(d->description){ //打印适配器的描述信息 printf("description:%s\n",d->description); }else{ //适配器不存在描述信息 printf("description:%s","no description\n"); } //打印本地环回地址 printf("\tLoopback: %s\n",(d->flags & PCAP_IF_LOOPBACK)?"yes":"no"); /** pcap_addr * next 指向下一个地址的指针 sockaddr * addr IP地址 sockaddr * netmask 子网掩码 sockaddr * broadaddr 广播地址 sockaddr * dstaddr 目的地址 */ pcap_addr_t *a; //网络适配器的地址用来存储变量 for(a = d->addresses;a;a = a->next){ //sa_family代表了地址的类型,是IPV4地址类型还是IPV6地址类型 switch (a->addr->sa_family) { case AF_INET: //代表IPV4类型地址 printf("Address Family Name:AF_INET\n"); if(a->addr){ //->的优先级等同于括号,高于强制类型转换,因为addr为sockaddr类型,对其进行操作须转换为sockaddr_in类型 printf("Address:%s\n",iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr)); } if (a->netmask){ printf("\tNetmask: %s\n",iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr)); } if (a->broadaddr){ printf("\tBroadcast Address: %s\n",iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr)); } if (a->dstaddr){ printf("\tDestination Address: %s\n",iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr)); } break; case AF_INET6: //代表IPV6类型地址 printf("Address Family Name:AF_INET6\n"); printf("this is an IPV6 address\n"); break; default: break; } } } //i为0代表上述循环未进入,即没有找到适配器,可能的原因为Winpcap没有安装导致未扫描到 if(i == 0){ printf("interface not found,please check winpcap installation"); } int num; printf("Enter the interface number(1-%d):",i); //让用户选择选择哪个适配器进行抓包 scanf_s("%d",&num); printf("\n"); //用户输入的数字超出合理范围 if(num<1||num>i){ printf("number out of range\n"); pcap_freealldevs(alldevs); return -1; } //跳转到选中的适配器 for(d=alldevs, i=0; i< num-1 ; d=d->next, i++); //运行到此处说明用户的输入是合法的 if((adhandle = pcap_open(d->name, //设备名称 65535, //存放数据包的内容长度 PCAP_OPENFLAG_PROMISCUOUS, //混杂模式 1000, //超时时间 NULL, //远程验证 errbuf //错误缓冲 )) == NULL){ //打开适配器失败,打印错误并释放适配器列表 fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n", d->name); // 释放设备列表 pcap_freealldevs(alldevs); return -1; } //打印输出,正在监听中 printf("\nlistening on %s...\n", d->description); //所在网络为无线局域网 if(pcap_datalink(adhandle) ==DLT_IEEE802){ printf("DLT_IEEE802"); } //所在网络为以太网,Ethernet (10Mb, 100Mb, 1000Mb, and up) if(pcap_datalink(adhandle) == DLT_EN10MB){ printf("DLT_EN10MB"); } //所在网络不是以太网,此处只取这种情况 if(pcap_datalink(adhandle) != DLT_EN10MB) { fprintf(stderr,"\nThis program works only on Ethernet networks.\n"); //释放列表 pcap_freealldevs(alldevs); return -1; } //先获得地址的子网掩码 if(d->addresses != NULL) /* 获得接口第一个地址的掩码 */ netmask=((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr; else /* 如果接口没有地址,那么我们假设一个C类的掩码 */ netmask=0xffffff; //pcap_compile()的原理是将高层的布尔过滤表 //达式编译成能够被过滤引擎所解释的低层的字节码 if(pcap_compile(adhandle, //适配器处理对象 &fcode, packet_filter, //过滤ip和UDP 1, //优化标志 netmask //子网掩码 )<0) { //过滤出现问题 fprintf(stderr,"\nUnable to compile the packet filter. Check the syntax.\n"); // 释放设备列表 pcap_freealldevs(alldevs); return -1; } //设置过滤器 if (pcap_setfilter(adhandle, &fcode)<0) { fprintf(stderr,"\nError setting the filter.\n"); /* 释放设备列表 */ pcap_freealldevs(alldevs); return -1; } //利用pcap_next_ex来接受数据包 while((res = pcap_next_ex(adhandle,&header,&pkt_data))>=0) { if(res ==0){ //返回值为0代表接受数据包超时,重新循环继续接收 continue; }else{ //运行到此处代表接受到正常从数据包 // 将时间戳转换成可识别的格式 //local_tv_sec = header->ts.tv_sec; //localtime_s(ltime,&local_tv_sec); //strftime( timestr, sizeof timestr, "%H:%M:%S", ltime); //header为帧的头部 printf("%.6ld len:%d", header->ts.tv_usec, header->len); // 获得IP数据包头部的位置 ih = (ip_header *) (pkt_data +14); //14为以太网帧头部长度 //获得UDP头部的位置 ip_len = (ih->ver_ihl & 0xf) *4; printf("ip_length:%d",ip_len); uh = (udp_header *)((u_char *)ih+ip_len); /* 将网络字节序列转换成主机字节序列 */ sport = ntohs( uh->sport ); dport = ntohs( uh->dport ); /* 打印IP地址和UDP端口 */ printf("%d.%d.%d.%d.%d -> %d.%d.%d.%d.%d\n", ih->saddr.byte1, ih->saddr.byte2, ih->saddr.byte3, ih->saddr.byte4, sport, ih->daddr.byte1, ih->daddr.byte2, ih->daddr.byte3, ih->daddr.byte4, dport); } } //释放网络适配器列表 pcap_freealldevs(alldevs); /** int pcap_loop ( pcap_t * p, int cnt, pcap_handler callback, u_char * user ); typedef void (*pcap_handler)(u_char *, const struct pcap_pkthdr *, const u_char *); */ //开始捕获信息,当捕获到数据包时,会自动调用这个函数 //pcap_loop(adhandle,0,packet_handler,NULL); int inum; scanf_s("%d", &inum); return 0; } /* 每次捕获到数据包时,libpcap都会自动调用这个回调函数 */ /** pcap_loop()函数是基于回调的原理来进行数据捕获的,如技术文档所说,这是一种精妙的方法,并且在某些场合下, 它是一种很好的选择。但是在处理回调有时候会并不实用,它会增加程序的复杂度,特别是在多线程的C++程序中 */ /* void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data) { struct tm *ltime = NULL; char timestr[16]; time_t local_tv_sec; // 将时间戳转换成可识别的格式 local_tv_sec = header->ts.tv_sec; localtime_s(ltime,&local_tv_sec); strftime( timestr, sizeof timestr, "%H:%M:%S", ltime); printf("%s,%.6ld len:%d\n", timestr, header->ts.tv_usec, header->len); } */ /* 将数字类型的IP地址转换成字符串类型的 */ #define IPTOSBUFFERS 12 char *iptos(u_long in) { static char output[IPTOSBUFFERS][3*4+3+1]; static short which; u_char *p; p = (u_char *)∈ which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1); sprintf_s(output[which], "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); return output[which]; }
好,以上就是抓取数据包的一系列基础知识。
注:
1. 由于配置环境的不同,比如Eclipse运行则需要在上述代码中添加一些头文件。VS或者codeblocks运行则不需要那么繁琐的配置,大家视具体情况而定,如果有代码不能识别则便是头文件引入的问题。
2.在VS中可能存在方法不安全的问题,比如scanf要改成scanf_s方法等等。
通过以上学习,我们应该掌握如下几点:
1. pcap_open的用法,打开网络适配器所用。
2.抓取数据包的两种方法,一种是用回调方法 pcap_loop,一种是不用回调方法pcap_next_ex。
3.为数据包添加过滤器,pcap_compile 和 pcap_setfilter
如果有问题,欢迎留言交流。