本文最后更新于:2024年11月2日 上午
WINPCAP编程
Winpcap简介
Winpcap是UNIX下的libpcap移植到windows下的产物,他是一个free and open source的项目。Winpcap工作于驱动(Driver)层,所以能以很高的效率进行网络操作。
其提供了以下强大的功能
捕获原始的数据包
设置fliter,只捕获自己感兴趣的数据包
方便地把捕获的数据包输出到文件和从文件输入
发送原始的数据包
统计网络流量
实验目的
实验原理
WinPcap是一个基于Win32平台的,用于捕获网络数据包并进行分析的开源库.
大多数网络应用程序通过被广泛使用的操作系统元件来访问网络,比如sockets。 这是一种简单的实现方式,因为操作系统已经妥善处理了底层具体实现细节(比如协议处理,封装数据包等等),并且提供了一个与读写文件类似的,令人熟悉的接口。
然而,有些时候,这种“简单的方式”并不能满足任务的需求,因为有些应用程序需要直接访问网络中的数据包。也就是说,那些应用程序需要访问原始数据包,即没有被操作系统利用网络协议处理过的数据包。
WinPcap产生的目的,就是为Win32应用程序提供这种访问方式; WinPcap提供了以下功能
捕获原始数据包,无论它是发往某台机器的,还是在其他设备(共享媒介)上进行交换的
在数据包发送给某应用程序前,根据用户指定的规则过滤数据包
将原始数据包通过网络发送出去
收集并统计网络流量信息
以上这些功能需要借助安装在Win32内核中的网络设备驱动程序才能实现,再加上几个动态链接库DLL。
所有这些功能都能通过一个强大的编程接口来表现出来,易于开发,并能在不同的操作系统上使用。这本手册的主要目标是在一些程序范例的帮助下,叙述这些编程接口的使用。
WinPcap可以被用来制作网络分析、监控工具。一些基于WinPcap的典型应用有:
网络与协议分析器 (network and protocol analyzers)
网络监视器 (network monitors)
网络流量记录器 (traffic loggers)
网络流量发生器 (traffic generators)
用户级网桥及路由 (user-level bridges and routers)
网络入侵检测系统 (network intrusion detection systems (NIDS))
网络扫描器 (network scanners)
安全工具 (security tools)
WINPCAP环境配置
打开VS新建一个空项目并添加一个空的源文件
点击菜单栏 项目->项目属性
选择C/C+±>预处理器,在预处理器定义中添加WIN32 、WPCAP和HAVE_REMOTE三个宏定义,然后点击 应用;
选择链接器->输入,在附加依赖项添加wpcap.lib、ws2_32.lib、Packet.lib三个库,然后点击应用;
选择VC++目录,添加WpdPack文件夹中的包含目录(include目录),然后点击应用
同样,添加WpdPack文件夹中的库目录(Lib目录),然后点击应用
选择C/C+±>语言,将 符合模式 设置为 否,点击确定
将解决方案平台设置为x64
环境配置到此结束。注意,使用C语言开发,因此新建.c文件而不是.cpp文件。
测试配置是否完成,复制以下代码到项目中运行测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #define WIN32 #include <stdio.h> #include <pcap.h> #pragma comment(lib, "wpcap.lib" ) main() { pcap_if_t * alldevs, * d; int i = 0 ; char errbuf[PCAP_ERRBUF_SIZE]; if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL , &alldevs, errbuf) == -1 ) { fprintf (stderr , "Error in pcap_findalldevs_ex: %s\n" , errbuf); exit (1 ); } for (d = alldevs; d != NULL ; 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 ; } pcap_freealldevs(alldevs); }
WINPCAP基本开发流程
WinPcap开发网络应用程序的大致流程:首先获取主机安装的网络设备列表,然后选择并激活某个网络设备,封装或过滤数据包,发送或解析数据包。
获取设备列表
WinPcap提供了pcap_findalldevs_ex()
函数来实现获取设备列表的功能,这个函数返回一个pcap_if
结构的链表,每个这样的结构都包含了一个适配器的详细信息。值得注意的是,数据域name
和description
表示一个适配器名称和一个可以让人们理解的描述。下面获取适配器列表,并在屏幕上显示出来,如果没有找到适配器,将打印错误信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <stdio.h> #include "pcap.h" #define PCAP_SRC_IF_STRING "rpcap://" #define PCAP_SRC_FILE_STRING "file://" main() { pcap_if_t *alldevs; pcap_if_t *d; int i = 0 ; char errbuf[PCAP_ERRBUF_SIZE]; if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL , &alldevs, errbuf) == -1 ) { fprintf (stderr , "Error in pcap_findalldevs_ex: %s\n" , errbuf); exit (1 ); } for (d = alldevs; d != NULL ; 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 ; } getchar(); pcap_freealldevs(alldevs); }
有关这段代码的一些说明
pcap_findalldevs_ex() 是 WinPcap 提供的用以查找主机网络适配器功能的函数,它是 pcap_findalldevs() 函数的升级版,现在基本都选择使用 ex 版本,它不仅能查找本地的设备,还能查找远程的设备,关于此函数的定义如下
1 2 3 4 5 6 7 8 9 int pcap_findalldevs_ex (char * source, struct pcap_rmtauth *auth, pcap_if_t ** alldevs, char * errbuf) ;
获取已安装设备的高级信息
参数alldevs
的类型是pcap_if_t
类型的二级指针,这里需要说明,pcap_findalldevs_ex()
函数再获取到设备信息之后会将详细信息存储在一个结构体pcap_if_t
中,他的定义如下:
1 2 3 4 5 6 7 8 struct pcap_if_t { struct pcap_if_t *next ; char *name; char *description; struct pcap_addr *addresses ; bpf_u_int32 flags; }
从中可以看出,我们需要建立一个pcap_if_t
类型的指针来存储调用函数获取到的设备信息,然后直接通过指针就可以访问指定设备的相关信息。
同样,从pcap_if_t
结构体的定义中可以看到,在结构体里面有定义了一个名为pcap_addr
的结构体,用来存储接口地址列表,pcap_addr
的定义如下:
1 2 3 4 5 6 7 8 struct pcap_addr { struct pcap_addr *next ; struct sockaddr *addr ; struct sockaddr *netmask ; struct sockaddr *broadaddr ; struct sockaddr *dstaddr ; }
另外,函数pcap_findalldevs_ex()
还能返回远程适配器信息和一个位于所给的本地文件夹的pcap文件列表。
下面的程序使用了ifprint()
函数来打印出pcap_if
结构体中的所有内容。程序对每一个由pcap_findalldevs_ex()
函数返回的pcap_if
,都调用ifprint()
函数来打印。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 #define _CRT_SECURE_NO_WARNINGS #include "pcap.h" #include <stdio.h> #define PCAP_SRC_IF_STRING "rpcap://" #define PCAP_SRC_FILE_STRING "file://" #ifndef WIN32 #include <sys/socket.h> #include <netinet/in.h> #else #include <winsock.h> #endif void ifprint (pcap_if_t * d) ;char * iptos (u_long in) ;int main () { pcap_if_t * alldevs; pcap_if_t * d; char errbuf[PCAP_ERRBUF_SIZE + 1 ]; char source[PCAP_ERRBUF_SIZE + 1 ]; printf ("Enter the device you want to list:\n" "rpcap:// ==> lists interfaces in the local machine\n" "rpcap://hostname:port ==> lists interfaces in a remote machine\n" " (rpcapd daemon must be up and running\n" " and it must accept 'null' authentication)\n" "file://foldername ==> lists all pcap files in the give folder\n\n" "Enter your choice: " ); fgets(source, PCAP_ERRBUF_SIZE, stdin ); source[PCAP_ERRBUF_SIZE] = '\0' ; if (pcap_findalldevs_ex(source, NULL , &alldevs, errbuf) == -1 ) { fprintf (stderr , "Error in pcap_findalldevs: %s\n" , errbuf); exit (1 ); } for (d = alldevs; d; d = d->next) { ifprint(d); } pcap_freealldevs(alldevs); getchar(); return 1 ; }void ifprint (pcap_if_t * d) { pcap_addr_t * a; printf ("%s\n" , d->name); if (d->description) printf ("\tDescription: %s\n" , d->description); printf ("\tLoopback: %s\n" , (d->flags & PCAP_IF_LOOPBACK) ? "yes" : "no" ); for (a = d->addresses; a; a = a->next) { printf ("\tAddress Family: #%d\n" , a->addr->sa_family); switch (a->addr->sa_family) { case AF_INET: printf ("\tAddress Family Name: AF_INET\n" ); if (a->addr) printf ("\tAddress: %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 ; default : printf ("\tAddress Family Name: Unknown\n" ); break ; } } printf ("\n" ); }#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 (output[which], "%d.%d.%d.%d" , p[0 ], p[1 ], p[2 ], p[3 ]); return output[which]; }
当我们完成了设备列表的使用,我们要调用pcap_freealldevs()
函数将其占用的内存资源释放。
打开适配器并捕获数据包
现在,我们已经知道如何获取适配器的信息了,那我们就开始一项更具意义的工作,打开适配器并捕获数据包。编写一个程序,将每一个通过适配器的数据包打印出来。
打开设备的函数是 pcap_open()
。下面是参数 snaplen
, flags
和 to_ms
的解释说明
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,则情况恰好相反,无论有没有数据包到达,读操作都会立即返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 #include "pcap.h" #define PCAP_SRC_IF_STRING "rpcap://" #define PCAP_SRC_FILE_STRING "file://" #define PCAP_OPENFLAG_PROMISCUOUS 1 #define PCAP_OPENFLAG_DATATX_UDP 2 #define PCAP_OPENFLAG_NOCAPTURE_RPCAP 4 #define PCAP_OPENFLAG_NOCAPTURE_LOCAL 8 #define PCAP_OPENFLAG_MAX_RESPONSIVENESS 16 void packet_handler (u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data) ; main() { pcap_if_t *alldevs; pcap_if_t *d; int inum; int i = 0 ; pcap_t *adhandle; char errbuf[PCAP_ERRBUF_SIZE]; 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 , 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 ; }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_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
的首部解析出来,并打印在屏幕上。
不用回调方法捕获数据包
下面范例程序所实现的功能和效果和上一讲的非常相似(打开适配器并捕获数据包),但本讲将用pcap_next_ex()
函数代替上面的pcap_loop()
函数。
pcap_loop()
函数是基于毁掉的原理来进行数据捕获,这是一种精妙的方法,并且在某些场合中,它是一种很好的选择。然而,处理回调有时候并不适用。他会增加程序的复杂度,特别是在拥有多线程的C++程序中。
可以通过直接调用pcap_next_ex()
函数来获得一个数据包。只有当编程人员使用了pcap_next_ex()
函数才能收到数据包。
这个函数的参数和捕获回调函数的参数是一样的。它包含一个网络适配器的描述符和两个可以初始化和返回给用户的指针(一个指向pcap_pkthdr
结构体,另一个指向数据包数据的缓冲)。
在下面的程序中,会再次用到上一讲中的有关回调方面的代码,只是我们将它放入了main()函数,之后调用pcap_next_ex()函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 #define _CRT_SECURE_NO_WARNINGS #include "pcap.h" #define PCAP_SRC_IF_STRING "rpcap://" #define PCAP_SRC_FILE_STRING "file://" #define PCAP_OPENFLAG_PROMISCUOUS 1 #define PCAP_OPENFLAG_DATATX_UDP 2 #define PCAP_OPENFLAG_NOCAPTURE_RPCAP 4 #define PCAP_OPENFLAG_NOCAPTURE_LOCAL 8 #define PCAP_OPENFLAG_MAX_RESPONSIVENESS 16 void packet_handler (u_char* param, const struct pcap_pkthdr* header, const u_char* pkt_data) ; main() { pcap_if_t * alldevs; pcap_if_t * d; int inum; int i = 0 ; pcap_t * adhandle; char errbuf[PCAP_ERRBUF_SIZE]; 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 , 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); int res; struct tm * ltime ; char timestr[16 ]; struct pcap_pkthdr * header ; const u_char* pkt_data; time_t local_tv_sec; while ((res = pcap_next_ex(adhandle, &header, &pkt_data)) >= 0 ) { if (res == 0 ) continue ; 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); } if (res == -1 ) { printf ("Error reading the packets: %s\n" , pcap_geterr(adhandle)); return -1 ; } return 0 ; return 0 ; }
为什么我们要用pcap_next_ex()
代替以前的pcap_next()
?因为pcap_next()
有一些不好的地方。首先,他效率低下,尽管它隐藏了回调的方式,但它依然依赖于函数pcap_dispatch()
。第二,它不能检测到文件末尾这个状态(EOF),因此,如果数据包是从文件读取来的,那么它就不那么有用了。
值得注意的是,pcap_next_ex()在成功,超时,出错或EOF的情况下,会返回不同的值。
分析数据包
捕获的数据包的协议首部,打印一些网络上传输的UDP数据的信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 #define _CRT_SECURE_NO_WARNINGS #include "pcap.h" #define PCAP_SRC_IF_STRING "rpcap://" #define PCAP_SRC_FILE_STRING "file://" #define PCAP_OPENFLAG_PROMISCUOUS 1 #define PCAP_OPENFLAG_DATATX_UDP 2 #define PCAP_OPENFLAG_NOCAPTURE_RPCAP 4 #define PCAP_OPENFLAG_NOCAPTURE_LOCAL 8 #define PCAP_OPENFLAG_MAX_RESPONSIVENESS 16 #include "pcap.h" typedef struct ip_address { u_char byte1; u_char byte2; u_char byte3; u_char byte4; }ip_address;typedef struct ip_header { u_char ver_ihl; u_char tos; u_short tlen; u_short identification; u_short flags_fo; u_char ttl; u_char proto; u_short crc; ip_address saddr; ip_address daddr; u_int op_pad; }ip_header;typedef struct udp_header { u_short sport; u_short dport; u_short len; u_short crc; }udp_header;void packet_handler (u_char* param, const struct pcap_pkthdr* header, const u_char* pkt_data) ; main() { pcap_if_t * alldevs; pcap_if_t * d; int inum; int i = 0 ; pcap_t * adhandle; char errbuf[PCAP_ERRBUF_SIZE]; u_int netmask; char packet_filter[] = "ip and udp" ; struct bpf_program fcode ; 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 , PCAP_OPENFLAG_PROMISCUOUS, 1000 , NULL , errbuf )) == NULL ) { fprintf (stderr , "\nUnable to open the adapter. %s is not supported by WinPcap\n" ); pcap_freealldevs(alldevs); return -1 ; } 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 netmask = 0xffffff ; if (pcap_compile(adhandle, &fcode, packet_filter, 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 ; } printf ("\nlistening on %s...\n" , d->description); pcap_freealldevs(alldevs); pcap_loop(adhandle, 0 , packet_handler, NULL ); return 0 ; }void packet_handler (u_char* param, const struct pcap_pkthdr* header, const u_char* pkt_data) { struct tm * ltime ; char timestr[16 ]; ip_header* ih; udp_header* uh; u_int ip_len; u_short sport, dport; 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 " , timestr, header->ts.tv_usec, header->len); ih = (ip_header*)(pkt_data + 14 ); ip_len = (ih->ver_ihl & 0xf ) * 4 ; uh = (udp_header*)((u_char*)ih + ip_len); sport = ntohs(uh->sport); dport = ntohs(uh->dport); 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); }
首先,将过滤器设置成"ip and udp"。在这种方式下,packet_handler
只会收到基于IPv4的UDP数据包;这将简化解析过程,提高程序的效率。其次,创建了用于描述IP首部和UDP首部的结构体。这些结构体中的各种数据会被packet_handler()合理地定位。在开始捕捉前,使用了pcap_datalink() 对MAC层进行了检测,以确保在处理一个以太网络,确保MAC首部是14位的。IP数据包的首部就位于MAC首部的后面,将从IP数据包的首部解析到源IP地址和目的IP地址。
处理UDP的首部有一些复杂,因为IP数据包的首部的长度并不是固定的,可以通过IP数据包的length域来得到它的长度。一旦知道了UDP首部的位置就能解析到源端口和目的端口。
被解析出来的值被打印在屏幕上,形式上图所示,每一行分别代表一个数据包。
Winpcap的一些基本功能的实现
捕获数据包
枚举所有可用的设备pcap_findalldevs_ex()
通过名字打开一个设备pcap_open()
在这里可以打开一个文件,只是在打开这个文件之前需要通过pcap_createsrcstr
创建相应的name string
设置Filter[pcap_compile,pcap_setfilter]
(可选)
捕获数据
有几种捕获数据的方法(捕获数据的数据都是最原始的数据包,即包含数据链路层的数据头)
是以回调的方式[pcap_loop,pcap_dispatch]
这两种方法基本相同,底层收集数据包,当满足一定的条件(timeout或者缓冲区满),就会调用回调函数,把收集到的原始数据包s,交给用户。他们返回的数据缓冲区包含多个包。
每当一个包到达以后,pcap_next_ex
就会返回,返回的数据缓冲区里只包含一个包
发送包
Winpcap中有发送单个包和发送多个包的方法。这里只说发送单个包。
通过名字打开一个设备[pcap_open]
自己构造一个原始数据包(这个数据包会不经过任何处理就发送出去,所以必须把包中的各个字段设置好。另外这个数据包是包含数据链路层报头的)
使用pcap_sendpacket()
发送数据包
统计网络流量
通过名字打开一个设备[pcap_open]
通过read_timeout来设置统计的时间间隔
设置filter[pcap_compile,pcap_setfilter]
(可选)
设置设备的模式为统计模式[pcap_setmode(MODE_STAT);]
开始统计,pcap_loop/pcap_dispatch()
在回调函数中的参数中就包含了统计信息。
习题与思考题
1、WINPCAP是否能实现服务质量的控制?
答:不能。WinPcap可以独立地通过主机协议发送和接受数据,如同TCP-IP。 这就意味着WinPcap不能阻止、过滤或操纵同一机器上的其他应用程序的通讯: 它仅仅能简单地"监视"在网络上传输的数据包。所以,它不能提供类似网络流量控制、服务质量调度和个人防火墙之类的支持,因而不能实现服务质量的控制。
tips
使用winpcap踩了大坑。
Npcap是基于Winpcap和Libpcap的,Winpcap已多年无人维护,其官网也推荐Windows XP之后的用户转移到Npcap上。Npcap基于WINPCAP,Winpcap基于libcap,并做出了诸多改进。
后面都是用Npcap开发了。