WinPcap: the Free Packet Capture Architecture for Windows


Last modified: Monday, March 26, 2001 10.48

Packet.dll API: programmer's Manual

1. Introduction

PACKET.DLL is a dynamic link library that interfaces the packet capture driver with user level applications. The DLL implements a set of functions that make the communication with the driver simpler. This avoids using system calls or IOCTLs in user programs. Moreover, it provides functions to handle network adapters, read and write packets from the network, set buffers and filters in the driver, and so on. There are two versions of PACKET.DLL: the first works in Windows 95/98, the second in Windows NT/2000. The two versions export the same programming interface, making easy to write system independent capture applications. Using the PACKET.DLL API, the same application can be run in Windows 95, 98 NT and 2000 without any modification. This manual describes how to use PACKET.DLL, giving the details of the functions and data structures exported by the DLL.

 

2. PACKET.DLL vs. wpcap

If you are writing a capture application and you do not have particular/low level requrements, you are recommended to use the functions of wpcap.dll, that are a superset of the packet capture library (libpcap), instead of the API described in this chapter. wpcap.dll uses the functions of PACKET.DLL as well, but provides a more powerful, immediate and easy to use programming environment. With wpcap.dll, operations like capturing a packet, creating a capture filter or saving a dump on a file are safely implemented and intuitive to use. Libpcap is able to provide all the functions needed by a standard network monitor or sniffer. Moreover, the programs written to use libpcap are easily compiled on UNIX because of the compatibility between Win32 and UNIX versions of this library.

However, the PACKET.DLL API offers some possibilities that are not given by libpcap.  Libpcap was written to be portable and to offer a system-independent capture API therefore it cannot exploit all the possibilities offered by the driver. In that case some functions of PACKET.DLL will be required.

 

3. Data structures

Data structures defined in packet32.h are:

The first two are packet driver specific, while the others were originally defined in libpcap. This second set of structures is used to do operations like setting a filter or interpreting the data coming from the driver. The driver in fact uses the same syntax of BPF to communicate with the applications, so the structure used are the same. A further structure, PACKET_IOD_DATA, is defined in the file ntddpack.h.

The PACKET structure

The PACKET structure has the following fields:

  • PVOID Buffer
  • UINT Length
  • PVOID Next
  • UINT ulBytesReceived

Buffer is a pointer to a buffer containing the packet�s data, Length indicates the size of this buffer.

UlBytesReceived indicates the length of the buffer�s portion containing valid data.

The ADAPTER structure

This structure describes a network adapter. It has four fields:

  • HANDLE hFile
  • TCHAR SymbolicLink
  • int NumWrites
  • HANDLE ReadEvent

hFile is a pointer to the handle of the driver: in order to communicate directly with the driver, an application needs to know its handle. In any case, this procedure is discouraged because PACKET.DLL offers a set of functions to do it.

SymbolicLink is a string containing the name of the network adapter currently opened.

NumWrites is for internal use and should be considered opaque by the user.

ReadEvent is a notification event associated with the read calls on the adapter. It can be passed to standard Win32 functions (like WaitForSingleObject or WaitForMultipleObjects) to wait until the driver's buffer contains some data without performing a read call, and is particularly useful in GUI applications that need to wait concurrently on several events. In Windows NT/2000 the PacketSetMinToCopy() function can be used to define the minimum amount of data in the kernel buffer that will cause the event to be signalled. 

The PACKET_OID_DATA structure

This structure is used to communicate with the network adapter through OID query and set operations. It has three fields:

  • ULONG Oid
  • ULONG Length
  • UCHAR Data

Oid is a numeric identifier that indicates the type of query/set function to perform on the adapter through the PacketRequest function. Possible values are defined in the ntddndis.h include file. It can be used, for example, to retrieve the status of the error counters on the adapter, its MAC address, the list of the multicast groups defined on it, and so on.

The Length field indicates the length of the Data field, that contains the information passed to or received from the adapter.

The bpf_insn structure

This structure contains a single instruction for the BPF register-machine. It is used to send a filter program to the driver. It has the following fields:

  • USHORT code
  • UCHAR jt
  • UCHAR jf
  • int k

The code field indicates the instruction type and addressing modes.

The jt and jf fields are used by the conditional jump instructions and are the offsets from the next instruction to the true and false targets.

