WinPcap - 捕获数据、发送数据

1、WinPcap

WinPcap是一个基于Win32平台的,用于捕获网络数据包并进行分析的开源库。WinPcap提供了以下功能:

  • 捕获原始数据包,无论它是发往某台机器的,还是在其他设备(共享媒介)上进行交换的;
  • 在数据包发送给某应用程序前,根据用户指定的规则过滤数据包;
  • 将原始数据包通过网络发送出去;
  • 收集并统计网络流量信息。

WinPcap的功能与LibPcap的功能很相似,但是WinPcap主要用在Win32平台,且提供了发送网络数据包的功能。

1.1、WinPcap的下载与安装

要使用WinPcap开发程序需要下载两部分:一部分是WinPcap的驱动和运行库,一部分是WinPcap的开发包。WinPcap驱动和运行库的下载可以访问:https://www.winpcap.org/install/default.htm,如下所示: 

 

WinPcap开发包的下载可以访问https://www.winpcap.org/devel.htm,如下图所示:

 需要注意的是虽然下载的是4.1.2版本的开发包,但是仍然兼容4.1.3版本的WinPcap驱动和动态库。

WinPcap程序的安装很简单,只需按照提示进行就可以,需要注意的是,需要保持如下图中的复选框的选中状态:

 然后点击Install安装即可。

下载WinPcap的开发包并解压:

 需要用到的只有Include和Lib两个文件夹。重新建立一个名为WinPcap的文件夹,将这两个文件夹拷贝到WinPcap中,然后将WinPcap拷贝到程序工程目录下。

1.2、WinPcap开发环境配置

在进行开发前,需要进行一些配置:

1、在每一个使用了库的源程序中,将 pcap.h 头文件包含(include)进来:

 2、如果你的程序使用了WinPcap的远程捕获功能,那么在预处理定义中加入HAVE_REMOTE不要直接把remote-ext.h直接加入到源文件中:

 3、设置VC++的链接器(Linker),把wpcap.lib库文件和ws2_32.lib库文件包含进来:

4、将头文件目录和库文件目录添加到工程目录下:

 

 

 当配置完成之后就可以进行WinPcap程序的开发了。

2、捕获数据包

2.1、获取本地网络设备列表

在WinPcap中,获取本地网络设备列表是通过函数pcap_findalldevs实现的,函数原型如下所示:

int pcap_findalldevs (pcap_if_t **alldevsp, char *errbuf);
// alldevsp: 存储本地网络设备列表
// errbuf: 存储错误信息
// 返回值:int,如果发生错误将返回-1,执行成功将返回0

 具体实例如下所示:

pcap_if_t*  alldevs;
char		errbuf[PCAP_ERRBUF_SIZE];
// 获取本地机器的设备列表
if( pcap_findalldevs(&alldevs, errbuf) == -1)
{
	printf(">>> Error - Error in pcap_findalldevs: %s\n", errbuf);
}

捕获的网络设备列表均保存在结构体链表alldevs中。

 2.2、打印本地网络设备列表

要打印网络设备的信息,首先要知道pcap_if_t结构体的具体组成,pcap_if_t结构体的原型如下所示:

struct pcap_if
{
pcap_if*    next, //指向下一个元素,为NULL则表示该元素为列表的最后一个元素
char*       name,//表示该网络设备的名称
char*       description,//表示该网络设备的描述
pcap_addr* address,//该网络设备中的地址信息,包括IPV4、IPV6、子网掩码等
u_int        flags,//用于表示是否为回环端口(loopback interface)
};
typedef struct pcap_if pcap_if_t;

可以看出,pcap_if_t结构体是pcap_if结构体的另外一种表示。结构体内包含了网络设备的名称、描述、地址信息等。

2.3、打开要操作的网络设备

打开网络设备采用pcap_open函数,pcap_open函数原型如下所示:

pcap_t* pcap_open(const char* source, //接口设备名,pcap_if_t结构体中的name
int   snaplen,   //捕获数据的保存长度,一本可以设置为65535,65536保证能捕获到不同数据链路层上的每个数据包的全部内容
int   flags,//是否设置为混杂模式,为0则表示不设置为混杂模式
int   read_timeout, //捕获数据时的超时时间,设置为0表示不抓取到数据不会返回,设置为-1表示不管有没有抓到数据都会返回,设置为1000表示,如果没有抓取到数据则会等待1000ms再返回
struct pcap_rmtauth* auth, //远程机器验证,如果不是远程捕获,则设为NULL
char* errbuf               //存储错误信息
);
返回值为pcap_t类型的指针,可以理解为打开的接口设备的句柄。接下来的操作都是针对该句柄进行的。如果函数执行错误则返回NULL并在errbuf中存储错误信息。

