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.
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.
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.
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.
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.
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.
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.
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:
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".
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.
This function frees the ADAPTER structure lpAdapter, and
closes the adapter pointed by it.
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.
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.
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.
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.
|