The k field is a generic field used for various purposes.

The bpf_program structure

This structure points to a BPF filter program, and is used by the PacketSetBPF function to set a filter in the driver. It has two fields:

  • UINT bf_len
  • struct bpf_insn *bf_insns;

The bf_len field indicates the length of the program.

bf_insns is a pointer to the first instruction of the program.

The PacketSetBPF function sets a new filter in the driver through an IOCTL call with the control code set to pBIOCSETF; a bpf_program structure is passed to the driver during this call.

The bpf_hdr structure

This structure defines the header used by the driver in order to deliver a packet to the application. The header is encapsulated with the bytes of the captured packet, and is used to maintain information like length and timestamp for each packet. It is the same used by BPF on UNIX. The bpf_hdr structure has the following fields:

  • struct timeval bh_tstamp
  • UINT bh_caplen
  • UINT bh_datalen
  • USHORT bh_hdrlen

bh_tstamp holds the timestamp associated with the captured packet. The timestamp has the same format used by BPF and it is stored in a TimeVal structure, that has two fields:

  • tv_sec: capture date in the standard UNIX time format (number of seconds from 1/1/1970)
  • tv_usec: microseconds of the capture

bh_caplen indicates the length of captured portion.

bh_datalen is the original length of the packet.

bh_hdrlen is the length of the header that encapsulates the packet. 

The bpf_stat structure

This structure is used by the driver to return the statistics of a capture session. It has two fields:

  • UINT bs_recv
  • UINT bs_drop

bs_recv indicates the number of packets that the driver received from the network adapter from the beginning of a capture. This value includes the packets lost by the filter, and should be a count of the packets transmitted by the network on which the adapter is connected to.

bs_drop indicates the number of packets that the driver lost from the beginning of a capture. Basically, a packet is lost when the the buffer of the driver is full. In this situation the packet cannot cannot be stored and the driver rejects it.

Note: bs_drop does not takes in consideration the packets that are lost when the driver's tap function is too slow, i.e. when the time elapsed to run the tap is longer than the time between two packets (this can happen if the filter is very complex, if the network is too fast or the processor is too slow). In this situation the tap is not executed, so the driver is not aware that a packet has been lost.

The NetType structure

This structure is used by the PacketGetNetType function to get from the driver the information on the current adapter's type. It ha two fieds:

  • UINT LinkType
  • UINT LinkSpeed

Linktype indicates the type of the current network adapter (see PacketGetNetType for more informations).

Linkspeed indicates the speed of the network in Bits per second.

 

4. Functions

PACKET.DLL provides a complete set of functions that can be used to send and receive a packet, query and set the parameters of a network adapter, open and close an adapter, handle the dynamic allocation of PACKET structures, set a BPF filter, change the size of the driver�s buffer and finally retrieve the statistics of the capture session. Available functions are:

 

ULONG PacketGetAdapterNames(PTSTR pStr, PULONG BufferSize)

Usually, this is the first function that should be used to communicate with the driver. It returns the names of the adapters installed on the system in the user allocated buffer pStr. After the names of the adapters, pStr contains a string that describes each of them.

BufferSize is the length of the buffer.

Warning: the result of this function is obtained by querying the operating system registry, therefore the format of the result in Windows NT/2000 is different from the one in Windows 95/98/ME. Windows 9x uses the ASCII encoding method to store a string, while Windows NTx uses (usually) UNICODE. After a call to PacketGetAdapterNames in Windows 95x,  pStr contains an ASCII string with the names of the adapters separated by a single ASCII "\0", a double "\0", followed by the descriptions of the adapters separated by a single ASCII "\0" . The string is terminated by a double "\0". In Windows NTx,  pStr contains the names of the adapters, in UNICODE format, separated by a single UNICODE "\0" (i.e. 2 ASCII "\0"), a double UNICODE "\0", followed by the descriptions of the adapters, in ASCII format, separated by a single ASCII "\0" . The string is terminated by a double ASCII "\0".

LPADAPTER PacketOpenAdapter(LPTSTR AdapterName)

This function receives a string containing the name of the adapter to open and returns the pointer to a properly initialized ADAPTER object. The names of the adapters can be obtained by calling the PacketGetAdapterNames function.

