libnetdude Concepts

Table of Contents
Trace Files
Protocols and Protocol Plugins
Packets
Trace Areas, Trace Parts and Trace Part Managers
Packet iterators
Packet filters
Feature plugins

This chapter introduces the main concepts of libnetdude, explains how the features are implemented, and how the code is used. The next chapter gives a few examples.


Trace Files

Trace files are libnetdude's bread and butter. They are represented by instances of LND_Trace, which maintain state for each trace file the user is manipulating. State information consists of consistency management data, filter settings, packet iteration configuration, modification status and more. See libnd_trace.h for details.

Instantiating a trace structure is done using libnd_trace_new(), passing it the path of the trace file to load or %NULL when you want to create a new file. Saving is done using libnd_trace_save() and libnd_trace_save_as(), and releasing a trace is done using libnd_trace_free().

When you open a trace file, libnetdude does not load any packets. If you want to load any packets, from what part of the file, and how many packets, is entirely up to you. This is explained in detail in the section on trace parts below.

To allow tools to seamlessly integrate libnetdude, you can register observers of each traces. These observers will then receive notifications when certain events occur on a trace file. A good example are GUI-based applications — these can register an observer to update the GUI whenever the trace is modified, for example. Trace observers are created using libnd_trace_observer_new(). The different possible events are defined in the LND-TraceObserverOp enumeration. The actual observer structures contain function pointers to the various event callbacks. These pointers are initialized to safe defaults when a new observer is created. You then hook different implementations into this structure, and register it with a trace using libnd_trace_add_observer(). Events are pushed to the observers using libnd_trace_tell_observers().

To associate arbitrary data items with a trace, every trace comes with a simple key/value-based data storage, accessible through libnd_trace_get_data(), libnd_trace_set_data(), and libnd_trace_del_data(). libnetdude only stores and retrieves the data objects — you have to do any cleaning-up yourself before releasing a trace.


Protocols and Protocol Plugins

Protocols make packet handling easy in libnetdude. Each protocol encodes knowledge of the protocol structure found in a packet, such as the protocol header, protocol data length etc. The more protocols libnetdude can access, the more detailed the interpretation of the raw packet data in packets becomes. In libnetdude, protocol knowledge is provided in protocol plugins, to allow support for new protocols to be added in a modular fashion.

By default, libnetdude ships with protocol plugins for ARP, Ethernet, FDDI, ICMP, IP, Linux SLL, SNAP headers, TCP, and UDP. Everything else is interpreted as raw protocol data. More protocol plugins are always welcome! For details, see the chapter on how to write a protocol plugin below.

Protocol plugins get picked up, initialized and registered automatically when libnetdude is bootstrapped. Each protocol is registered in the protocol registry, from which you can retrieve protocols by providing the layer in the protocol stack that the protocol resides in (as defined through the LND_ProtocolLayer enumeration), and the magic value that the protocol is commonly known under. At the linklevel, these are the DLT_xxx values as defined in /usr/include/net/bpf.h, at the network layer the ETHERTYPE_xxx values of /usr/include/net/ethernet.h, at the transport layer the IPPROTO_xxx values of /usr/include/net/in.h, and at the application level, these are the well-known port numbers as found in /etc/services. We'll look at an example of how to use this registry once we have introduced libnetdude's packets.


Packets

Packets are instances of type LND_Packet. When requested, libnetdude initializes packets so that they contain detailed information about the nesting of the protocols contained in them. This depends on the number of protocols available to the library. libnetdude's packets also contain the packet data and packet header as provided by libpcap.

You normally do not need to create and initialize packets manually. Typically, you will either iterate over the packets in a trace area and obtain the initialized current packet from a packet iterator, or you will ask libnetdude to load a number of packets into memory for you and then directly operate in that sequence of loaded and initialized packets.

Initialized packets make it easy to access protocol information of a given protocol at a given nesting level. You can easily obtain the first nested IP header from a packet, for example. Forget writing your own protocol-demuxer from now on.

Similarly to traces, applications can register observers to be informed when certain operations are performed on a packet.

Now that protocols and packets are introduced, let's look at some code. Let's assume that we want to obtain the TCP header of a packet. We first retrieve the TCP protocol from the protocol registry.
#include <libnd.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

LND_Trace    *trace;
LND_Packet   *packet;
LND_Protocol *tcp;

struct tcphdr *tcphdr;

/* Open a tracefile, load a few packets ... */

/* Obtain the packet somehow: */
packet = libnd_trace_get_packets(trace);

if (! (tcp = libnd_proto_registry_find(LND_PROTO_LAYER_TRANS, IPPROTO_TCP)))
  {
     /* Protocol not found -- handle accordingly. */
  }
        
Then, we try to obtain the TCP header from a packet by using the TCP protocol just obtained. If %NULL is returned, we know that the packet does not contain TCP data.

if (! (tcphdr = (struct tcphdr *) libnd_packet_get_data(packet, tcp, 0)))
  {
     /* This packet does not contain TCP headers */
  }
        


Trace Areas, Trace Parts and Trace Part Managers

As mentioned above, libnetdude never just loads packets into memory. It can do so upon request, but typically you will just iterate over packets in a certain trace area, loading them one at a time to perform some operation on them. In code, trace areas are instances of LND_TraceArea.

Every trace has an active trace area. At creation time of the trace, this trace area is initialized to the entire trace. Trace areas are created in one of two ways; either in terms of start- and end timestamps (e.g., from 08:00:00 to 08:10:30 on August 4) or start- and end fractions (e.g., from 0.5 to 1.0, that is, from the middle of the file to its end). You can set and obtain this area using libnd_trace_set_area() and libnd_trace_get_area().

Iterating over the packets in a trace area and modifying them creates a trace part (in code: instances of type LND_TracePart) — a temporary overlay over the original trace, with its extents defined by the trace area. When a multple different trace areas are used, trace parts begin to pile up on the original input trace file, called the base file. The following figure illustrates the concepts of the base file, trace areas and trace parts.

Making sure that the trace parts are properly maintained, overlayed and disposed of is the job of the trace part manager, abbreviated TPM. Each trace has one. TPMs organize the trace parts in such a way that the user always sees a consistent view of the modified trace. In code, a TPM is an instance of type LND_TPM.


Packet iterators


Packet filters


Feature plugins

Feature plugins are libnetdude's mechanism to provide reusable, useful packet-handling modules and can contain arbitrary code. Examples would be advanced filters ("drop all incomplete TCP flows"), address mappers, or statistical analyzers. For details on writing a feature plugin, see the chapter below.