npcap开发指南

您所在的位置:网站首页 npcap有什么用 npcap开发指南

npcap开发指南

2024-07-12 13:54| 来源: 网络整理| 查看: 265

上一篇文章介绍了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 #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 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; inext, 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) 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 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; inext, 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)


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3