Note: as already said, the Windows 95 version of the capture driver works with the ASCII format, the Windows NT version with UNICODE. Therefore, AdapterName should be an ASCII string in Windows 95, and a UNICODE string in Windows NT. This difference is not a problem if the string pointed by AdapterName was obtained through the PacketGetAdapterNames function, because it returns the names of the adapters in the proper format. Problems can arise in Windows NT when the string is obtained from ANSI C functions like scanf, because they use the ASCII format. This can be a relevant problem during the porting of command-line applications from UNIX. To avoid it, we included in the Windows NT version of PacketOpenAdapter a routine to convert strings from ASCII to UNICODE. PacketOpenAdapter in Windows NT accepts both the ASCII and the UNICODE format. If a ASCII string is received, it is converted to UNICODE by PACKET.DLL before being passed to the driver.

VOID PacketCloseAdapter(LPADAPTER lpAdapter)

This function frees the ADAPTER structure lpAdapter, and closes the adapter pointed by it.

LPPACKET PacketAllocatePacket(void)

Allocates a PACKET structure and returns a pointer to it. The returned structure should be properly initialized by calling the PacketInitPacket function.

Warning: The Buffer field of the PACKET structure is not set by this function. The buffer must be allocated by the programmer, and associated to the PACKET structure with a call to PacketInitPacket.

VOID PacketInitPacket(LPPACKET lpPacket, PVOID Buffer, UINT Length)

It initializes a PACKET structure. There are three input parameters:

  • a pointer to the structure to be initialized
  • a pointer to the user-allocated buffer that will contain the captured data
  • the length of the buffer. This is the maximum buffer size that will be transferred from the driver to the application using a single read.

Note: The size of the buffer associated with the PACKET structure is a parameter that can sensibly influence the performances of the capture process, since this buffer holds the packets received from the the driver. The driver is able to return several captured packets using a single read call (see the PacketReceivePacket function), and the number of packets transferable to the application in a single call is limited only by the size of the buffer associated with the PACKET structure used to perform the reading. Therefore setting a big buffer with PacketInitPacket can decrease the number of system calls, improving the capture speed.

VOID PacketFreePacket(LPPACKET lpPacket)

This function frees the PACKET structure pointed by lpPacket.

Warning: The Buffer field of the PACKET structure is not deallocated by this function and must be explicitly deallocated by the programmer.

BOOLEAN PacketReceivePacket(LPADAPTER AdapterObject, LPPACKET lpPacket,BOOLEAN Sync)

This function performs the capture of a set of packets. It has the following input parameters:

  • a pointer to an ADAPTER structure identifying the network adapter from which the packets must be captured
  • a pointer to a PACKET structure that will contain the packets
  • a flag that indicates if the operation will be done in a synchronous or asynchronous way. This parameter is obsolete and is ignored by recent versions of PACKET.DLL, because the access to the driver is always synchronous.

The number of packets received with this function is variable. It depends on the number of packets actually stored in the driver�s buffer, on the size of these packets and on the size of the buffer associated with the lpPacket parameter. Figure 3.1 shows the format used by the driver to send packets to the application.

pic5.gif (7446 byte)

Figure 3.1: method used to encode the packets

Packets are stored in the buffer associated with the lpPacket PACKET structure. Each packet has a header consisting in a bpf_hdr structure that defines its length and holds its timestamp. A padding field is used to word-align the data in the buffer (to increase the speed of the copies). The bh_datalen and bh_hdrlen fields of the bpf_hdr structures should be used to extract the packets from the buffer. Examples can be seen either in the 'TestApp' sample application provided in the developer's pack, or in the pcap_read() function in the pcap-win32.c file (that can be found in the winpcap source code distribution). libpcap extracts correctly each incoming packet before passing it to the application, so an application that uses it will not have to do this operation.

It is possible to set a timeout on read calls with the PacketSetReadTimeout function. In this case the call returns even if no packets have been captured if the timeout set by this function expires.

BOOLEAN PacketSetMinToCopy(LPADAPTER AdapterObject,int nbytes)

This function can be used to define the minimum amount of data in the kernel buffer that will cause the driver to release a read (i.e. a PacketReceivePacket) in progress. nbytes specifies this value in bytes.

