班级 | xxx | 实验环境 | Win10 Pro 1709(64位) |
---|---|---|---|
姓名 | xxx | 开发环境 | Visual Studio 2013 |
学号 | xxx | 软件版本 | WinPcap 4.1.3 |
WinPcap是一个基于Win32平台的,用于捕获网络数据包并进行分析的开源库.
大多数网络应用程序通过被广泛使用的操作系统元件来访问网络,比如sockets。 这是一种简单的实现方式,因为操作系统已经妥善处理了底层具体实现细节(比如协议处理,封装数据包等等),并且提供了一个与读写文件类似的,令人熟悉的接口。
然而,有些时候,这种“简单的方式”并不能满足任务的需求,因为有些应用程序需要直接访问网络中的数据包。也就是说,那些应用程序需要访问原始数据包,即没有被操作系统利用网络协议处理过的数据包。
WinPcap产生的目的,就是为Win32应用程序提供这种访问方式; WinPcap提供了以下功能 :
1.捕获原始数据包,无论它是发往某台机器的,还是在其他设备(共享媒介)上进行交换的
2.在数据包发送给某应用程序前,根据用户指定的规则过滤数据包
3.将原始数据包通过网络发送出去
4.收集并统计网络流量信息
以上这些功能需要借助安装在Win32内核中的网络设备驱动程序才能实现,再加上几个动态链接库DLL。
所有这些功能都能通过一个强大的编程接口来表现出来,易于开发,并能在不同的操作系统上使用。这本手册的主要目标是在一些程序范例的帮助下,叙述这些编程接口的使用。
WinPcap可以被用来制作网络分析、监控工具。一些基于WinPcap的典型应用有:
1.网络与协议分析器 (network and protocol analyzers)
2.网络监视器 (network monitors)
3.网络流量记录器 (traffic loggers)
4.网络流量发生器 (traffic generators)
5.用户级网桥及路由 (user-level bridges and routers)
6.网络入侵检测系统 (network intrusion detection systems (NIDS))
7.网络扫描器 (network scanners)
8.安全工具 (security tools)
通过学习WINPCAP架构,编写一个网络抓包程序。
WinPcap提供了 pcap_findalldevs_ex()
函数来实现获取设备列表的功能,这个函数返回一个 pcap_if
结构的链表, 每个这样的结构都包含了一个适配器的详细信息。值得注意的是,数据域 name
和 description
表示一个适配器名称和一个可以让人们理解的描述。下面获取适配器列表,并在屏幕上显示出来,如果没有找到适配器,将打印错误信息。
#include
#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 /* auth is not needed */, &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);
}
运行结果:
WinPcap获取的设备列表显示主机有四块逻辑网卡,与控制面板-网络适配器显示的一致
上一部分的获取设备列表,获取了适配器的基本信息 (如设备的名称和描述),而WinPcap提供了其他更高级的信息。由 pcap_findalldevs_ex()
返回的每一个 pcap_if
结构体,都包含一个 pcap_addr
结构体,这个结构体由如下元素组成:
另外,函数 pcap_findalldevs_ex()
还能返回远程适配器信息和一个位于所给的本地文件夹的pcap文件列表。
下面的程序使用了 ifprint()
函数来打印出 pcap_if
结构体中所有的内容。程序对每一个由 pcap_findalldevs_ex()
函数返回的 pcap_if
,都调用 ifprint()
函数来实现打印。
#include "pcap.h"
#include
#define PCAP_SRC_IF_STRING "rpcap://"
#define PCAP_SRC_FILE_STRING "file://"
#ifndef WIN32
#include
#include
#else
#include
#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;
/* 设备名(Name) */
printf("%s\n", d->name);
/* 设备描述(Description) */
if (d->description)
printf("\tDescription: %s\n", d->description);
/* Loopback Address*/
printf("\tLoopback: %s\n", (d->flags & PCAP_IF_LOOPBACK) ? "yes" : "no");
/* IP addresses */
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");
}
/* 将数字类型的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(output[which], "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
return output[which];
}
运行结果:
结果分析:
WinPcap获取了4块逻辑网卡的高级信息:包含设备描述、IP地址、子网掩码、广播地址等,如下表所示
编号 | 备注 | IP地址 | 子网掩码 | 广播地址 |
---|---|---|---|---|
1 | Sangfor SSL VPN支持 | 未获得 | 未获得 | 未获得 |
2 | 蓝牙网络连接 | 未获得 | 未获得 | 未获得 |
3 | 无线网络连接 | 10.0.0.106 | 255.255.255.0 | 255.255.255.255 |
4 | 有线网络连接 | 10.0.0.108 | 255.255.255.0 | 255.255.255.255 |
编写一个程序,将每一个通过适配器的数据包打印出来。
打开设备的函数是 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,则情况恰好相反,无论有没有数据包到达,读操作都会立即返回。
#include "pcap.h"
#define PCAP_SRC_IF_STRING "rpcap://"
#define PCAP_SRC_FILE_STRING "file://"
#define PCAP_OPENFLAG_PROMISCUOUS 1
//Defines if the adapter has to go in promiscuous mode.
#define PCAP_OPENFLAG_DATATX_UDP 2
//Defines if the data trasfer(in case of a remote capture) has to be done with UDP protocol.
#define PCAP_OPENFLAG_NOCAPTURE_RPCAP 4
//Defines if the remote probe will capture its own generated traffic.
#define PCAP_OPENFLAG_NOCAPTURE_LOCAL 8
//Defines if the local adapter will capture its own generated traffic.
#define PCAP_OPENFLAG_MAX_RESPONSIVENESS 16
//This flag configures the adapter for maximum responsiveness.
/* packet handler 函数原型 */
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, // 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;
}
/* 每次捕获到数据包时,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_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
的首部解析出来,并打印在屏幕上。
捕获的数据包的协议首部,打印一些网络上传输的UDP数据的信息。
#include "pcap.h"
#define PCAP_SRC_IF_STRING "rpcap://"
#define PCAP_SRC_FILE_STRING "file://"
#define PCAP_OPENFLAG_PROMISCUOUS 1
//Defines if the adapter has to go in promiscuous mode.
#define PCAP_OPENFLAG_DATATX_UDP 2
//Defines if the data trasfer(in case of a remote capture) has to be done with UDP protocol.
#define PCAP_OPENFLAG_NOCAPTURE_RPCAP 4
//Defines if the remote probe will capture its own generated traffic.
#define PCAP_OPENFLAG_NOCAPTURE_LOCAL 8
//Defines if the local adapter will capture its own generated traffic.
#define PCAP_OPENFLAG_MAX_RESPONSIVENESS 16
//This flag configures the adapter for maximum responsiveness.
#include "pcap.h"
/* 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;
/* 回调函数原型 */
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, // 要捕捉的数据包的部分
// 65535保证能捕获到不同数据链路层上的每个数据包的全部内容
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
/* 如果接口没有地址,那么我们假设一个C类的掩码 */
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;
}
/* 回调函数,当收到每一个数据包时会被libpcap所调用 */
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);
/* 获得IP数据包头部的位置 */
ih = (ip_header *)(pkt_data +
14); //以太网头部长度
/* 获得UDP首部的位置 */
ip_len = (ih->ver_ihl & 0xf) * 4;
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);
}
运行结果:
首先,将过滤器设置成"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首部的位置就能解析到源端口和目的端口。
被解析出来的值被打印在屏幕上,形式上图所示,每一行分别代表一个数据包。
1、WINPCAP是否能实现服务质量的控制?
答:不能。WinPcap可以独立地通过主机协议发送和接受数据,如同TCP-IP。 这就意味着WinPcap不能阻止、过滤或操纵同一机器上的其他应用程序的通讯: 它仅仅能简单地"监视"在网络上传输的数据包。所以,它不能提供类似网络流量控制、服务质量调度和个人防火墙之类的支持,因而不能实现服务质量的控制。