来源:http://blog.sina.com.cn/s/blog_475454d501000avb.html
Winpcap 程序员手册
Packet.dll API :程序员手册
1.介绍
PACKET.DLL 是一个动态连接库。它提供了一套面向用户的包捕捉驱动程序接口。这套动态连接库实现了一系列的函数,使得用户与驱动器的通信变得更为简便。这避免了在用户程序中使用系统调用或控制输入/输出设备驱动模式(IOCTLs)。而且,这套动态连接库还提供了网络适配器控制函数,对网络上捕获的数据包进行读取/写入函数,在驱动器中设置缓冲区和过滤器函数等等。目前,有两个版本的PACKET.DLL:第一个适用于Windows95/98,第二个版本适用于WindowsNT/2000。这两个版本都提供了相同的应用程序接口,这使得编写独立于系统的包捕获应用程序变得更为容易。通过调用PACKET.DLL API,同一个应用程序可不做任何修改就在Windows95/98/NT/2000环境下运行。本手册讲述了如何使用PACKET.DLL,并详细讲解这套动态连接库提供的函数和数据结构。
2.PACKET.DLL 与wpcap的比较
如果你要编写一个没有特别/底层要求的包捕获应用程序,推荐使用wpcap.dll的函数,这是包捕获库(libpcap)的一个扩展集,而不是本章节中讨论的API。在wpcap.dll中也使用PACKET.DLL的函数。但wpcap.dll提供了一个更强大、更直接、更简洁的编程环境。通过使用wpcap.dll,诸如捕获包、创建一个包过滤器或者在文件中保存dump这些操作会被准确地执行并使程序变得直观。Libpcap能够为一个标准的网络监听或嗅探程序提供所有需要的函数。而且,利用libpcap编写的程序也能在UNIX平台上进行编译,这是因为这个库的Win32版本和UNIX版本有很好的兼容性。
然而,PACKET.DLL API 有一些libpcap没有提供的可能的操作。Libpcap相对简单,它提供了一套系统无关的捕获包的API。因此,libpcap不能使用所有驱动提供的可能操作。在这种情况下,就需要一些PACKET.DLL的函数。
3.数据结构
在packet32.h中定义的数据结构有:
PACKET
ADAPTER
bpf_insn
bpf_program
bpf_hdr
bpf_stat
NetType
第一、二个结构是packet驱动特有的。而其他几个数据结构最初在libpcap中定义,这些数据结构一般用于诸如设置过滤器或者解释来至于设备驱动上的数据。设备驱动程序实际上使用了与BPF相同的语法来和应用程序进行通信,所以这些数据结构的用法都一样。更深一层的结构体,如PACKET_IOD_DATA,在头文件ntddpack.h中定义了。
PACKET结构体
PACKET结构有以下参数:
PVOID Buffer
UINT Length
PVOID Next
UINT ulBytesReceived
Buffer是一个指向存放数据包缓冲区的指针,该缓冲区中包含捕获的数据包。
Length指定缓冲区的字符数。
UlBytesReceived指定缓冲区中有效数据的实际长度。
ADAPTER结构体
这个结构体描述了一个网络适配器,它有四个参数:
HANDLE hFile
TCHAR SymbolicLink
int NumWrites
HANDLE ReadEvent
hFile为指向一个NPF驱动句柄的指针:为了和驱动程序直接通信,应用程序要得到驱动的句柄。然而,由于PACKET.DLL提供了一套解决这个问题的函数,所以这个结构不推荐使用。
SymbolicLink为包含当前打开的网络适配器名字的字符串。
NumWrites为结构体内部调用参数,此参数对用户不可见,它表示写在网络适配器上的数据包将在物理线路上发送的次数。
ReadEvent为与适配器的读取调用相关联的消息事件。它能被传递到Win32标准函数中(例如:WaitForSingleObject()或者WaitForMultipleObject())等待,直到在未进行读取调用的情况下驱动的缓冲区得到一些数据。这个参数在需要等待同时发生几个事件的GUI应用程序中特别有用。在Windows NT/2000中,PacketSetMinToCopy()函数可定义通知事件的内核缓冲区中所需的最小数据量。
PACKET_OID_DATA数据结构
这个结构体通过ODI询问和设置操作来域网络适配器进行通信。它含有三个参数:
ULONG Oid
ULONG Length
UCHAR Data
Oid是一个数字标识,它通过PacketRequest函数指定了适配器上运行的询问/设置函数的类型。可能的值在ntddndis.h头文件中已经定义。例如:它可用于得到适配器的错误计数器的状态,本机的MAC地址,组播列表等等。
Length指定参数Data的长度。
Data中包含从传递给适配器或从适配器中得到的信息。
bpf_insn数据结构
这个结构体包含了一个BPF注册机的单指令。它传递过滤器程序给适配器。它含有以下参数:
USHORT code
UCHAR jt
UCHAR jf
int k
code指定了指令类型和寻址模式。
jt和jf两个参数由条件跳变指令调用。分别表示目标为真/假时下一个指令跳变的偏移量。
k表示各种不同目的普通参数。
bpf_program数据结构
这个结构体指向BPF过滤器程序,通过PacketSetBPF函数调用此结构体来在驱动中设置过滤器。它有以下两个参数:
UINT bf_len
Struct bpf_insn *bf_insns;
bf_len指明BPF程序的长度。
bf_insns为指向BPF程序的所在空间。
函数PacketSetBPF通过将控制代码设置为pBIOCSETF的IOCTL调用,在驱动中设置一个新的过滤器。一个bpf_program结构体在这个调用中传递给驱动。
bpf_hdr数据结构
为了将一个数据包传递给应用程序,这个结构体定义了一个驱动使用的包头。这个包头中封装了所捕获数据包的字节数,保存了诸如每个包的长度,时间戳等信息。在UNIX平台下的BPF程序用法相同。bpf_hdr结构体的参数如下:
Struct timeval bh_tstamp
UINT bh_caplen
UINT bh_datalen
USHORT bh_hdrlen
bh_tstamp:接收所捕获包的时间戳。时间戳的格式和BPF相同。它存放在结构体TimeVal中,有以下两个参数:
tv_sec: UNIX平台时间格式表示捕获包的时间。
tv_usec: 捕获包时的微秒。
bh_caplen:捕获数据的长度。
bh_datalen:原始包的长度。
bh_hdrlen:bpf头长度(包括本结构和填充字段长度)。
bpf_stat数据结构
驱动利用这个结构体返回一组所捕获包的统计数据。它有两个参数:
UINT bs_recv
UINT bs_drop
bs_recv:指驱动从网络适配器接收到的所捕获的包数目。这个值包括了过滤器丢失的数据包,并且应该是适配器所连网络传送的数据包的数目。
bs_drop:指驱动在网络适配器上丢弃的捕获的包数目。一般,当驱动中的缓冲区充满时就会丢弃一个包。在这种情况下,不能在缓冲区中存储一个数据包,驱动将其丢弃。
注意:bs_drop没有考虑驱动的tap函数速度过慢而引起的数据包丢失。例如:当运行tap函数的时间比接收两个包之间的时间间隔长时(如果过滤器很复杂,网络过快或CPU过慢时,会发生这种情况)。在这种情况下,tap函数没有执行,所以驱动没有意识到数据包被丢弃。
NetType数据结构
PacketGetNetType函数通过利用这个结构体从驱动中得到当前适配器的信息。该结构体有两个参数:
UINT LinkType
UINT LinkSpeed
LinkType:指明当前网络适配器的类型(参见PacketGetNetType得到更多信息)。
LinkSpeed:指明网络速度。单位:bit/second。
4.函数
PACKET.DLL提供了一套完整的函数,可以用来发送/接收数据包、询问/设置网络适配器的参数、打开/关闭适配器、处理PACKET结构体的动态分配、设置BPF过滤器、改变驱动的缓冲区大小和最后获取捕获包的统计数据。可运用函数如下:
PacketGetAdapterNames
PacketOpenAdapter
PacketCloseAdapter
PacketAllocatePacket
PacketInitPacket
PacketFreePacket
PacketReceivePacket
PacketSetMinToCopy
PacketSendPacket
PacketSetHwFilter
PacketRequest
PacketSetBuff
PacketSetBpf
PacketGetStats
PacketGetNetType
PacketSetReadTimeout
PacketSetMode
PacketSetNumWrites
PacketGetNetInfo
ULONG PacketGetAdapterNames(PTSTR pStr,PULONG BufferSize)(例01)
通常,这应该是第一个与适配器建立通信的函数。该函数将系统中适配器的名字返回到用户分配的缓冲区pStr中。在适配器名字后,pStr还含有描述适配器的字符串。BufferSize表示缓冲区的长度。
注意:这个函数的结果是通过查询操作系统注册表得到。因此,在Windows NT/2000中返回结果的格式与Windows 95/98/ME返回的有所不同。在Windows 9X系统中对字符串采用ASCII编码方式,而在Windows NTx系统中通常采用UNICODE编码方式。
在Windows 95x中调用PacketGetAdapterNames函数后,pStr获得由单字节ASCII符"/0"分隔开的适配器名字字符串,随后是一个双字节ASCII字符"/0",接着是一个由单字节ASCII符"/0"分隔开的对适配器进行描述的ASCII字符串,最后以一个双字节ASCII字符"/0"结束。
在Windows NTx中调用PacketGetAdapterNames函数后,pStr获得由单字节"/0"符分隔的适配器名字字符串(以UNICODE格式表示),随后是一个双字节UNICODE字符"/0",接着是一个由单字节ASCII符"/0"分隔开的对适配器进行描述的ASCII格式字符串,最后以一个双字节ASCII字符"/0"结束。
LPADAPTER PacketOpenAdapter(LPTSTR AdapterName)(例02)
这个函数接收一个含有适配器名的字符串,如成功打开一个适配器,该函数返回一个指向已正确初始化的ADAPTER结构体指针。适配器名可通过PacketGetAdapterNames函数取得。
注意:Windows95版本的捕获驱动程序是以ASCII格式运行,而Windows NT版本的则以UNICODE格式运行。因此,在Windows95中AdapterName应该为ASCII字符串,在Windows NT中应该为UNICODE字符串。如果AdapterName字符串是通过PacketGetAdapetNames函数取得,则可不考虑此问题。因为该函数已将适配器名按正确格式返回。但当字符串是通过scanf这类ANSI C函数获得,则会在Windows NT环境下出现问题,因为这类函数使用的是ASCII格式。在UNIX平台下输入命令行时也会出现类似问题。为避免这种问题产生,我们在Windows NT版本的PacketOpenAdapter函数中包含了一个将字符串从ASCII转换为UNICODE格式的程序。这样,Windows NT版本的PacketOpenAdapter函数能接受ASCII或UNICODE格式的字符串。如果传入一个ASCII字符串,PACKET.DLL会将这个字符串转换为UNICODE格式再传到驱动。
VOID PacketCloseAdapter(LPADAPTER lpAdapter) (例03)
这个函数释放ADAPTER结构体变量lpAdapter,并关闭该指针变量指向的适配器。
LPPACKET PacketAllocatePacket(void) (例04)
该函数给PACKET结构体分配内存大小并返回一个指向该结构体的指针。返回的结构体要通过调用PacketInitPacket函数来进行正确的初始化。
注意:分配存放结构体PACKET的缓冲区的大小不使用这个函数,而是靠程序员自己分配。与PACKET结构体相关联的调用PacketInitPacket函数分配。
VOID PacketInitPacket(LPPACKET lpPacket,PVOID Buffer,UINT Length) (例05)
该函数初始化一个PACKET结构体。它有三个输入参数:
lpPacket:要进行初始化的PACKET结构体。
Buffer:由用户分配的存放数据包的缓冲区。
Length:缓冲区的大小。这是通过只读方式将数据从缓冲区转移到应用程序的最大缓冲大小。
注意:与PACKET结构体关联的缓冲区大小是一个会明显影响捕获数据包过程的参数,因为这个缓冲区中存放了从驱动传来的捕获的数据包,而驱动能通过只读调用(参见PacketReceivePacket函数)返回几个捕获的数据包。而一次调用可传给应用程序的包数量与缓冲区大小有关,这个缓冲区与进行读取的PACKET结构体关联。因此,用PacketInitPacket函数设置较大的缓冲区可以减少系统调用的次数,提高捕获包的速度。
VOID PacketFreePacket(LPPACKET lpPacket) (例06)
这个函数释放指向PACKET结构体的指针lpPacket。
注意:这个函数不能释放结构体PACKET的变量Buffer,必须明确地由程序员释放。
BOOLEAN PacketReceivePacket(LPADAPTER AdapterObject,LPPACKET lpPacket,BOOLEAN Sync) (例07)
利用这个函数可以捕获一组数据包。该函数有以下三个输入参数:
AdapterObject:指向ADAPTER结构体的指针,指定接收数据包的网络适配器。
lpPacket:指向一个PACKET结构体的指针,该结构体存放收到的数据包。
Sync:说明操作是同步方式还是异步方式的标识符。这个参数较陈旧。因为对适配器的使用总是同步的,所以在新近版本的PACKET.DLL中可忽略该参数。
用这个函数获得的数据包的数量是变化的。它依赖于实际存储在驱动缓冲区中的数据包的数目,依赖于数据包的大小,依赖于和lpPacket相关的缓冲区大小。图1显示了驱动传递给应用程序的数据包的格式。
图1 数据包编码方式
捕获的数据包存放在与PACKET结构体关联的lpPacket缓冲区中,每个包都有一个bpf_hdr结构体组成的报头,说明了数据包的长度和捕获包的时间戳。填充变量校验缓冲区中的数据(提高接收包的速度)。bpf_hdr结构体中的bh_datalen和bh_hdrlen参数可以将数据包从缓冲区中提取出来。在例程TestApp.cpp和pcap-win32.c文件的pcap_read()函数中都可找到例子。Lippcap正确的提取每一个捕获的数据包并传给应用程序,因此运用程序不必再进行这些操作。用PacketSetReadTimeout函数可以在读取数据时设置等待时间。这样即使没有捕获包,但设置的等待时间已过,读取调用也会返回。
BOOLEAN PacketSetMinToCopy(LPADAPTER AdapterObject, int nbytes) (例08)
这个函数能定义内核缓冲区最小的数据空间大小。这会导致驱动释放进程中的一个读取调用(例如:PacketReceivePacket()函数)。Nbytes就规定了内核的这个缓冲区大小。AdapterObject是一个指向ADAPTER结构体的指针。
给nbytes设定一个较大的值,内核会在接收到几个数据包后在将数据复制给用户。这保证了系统的低调用率,例如,CPU较低的使用率,较高的性能。这样对sniffer,Vice versa这类应用程序有利。反之,如果nbytes值较小的话,应用程序已准备好接收,内核会把数据包复制给它。对于象网桥这种要内核有较快回应的实时应用程序,建议将nbytes设置得较小。
注意:这个函数仅对Windows NTx有效。Windows95X的驱动不支持这种操作。因此PacketSetMinToCopy函数只能在这种系统下执行。
BOOLEAN PacketSendPacket(LPADAPTER AdapterObject,LPPACKET pPacket,BOOLEAN Sync)(例09)
利用这个函数,可通过该函数传入的AdapterObject参数指定的网络适配器将原始数据包
发到网络中。所谓"原始数据包"是指程序员要自己构造各种报头,因为这个函数会将数据包按照其现有的样子发送到网络中。程序员不必在数据包前加bpf_hdr数据头。也不必计算CRC并将其加到包中,因为它已经通过网络接口加到了数据包尾部。
PacketSendPacket函数的语法与PacketReceivePacket函数的相同。
PacketSendPacket函数的作用会受到PacketSetNumWriters函数的影响。用PacketSetNumWrites函数可以设置只写数据反复的次数。如果这个数设置为1,每个PacketSendPacket函数会响应一个发送到网络中的数据包。如果这个值比1大,例如是1000,则应用程序写在驱动设备文件上的每一个原始数据包将会向网络发送1000次。这个特性可以减少环境转换时的系统开销,从而产生高速通信。这对测试网络、路由器、服务器的工具特别有用。
注意:构造多重数据包目前只能在Windows NTx版本的驱动中才能实现。在Windows 95/98/Me系统中,这可以仿效PACKET.DLL中的用户级实现。这意味着在Windows 9X系统中应用程序也可以使用构造多重数据包方法,但与Windows NTx相比,速度就非常慢了。最优化发送过程局限于一次发送一个包,不能一次发送多重数据包。
BOOLEAN PacketSetHwFilter(LPADAPTER AdapterObject,ULONG Filter)(例10)
通过这个函数给引入的数据包设置硬件过滤器。定义过滤器的常量在ntddndis.h头文件
中申明了。输入的参数是设置过滤器的网络适配器和过滤器的标示符。如果运行成功则返
回TRUE值。以下是最有用的过滤器的列表:
NDIS_PACKET_TYPE_PROMISCUOUS:将网络适配器设置为混杂模式。这样网络适配器会接收每一个数据包。
NDIS_PACKET_TYPE_DIRECTED:将网络适配器设置为直接模式。这样网络适配器接收发送到该工作站的数据包。
NDIS_PACKET_TYPE_BROADCAST:将网络适配器设置为广播模式。这样网络适配器会接收网络中广播数据包。
NDIS_PACKET_TYPE_MULTICAST:将网络适配器设置为组播模式。这样网络适配器能接收属于该组成员的组播数据包。
NDIS_PACKET_TYPE_ALL_MULTICAST:将网卡设置为该模式,可以接收所有的组播数据包。
NDIS_PACKET_TYPE_ALL_LOCAL:将网络适配器设置为本地模式。这样网络适配器可以接收所有本地的数据包。例如:NDIS_PACEKT_TYPE_DIRECTED+NDIS_PACEKT_TYPE_BROADCAST+NDIS_PACKET_TYPE_MULTICAST。
BOOLEAN PacketRequest(LPADAPTER AdapterObject,BOOLEAN Set,PPACKET_OID_DATA
OidData)
这个函数对AdapeterObject指向的网络适配器执行查询/设置操作。通过这个函数,可以获得或定义网络适配器的各种参数,比如内核缓冲区的大小,连接速度或错误的数据包的计数器。第二个参数规定如果操作是设置(Set=TRUE时)或询问(Set=FALSE时)。第三个参数是一个指向PACKET_OID_DATA结构体(参见数据结构体部分)的指针。如果该函数完成而不发生任何错误,则返回值为TRUE。定义操作方式的常量在ntddndis.h头文件中申明。这部分的详细内容可参见Microsoft DDK提供的文档。
注意:不是所有的网络适配器都执行所有的询问/设置函数。有一套托管OID函数和一套特许函数能够在各种适配器上运行,但不是所有适配器都提供。(查询DDKs可知哪些函数是托管的)。如果你使用了特许函数,你要将这个函数放到if语句中检查其结果。
BOOLEAN PacketSetBuff(LPADAPTER AdapterObject,int dim)(例11)
通过这个函数,可以设置AdapterObject指向的与网络适配器关联的驱动程序缓冲区的大小。参数dim指定设置缓冲区的大小(用byte表示)。如果成功完成设置,该函数返回TRUE值,如果没有足够的内存分配给新的缓冲区,则该函数返回FALSE值。当设置一个新的缓冲区大小,存放在原缓冲区中的数据会被丢弃,导致存放的数据包会被丢失。
注意:驱动中缓冲区的大小对捕获数据包进程有很大的影响。一个捕获包的应用程序要对每一个数据包进行操作,特别是当CPU还要同时处理多个任务。因此当网络通信繁忙或突然爆发时,同时由于其他程序使得CPU符合较重时,这个应用程序就不能按照网络速度工作了。这种问题在速度较慢的机器上显得更明显。另一方面,驱动以内核模式工作,并且对捕获的数据包进行读写。这样速度会很快,通常也不会丢失数据包。因此驱动中有足够的缓冲区就会在应用程序忙时存放捕获的包,这样弥补了应用程序反应慢和避免了网络高吞吐量时数据包的丢失。当打开一个驱动实例时,缓冲区的大小设置为0,所以程序员一定要记住个驱动设置一个合适的缓冲区大小。Libpcap调用这个函数,并在捕获包前将缓冲区设置为1MB。因此调用libpcap编写的程序通常不必处理这个问题。
BOOLEAN PacketSetBpf(LPADAPTER AdapterObject,struct bpf_program *fp)
这个函数将一个新的BPF过滤器和一个网络适配器AdapterObject关联起来。fp指向的过滤器是一套指令,通过这些指令,驱动中的BPF注册机会处理每一个捕获的包。BPF过滤器的有关细节可参阅[McCanne and Jacobson 1993]。如果驱动设置成功,这个函数返回TRUE值。如果发生错误或者过滤器程序没有接收,就返回FALSE值。为了避免由于这些程序造成系统崩溃,驱动会对每一个新的过滤器进行检查,并拒绝无效的过滤器。利用libpcap库中的pcap_compile函数可以自动创建一个过滤器。这个函数通过WinDump(详见WinDump手册)的语法可以将个一个文本过滤器转换为BPF格式。如果你不想用libpcap库,但却要知道过滤器的代码,你可以通过参数-d、-dd或-ddd来启动WinDump,从而获得过滤器的伪代码。
BOOLEAN PacketGetStats(LPADAPTER AdapterObject,struct bpf_stat *s)
利用这个函数,程序员可以得到驱动中两个内部变量的值。
由AdapterObject指向的网络适配器所收到的数据包的数量。开始的时间是当PacketOpenAdapter函数打开一个网络适配器时。
网络适配器接收到但内核丢弃掉的数据包的数量。当用户应用程序未做好接收准备,与适配器相关联的内核缓冲区占满时,会丢弃掉一个数据包。
这两个值是通过应用程序中复制bpf_stat结构体得到的。这两个值对了解网络状态和捕获包的过程都很有用。
BOOLEAN PacketGetNetType(LPADAPTER AdapterObject,NetType *type)
这个函数可以将AdapterObject指向的网络适配器的类型返回到NetType结构体中。这个函数可以将连接类型参数(LinkType)设置为以下几个值:
NdisMedium802_3:Ethernet(802.3)
NdisMedium802_5:Token Ring(802.5)
NdisMediumFddi:FDDI
NdisMediumWan:WAN
NdisMediumLocalTalk:LocalTalk
NdisMediumDix:DIX
NidsMediumAtm:ATM
NdisMediumArcnetRaw:ARCNET(raw)
NdisMediumArcnet878_2:ARCNET(878.2)
NdisMediumWirelessWan:Various types of NdisWirelessXxx media
BPF捕获包驱动程序支持NidsMediumWan、NdisMedium802_3、NdisMedium802_5、NdisMediumFddi、NdisMediumArcnet878_2和NdisMediumAtm。
连接速度参数(LinkSpeed)说明网络速度(单位:比特/秒)。如果该函数成功调用,返回一个非零值。
BOOLEAN PacketSetReadTimeout(LPADAPTER AdapterObject,int timeout)
这个函数设置了AdapterObject指向的网络适配器的读取数据超时的值。参数timeout设定PacketReceivePacket()返回后(或者驱动没有捕获到包)超时的长度(单位:毫秒)。将timeout设置为0表示没有超时设定,例如:如果没有接收到数据包,PacketReceivePacket()不返回值。将timeout设置为-1,则PacketReceive()总是立刻返回。在网络适配器处于统计工作模式下,这个函数仍然有效。它可以用来设置内部两个统计报告之间的间隔时间。
BOOLEAN PacketSetMode(LPADAPTER AdapterObject,int mode)
这个函数设置AdapterObject指向的网络适配器的工作模式。mode参数由下面两种可能的值:
PACKET_MODE_CAPT:标准捕获模式。调用PacketOpenAdapter后,这被设置为网络适配器的默认工作模式。
PACKET_MODE_STAT:统计模式。这是BPF捕获包驱动程序特有的工作模式,它可以对网络流量进行实时统计。当处在统计模式下,驱动程序并不去捕获包,而只是统计包的数量及符合用户定义的BPF过滤器的比特总数。这些计数器可以通过标准的PacketReceivePacket函数取得,并且每次达到超时设定的时间时取得一次--默认的超时设定为1秒,但也可以通过PacketSetReadTimeout函数设定为其他任意的值(可以精确到1毫秒)。这些计数器先封装在bpf_hdr结构体中再传给应用程序。为了与用libpcap库捕获数据包的时间格式统一,也可以用精确到微秒的时间戳。这种模式下的捕获数据包过程对系统的影响很小。
如果一个程序要使用统计模式,要先进行以下操作:
开启一个网络适配器。
用PacketSetMode设置为统计模式。
设置一个过滤器,使捕获的包都要经过PacketSetBpf计数。
用PacketSetReadTimeout函数设置正确的时间间隔。
用PacketReceivePacket函数接收捕获的包。这个函数返回捕获包的数量和在最近一次时间间隔内满足过滤器的比特总数。这些值都是64位的整数,都被封装在bpf_hdr结构体内。因此PacketReceivePacket函数返回的数据是34比特长的整数,格式如下:
struct timeval bh_tstamp
UINT bh_caplen=16
UINT bh_datalen=18
USHORT bh_hdrlen=18
LARGE_INTEGER PacketAccepted
LARGE_INTEGER BytesAccepted
图2 统计模式下返回的数据结构
请察看developer'pack中的NetMeter例,这是一个使用统计模式的例子。
注意:如果接口处于统计工作模式下,这样不需要内核缓冲区,因为统计值不必存储在其
中。所以内核缓冲区可用PacketSetBuff设置为0。
BOOLEAN PacketSetNumWrites(LPADAPTER AdapterObject,int nwrites)
这个函数将nwrites设置为PacketSendPacket函数构造的数据包在AdapterObject指向的网络适配器上要重复的次数。详见PacketSendPacket函数。
BOOLEAN PacketGetNetInfo(LPTSTR AdapterName,PULONG netp,PULONG maskp)
该函数返回网卡的IP地址,存放到呢netp中,返回网卡的MAC地址,存放到maskp中。
5.编程技巧:如何编写高效的捕获数据包程序
通过PacketSetBuff函数给内核缓冲区设置合适的大小。因为缓冲区的大小对捕获包非常的重要。请记住当时用PACKET.DLL库时,默认的缓冲区大小为0(如果使用libpcap库,则缓冲区的大小自动设置为1MB),这意味着捕获包的性能很差。通常对普通捕获包程序而言,缓冲区合适的大小应该设置为512KB/1MB。WinDump使用libpcap库,它默认缓冲区为1MB。
同样,与PACKET结构体相关联的缓冲区(例如:应用程序接收包的缓冲区)大小也非常的重要。将缓冲区设置得较大可以减少系统调用次数和提高捕获包的速度。如果你想获得最佳的性能,就将缓冲区的大小设置成与驱动的循环缓冲区大小相同。这保证了驱动从不浏览循环缓冲区,去计算复制的字节总数,因此提高了复制的速度。Libpcap(以及WinDump)都定义了大小固定为256KB的用户缓冲区。
设置应用程序需要的合适的包过滤器。一个有选择性的过滤器可以减少驱动放入缓冲区以及程序复制的包的数目。这样使得缓冲区只存放需要的数据包,并减少了系统的负荷。
如果包的数据不是所需要的(比如大多数捕获包程序),那设置一个过滤器只保存包头。例如:WinDump设置一个过滤器,通知驱动只保存每个包的前96个字节(这对解析大多数协议的头部足够了)。输入'WinDump -d'或'WinDump -dd',可以看到过滤器是怎么定义的。
如果你对驱动回应时间不做任何要求,请用PacketSetMinToCopy函数增加复制所读取数据的最小数量。这样减少了系统调用次数,降低了CPU的使用率。
如果你要进行实时分析或网络流量统计,请使用统计模式。这占用CPU的时间较少,而且这不需要任何内核缓冲区。因此,你可以将内核缓冲区的大小设置为0。