具体示例如下所示:

// 打开想要捕获的网络设备
pcap_t* g_Capture_Adhandle = NULL;
if((g_Capture_Adhandle = pcap_open(d->name,// 要捕获的接口设备名
65536,	// 65536保证能捕获到不同数据链路层上的每个数据包的全部内容
0,	// 不设置为混杂模式
0,	// 读取超时时间。设置为0,表示如果没有数据包到达的话,则读取操作永远不会返回。
NULL,			// 远程机器验证
errbuf)) == NULL)// 错误缓冲区
{
	printf("\nUnable to open the adpater. %s is not supported by Winpcap.\n", d->name);
	pcap_freealldevs(alldevs);//释放所有的机器列表
	return;
}

2.4、设置捕获过滤器

在网络中传输的有各种各样的数据,为了准确抓取网络中传输的数据,需要在抓取数据时设置捕获过滤器,将我们不需要的数据过滤掉,之抓取我们想要的数据。设置过滤器时需要用到两个函数:pcap_compile和pcap_setfilter。

pcap_compile函数是用来编译过滤器,pcap_compile函数的原型如下所示:

int pcap_compile(pcap_t* p, //打开的接口设备的句柄,pcap_open函数的返回值
struct bpf_program*   fp,  //bpf_program结构体指针,函数执行成功后会填入内容
char*   str,                //存储过滤规则的字符串
int   optimize, //用于控制是否会在结果代码(resulting code)上执行优化,一般设置为1
bpf_u_int32 netmask //用于指定要捕获的网络设备上的IPV4的掩码
);
函数如果执行失败,则会返回-1。

pcap_setfilter函数是用来设置过滤器的,是将一个过滤器与内核捕获回话相关联,pcap_setfilter函数原型如下所示:

int pcap_setfilter(pcap_t* p, //打开的接口设备的句柄,pcap_open函数的返回值
struct bpf_program*   fp,  //pcap_compile函数执行后生成的bpf_program结构体指针,已包含相关数据
);
函数如果执行失败,则会返回-1;执行成功则会返回0。

设置过滤器的实例如下所示:

