How to write a libpcap module (draft)
by Guy Harris
This HOWTO is not yet complete, it can and should be improved. If you wish to contribute, this document explains how to do it.
Introduction
WARNING: this document describes an unstable interface; future releases of libpcap may, and some probably will, change the interface in an incompatible fashion. If you submit your module to the libpcap developers for inclusion in libpcap, not only does that make it more likely that it will be available in the libpcap provided by operating system vendors (such as Linux distributions), but it also means that we will attempt to update it to handle future changes to this interface. If we add new capabilities, we may have to ask you how to provide those additional capabilities if you're using an underlying mechanism for which we have neither the source code nor the documentation.
NOTE: this document assumes familiarity with the entire libpcap API.
TODO: more routines, more stuff that the activate
routine has to do (such as setting the list of DLT_
s)?
On Linux, *BSD, macOS, Solaris, AIX, HP-UX, IRIX, and Tru64 UNIX, Libpcap supports capturing on network interfaces as supported by the operating system networking stack, using the native packet capture mechanism provided by the OS. On Windows, it supports it with the help of the driver and library supplied by WinPcap and Npcap.
In addition, it also supports capturing on other types of devices, such as:
- specialized capture cards, such as Endace DAG cards;
- network adapters that provide special high-performance code paths, such as CSPI Myricom adapters;
- buses such as USB;
- software communication channels such as D-Bus and Linux netlink;
- etc.
Support for those devices is provided by modules compiled into libpcap.
Where to start
If you want to add such a module, you would first have to check the list of link-layer header types supported by libpcap, to see if one of those would be sufficient for your device.
If none of those would work for your device, please follow the guidelines on the same page to request a new link-layer header type.
Once you have a link-layer header type value or values that you can use, you can add new module.
The module should be a C source file, with a name of the form
pcap-{MOD}.c
, where {MOD}
is a name
appropriate for your device; for example, the support for DAG cards
is in pcap-dag.c
, and the support for capturing USB
traffic on Linux is pcap-usb-linux.c
.
Your module is assumed to support one or more named devices. The
names should be relatively short names, containing only lower-case
alphanumeric characters, consisting of a prefix that ends with an
alphabetic character and, if there can be more than one device
instance, possibly followed by a numerical device ID, such as
mydevice
or
mydevice0
/mydevice1
/… If you have
more than one type of device that you can support, you can have more
than one prefix, each of which can be followed by a numerical device
ID.
The two exported functions that your module must provide are
routines to provide a list of device instances and a program to
initialize a created-but-not-activated pcap_t
for an
instance of one of your devices.
The list device instances routine
The "list of device instances" routine takes, as arguments:
- a pointer to a
pcap_if_list_t
; - a pointer to an error message buffer.
The error message buffer may be assumed to be
PCAP_ERRBUF_SIZE
bytes large, but must not be assumed
to be larger. By convention, the routine typically has a name
containing "findalldevs".
The routine should attempt to determine what device instances are available and add them to the list pointed to by the first argument; this may be impossible for some modules, but, for those modules, it may be difficult to capture on the devices using Wirehshark (although it should be possible to capture on them using tcpdump, TShark, or other programs that take a device name on the command line), so we recommend that your routine provide the list of devices if possible. If it cannot, it should just immediately return 0.
The routine should add devices to the list by calling the
add_dev()
routine in libpcap, declared in the
pcap-int.h header
. It takes, as arguments:
-
the pointer to the
pcap_if_list_t
passed as an argument to the routine; - the device name, as described above;
- a 32-bit word of flags, as provided by pcap_findalldevs(3PCAP);
-
a text description of the device, or
NULL
if there is no description; - the error message buffer pointer provided to the routine.
add_dev()
will, if it succeeds, return a pointer to a
pcap_if_t
that was added to the list of devices. If it
fails, it will return NULL
; in this case, the error
message buffer has been filled in with an error string, and your
routine must return -1 to indicate the error.
If your routine succeeds, it must return 0. If it fails, it must fill in the error message buffer with an error string and return -1.
The initialize routine
The "initialize the pcap_t
" routine takes, as
arguments:
- a pointer to a device name;
- a pointer to an error message buffer;
-
a pointer to an
int
.
It returns a pointer to a pcap_t
.
Your module will probably need, for each pcap_t
for an
opened device, a private data structure to maintain its own
information about the opened device. These should be allocated per
opened instance, not per device; if, for example,
mydevice0
can be captured on by more than one program
at the same time, there will be more than one pcap_t
opened for mydevice0
, and so there will be separate
private data structures for each pcap_t
. If you need
to maintain per-device, rather than per-opened instance information,
you will have to maintain that yourself.
The routine should first check the device to see whether it looks
like a device that this module would handle; for example, it should
begin with one of the device name prefixes for your module and, if
your devices have instance numbers, be followed by a number. If it
is not one of those devices, you must set the integer pointed to by
the third argument to 0, to indicate that this is
not one of the devices for your module, and return
NULL
.
If it is one of those devices, it should call
pcap_create_common()
, passing to it the error message
buffer as the first argument and the size of the per-opened instance
data structure as the second argument. If it fails, it will return
NULL
; you must return NULL
in this case.
If it succeeds, the pcap_t
pointed to by the return
value has been partially initialized, but you will need to complete
the process. It has a priv
member, which is a
void *
that points to the private data structure
attached to it; that structure has been initialized to zeroes.
What you need to set are some function pointers to your routines to handle certain operations:
activate_op
-
the routine called when
pcap_activate(3PCAP)
is done on the
pcap_t
can_set_rfmon_op
-
the routine called when
pcap_can_set_rfmon(3PCAP)
is done on the
pcap_t
—if your device doesn't support 802.11 monitor mode, you can leave this as initialized bypcap_create_common()
, as that routine will return "no, monitor mode isn't supported".
Once you've set the activate_op
and, if necessary, the
can_set_rfmon_op
, you must return the
pcap_t *
that was returned to you.
The activate routine
Your activate routine takes, as an argument, a pointer to the
pcap_t
being activated, and returns an
int
.
The perameters set for the device in the
pcap_create(3PCAP)
call, and after that call()
, are mostly in the
opt
member of the pcap_t
:
device
- the name of the device
timeout
- the buffering timeout, in milliseconds
buffer_size
- the buffer size to use
promisc
- 1 if promiscuous mode is to be used, 0 otherwise
rfmon
- 1 if monitor mode is to be used, 0 otherwise
immediate
- 1 if the device should be in immediate mode, 0 otherwise
nonblock
- 1 if the device should be in non-blocking mode, 0 otherwise
tstamp_type
- the type of time stamp to supply
tstamp_precision
- the time stamp precision to supply
The snapshot
member of the pcap_t
structure will contain the snapshot length to be used.
Your routine should attempt to set up the device for capturing. If
it fails, it must return an error indication which is one of the
PCAP_ERROR
values. For PCAP_ERROR
, it
must also set the errbuf
member of the
pcap_t
to an error string. For
PCAP_ERROR_NO_SUCH_DEVICE
and
PCAP_ERROR_PERM_DENIED
, it may set it to an error
string providing additional information that may be useful for
debugging, or may just leave it as a null string.
If it succeeds, it must set certain function pointers in the
pcap_t
structure:
read_op
inject_op
setfilter_op
setdirection_op
set_datalink_op
getnonblock_op
setnonblock_op
stats_op
cleanup_op
and must also set the linktype member to the DLT_
value
for the device.
On UN*Xes, if the device supports waiting for packets to arrive with
select()
/poll()
/epoll()
/kqueues
etc., it should set the selectable_fd
member of the
structure to the descriptor you would use with those calls. If it
does not, then, if that's because the device polls for packets
rather than receiving interrupts or other signals when packets
arrive, it should have a struct timeval
in the private
data structure, set the value of that struct timeval
to
the poll timeout, and set the required_select_timeout
member of the pcap_t
to point to the
struct timeval
.
The read routine
The read_op
routine is called when
pcap_dispatch(3PCAP),
pcap_loop(3PCAP),
pcap_next(3PCAP),
or
pcap_next_ex(3PCAP)
is called. It is passed the same arguments as
pcap_dispatch() is called.
The routine should first check if the break_loop
member
of the pcap_t
is non-zero and, if so, set that member
to zero and return PCAP_ERROR_BREAK
.
Then, if the pcap_t
is in blocking mode (as opposed to
non-blocking mode), and there are no packets immediately available
to be passed to the callback, it should block waiting for packets to
arrive, using the buffering timeout, first, and read packets from
the device if necessary.
Then it should loop through the available packets, calling the callback routine for each packet:
If the PACKET_COUNT_IS_UNLIMITED() macro evaluates to true when passed the packet count argument, the loop should continue until there are no more packets immediately available or the break_loop member of the pcap_t is non-zero. If the break_loop member is found to be non-zero, it should set that member to zero and return PCAP_ERROR_BREAK. If it doesn't evaluate to true, then the loop should also terminate if the specified number of packets have been delivered to the callback.
Note that there is NO requirement that the packet header or data provided to the callback remain available, or valid, after the callback routine returns; if the callback needs to save the data for other code to use, it must make a copy of that data. This means that the module is free to, for example, overwrite the buffer into which it read the packet, or release back to the kernel a packet in a memory-mapped buffer shared between the kernel and userland, after the callback returns.
If an error occurs when reading packets from the device, it must set
the errbuf
member of the pcap_t
to an
error string and return PCAP_ERROR
.
If no error occurs, it must return the number of packets that were supplied to the callback routine.
The inject routine
The inject routine is passed a pointer to the pcap_t
, a
buffer containing the contents of the packet to inject, and the
number of bytes in the packet. If the device doesn't support packet
injection, the routine must set the errbuf
member of
the pcap_t
to a message indicating that packet
injection isn't supported and return PCAP_ERROR
.
Otherwise, it should attempt to inject the packet; if the attempt
fails, it must set the errbuf
member of the
pcap_t
to an error message and return
PCAP_ERROR
. Otherwise, it should return the number of
bytes injected.
The setfilter routine
The setfilter routine is passed a pointer to the pcap_t
and a pointer to a struct bpf_program
containing a BPF
program to be used as a filter. If the mechanism used by your
module can perform filtering with a BPF program, it would attempt to
set that filter to the specified program.
If that failed because the program was too large, or used BPF
features not supported by that mechanism, the module should fall
back on filtering in userland by saving a copy of the filter with a
call to install_bpf_program()
, setting a flag in the
private data instructure indicating that filtering is being done by
the module and, in the read routine's main loop, checking the flag
and, if it's set, calling
pcap_filter(3PCAP),
passing it the fcode.bf_insns
member of the
pcap_t
, the raw packet data, the on-the-wire length of
the packet, and the captured length of the packet, and only passing
the packet to the callback routine, and counting it, if
pcap_filter() returns a non-zero value. (If the flag is not
set, all packets should be passed to the callback routine and
counted, as the filtering is being done by the mechanism used by the
module.) If install_bpf_program()
returns a negative
value, the routine should return PCAP_ERROR
.
If the attempt to set the filter failed for any other reason, the
routine must set the errbuf
member of the
pcap_t
to an error message and return
PCAP_ERROR
.
If the attempt to set the filter succeeded, or it failed because the
mechanism used by the module rejected it and the call to
install_bpf_program()
succeeded, the routine should
return 0.
If the mechanism the module uses doesn't support filtering, the
pointer to the setfilter routine can just be set to point to
install_bpf_program()
; the module does not need a
routine of its own to handle that.
The setdirection routine
The setdirection routine is passed a pointer to the
pcap_t
and a pcap_direction_t
indicating
which packet directions should be accepted. If the module can't
arrange to handle only incoming packets or only outgoing packets, it
can set the pointer to the setdirection routine to
NULL
, and calls to
pcap_setdirection(3PCAP)
will fail with an error message indicating that setting the
direction isn't supported.
TBD
XXX describe set_datalink()
, including
what the activate routine has to do XXX
XXX describe the rest of the routines XXX