In presence of a large value for this variable, the kernel waits for the arrival of several packets before copying the data to the user. This guarantees a low number of system calls, i.e. low processor usage, i.e. better performance, which is a good setting for applications like sniffers. Vice versa, a small value means that the kernel will copy the packets as soon as the application is ready to receive them. This is suggested for real time applications (like, for example, an ARP redirector) that need the better responsiveness from the kernel.

note: this function has effect only in Windows NT/2000. The driver for Windows 95/98/ME does not offer this possibility to modify the amount of data to unlock a read, therefore this call is implemented under these systems only for compatibility.

BOOLEAN PacketSendPacket(LPADAPTER AdapterObject, LPPACKET pPacket, BOOLEAN Sync)

This function is used to send a raw packet to the network through the adapter specified with the AdapterObject parameter. 'Raw packet' means that the programmer will have to build the various headers because the packet is sent to the network 'as is'. The user will not have to put a bpf_hdr header before the packet. Either the CRC needs not to be calculated and added to the packet, because it is transparently put after the end of the data portion by the network interface.

This function has the same syntax of PacketReceivePacket.

The behavior of this function is influenced by the PacketSetNumWrites function. With PacketSetNumWrites, it is possible to set the number of times a single write must be repeated. If this number is 1, every PacketSendPacket call will correspond to one packet sent to the network. If this number is greater than 1, for example 1000, every raw packet written by the application on the driver's device file will be sent 1000 times on the network. This feature can be used to generate high speed traffic because the overhead of the context switches is no longer present and it is particularly useful to write tools to test networks, routers, and servers. Notice that the ability to write multiple packets is present at the moment only in the Windows NT and Windows 2000 versions of the packet driver. In Windows 95/98/ME it is emulated at user level in PACKET.DLL. This means that an application that uses the 'repeated' write method will run in Windows 9x as well, but its speed will be very low compared to the one of WindowsNTx.

The optimized sending process is still limited to one packet at a time: for the moment it cannot be used to send a buffer with multiple packets.

BOOLEAN PacketResetAdapter(LPADAPTER AdapterObject)

It resets the adapter received as input parameter. It returns TRUE if the operation is performed successfully.

BOOLEAN PacketSetHwFilter(LPADAPTER AdapterObject, ULONG Filter)

This function sets a hardware filter on the incoming packets. The constants that define the filters are declared in the file ntddndis.h. The input parameters are the adapter on which the filter must be defined, and the identifier of the filter. The value returned is TRUE if the operation was successful. Here is a list of the most useful filters:

  • NDIS_PACKET_TYPE_PROMISCUOUS: sets the promiscuous mode. Every incoming packet is accepted by the adapter.
  • NDIS_PACKET_TYPE_DIRECTED: only packets directed to the workstation's adapter are accepted.
  • NDIS_PACKET_TYPE_BROADCAST: only the broadcast packets are accepted.
  • NDIS_PACKET_TYPE_MULTICAST: only the multicast packets belonging to the groups of which this adapter is a member are accepted.
  • NDIS_PACKET_TYPE_ALL_MULTICAST: every multicast packet is accepted.
  • NDIS_PACKET_TYPE_ALL_LOCAL: all local packets, i.e. NDIS_PACKET_TYPE_DIRECTED + NDIS_PACKET_TYPE_BROADCAST + NDIS_PACKET_TYPE_MULTICAST

BOOLEAN PacketRequest(LPADAPTER AdapterObject,BOOLEAN Set, PPACKET_OID_DATA OidData)

This function is used to perform a query/set operation on the adapter pointed by AdapterObject. With this function it is possible to obtain or define various parameters of the network adapter, like the dimension of the internal buffers, the link speed or the counter of corrupted packets. 

The second parameter specifies if the operation is a set (set=TRUE) or a query (set=FALSE). The third parameter is a pointer to a PACKET_OID_DATA structure (see the section on the data structures). The return value is true if the function is completed without errors. The constants that define the operations are declared in the file ntddndis.h. More details on the argument can be found in the documentation provided with the Microsoft DDK.

NOTE: not all the network adapters implement all the query/set functions. There is a set of mandatory OID functions that is granted to be present on all the adapters, and a set of facultative functions, not provided by all the adapters (see the DDKs to see which functions are mandatory). If you use a facultative function, be careful to enclose it in an if statement to check the result.