// 首先获得掩码
u_int netmask;
pcap_if_t* d;  //选中的网络接口设备
pcap_t* g_Capture_Adhandle; //打开的网络设备的句柄
if(d->addresses != NULL)
{
	// 获得该接口的第一个地址的掩码
	netmask = ((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
}
else
	netmask = 0xffffffff;	// 如果接口没有地址, 那么就设置一个C类掩码
struct bpf_program fcode;
char packet_filter[] = "ip and udp";//过滤器,表示只抓取UDP报文
if(pcap_compile(g_Capture_Adhandle, &fcode, packet_filter, 1, netmask) < 0)
{
	printf("\nUnable to compile the packet filter.Check the Syntax!\n");
	pcap_freealldevs(alldevs);
	return;
}
// 设置过滤器
if(pcap_setfilter(g_Capture_Adhandle, &fcode) < 0)
{
	fprintf(stderr, "\nError setting the filter!\n");
	pcap_freealldevs(alldevs);
	return;
}

2.5、开始捕获数据

捕获数据可以采取多种方式进行,可以采用pcap_loop通过回调函数的方式接收数据,也可以通过pcap_next_ex直接捕获,下面主要介绍通过pcap_next_ex函数捕获数据的方式。pcap_next_ex函数原型如下所示:

int pcap_next_ex(pcap_t* p, //要捕获数据的接口设备的句柄,pcap_open函数的返回值
struct pcap_pkthdr**   pkt_header,  //捕获数据的一些信息
const u_char** pkt_data    //捕获的数据
);
函数的返回值:
1:函数执行成功
0:读取超时
-1:发生错误
-2:读取离线文件时,当读取到EOF时会返回-2

结构体pkt_header存储了关于捕获到的数据一些基本信息,结构体原型如下所示:

struct pcap_pkthdr
{
timeval ts,         //时间戳
bpf_u_int32 caplen,//捕获到的数据长度
bpf_u_int32 len    //数据长度
};

捕获数据的具体实例如下所示:

int res;
struct pcap_pkthdr*	header;
const u_char*		pkt_data;
if( (res = pcap_next_ex(g_Capture_Adhandle, &header, &pkt_data)) >= 0)
{
	if(res == 0)
	{
		printf("\n>>> Time out.\n");
		continue;
	}
	else
	{
		int recv_data_length = header->len;//接收到的数据长度
		char msg[MAX_LENGTH] = {0}; // MAX_LENGTH > recv_data_length
		memcpy(msg, (void *)(pkt_data), recv_data_length);
	}
}

3、发送数据

winpcap另外一个很强大的功能就是数据包的发送功能,它能够发送任何类型/协议的数据包,只需要将数据包的格式转换为对应协议的数据格式即可。

3.1 单个发送数据包

winpcap中发送单个数据包的函数是pcap_sendpacket,函数原型如下所示:

int pcap_sendpacket(pcap_t* p, //要发送数据的接口设备的句柄,pcap_open函数的返回值
u_char*   buf,  //要发送的数据
int        size    //要发送的数据长度
);
函数的返回值:
1:函数执行发送数据失败
0:函数执行成功

具体实例如下所示:

pcap_t* g_Capture_Adhandle; // 要发送数据的网络接口设备的句柄
int len; // 要发送的数据的长度
u_char* data; //要发送的数据,数据长度为len
if( pcap_sendpacket(g_Capture_Adhandle, data, len) != 0 )
{
	printf("\nError - Error sending the packet;\n", pcap_geterr(g_Capture_Adhandle));
	return 0;
}

3.2、队列发送数据包

pcap_sendpacket虽然能够向目标网络接口设备发送数据,但是只能单个发送,且执行效率并不是很高。winpcap中有另外一个机制用于发送数据,那就是pcap_sendqueue_transmit,即通过队列的方式进行发送,采用这种方式,会一次性向目标设备发送队列内的数据,效率比较高。相关的函数有pcap_sendqueue_allocpcap_sendqueue_queuepcap_sendqueue_transmit

其中pcap_sendqueue_alloc是用来申请队列的长度,单位为字节,所以计算出来的长度需要精确到字节,函数原型:pcap_send_queue* pcap_sendqueue_alloc(u_int memsize);其中返回值为该发送队列的句柄。

pcap_sendqueue_queue是用来将单个数据添加到队列中,函数原型为:int pcap_sendqueue_queue(pcap_send_queue* queue, const struct pcap_pkthdr* pkt_header, const u_char* pkt_data);其中queue为pcap_sendqueue_alloc函数创建的队列句柄,pkt_header为为数据包添加的数据包头,pkt_data为要发送的数据。

pcap_sendqueue_transmit是用来将数据发送出去的,函数原型为:u_int pcap_sendqueue_transmit(pcap_t* p, pcap_send_queue* queue, int sync);其中p为要发送数据的网络接口设备句柄,queue为创建的发送队列,sync为同步标志,如果非0表示发送过程将是同步进行,即只有时间戳相符的数据包才会被处理。这个操作会消耗大量的CPU资源,但是数据包的处理很精确。

通过队列发送数据包的具体实例如下所示:

pcap_t* g_Capture_Adhandle; // 要发送数据的网络接口设备的句柄
pcap_send_queue* s_queue;
u_char	winpcap_send_data[DATA_LENGTH] = {0}; //要发送的数据,数据长度为DATA_LENGTH
// 计算发送队列的大小= (包大小+包头长度)* 队列最大个数
u_int alloc_len		= (DATA_LENGTH + sizeof(struct pcap_pkthdr)) * QueueLen;
s_queue	= pcap_sendqueue_alloc(alloc_len);		// 分配长度为alloc_len的发送队列
struct pcap_pkthdr	pkt_header;
pkt_header.ts		= ts;
pkt_header.caplen	= packet_send_len;
pkt_header.len		= packet_send_len;
//会执行多次,将多有的数据都添加到队列中
if(pcap_sendqueue_queue(s_queue, &pkt_header, send_data) == -1)
{
	printf(">>> Warning: Packet buffer is too small, not all the packets will be sent!\n");
}
int sent_len; //真实发送出去的数据队列的长度
if( (sent_len = pcap_sendqueue_transmit(g_Capture_Adhandle, s_queue, FALSE)) < s_queue->len)
{
	printf(">>> Error: An error occurred sending the packets: %s, only %d bytes were sent.\n", pcap_geterr(g_Capture_Adhandle), sent_len);
	//continue;
}
cur_queue_len = 0;
s_queue->len = 0;	// 发送完一个队列之后一定记得将队列的长度置为0

pcap_sendqueue_destroy(s_queue);// 队列使用完之后一定记得要释放队列

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章