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.
|