在谍战片中,我们经常会看到特工人员发送秘密的情报,用于判断最新的军事局面。在网络应用中,网络安全管理人员也经常需要向网络发送原始数据包,用于分析网络存在的各种问题。例如,在网络测试中分析接收端软件接收数据包的性能,就需要了解达到多大网络流量时软件将会出现丢失数据包的现象。在这种情况下,就需要人为的生成不同的网络流量来测试软件的性能指标了。
比如,为了测试网络的传输性能,我们可能还需要生成网络的峰值流量,即接近网卡带宽极限的流量。例如 100Mbits/s的网卡需要发送 99.8Mbits/s的流量。在某些情况下还可能会要求在特定时间段内生成恒定的网络流量,如连续生成 50Mbits/s的网络流量两个小时。甚至有时候还需要严格控制每个数据包发送的时间间隔,如每 200us向网络发送一个数据包。诸如此类的要求,是通过操作系统所提供的 Socket方式无法有效实现的,但通过使用WinPcap库提供的数据包发送功能,则可很容易地满足这些要求。
在接下来的内容中,我们将演示一些使用WinPcap发送数据包的实例,同时结合WinPcap源代码,对网络分析中如何设计与实现灵活高效率的数据包发送功能进行详细的分析。
发送数据包的基础知识
在网络分析中,要灵活高效率地发送数据包在设计与实现上存在着如下主要难点:需要有非常高的网络流量,以及极高的网络带宽利用率;对于同步发送方式,需要有精准的时间控制;由于数据包的发送过程贯穿于用户空间与内核空间,所以还需要有效地提高交互效率;需要给用户提供齐备的机制,才能使用户灵活使用所提供的功能。因此,需要有一些技巧来克服以上困难。
1.发送数据包的几种方式
针对不同的应用情况,WinPcap提供了下列四种方式来把原始数据包发送到网络中。
第一种方式:每次发送一个数据包一次。
第二种方式:每次发送一个数据包,但在内核中重复发送多于一次,需要预先设定次数。
第三种方式:每次发送一个发送队列(发送队列是一个容器,它能容纳不同数量的数据包),并根据每个数据包的时间戳严格按顺序发送各数据包(称为同步方式)。;
第四种方式:每次发送一个发送队列,但不根据每个数据包的时间戳发送各数据包,而是尽可能快速地发送各数据包(称为异步方式)。
2.常见的函数说明
为了使用户能够顺利完成数据包的发送任务,在 WinPcap的库 wpcap.dll中提供了下列函数,用户可以很方便地使用它们。
int pcap_sendpacket(pcap_t *p,u_char *buf,int size);
函数发送单个原始数据包一次或多次pcap_send_queue* pcap_sendqueue_alloc(u_int memsize);
函数分配一个发送队列int pcap_sendqueue_queue(pcap_send_queue *queue,const struct pcap_pkthdr *pkt_header,const u_char *pkt_data);
函数把一个原始数据包添加到 queue参数所指定的发送队列的尾部u_int pcap_sendqueue_transmit(pcap_t *p,pcap_send_queue *queue,int sync);
函数发送一个数据包队列到网络void pcap_sendqueue_destroy(pcap_send_queue *queue);
函数释放与一个发送队列相关的所有资源
这里重点分析一下单个数据包的发送函数。如果设置为只发送一次,那么每调用一次pcap_sendpacket函数,就会发送单个原始数据包一次。该函数的原型如下:int pcap_sendpacket(pcap_t *p, const u_char *buf, int size)
其中,参数 p用来发送数据包的一个 pcap_t类型描述符(可通过 pcap_open函数获得该描述符),参数 buf包含所要发送数据包的内容(包含数据包的协议头),参数 size是buf所指缓冲区的大小,也就是所要发送的数据包的大小。另外,无须在数据包中包含 MAC的CRC校验字段,它是由网络接口驱动程序计算并添加的。函数 pcap_sendpacket如果执行成功则返回 0,否则返回-1。
该函数的具体实现如下:
int pcap_sendpacket(pcap_t *p, const u_char *buf, int size) { if (p-inject_op(p, buf, size) == -1) return (-1); return (0); }
数据包发送实例详解
为了更好地理解在网络分析中如何根据实际要求发送数据包,我们使用 WinPcap所提供的函数来演示各种发送方式,同时还分别提供了典型的使用示例程序与简单的测试结果。
1.发送单个数据包
WinPcap发送单个数据包的实现主要依赖于 wpcap.dll库中的 pcap_sendpacket函数,而该函数主要依赖于 Packet.dll库所提供的 PacketSendPacket函数,不过,最终的数据包发送是在内核空间的 NPF_Write函数中调用 NDIS库的 NdisSend函数完成的。下面的代码将演示如何通过 WinPcap提供的 pcap_sendpacket函数发送单个数据包一次。
/*生成数据包的函数*/ void gen_packet(unsigned char *buf,int len); int main() { /*获得系统的网络适配器设备列表,并选择一个的适配器设备,打开它*/ … /*发送数据包*/ //生成数据包 int packetlen=100; //数据包长度为 100字节 unsigned char *buf= (unsigned char *)malloc(packetlen); memset(buf,0x0,packetlen); gen_packet(buf,packetlen); //获得生成的数据包,长度为 packetlen //开始数据包发送 if ( (ret=pcap_sendpacket(adhandle,buf,packetlen))==-1) { //发送失败,释放资源,程序返回 … return -1; } /* *释放资源, *包括关闭打开的适配器,释放适配器设备列表,释放存储待发数据包的内存 */ … return 0; }
此示例程序在打开了合适的网络适配器后,为了存储数据包,会执行内存分配并清零。然后调用 gen_packet函数生成一个数据包,该数据包的长度为 100个字节。最后调用 WinPcap库的 pcap_sendpacket函数把该数据包发送到网络上。
其中 gen_packet函数负责生成数据包,其参数 buf用来保存所生成数据包的内容,len为要求生成的数据包的长度。函数 gen_packet的具体实现如下:
void gen_packet(unsigned char *buf,int len) { int i=0; //给数据包的第 0到 5字节填充目标 MAC地址:01:01:01:01:01 for (i=0;i6;i++) { buf[i]=0x01; } //给数据包的第 6到 11字节填充源 MAC地址:02:02:02:02:02 for (i=6;i12;i++) { buf[i]=0x02; } //给数据包的第 12到 13字节填充协议标识 0xcd,无任何实际意义 buf[12]=0xc; buf[13]=0xd; //从第 14字节开始填充数据包内容,从 0开始递增,添数 for(i=14;ilen;i++) { buf[i]=i-14; } }
在上面的示例程序中使用了下列代码生成待发的数据包,数据包长度为 100字节:
//数据包长度为 100字节 int packetlen=100; //分配内存 unsigned char *buf= (unsigned char *)malloc(packetlen); //内存清零 memset(buf,0x0,packetlen); //生成数据包 gen_packet(buf,packetlen);
代码最终生成的数据包内容如图 1所示。
0101010101010202020202020c0d000102 …5455 目标MAC地址 源MAC地址 协议类型 数据内容
图 1生成的数据包
换句话说,在网络上实际接收到的数据包内容也应该与此相同。我们使用 Wireshark工具来接收该示例程序所发送的数据包,实验结果如图 2所示。
从图 2可见,其所接收的数据包与图 1一致,这表示示例代码是正确的。
2.重复发送单个数据包
在发送单个数据包的实现中,每发送一个数据包,用户空间的应用程序就需要一次WriteFile系统调用,以便通过内核驱动程序执行数据包的发送。而这会导致网络数据包的发送效率不太高。正因为如此,为了满足生成大的网络流量、高网络带宽利用率的需要,WinPcap提供了使用一次 WriteFile系统调用就能把单个数据包重复发送多次的功能。该重复发送的功能是在内核中实现的,由于上下文切换的负载不再出现,因此性能得到了显著性的提升,所以数据包的发送效率非常高。不过该功能只能对数据包内容进行简单的、机械性的重复。该功能通过用户空间的应用程序设置单个数据包重复发送的次数来实现。例如设为1000,那么,应用程序发送的每个原始数据包,在内核驱动程序中都将会重复发送 1000次。
如果是测试,就可使用该特性生成高速网络流量。
注意,目前只在 Packet.dll库中提供了辅助接口函数 PacketSetNumWrites来实现重复发送功能,而在 wpcap.dll库中并没有对应的函数。
接下来将演示如何在内核中重复发送单个数据包。其实很简单,在[send工程]的源文件
main.cpp中添加下面的代码即可。
#include pcap-int.h /*调用 Packet.dll库提供的 PacketSetNumWrites函数设置重复发送次数*/ //重复 50次 PacketSetNumWrites((LPADAPTER)(adhandle-adapter),50);
因为 wpcap.dll库中并没有提供设置重复发送次数的函数,所以此处只能调用 Packet.dll库提供的 PacketSetNumWrites函数来设置相应的值。运行示例程序,用 Wireshark工具接收示例程序所发送的数据包,其结果如图 3所示。
图 3 Wireshark所接收的数据包
从图 3可以看出,数据包的确重复发送了 50次,而且数据包的内容也与图 1一致,可见示例代码是正确的。
发送数据包的性能测试与分析为了更好验证上面的发送过程,我们使用 WinPcap提供的不同发送方式,来测试不同发送方式之间的区别。测试的基本过程是:在发送方连续发送 10万个长度为 1514个字节的数据包,同时在发送方监视 CPU占用率、统计网络流量与每个数据包之间的平均时间间隔。
1.测试一:单次发送数据包的重复调用
可通过对pcap_sendpacket函数多次重复调用,来实现多次发送数据包的功能,主要实现代码如下。
for(int i=0;isendtimes;i++)//循环,实现多次重复发送 { //获得生成的数据包,长度为 max_packet_len gen_packet(buf,max_packet_len,i); //发送数据包 if ( (ret=pcap_sendpacket(adhandle,buf,max_packet_len)) ==-1) { //发送失败 … return -1; } }
其中函数gen_packet的原型如下:
void gen_packet(unsigned char *buf,int len,int order)数order用来设置需要生成数据包的序号。
发送时使用如下命令,从而把长度为1514字节的数据包重复发送十万次。
send.exe 100000
接收方的数据包捕获如图 4所示,所接收的每个数据包之间的平均时间间隔为202.22us。
2.测试二:重复发送单个数据包
通过调用pcap_set_num_write函数,实现对同一个数据包重复多次发送的功能,主
要实现代码如下。
/*设置重复发送次数*/ pcap_set_num_write(adhandle,sendtimes); /*开始数据包发送*/ int max_packet_len=1514; unsigned char *buf=(unsigned char *)malloc(max_packet_len); memset(buf,0x0,max_packet_len); /*获得生成的数据包*/ gen_packet(buf,max_packet_len); if ( (ret=pcap_sendpacket(adhandle,buf, max_packet_len)) ==-1) { //发送失败 … return -1; free(buf);
其中函数gen_packet的原型如下:
/*设置重复发送次数*/ pcap_set_num_write(adhandle,sendtimes); /*开始数据包发送*/ int max_packet_len=1514; unsigned char *buf=(unsigned char *)malloc(max_packet_len); memset(buf,0x0,max_packet_len); /*获得生成的数据包*/ gen_packet(buf,max_packet_len); if ( (ret=pcap_sendpacket(adhandle,buf, max_packet_len)) ==-1) { //发送失败 … return -1; free(buf);
其中,参数buf用来存储生成的数据包内容;参数len用来设置需要生成数据包的长度。
发送时使用如下命令,从而把长度为1514个字节的数据包重复发送10万次。
send_n_wpcap.exe 100000
接收方的数据包捕获如图6所示,所接收的每个数据包的平均时间间隔为123.27us.
从测试数据可以看出,第二种方式(重复发送单个数据包)所占用的发送方的 CPU很少,发送速度快,生成的的网络流量很大。不过,该方式的缺点就是无法给数据包本身添加数据包是否掉包的判断依据(如数据包的序号)。
通过上面的完整过程演示,对发送数据包的过程及特点进行了详细的分析,同时也进行了性能测试与分析。整个过程的梳理,对于网络安全防范也将有很好的启示作用,尤其可以帮助读者对这些不同的方式具有定量的认识,从而在应用中选择合适的策略,真正做到举一反三。