BOOLEAN PacketSetBuff(LPADAPTER AdapterObject, int dim)

This function is used to set to a new size the driver�s buffer associated with the adapter pointed by AdapterObject. dim is the new dimension in bytes. The function returns TRUE if successfully completed, FALSE if there is not enough memory to allocate the new buffer. When a new dimension is set, the data in the old buffer is discarded and the packets stored in it are lost.

Note: the dimension of the driver�s buffer affects heavily the performances of the capture process. A capture application needs to make operations on each packet while the CPU is shared with other tasks. Therefore the application could not be able to work at network speed during heavy traffic or bursts, especially in presence of high CPU load due to other applications. This problem is more noticeable on slower machines. The driver, on the other hand, runs in kernel mode and is written explicitly to capture packets, so it is very fast and usually does not loose packets. Therefore, an adequate buffer in the driver is able to store the packets while the application is busy, so compensating the slowness of the application and avoiding the loss of packets during bursts or high network activity. The buffer size is set to 0 when an instance of the driver is opened: the programmer must remember to set it to a proper value.

Libpcap calls this function and sets the buffer size to 1MB at the beginning of a capture. Therefore programs written using libpcap usually do not need to cope with this problem.

BOOLEAN PacketSetBpf(LPADAPTER AdapterObject, struct bpf_program *fp)

This function associates a new BPF filter to the adapter AdapterObject. The filter, pointed by fp, is a set of instructions that the BPF register-machine of the driver will execute on each incoming packet. Details about BPF filters can be found in [McCanne and Jacobson 1993]. This function returns TRUE if the driver is set successfully, FALSE if an error occurs or if the filter program is not accepted. The driver performs a check on every new filter in order to avoid system crashes due to bogus or buggy programs, and it rejects invalid filters.

A filter can be automatically created by using the pcap_compile function of libpcap. This function converts a text filter with the syntax of WinDump (see the manual of WinDump for more details) into a BPF program. If you don't want to use libpcap, but you need to know the code of a filter, you can launch WinDump with the -d or -dd or -ddd parameters to obtain the pseudocode of the filter.

BOOLEAN PacketGetStats(LPADAPTER AdapterObject, struct bpf_stat *s)

With this function, the programmer can know the value of two internal variables of the driver:

  • the number of packets that have been received by the adapter AdapterObject, starting at the time in which it was opened with PacketOpenAdapter.
  • the number of packets received by the adapter but that have been dropped by the kernel. A packet is dropped when the user-level application is not ready to get it and the kernel buffer associated with the adapter is full.

The two values are copied by the driver in a bpf_stat structure provided by the application. These values are useful to know the state of the network and the behavior of the capture session. 

BOOLEAN PacketGetNetType (LPADAPTER AdapterObject,NetType *type)

Returns the type of the adapter pointed by AdapterObject in a NetType structure. The LinkType of the type parameter can be set by this function to one of the following values:

  • NdisMedium802_3: Ethernet (802.3)
  • NdisMedium802_5: Token Ring (802.5)
  • NdisMediumFddi: FDDI
  • NdisMediumWan: WAN
  • NdisMediumLocalTalk: LocalTalk
  • NdisMediumDix: DIX
  • NdisMediumAtm: ATM
  • NdisMediumArcnetRaw: ARCNET (raw)
  • NdisMediumArcnet878_2: ARCNET (878.2)
  • NdisMediumWirelessWan: Various types of NdisWirelessXxx media.

The BPF capture driver at the moment supports NdisMediumWan, NdisMedium802_3, NdisMedium802_5, NdisMediumFddi, NdisMediumArcnet878_2 and NdisMediumAtm.

The LinkSpeed field indicates the speed of the network in bits per second.

The return value is TRUE if the operation is performed successfully.

BOOLEAN PacketSetReadTimeout ( LPADAPTER AdapterObject , int timeout )

This function sets the value of the read timeout associated with the AdapterObject adapter. timeout indicates the timeout in milliseconds after which PacketReceivePacket() will return (also if no packets have been captured by the driver). Setting timeout to 0 means no timeout, i.e. PacketReceivePacket() never returns if no packet arrives.  A timeout of -1 causes PacketReceivePacket() to always return immediately.

