Packet Driver API: programmer’s manual


1. Introduction

PACKET.DLL is a dynamic link library that interfaces the NDIS packet capture driver with the user level applications. The DLL implements a set of functions that make simpler the communication with the driver in order to avoid the use of system calls or IOCTLs in user programs. The DLL provides functions to handle the 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. The two versions export the same programming interface, making easy for a programmer to write system independent capture applications. In fact, if using the PACKET.DLL API, the same capture application can run in Windows 95 and Windows NT without any modification. This feature allowed to write a single version of libpcap (and, therefore, of WinDump) for the different families of Windows operating systems. In this chapter we will describe how to use PACKET.DLL and the NDIS packet capture driver in an application and the details of the API exported by the DLL.

 

2. Packet Driver (PACKET.DLL) vs. Capture Library (libpcap)

If you are writing a capture application, we suggest you to use the packet capture library (libpcap) instead of the API described in this chapter. Libpcap uses the functions of PACKET.DLL as well, but it provides a more powerful, immediate and easy to use programming environment. With libpcap, operations like capturing a packet, creating a capture filter or saving a dump on a file are safely implemented and immediate to use. All the functions needed by a standard network monitor or sniffer are provided by libpcap. Moreover, the programs written to use libpcap are easier to port to 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. For example, a network analyzer could need to send packets to the network for testing purposes. The NDIS packet capture driver offers the possibility to write packets, and PACKET.DLL has a function (PacketSendPacket) to do this, but libpcap doesn’t have such a feature. Libpcap was written to be portable and to offer a system-independent capture API therefore it doesn’t use all the possibilities that the driver offers. To use some of these possibilities, the functions of PACKET32.DLL will be necessary.

 

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:

OverLapped is used to handle the asynchronous calls to the driver; an example of its use can be found in the PacketReceivePacket function. The OVERLAPPED structure is defined in both the Windows 95 and Windows NT DDKs. In particular, it has a hEvent field that is reset (calling the ResetEvent function) when the packet must be received asynchronously. The driver sets this event to signal the end of an operation. The application is not obliged to check if the event is set, because the Win32 GetOverlappedResult function does it automatically.

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

UlBytesReceived indicates the length of the buffer’s portion containing valid data.
The field bIoComplete indicates whether the packet contains valid data after an asynchronous call. It is reset by PacketReceivePacket and by PacketSendPacket, and is set by PacketWaitPacket.

The ADAPTER structure

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

hFile is a pointer to the handle of the driver: in order to communicate directly with the driver, an application must 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.

The PACKET_OID_DATA structure

This structure is used to communicate with the network adapter. It has three fields:

Oid is a numeric identifier that indicates the type of query/set function to perform on the adapter through the PacketRequest function. Possible values for this field are defined in the ntddndis.h include file. It is possible for example to know the status of the error counters of 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:

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:

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:

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:

bh_caplen indicates the length of captured portion while bh_datalen is the original length of the packet.

bh_hdrlen is the length of the header that encapsulates the packet. This field is used only by the UNIX version of BPF. BPF is able to change the length of the header associated with a packet, inserting a variable offset for data alignment. This optimization is not yet implemented in our packet capture driver. For this reason, bh_hdrlen contains a fixed value, that is the length of the bpf_hdr structure.

The bpf_stat structure

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

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

bs_drop indicates the number of packets that the driver lost from the beginning of the current 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 must reject it.

These variables do not account for the packet that are discarded directly by the network interface, due for example to a buffer overrun.

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 fileds:

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 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. The following is the list of the functions.

 

ULONG PacketGetAdapterNames(PTSTR pStr, PULONG BufferSize)

This is the first function that must be used to communicate with the driver. It returns the names of the adapters installed in the system through the user allocated buffer pStr. BufferSize is the length of the buffer.

