上一篇文章介绍了Qt 5 Creator配置nPcap的详细过程;
这一篇主要是翻译一下nPcap的应用指导部分;
这一部分内容是下载的SDK里面的doc部分的npcap-tutorial.html
翻译一下,方便自己后期查看,也方便想使用nPcap的客官查阅。
npcap开发指南
Npcap Development Tutorial
1,摘要
Abstract
编写使用Npcap列出网络适配器、捕获数据包和发送网络流量的软件的分步指南。
本节介绍如何使用Npcap API的功能。它被组织为一个教程,细分为一系列课程,将以循序渐进的方式向读者介绍使用Npcap的程序开发,从基本功能(获取适配器列表、启动捕获等)到最高级的功能(处理发送队列和收集有关网络流量的统计信息)。
示例是用纯C编写的,因此需要掌握C编程的基本知识。此外,由于这是一个关于处理“原始”网络数据包的库的教程,因此假设您对网络和网络协议有很好的了解。
本节中的代码是从源代码发行版和SDK中称为“示例”的部分复制的。该代码根据BSD-3条款许可和版权发布:NetGroup,Politecnico di Torino(意大利);CACE Technologies,戴维斯(加利福尼亚州);和不安全。com,LLC。代码许可证的全文可以在每个源文件中找到。
2,获取设备列表
Obtaining the device list
通常,基于Npcap的应用程序所做的第一件事是获取连接的网络适配器列表。libpcap和Npcap都为此提供了pcap_findalldevs_ex()函数:该函数返回一个pcap_if结构的链接列表,每个结构都包含有关连接的适配器的全面信息。特别地,字段name和description分别包含相应设备的名称和人类可读的描述。
下面的代码检索适配器列表并将其显示在屏幕上,如果未找到适配器,则打印错误。
#include "pcap.h"
main()
{
pcap_if_t *alldevs;
pcap_if_t *d;
int i=0;
char errbuf[PCAP_ERRBUF_SIZE];
/* Retrieve the device list from the local machine */
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);
}
/* Print the list */
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 Npcap is installed.\n");
return;
}
/* We don't need any more the device list. Free it */
pcap_freealldevs(alldevs);
}
关于此代码的一些注释。
首先,与其他libpcap函数一样,pcap_findalldevs_ex()有一个errbuf参数。这个参数指向一个由libpcap填充的字符串,其中包含了出错时的错误描述。
其次,请记住,并非所有libpcap支持的操作系统都提供了网络接口的描述,因此,如果我们想编写一个可移植应用程序,我们必须考虑描述为空的情况:在这种情况下,我们打印字符串“No description available”。
最后请注意,当我们完成列表后,我们用pcap_freealldevs()释放它一次。
假设我们已经编译了程序,让我们试着运行它。在特定的Windows工作站上,我们得到的结果是
1. \Device\NPF_{4E273621-5161-46C8-895A-48D0E52A0B83} (Realtek RTL8029(AS) Ethernet Adapter)
2. \Device\NPF_{5D24AE04-C486-4A96-83FB-8B5EC6C7F430} (3Com EtherLink PCI)
正如您所看到的,Windows下的网络适配器的名称(打开设备时将传递给libpcap)非常不可读,因此括号中的描述非常有用。
3,获取有关已安装设备的高级信息
Obtaining advanced information about installed devices
第1课(名为“获取设备列表”的部分)演示了如何获取有关可用适配器的基本信息(即设备名称和描述)。实际上,Npcap还提供了其他高级信息。特别是,pcap_findalldevs_ex()返回的每个pcap_if结构还包含一个pcap_addr结构列表,其中:
该接口的地址列表。
网络掩码列表(每个网络掩码对应于地址列表中的一个条目)。
广播地址列表(每个地址对应于地址列表中的条目)。
目的地地址列表(每个地址对应于地址列表中的条目)。
此外,pcap_findalldevs_ex()还可以返回远程适配器和位于给定本地文件夹中的pcap文件列表。
下面的示例提供了一个ifprint()函数,用于打印pcap_if结构的完整内容。程序会为pcap_findalldevs_ex()返回的每个条目调用它。
/* Print all the available information on the given interface */
void ifprint(pcap_if_t *d)
{
pcap_addr_t *a;
char ip6str[128];
/* 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;
case AF_INET6:
printf("\tAddress Family Name: AF_INET6\n");
if (a->addr)
printf("\tAddress: %s\n", ip6tos(a->addr, ip6str, sizeof(ip6str)));
break;
default:
printf("\tAddress Family Name: Unknown\n");
break;
}
}
printf("\n");
}
4,打开适配器并捕获数据包
Opening an adapter and capturing the packets
既然我们已经了解了如何获得一个适配器来玩,让我们开始真正的工作,打开一个适配器并捕获一些流量。在本课中,我们将编写一个程序,打印关于流经适配器的每个数据包的一些信息。
打开捕获设备的函数是pcap_open()。参数snaplen、flags和to_ms值得一些解释。
snaplen指定要捕获的数据包部分。在某些操作系统(如xBSD和Win32)上,数据包驱动程序可以配置为仅捕获任何数据包的初始部分:这减少了复制到应用程序的数据量,从而提高了捕获的效率。在这种情况下,我们使用的值65536高于我们可能遇到的最大MTU。以这种方式,我们确保应用程序将始终接收整个数据包。
标志:最重要的标志是指示适配器是否将处于混杂模式的标志。在正常操作中,适配器只从网络捕获发往它的数据包;由其他主机交换的分组因此被忽略。相反,当适配器处于混杂模式时,它会捕获所有数据包,无论它们是否指向它。这意味着在共享介质(如非交换以太网)上,Npcap将能够捕获其他主机的数据包。杂乱模式是大多数捕获应用程序的默认模式,因此我们在下面的示例中启用了它。
toms指定读取超时,以毫秒为单位。适配器上的读取(例如,使用pcap_dispatch()或pcap_next_ex())将始终在to_ms毫秒后返回,即使网络上没有可用的数据包。如果适配器处于统计模式,to_ms还定义了统计报告之间的间隔(有关统计模式的信息,请参见课程“\ref wpcap_tut9”)。将_ms设置为0意味着没有超时,如果没有数据包到达,适配器上的读取永远不会返回。另一侧的-1超时导致适配器上的读取总是立即返回。
#include <pcap.h>
#include "misc.h" /* LoadNpcapDlls */
/* prototype of the packet handler */
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);
int main()
{
pcap_if_t *alldevs;
pcap_if_t *d;
int inum;
int i=0;
pcap_t *adhandle;
char errbuf[PCAP_ERRBUF_SIZE];
/* Load Npcap and its functions. */
if (!LoadNpcapDlls())
{
fprintf(stderr, "Couldn't load Npcap\n");
exit(1);
}
/* Retrieve the device list on the local machine */
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);
exit(1);
}
/* Print the list */
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 Npcap is installed.\n");
return -1;
}
printf("Enter the interface number (1-%d):",i);
scanf_s("%d", &inum);
if(inum < 1 || inum > i)
{
printf("\nInterface number out of range.\n");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
/* Jump to the selected adapter */
for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
/* Open the device */
if ( (adhandle= pcap_open(d->name, // name of the device
65536, // portion of the packet to capture
// 65536 guarantees that the whole packet will
// be captured on all the link layers
PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode:混杂模式
1000, // read timeout
NULL, // authentication on the remote machine
errbuf // error buffer
) ) == NULL)
{
fprintf(stderr,
"\nUnable to open the adapter. %s is not supported by Npcap\n",
d->name);
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
printf("\nlistening on %s...\n", d->description);
/* At this point, we don't need any more the device list. Free it */
pcap_freealldevs(alldevs);
/* start the capture */
pcap_loop(adhandle, 0, packet_handler, NULL);
return 0;
}
/* Callback function invoked by libpcap for every incoming packet */
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;
/*
* unused variables
*/
(VOID)(param);
(VOID)(pkt_data);
/* convert the timestamp to readable format */
local_tv_sec = header->ts.tv_sec;
localtime_s(<ime, &local_tv_sec);
strftime( timestr, sizeof timestr, "%H:%M:%S", <ime);
printf("%s,%.6d len:%d\n",
timestr, header->ts.tv_usec, header->len);
}
打开适配器后,可以使用pcap_dispatch()或pcap_loop()启动捕获。这两个函数非常相似,不同的是,当超时过期时,pcap_dispatch()返回(虽然不保证),而pcap_loop()在捕获cnt数据包之前不会返回,因此它可以在未充分利用的网络上阻塞任意一段时间。pcap_loop()对于本示例来说已经足够了,而pcap_dispatch()通常用于更复杂的程序中。
这两个函数都有一个回调参数packet_handler,指向将接收数据包的函数。libpcap为来自网络的每个新数据包调用此函数,并接收一个通用状态(对应于pcap_loop()和pcap_dispatch()的用户参数),一个包含数据包的一些信息的标头,如时间戳和长度,以及数据包的实际数据,包括所有协议标头。请注意,帧CRC通常不存在,因为它在帧验证后被网络适配器删除。还要注意的是,大多数适配器丢弃了带有错误CRC的数据包,因此Npcap通常无法捕获它们。
上面的示例从pcap_pkthdr头中提取每个数据包的时间戳和长度,并将其打印在屏幕上。
请注意,使用pcap_loop()可能有一个缺点,主要与处理程序被数据包捕获驱动程序调用有关;因此,用户应用程序不能直接控制它。另一种方法(以及拥有更可读的程序)是使用pcap_next_ex()函数,该函数在下一个示例中给出(名为“捕获数据包而不使用回调”的部分)。
5,捕获数据包而不使用回调
Capturing the packets without the callback
本课中的示例程序的行为与前一个程序完全相同(名为“打开适配器并捕获数据包”的部分),但它使用pcap_next_ex()而不是pcap_loop()。
pcap_loop()的基于回调的捕获机制非常优雅,在某些情况下可能是一个不错的选择。然而,处理回调有时并不实际,它通常会使程序更复杂,尤其是在多线程应用程序或C++类的情况下。
在这些情况下,pcap_next_ex()通过使用pcap_next()的直接调用来检索数据包,只有当程序员需要时才会接收数据包。
此函数的参数与捕获回调相同。它包含一个适配器描述符和一对指针,这些指针将被初始化并返回给用户(一个指向pcap_pkthdr结构,另一个指向包含数据包的缓冲区)。
在下面的程序中,我们回收了上一课示例的回调代码,并在调用pcap_next_ex()之后将其移到main()中。
/* Open the device */
if ( (adhandle= pcap_open(d->name, // name of the device
65536, // portion of the packet to capture.
// 65536 guarantees that the whole packet will
// be captured on all the link layers
PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode
1000, // read timeout
NULL, // authentication on the remote machine
errbuf // error buffer
) ) == NULL)
{
fprintf(stderr,
"\nUnable to open the adapter. %s is not supported by Npcap\n",
d->name);
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
printf("\nlistening on %s...\n", d->description);
/* At this point, we don't need any more the device list. Free it */
pcap_freealldevs(alldevs);
/* Retrieve the packets */
while((res = pcap_next_ex( adhandle, &header, &pkt_data)) >= 0){
if(res == 0)
/* Timeout elapsed */
continue;
/* convert the timestamp to readable format */
local_tv_sec = header->ts.tv_sec;
localtime_s(<ime, &local_tv_sec);
strftime( timestr, sizeof timestr, "%H:%M:%S", <ime);
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;
}
为什么我们使用pcap_next_ex()而不是旧的pcap_next()?因为pcap_next()有一些缺点。首先,它效率低下,因为它隐藏了回调方法,但仍然依赖于pcap_dispatch()。其次,它无法检测EOF,因此在从文件中收集数据包时,它不是很有用。
还请注意,pcap_next_ex()返回成功、超时、错误和EOF条件的不同值。
6,过滤流量
Filtering the traffic
Npcap(以及libpcap)提供的最强大的功能之一是过滤引擎。它提供了一种非常有效的方式来接收网络流量的子集,并且(通常)与Npcap提供的捕获机制集成。用于过滤数据包的函数是pcap_compile()和pcap_setfilter()。
pcap_compile()获取一个包含高级布尔(过滤器)表达式的字符串,并生成一个低级字节代码,该代码可以由数据包驱动程序中的fileter引擎解释。布尔表达式的语法可以在本文档的筛选表达式语法部分找到。
pcap_setfilter()将过滤器与内核驱动程序中的捕获会话相关联。一旦调用pcap_setfilter(),关联的过滤器将应用于来自网络的所有数据包,并且所有一致的数据包(即布尔表达式计算为真的数据包)将实际复制到应用程序。
下面的代码显示了如何编译和设置过滤器。请注意,我们必须从描述适配器的pcap_if结构中检索网络掩码,因为某些由pcap_compile()创建的过滤器需要它。
在这个代码段中传递给pcap_compile()的过滤器是“ip和tcp”,这意味着“只保留IPv4和tcp的数据包,并将它们传递给应用程序”。
if (d->addresses != NULL)
/* Retrieve the mask of the first address of the interface */
netmask=((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
else
/* If the interface is without an address
* we suppose to be in a C class network */
netmask=0xffffff;
//compile the filter
if (pcap_compile(adhandle, &fcode, "ip and tcp", 1, netmask) < 0)
{
fprintf(stderr,
"\nUnable to compile the packet filter. Check the syntax.\n");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
//set the filter
if (pcap_setfilter(adhandle, &fcode) < 0)
{
fprintf(stderr,"\nError setting the filter.\n");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
如果您想看到一些使用本课中显示的过滤函数的代码,请查看下一课中的示例,即“解释数据包”一节。
7,解释数据包
Interpreting the packets
既然我们能够捕获和过滤网络流量,我们希望将我们的知识应用于一个简单的“真实世界”应用程序。
在本课中,我们将从前面的课程中获取代码,并使用这些代码构建更有用的程序。当前程序的主要目的是展示如何解析和解释捕获的数据包的协议头。生成的应用程序称为UDPdump,它打印网络上UDP流量的摘要。
我们选择解析和显示UDP协议,因为它比TCP等其他协议更容易访问,因此是一个很好的初始示例。让我们看看代码:
#include <pcap.h>
#include <Winsock2.h>
#include <tchar.h>
BOOL LoadNpcapDlls()
{
_TCHAR npcap_dir[512];
UINT len;
len = GetSystemDirectory(npcap_dir, 480);
if (!len) {
fprintf(stderr, "Error in GetSystemDirectory: %x", GetLastError());
return FALSE;
}
_tcscat_s(npcap_dir, 512, _T("\\Npcap"));
if (SetDllDirectory(npcap_dir) == 0) {
fprintf(stderr, "Error in SetDllDirectory: %x", GetLastError());
return FALSE;
}
return TRUE;
}
/* 4 bytes IP address */
typedef struct ip_address{
u_char byte1;
u_char byte2;
u_char byte3;
u_char byte4;
}ip_address;
/* IPv4 header */
typedef struct ip_header{
u_char ver_ihl; // Version (4 bits) + IP header length (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 header*/
typedef struct udp_header{
u_short sport; // Source port
u_short dport; // Destination port
u_short len; // Datagram length
u_short crc; // Checksum
}udp_header;
/* prototype of the packet handler */
void packet_handler(u_char *param,
const struct pcap_pkthdr *header,
const u_char *pkt_data);
int 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;
/* Load Npcap and its functions. */
if (!LoadNpcapDlls())
{
fprintf(stderr, "Couldn't load Npcap\n");
exit(1);
}
/* Retrieve the device list */
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);
exit(1);
}
/* Print the list */
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 Npcap is installed.\n");
return -1;
}
printf("Enter the interface number (1-%d):",i);
scanf_s("%d", &inum);
if(inum < 1 || inum > i)
{
printf("\nInterface number out of range.\n");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
/* Jump to the selected adapter */
for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
/* Open the adapter */
if ( (adhandle= pcap_open(d->name, // name of the device
65536, // portion of the packet to capture.
// 65536 grants that the whole packet
// will be captured on all the MACs.
PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode
1000, // read timeout
NULL, // remote authentication
errbuf // error buffer
) ) == NULL)
{
fprintf(stderr,
"\nUnable to open the adapter. %s is not supported by Npcap\n",
d->name);
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
/* Check the link layer. We support only Ethernet for simplicity. */
if(pcap_datalink(adhandle) != DLT_EN10MB)
{
fprintf(stderr,"\nThis program works only on Ethernet networks.\n");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
if(d->addresses != NULL)
/* Retrieve the mask of the first address of the interface */
netmask=((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
else
/* If the interface is without addresses
* we suppose to be in a C class network */
netmask=0xffffff;
//compile the filter
if (pcap_compile(adhandle, &fcode, packet_filter, 1, netmask) <0 )
{
fprintf(stderr,"\nUnable to compile the packet filter. Check the syntax.\n");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
//set the filter
if (pcap_setfilter(adhandle, &fcode)<0)
{
fprintf(stderr,"\nError setting the filter.\n");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
printf("\nlistening on %s...\n", d->description);
/* At this point, we don't need any more the device list. Free it */
pcap_freealldevs(alldevs);
/* start the capture */
pcap_loop(adhandle, 0, packet_handler, NULL);
return 0;
}
/* Callback function invoked by libpcap for every incoming packet */
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;
/*
* Unused variable
*/
(VOID)(param);
/* convert the timestamp to readable format */
local_tv_sec = header->ts.tv_sec;
localtime_s(<ime, &local_tv_sec);
strftime( timestr, sizeof timestr, "%H:%M:%S", <ime);
/* print timestamp and length of the packet */
printf("%s.%.6d len:%d ", timestr, header->ts.tv_usec, header->len);
/* retireve the position of the ip header */
ih = (ip_header *) (pkt_data +
14); //length of ethernet header
/* retireve the position of the udp header */
ip_len = (ih->ver_ihl & 0xf) * 4;
uh = (udp_header *) ((u_char*)ih + ip_len);
/* convert from network byte order to host byte order */
sport = ntohs( uh->sport );
dport = ntohs( uh->dport );
/* print ip addresses and udp ports */
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和udp”。通过这种方式,我们可以确定packet_handler()将只通过IPv4接收UDP数据包:这简化了解析并提高了程序的效率。
我们还创建了两个描述IP和UDP头的结构。packet_handler()使用这些结构来正确定位各种头字段。
packet_handler()虽然仅限于一个协议分析器(UDP over IPv4),但它显示了tcpdump/WinDump等复杂的“嗅探器”如何解码网络流量。由于我们对MAC头不感兴趣,所以我们跳过它。为了简单起见,在开始捕获之前,我们使用pcap_datalink()检查MAC层,以确保我们处理的是以太网。通过这种方式,我们可以确保MAC报头正好是14个字节。
IP报头位于MAC报头之后。我们将从IP头中提取IP源地址和目标地址。
到达UDP报头有点复杂,因为IP报头没有固定的长度。因此,我们使用IP报头的长度字段来了解其大小。一旦我们知道UDP头的位置,我们就提取源端口和目标端口。
提取的值将打印在屏幕上,结果如下:
\Device\Packet_{A7FD048A-5D4B-478E-B3C1-34401AC3B72F} (Xircom t 10/100 Adapter)
Enter the interface number (1-2):1
listening on Xircom CardBus Ethernet 10/100 Adapter...
16:13:15.312784 len:87 130.192.31.67.2682 -> 130.192.3.21.53
16:13:15.314796 len:137 130.192.3.21.53 -> 130.192.31.67.2682
16:13:15.322101 len:78 130.192.31.67.2683 -> 130.192.3.21.53
最后3行中的每一行表示不同的分组。
8,处理脱机转储文件
Handling offline dump files
在本节中,我们将学习如何处理数据包捕获到文件(转储到文件)。Npcap提供了广泛的功能,可以将网络流量保存到文件中,并读取转储的内容。本课将介绍如何使用所有这些功能。
转储文件的格式是libpcap格式。该格式以二进制形式包含捕获的数据包的数据,是许多网络工具(包括WinDump、Wireshark和Snort)使用的标准。
将数据包保存到转储文件
Saving packets to a dump file
首先,让我们看看如何以libpcap格式编写数据包。
下面的示例从所选界面捕获数据包,并将其保存在用户提供名称的文件中。
#include <pcap.h>
#include "misc.h" /* LoadNpcapDlls */
/* prototype of the packet handler */
void packet_handler(u_char *param,
const struct pcap_pkthdr *header,
const u_char *pkt_data);
int main(int argc, char **argv)
{
pcap_if_t *alldevs;
pcap_if_t *d;
int inum;
int i=0;
pcap_t *adhandle;
char errbuf[PCAP_ERRBUF_SIZE];
pcap_dumper_t *dumpfile;
/* Load Npcap and its functions. */
if (!LoadNpcapDlls())
{
fprintf(stderr, "Couldn't load Npcap\n");
exit(1);
}
/* Check command line */
if(argc != 2)
{
printf("usage: %s filename", argv[0]);
return -1;
}
/* Retrieve the device list on the local machine */
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING,
NULL, &alldevs, errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);
exit(1);
}
/* Print the list */
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 Npcap is installed.\n");
return -1;
}
printf("Enter the interface number (1-%d):",i);
scanf_s("%d", &inum);
if(inum < 1 || inum > i)
{
printf("\nInterface number out of range.\n");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
/* Jump to the selected adapter */
for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
/* Open the device */
if ( (adhandle= pcap_open(d->name, // name of the device
65536, // portion of the packet to capture
// 65536 guarantees that the whole packet
// will be captured on all the link layers
PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode
1000, // read timeout
NULL, // authentication on the remote machine
errbuf // error buffer
) ) == NULL)
{
fprintf(stderr,
"\nUnable to open the adapter. %s is not supported by Npcap\n",
d->name);
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
/* Open the dump file */
dumpfile = pcap_dump_open(adhandle, argv[1]);
if(dumpfile==NULL)
{
fprintf(stderr,"\nError opening output file\n");
return -1;
}
printf("\nlistening on %s... Press Ctrl+C to stop...\n", d->description);
/* At this point, we no longer need the device list. Free it */
pcap_freealldevs(alldevs);
/* start the capture */
pcap_loop(adhandle, 0, packet_handler, (unsigned char *)dumpfile);
return 0;
}
/* Callback function invoked by libpcap for every incoming packet */
void packet_handler(u_char *dumpfile,
const struct pcap_pkthdr *header,
const u_char *pkt_data)
{
/* save the packet on the dump file */
pcap_dump(dumpfile, header, pkt_data);
}
正如你所看到的,程序的结构与我们在前几节课中看到的非常相似。不同之处在于:
一旦接口打开,就会发出对pcapdumpopen()的调用。此调用打开一个转储文件并将其与接口关联。
通过packet_handler()回调的pcapdump()将数据包写入该文件。pcap_dump()的参数与pcap_handler()参数的对应关系为1-1。
从转储文件读取数据包
Reading packets from a dump file
现在我们有了可用的转储文件,我们可以尝试读取其内容。下面的代码打开一个Npcap/libpcap转储文件,并显示文件中包含的每个数据包。使用pcap_open_offline()打开文件,然后使用通常的pcap_loop()对数据包进行排序。如您所见,从脱机捕获读取数据包与从物理接口接收数据包几乎相同。
此示例引入了另一个函数:pcap_createsrcstr()。此函数用于创建一个以标记开头的源字符串,该标记用于告诉Npcap源的类型,例如,如果要打开适配器,请使用“rpcap://”;如果要打开文件,则使用“file://”。使用pcap_findalldevs_ex()时不需要此步骤(返回的值已经包含这些字符串)。但是,在本例中是必需的,因为文件名是从用户输入中读取的。
#include <stdio.h>
#include <pcap.h>
#include "misc.h" /* LoadNpcapDlls */
#define LINE_LEN 16
void dispatcher_handler(u_char *,
const struct pcap_pkthdr *,
const u_char *);
int main(int argc, char **argv)
{
pcap_t *fp;
char errbuf[PCAP_ERRBUF_SIZE];
char source[PCAP_BUF_SIZE];
/* Load Npcap and its functions. */
if (!LoadNpcapDlls())
{
fprintf(stderr, "Couldn't load Npcap\n");
exit(1);
}
if(argc != 2){
printf("usage: %s filename", argv[0]);
return -1;
}
/* Create the source string according to the new Npcap syntax */
if ( pcap_createsrcstr( source, // variable that will keep the source string
PCAP_SRC_FILE, // we want to open a file
NULL, // remote host
NULL, // port on the remote host
argv[1], // name of the file we want to open
errbuf // error buffer
) != 0)
{
fprintf(stderr,"\nError creating a source string\n");
return -1;
}
/* Open the capture file */
if ( (fp= pcap_open(source, // name of the device
65536, // portion of the packet to capture
// 65536 guarantees that the whole packet
// will be captured on all the link layers
PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode
1000, // read timeout
NULL, // authentication on the remote machine
errbuf // error buffer
) ) == NULL)
{
fprintf(stderr,"\nUnable to open the file %s.\n", source);
return -1;
}
// read and dispatch packets until EOF is reached
pcap_loop(fp, 0, dispatcher_handler, NULL);
return 0;
}
void dispatcher_handler(u_char *temp1,
const struct pcap_pkthdr *header,
const u_char *pkt_data)
{
u_int i=0;
/*
* Unused variable
*/
(VOID)temp1;
/* print pkt timestamp and pkt len */
printf("%ld:%ld (%ld)\n", header->ts.tv_sec, header->ts.tv_usec, header->len);
/* Print the packet */
for (i=1; (i < header->caplen + 1 ) ; i++)
{
printf("%.2x ", pkt_data[i-1]);
if ( (i % LINE_LEN) == 0) printf("\n");
}
printf("\n\n");
}
下面的示例与上一个示例的目的相同,但使用了pcap_next_ex()而不是pcap_loop()回调方法。
#include <stdio.h>
#include <pcap.h>
#include "misc.h" /* LoadNpcapDlls */
#define LINE_LEN 16
int main(int argc, char **argv)
{
pcap_t *fp;
char errbuf[PCAP_ERRBUF_SIZE];
char source[PCAP_BUF_SIZE];
struct pcap_pkthdr *header;
const u_char *pkt_data;
u_int i=0;
int res;
/* Load Npcap and its functions. */
if (!LoadNpcapDlls())
{
fprintf(stderr, "Couldn't load Npcap\n");
exit(1);
}
if(argc != 2)
{
printf("usage: %s filename", argv[0]);
return -1;
}
/* Create the source string according to the new Npcap syntax */
if ( pcap_createsrcstr( source, // variable that will keep the source string
PCAP_SRC_FILE, // we want to open a file
NULL, // remote host
NULL, // port on the remote host
argv[1], // name of the file we want to open
errbuf // error buffer
) != 0)
{
fprintf(stderr,"\nError creating a source string\n");
return -1;
}
/* Open the capture file */
if ( (fp= pcap_open(source, // name of the device
65536, // portion of the packet to capture
// 65536 guarantees that the whole packet
// will be captured on all the link layers
PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode
1000, // read timeout
NULL, // authentication on the remote machine
errbuf // error buffer
) ) == NULL)
{
fprintf(stderr,"\nUnable to open the file %s.\n", source);
return -1;
}
/* Retrieve the packets from the file */
while((res = pcap_next_ex( fp, &header, &pkt_data)) >= 0)
{
/* print pkt timestamp and pkt len */
printf("%ld:%ld (%ld)\n", header->ts.tv_sec, header->ts.tv_usec, header->len);
/* Print the packet */
for (i=1; (i < header->caplen + 1 ) ; i++)
{
printf("%.2x ", pkt_data[i-1]);
if ( (i % LINE_LEN) == 0) printf("\n");
}
printf("\n\n");
}
if (res == -1)
{
printf("Error reading the packets: %s\n", pcap_geterr(fp));
}
return 0;
}
9,发送数据包
Sending Packets
尽管名称Npcap清楚地表明了该库的目的是数据包捕获,但它还提供了用于原始网络的其他有用功能。其中,用户可以找到一整套发送数据包的功能。
使用pcap_sendpacket()发送单个数据包
下面的代码片段显示了发送数据包的最简单方法。打开适配器后,调用pcap_sendpacket()发送手工制作的数据包。pcapsendpacket()将包含要发送的数据、缓冲区长度和将发送数据的适配器的缓冲区作为参数。请注意,缓冲区按原样发送到网络,没有任何操作。这意味着应用程序必须创建正确的协议头,才能发送有意义的内容。
#include <stdlib.h>
#include <stdio.h>
#include <pcap.h>
#include "misc.h" /* LoadNpcapDlls */
void main(int argc, char **argv)
{
pcap_t *fp;
char errbuf[PCAP_ERRBUF_SIZE];
u_char packet[100];
int i;
/* Load Npcap and its functions. */
if (!LoadNpcapDlls())
{
fprintf(stderr, "Couldn't load Npcap\n");
exit(1);
}
/* Check the validity of the command line */
if (argc != 2)
{
printf("usage: %s interface (e.g. 'rpcap://eth0')", argv[0]);
return;
}
/* Open the output device */
if ( (fp= pcap_open(argv[1], // name of the device
100, // portion of the packet to capture
PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode
1000, // read timeout
NULL, // authentication on the remote machine
errbuf // error buffer
) ) == NULL)
{
fprintf(stderr,
"\nUnable to open the adapter. %s is not supported by Npcap\n",
argv[1]);
return;
}
/* Supposing to be on ethernet, set mac destination to 1:1:1:1:1:1 */
packet[0]=1;
packet[1]=1;
packet[2]=1;
packet[3]=1;
packet[4]=1;
packet[5]=1;
/* set mac source to 2:2:2:2:2:2 */
packet[6]=2;
packet[7]=2;
packet[8]=2;
packet[9]=2;
packet[10]=2;
packet[11]=2;
/* Fill the rest of the packet */
for(i=12;i<100;i++)
{
packet[i]=(u_char)i;
}
/* Send down the packet */
if (pcap_sendpacket(fp, packet, 100 /* size */) != 0)
{
fprintf(stderr,"\nError sending the packet: %s\n", pcap_geterr(fp));
return;
}
return;
}
发送队列
Send queues
虽然pcap_sendpacket()提供了一种简单而直接的发送单个数据包的方法,但发送队列提供了一个高级、强大和优化的机制来发送一组数据包。发送队列是将被发送到网络的可变数量的数据包的容器。它有一个大小,表示它可以存储的最大字节数。
通过调用pcap_sendqueue_alloc()函数创建发送队列,指定新发送队列的大小。
创建发送队列后,可以使用pcap_sendqueue_queue()将数据包添加到发送队列。此函数获取一个带有时间戳和长度的pcap_pkthdr,以及一个包含数据包数据的缓冲区。这些参数与pcap_next_ex()和pcap_handler()接收的参数相同,因此对刚捕获或从文件读取的数据包进行排队需要将这些参数传递给pcap_sendqueue_queue()。
为了传输发送队列,Npcap提供了pcap_sendqueue_transmit()函数。注意第三个参数:如果非零,发送将被同步,即数据包的相对时间戳将被尊重。这个操作需要大量的CPU,因为同步是在内核驱动程序中使用“忙碌等待”循环进行的。尽管此操作相当耗费CPU,但它通常会导致非常高精度的数据包传输(通常在几微秒或更少)。
请注意,使用pcap_sendqueue_transmit()传输发送队列比执行一系列pcap_sndpacket()更高效,因为发送队列在内核级别进行缓冲,大大减少了上下文切换的数量。
当不再需要队列时,可以使用pcap_sendqueue_destroy()将其删除,该方法释放与发送队列关联的所有缓冲区。
下一个程序将演示如何使用发送队列。它使用pcap_open_offline()打开一个捕获文件,然后将数据包从该文件移动到一个正确分配的发送队列。在他的点上,它传输队列,如果用户请求,则进行同步。
请注意,将转储文件的链接层与使用pcap_datalink()发送数据包的接口进行比较,如果它们不同,则会打印警告。重要的是,捕获文件链接层与适配器的链接层相同,否则传输将毫无意义。
#include <stdlib.h>
#include <stdio.h>
#include <pcap.h>
#ifdef _WIN32
#include <tchar.h>
BOOL LoadNpcapDlls()
{
TCHAR npcap_dir[512];
UINT len;
len = GetSystemDirectory(npcap_dir, 480);
if (!len) {
fprintf(stderr, "Error in GetSystemDirectory: %x", GetLastError());
return FALSE;
}
_tcscat_s(npcap_dir, 512, TEXT("\\Npcap"));
if (SetDllDirectory(npcap_dir) == 0) {
fprintf(stderr, "Error in SetDllDirectory: %x", GetLastError());
return FALSE;
}
return TRUE;
}
#endif
void usage();
void main(int argc, char **argv)
{
pcap_t *indesc,*outdesc;
char errbuf[PCAP_ERRBUF_SIZE];
char source[PCAP_BUF_SIZE];
FILE *capfile;
int caplen, sync;
u_int res;
pcap_send_queue *squeue;
struct pcap_pkthdr *pktheader;
u_char *pktdata;
float cpu_time;
u_int npacks = 0;
errno_t fopen_error;
#ifdef _WIN32
/* Load Npcap and its functions. */
if (!LoadNpcapDlls())
{
fprintf(stderr, "Couldn't load Npcap\n");
exit(1);
}
#endif
/* Check the validity of the command line */
if (argc <= 2 || argc >= 5)
{
usage();
return;
}
/* Retrieve the length of the capture file */
fopen_error = fopen_s(&capfile, argv[1],"rb");
if(fopen_error != 0){
printf("Error opening the file, errno %d.\n", fopen_error);
return;
}
fseek(capfile , 0, SEEK_END);
caplen= ftell(capfile)- sizeof(struct pcap_file_header);
fclose(capfile);
/* Chek if the timestamps must be respected */
if(argc == 4 && argv[3][0] == 's')
sync = TRUE;
else
sync = FALSE;
/* Open the capture */
/* Create the source string according to the new WinPcap syntax */
if ( pcap_createsrcstr(
source, // variable that will keep the source string
PCAP_SRC_FILE, // we want to open a file
NULL, // remote host
NULL, // port on the remote host
argv[1], // name of the file we want to open
errbuf // error buffer
) != 0)
{
fprintf(stderr,"\nError creating a source string\n");
return;
}
/* Open the capture file */
if ( (indesc= pcap_open(source, 65536, PCAP_OPENFLAG_PROMISCUOUS,
1000, NULL, errbuf) ) == NULL)
{
fprintf(stderr,"\nUnable to open the file %s.\n", source);
return;
}
/* Open the output adapter */
if ( (outdesc= pcap_open(argv[2], 100, PCAP_OPENFLAG_PROMISCUOUS,
1000, NULL, errbuf) ) == NULL)
{
fprintf(stderr,"\nUnable to open adapter %s.\n", source);
return;
}
/* Check the MAC type */
if (pcap_datalink(indesc) != pcap_datalink(outdesc))
{
printf("Warning: the datalink of the capture differs"
" from the one of the selected interface.\n");
printf("Press a key to continue, or CTRL+C to stop.\n");
getchar();
}
/* Allocate a send queue */
squeue = pcap_sendqueue_alloc(caplen);
/* Fill the queue with the packets from the file */
while ((res = pcap_next_ex( indesc, &pktheader, &pktdata)) == 1)
{
if (pcap_sendqueue_queue(squeue, pktheader, pktdata) == -1)
{
printf("Warning: packet buffer too small, not all the packets will be sent.\n");
break;
}
npacks++;
}
if (res == -1)
{
printf("Corrupted input file.\n");
pcap_sendqueue_destroy(squeue);
return;
}
/* Transmit the queue */
cpu_time = (float)clock ();
if ((res = pcap_sendqueue_transmit(outdesc, squeue, sync)) < squeue->len)
{
printf("An error occurred sending the packets: %s."
" Only %d bytes were sent\n", pcap_geterr(outdesc), res);
}
cpu_time = (clock() - cpu_time)/CLK_TCK;
printf ("\n\nElapsed time: %5.3f\n", cpu_time);
printf ("\nTotal packets generated = %d", npacks);
printf ("\nAverage packets per second = %d", (int)((double)npacks/cpu_time));
printf ("\n");
/* free the send queue */
pcap_sendqueue_destroy(squeue);
/* Close the input file */
pcap_close(indesc);
/*
* close the output adapter
* IMPORTANT: remember to close the adapter, otherwise there will be no
* guarantee that all the packets will be sent!
*/
pcap_close(outdesc);
return;
}
void usage()
{
printf("\nSendcap, sends a libpcap/tcpdump capture file to the net."
" Copyright (C) 2002 Loris Degioanni.\n");
printf("\nUsage:\n");
printf("\t sendcap file_name adapter [s]\n");
printf("\nParameters:\n");
printf("\nfile_name: the name of the dump file that will be sent to the network\n");
printf("\nadapter: the device to use. Use \"WinDump -D\" for a list of valid devices\n");
printf("\ns: if present, forces the packets to be sent synchronously,"
" i.e. respecting the timestamps in the dump file.\n\n");
exit(0);
}
10,正在收集有关网络流量的统计信息
Gathering Statistics on the network traffic
本课展示了Npcap的另一个高级功能:收集网络流量统计信息的能力。统计引擎利用内核级数据包过滤器对传入数据包进行有效分类。
为了使用这个特性,程序员必须打开一个适配器并将其置于统计模式。这可以通过pcap_setmode()完成。特别是,MODE_STAT必须用作此函数的模式参数。
在统计模式下,制作一个监控TCP流量负载的应用程序只需要几行代码。下面的示例演示了如何执行此操作。
#include <stdlib.h>
#include <stdio.h>
#include <pcap.h>
#include <tchar.h>
BOOL LoadNpcapDlls()
{
_TCHAR npcap_dir[512];
UINT len;
len = GetSystemDirectory(npcap_dir, 480);
if (!len) {
fprintf(stderr, "Error in GetSystemDirectory: %x", GetLastError());
return FALSE;
}
_tcscat_s(npcap_dir, 512, _T("\\Npcap"));
if (SetDllDirectory(npcap_dir) == 0) {
fprintf(stderr, "Error in SetDllDirectory: %x", GetLastError());
return FALSE;
}
return TRUE;
}
void usage();
void dispatcher_handler(u_char *, const struct pcap_pkthdr *, const u_char *);
void main(int argc, char **argv)
{
pcap_t *fp;
char errbuf[PCAP_ERRBUF_SIZE];
struct timeval st_ts;
u_int netmask;
struct bpf_program fcode;
/* Load Npcap and its functions. */
if (!LoadNpcapDlls())
{
fprintf(stderr, "Couldn't load Npcap\n");
exit(1);
}
/* Check the validity of the command line */
if (argc != 2)
{
usage();
return;
}
/* Open the output adapter */
if ( (fp= pcap_open(argv[1], 100, PCAP_OPENFLAG_PROMISCUOUS,
1000, NULL, errbuf) ) == NULL)
{
fprintf(stderr,"\nUnable to open adapter %s.\n", errbuf);
return;
}
/* Don't care about netmask, it won't be used for this filter */
netmask=0xffffff;
//compile the filter
if (pcap_compile(fp, &fcode, "tcp", 1, netmask) <0 )
{
fprintf(stderr,"\nUnable to compile the packet filter. Check the syntax.\n");
/* Free the device list */
return;
}
//set the filter
if (pcap_setfilter(fp, &fcode)<0)
{
fprintf(stderr,"\nError setting the filter.\n");
pcap_close(fp);
/* Free the device list */
return;
}
/* Put the interface in statstics mode */
if (pcap_setmode(fp, MODE_STAT)<0)
{
fprintf(stderr,"\nError setting the mode.\n");
pcap_close(fp);
/* Free the device list */
return;
}
printf("TCP traffic summary:\n");
/* Start the main loop */
pcap_loop(fp, 0, dispatcher_handler, (PUCHAR)&st_ts);
pcap_close(fp);
return;
}
void dispatcher_handler(u_char *state,
const struct pcap_pkthdr *header,
const u_char *pkt_data)
{
struct timeval *old_ts = (struct timeval *)state;
u_int delay;
LARGE_INTEGER Bps,Pps;
struct tm ltime;
char timestr[16];
time_t local_tv_sec;
/* Calculate the delay in microseconds from the last sample. This value
* is obtained from the timestamp that the associated with the sample. */
delay = (header->ts.tv_sec - old_ts->tv_sec) * 1000000
- old_ts->tv_usec + header->ts.tv_usec;
/* Get the number of Bits per second */
Bps.QuadPart=(((*(LONGLONG*)(pkt_data + 8)) * 8 * 1000000) / (delay));
/* ^ ^
| |
| |
| |
converts bytes in bits -- |
|
delay is expressed in microseconds --
*/
/* Get the number of Packets per second */
Pps.QuadPart=(((*(LONGLONG*)(pkt_data)) * 1000000) / (delay));
/* Convert the timestamp to readable format */
local_tv_sec = header->ts.tv_sec;
localtime_s(<ime, &local_tv_sec);
strftime( timestr, sizeof timestr, "%H:%M:%S", <ime);
/* Print timestamp*/
printf("%s ", timestr);
/* Print the samples */
printf("BPS=%I64u ", Bps.QuadPart);
printf("PPS=%I64u\n", Pps.QuadPart);
//store current timestamp
old_ts->tv_sec=header->ts.tv_sec;
old_ts->tv_usec=header->ts.tv_usec;
}
void usage()
{
printf("\nShows the TCP traffic load, in bits per second and packets per second."
"\nCopyright (C) 2002 Loris Degioanni.\n");
printf("\nUsage:\n");
printf("\t tcptop adapter\n");
printf("\t You can use \"WinDump -D\" if you don't know the name of your adapters.\n");
exit(0);
}
在启用统计模式之前,用户可以选择设置一个过滤器,该过滤器定义要监视的网络流量子集。有关详细信息,请参阅筛选表达式语法文档。如果未设置过滤器,则将监控所有流量。
一旦:
the filter is set
pcap_setmode() is called
callback invocation is enabled with pcap_loop()
过滤器已设置
调用pcap_setmode()
使用pcap_loop()启用回调调用
接口描述符开始以统计模式工作。请注意pcap_open()的第四个参数(to_ms):它定义了统计样本之间的间隔。回调函数每隔to_ms毫秒接收驱动程序计算的样本。这些示例封装在回调函数的第二个和第三个参数中。提供了两个64位计数器:数据包数和在最后一个间隔期间接收的字节数。
在本例中,适配器以1000毫秒的超时打开。这意味着dispatcher_handler()每秒调用一次。此时,编译并设置了只保留tcp数据包的过滤器。然后调用pcap_setmode()和pcap_loop()。请注意,结构timeval指针作为用户参数传递给pcap_loop()。此结构将用于存储时间戳,以便计算两个样本之间的间隔。dispatcher_handler()使用此间隔获取每秒的比特数和每秒的数据包数,然后在屏幕上打印这些值。
最后请注意,这个示例比以传统方式捕获数据包并在用户级别计算统计数据的程序要高效得多。统计模式需要最小数量的数据拷贝和上下文切换,因此CPU得到了优化。此外,所需的内存非常少。