计算机网络 | 实验二 WINPCWP编程

实验二 WINPCWP编程

班级 xxx 实验环境 Win10 Pro 1709(64位)
姓名 xxx 开发环境 Visual Studio 2013
学号 xxx 软件版本 WinPcap 4.1.3

一、实验目的

  • 了解WINPCAP的架构
  • 学习WINPCAP编程

二、实验原理

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架构,编写一个网络抓包程序。

四、实验过程

1.WinPcap的下载、安装与配置

  • 访问WinPcap官网:https://www.winpcap.org/
  • 选择WinPcap的最新版,下载Installer for Windows并安装
  • 在VS中导入相应头文件和lib文件

2.获取设备列表

WinPcap提供了 pcap_findalldevs_ex() 函数来实现获取设备列表的功能,这个函数返回一个 pcap_if 结构的链表, 每个这样的结构都包含了一个适配器的详细信息。值得注意的是,数据域 namedescription 表示一个适配器名称和一个可以让人们理解的描述。下面获取适配器列表,并在屏幕上显示出来,如果没有找到适配器,将打印错误信息。

#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);
}

运行结果:

计算机网络 | 实验二 WINPCWP编程_第1张图片

WinPcap获取的设备列表显示主机有四块逻辑网卡,与控制面板-网络适配器显示的一致

计算机网络 | 实验二 WINPCWP编程_第2张图片

3.获取已安装设备的高级信息

上一部分的获取设备列表,获取了适配器的基本信息 (如设备的名称和描述),而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];
}

运行结果:

计算机网络 | 实验二 WINPCWP编程_第3张图片

结果分析:

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

4.打开适配器并捕获数据包

编写一个程序,将每一个通过适配器的数据包打印出来。
打开设备的函数是 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);

}

运行结果(捕获的是无线网卡的数据包)

计算机网络 | 实验二 WINPCWP编程_第4张图片

结果分析:

适配器被打开,捕获工作就可以用 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 的首部解析出来,并打印在屏幕上。

5.分析数据包

捕获的数据包的协议首部,打印一些网络上传输的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);
}

运行结果:

计算机网络 | 实验二 WINPCWP编程_第5张图片

首先,将过滤器设置成"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不能阻止、过滤或操纵同一机器上的其他应用程序的通讯: 它仅仅能简单地"监视"在网络上传输的数据包。所以,它不能提供类似网络流量控制、服务质量调度和个人防火墙之类的支持,因而不能实现服务质量的控制。

你可能感兴趣的:(实验,考试与课设)