Warning: the result of this function is obtained querying directly the registry, therefore the format of the result in Windows NT is different from the one in Windows 95/98. This is due to the fact that Windows 95 uses the ASCII encoding method to store a string, while Windows NT uses UNICODE. After a call to PacketGetAdapterNames in Windows 95,  pStr contains an ASCII string with the names of the adapters separated by ASCII "\0". The string is terminated by a double "\0". In Windows NT, pStr contains a UNICODE string with the names of the adapters separated by a "\0" UNICODE character (i.e. 2 ASCII "\0"), and the string ends with a double UNICODE "\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 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. Instead, some problems can arise in Windows NT if the string is obtained from ANSI C functions like scanf, because they use the ASCII format. This can be a relevant problem when porting 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 before being passed to the driver.

VOID PacketCloseAdapter(LPADAPTER lpAdapter)

This function deallocates 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 structure returned must 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 structure PACKET. There are three input parameters:

Note: The dimension of the buffer associated with the PACKET structure is a parameter that can sensibly influence the performances of the capture process. This buffer will in fact receive the packets from the packet capture driver. The driver is able to collect data from several packets, returning it with only one read call (see the PacketReceivePacket function). The number of packets that the driver can transfer to the application in a single call with this method is limited only by the dimension of the buffer associated with the PACKET structure used to perform the reading. Therefore setting a big buffer with PacketInitPacket can throw down the number of system calls, improving the capture speed. Notice also that, when the application performs a PacketReceivePacket, it is usually NOT blocked until the buffer associated with the PACKET structure full. The driver copies the data present in its buffer, but awakes the application without filling its buffer if it has not enough data at the moment. In this way, also with big buffers, the application works efficiently when the data rate on the network is low.

ID 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 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:

The number of packets received with this function cannot be known before the call and can vary a lot. 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 method used by the driver in order to send the 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 trailer consisting in a bpf_hdr structure that defines its length and holds its timestamp. At the end of the packet there is a padding used to word-align the data in the buffer (to increase the speed of the copies). In order to extract the packets from the buffer the bh_datalen and bh_hdrlen of the bpf_hdr structures should be used. An example can be seen in the 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 source distribution). Pcap extracts correctly each incoming packet before passing it to the application, so an application that uses it will not have to do this operation.

BOOLEAN PacketWaitPacket(LPADAPTER AdapterObject, LPPACKET lpPacket)

This function is used to verify the completion of an I/O operation on the packet capture driver. It is blocking if the operation has still to be completed by the driver. The return value is TRUE if the operation was successful, FALSE otherwise, and the SDK GetLastError function can be used in order to retrieve the error code.

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

This function is used to send a packet to the network through the adapter specified with the AdapterObject parameter. It has the same syntax of the PacketReceivePacket function. This function can be used to send only a packet at a time and the user will not have to put a bpf_hdr header before it.

BOOLEAN PacketResetAdapter(LPADAPTER AdapterObject)

It resets the adapter passed as input parameter. 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:

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. The second parameter defines if the operation is a set (set=1) or a query (set=0). 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 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, no provided by all the adapters (see the DDKs to see which functions are mandatory). If you use a facultative function, please be careful and enclose it in an if statement to check the result.

BOOLEAN PacketSetBuff(LPADAPTER AdapterObject, int dim)

This function is used to set a new dimension of the driver’s circular 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. In fact, a capture application needs to make operations on each packet while the CPU is shared with other tasks. Therefore the application should 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 to store the packets while the application is busy can compensate the slowness of the application and can avoid the loss of packets during bursts or high network activity. When an instance of the driver is opened the dimension of the buffer is set to 0. The programmer must remember to set it to a proper value.

Libpcap calls this functions and sets the buffer size to 1MB. 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 with 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 packet. Details can be found into the chapter on the driver, or 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 the invalid filters.

If you need to create a filter, use the pcap_compile function of libpcap. It 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, launch WinDump with the -d or -dd or -ddd parameters.

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 two values are copied by the driver in a bpf_stat structure (see section 3 of this manual) provided by the application. These values are very useful to know the situation of the network and the behavior of the capture application. They are also very useful to tune the capture stack and to choose the dimension of the buffers. In fact:

BOOLEAN PacketGetNetType (LPADAPTER AdapterObject,NetType *type)

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

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

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

 

5. Tips: how to write high-performance capture programs