This function works also if the adapter is working in statistics mode, and can be used to set the time interval between two statistic reports.

BOOLEAN PacketSetMode(LPADAPTER AdapterObject,int mode)

This function sets the adapter AdapterObject into mode mode. Mode can have two possible values:

  • MODE_CAPT: standard capture mode. It is set by default after the PacketOpenAdapter call.
  • MODE_STAT: statistics mode, a particular working mode of the BPF capture driver that can be used to perform real time statistics on the network traffic. The driver does not capture anything when in statistics mode and it limits itself to count the number of packets and the amount of bytes that satisfy the user-defined BPF filter. These counters can be obtained by the application with the standard PacketReceivePacket function, and are received at regular intervals, every time a timeout expires. The default value of this timeout is 1 second, but it can be set to any other value (with a 1 ms precision) with the PacketSetReadTimeout function. The counters are encapsulated in a bpf_hdr structure before being passed to the application. This allows microsecond-precise timestamps in order to have the same time scale among the data capture in this way and the one captured using libpcap. Captures in this mode have a very low impact with the system performance.

An application that wants to use statistics mode should perform the following operations:

  • open the adapter;
  • set it in statistics mode with PacketSetMode;
  • set a filter that defines the packets that will be counted with PacketSetBpf.
  • set the correct time interval with PacketSetReadTimeout;
  • receive the results with PacketReceivePacket. This function returns the number of packets and the amount of bytes satisfying the filter captured in the last time interval. These values are 64 bit integers and are encapsulated in a bpf_hdr structure. Therefore the data returned by PacketReceivePacket will be 34 bytes long, and will have the following format:

struct timeval bh_tstamp

UINT bh_caplen=16

UINT bh_datalen=16

USHORT bh_hdrlen=18

LARGE_INTEGER PacketsAccepted

LARGE_INTEGER BytesAccepted

Look at the NetMeter example in the developer's pack to see an example of the use of statistics mode.

NOTE: if the interface is working in statistics mode, there is no need of the kernel buffer because the statistic values are calculated without storing any data in it. So its dimension can be set to 0 with PacketSetBuff.

BOOLEAN PacketSetNumWrites(LPADAPTER AdapterObject,int nwrites)

This function sets to nwrites  the number of times a single write on the adapter AdapterObject  must be repeated. See PacketSendPacket for more details.

BOOLEAN PacketGetNetInfo(LPTSTR AdapterName, PULONG netp, PULONG maskp)

Returns the IP address (in netp) and the netmask (in maskp) of the adapter whose name is specified by AdapterName.

5. Programming tips: how to write high-performance capture programs

  • Set an adequate kernel buffer with the PacketSetBuff function. As already said, the size of the buffer is a very important parameter for the capture. Remember that the default buffer size if you use PACKET.DLL is 0 (if you use libpcap a 1MB buffer is automatically set), that means VERY poor capture performances. A good size for normal captures is 512KB/1MB. WinDump uses the libpcap default, i.e. 1MB buffer.
  • Also the size of the buffer associated with the PACKET structure (i.e. the buffer in which the application receives the packets) is important. A large size means few system calls and thus higher capture speed. If you want best performance, set the size of this buffer to the same dimension of the driver's circular buffer. This ensures that the driver has never to scan the circular buffer to calculate the amount of bytes to be copied, and therefore increases the speed of the copies. Libpcap (and therefore WinDump) defines a fixed 256KB user buffer.
  • Set the most selective filter on the packets needed by the application. A selective filter decreases the number of packets buffered by the driver and copied to the application. This makes space in the buffer for the needed packets only and decrease the load on the system.
  • If the data of the packets is not needed (like in the most part of the capture applications), set a filter that keeps the headers only. For example, WinDump sets a filter that tells the driver to save only the first 96 bytes of each packet (enough for decoding the headers of most protocols). Type �WinDump �d� or �WinDump �dd� to see how this kind of filter is defined.
  • If you don't have any requirement on the response time of the driver, use PacketSetMinToCopy() to increase the minimum amount of data copied in a single read. This decreases the number of system calls, reducing the CPU usage.
  • If you are either doing real time analysis or you want statistics about the network, use statistics mode. It uses few processor time, and it does not need any kernel buffer. Therefore, you can set the kernel buffer to 0.