|
USENIX '04 Paper   
[USENIX '04 Technical Program]
Design and Implementation of Netdude,
|
Figure 2: Editing different trace areas causes resulting trace parts to be layered on top of the original trace file. |
When accessing a packet, the library always uses the trace part in the uppermost layer at the current offset. When a trace file is saved, the trace area layers are flattened onto the original trace file, honoring any inserted or removed packets. The result is a new trace file that contains all modifications made to the original input file. The process is illustrated in Figure 3. The flattening process is performed implicitly through packet iteration: starting at the beginning of the base trace, iteration moves up to areas at higher layers as soon as they are encountered, and returns to lower layers at the end of each trace part.
Note that packet insertion and deletion are straightforward in this approach: the actual composition of packets in a trace area can change but trace parts are still merged onto lower parts at the original boundaries, regardless of the new higher part's size. This is illustrated in Figures 4 and 5. Furthermore, this approach makes it easy to provide ``undo'' functionality: removal of the most recent trace part from the stack reverts the most recent modification of the trace file.
Figure 4: Packet insertion: a trace part is growing in size. When merged onto the base, the original boundaries of the modified trace part are maintained. |
Figure 5: Packet deletion: a trace part is shrinking. Again, the original boundaries of the modified trace part are maintained when merging. |
In addition, libnetdude provides a number of other features:
The Netdude framework provides a GUI application that leverages the functionality libnetdude provides. The main window is shown in Figure 6. The application provides graphical interfaces for all underlying abstractions: users can open and save trace files, navigate to arbitrary locations in the trace, inspect packets, configure trace areas to which packet modifications are applied, modify protocol header fields and payload content, and access add-on features through installed feature plugins.
Figure 6: Main window of the Netdude GUI, with three trace files opened, 200 packets of the first file loaded into memory, and the TCP header of the selected packet displayed. The red highlight indicates that the TCP checksum in this packet is incorrect. |
The plugin concept of libnetdude is mirrored at the GUI level: protocol plugins allow the visual presentation of a particular protocol's header data, while feature plugins provide GUI access to underlying functionality. The visual representation of header data is entirely up to the plugin author: fixed-width cells can be rendered in a tabular layout, header fields can be color-coded depending on the field value, whereas string-based data may be better represented using list or tree elements.
In order to ensure good performance of trace file operations regardless of the file size, the application relies on libnetdude's approach of limiting the number of packets loaded into memory to a configurable number. When the user jumps to a different location in the trace file, up to this number of packets are loaded into memory and presented as a list in the GUI. Individual packets are analyzed by selecting them from that list. The user can then browse the protocol data in a notebook containing one tab per protocol header contained in a packet. When no plugin can be found to visualize a protocol header, a fallback hex editor allows for inspection and modification of packet data using two different modes: a hexadecimal mode that presents each byte in ASCII and hex, and a pure ASCII representation suitable for text-based data.
We illustrate the usage of the framework at the GUI and API levels using two examples: iterating over packets, and accessing selected protocol headers in a packet. Figure 7 shows libnetdude code for these scenarios. To illustrate the flexibility of the plugin mechanism, we then present a few feature plugins for libnetdude.
#include <libnd.h> #include <netinet/in.h> #include <netinet/tcp.h> void iterate_tcp_dports(const char *tracefile) { LND_Trace *trace; LND_PacketIterator pit; LND_TraceArea area; LND_Protocol *tcp; struct tcphdr *tcphdr; /* Obtain a handle to the TCP protocol */ if (! (tcp = libnd_proto_registry_find(LND_PROTO_LAYER_TRANS, IPPROTO_TCP))) { /* Protocol not found -- handle accordingly. */ } /* Open the trace file: */ if (! (trace = libnd_trace_new(tracefile))) { /* Didn't work -- appropriate error handling. */ } /* Set the trace's active area to the second half of the file. */ libnd_trace_area_init_space(&area, 0.5, 1.0); libnd_trace_set_area(trace, &area); /* Iterate over all packets in that trace area */ for (libnd_pit_init(&pit, trace); libnd_pit_get(&pit); libnd_pit_next(&pit)) { /* Request the TCP header of the current packet. */ tcphdr = (struct tcphdr *) libnd_packet_get_data(libnd_pit_get(&pit), tcp, 0); /* If a TCP header was found, print its destination port. */ if (tcphdr) printf("Dest. port: %u\n", ntohs(tcphdr->th_dport)); } } |
Figure 7: A libnetdude example, iterating the second half of a trace and printing out the destination ports of all TCP packets in that area. |
Using libnetdude, packet iteration is done in two steps: first the area of the trace that the user wants to iterate is specified. Then, a packet iterator instance is used in a for-loop. In each iteration, the current packet can be obtained from the packet iterator. libnetdude differentiates between read-only and read/write iteration because packet modifications require the creation of a new trace part for the trace area iterated. In this case, the user can selectively drop packets during the iteration.
Using the GUI, the user first defines the trace area using a dialog. The iteration is then performed implicitly when the user modifies a packet (for example by setting a header field to a certain value, or fixing checksums): the same modification is applied to all packets in the configured trace area, subject to configured packet filters.
Using libnetdude, the user obtains a handle for the desired protocol by specifying the protocol's layer in the network stack and the identifier of the protocol commonly used at that layer (e.g., IPPROTO_xxx values at the network layer). The user then requests a pointer to this protocol's header data in a packet, for the desired nesting level. Note that this querying mechanism does not imply that the protocol must be used at the specified protocol layer in the packet data. The layer is only used to obtain a handle to the data structure representing the protocol in libnetdude.
Using the GUI, the user first selects a packet from the list of packets currently loaded into memory. The GUI then provides access to the individual protocol headers contained in that packet. The user selects the desired protocol header and directly manipulates the header's bit fields as visualized by the responsible plugin (e.g., using pull-down menus for fixed-range values, or entry fields for variable fields).
When making new features available at the API and GUI levels, we prefer to first provide functionality in libnetdude feature plugins and then add Netdude GUI frontends later on. libnetdude's plugin-based architecture has important implications for developers: consider a programmer who wants to develop an application that uses functionality provided by a feature plugin. When using C, he or she would typically use the plugin's API by including the plugin's header file(s). However, this causes problems at link time due to the late-binding design of libnetdude's plugins: when linking the new application's code to libnetdude, the symbol definitions of the required plugins are not available since the object files are only linked in after the library's bootstrapping process completes. The result is an undefined symbol error. Adding the plugin's shared object files at link time is insufficient in case the plugin itself requires other plugins; this approach would quickly result in all plugin object files being added at link time, violating our design goal of flexible extensibility.
As a more scalable solution, we ask plugin developers to provide the new functionality in libnetdude plugins themselves. This way, symbols are only resolved at runtime (since the plugins are built as shared objects) and linking can remain constrained to the new plugin. Undefined symbols can still be caught at compile time using suitable compiler options, such as -Werror-implicit-function-declaration when using GCC.
To make the plugin's functionality accessible, developers have two options: the first is to provide their own executable that initializes the library, queries the plugin, and runs it with appropriate parameters. This only needs a few lines of code. The second option is to use the lndtool command line frontend that is provided by libnetdude. This tool can be used to query several parameters of the local libnetdude installation. As an example, Figure 8 shows how lndtool lists installed plugins.
cpk25@ghouls:/auto/homes/cpk25 > lndtool --plugins libnetdude protocol plugins: -------------------------------------------------- Ethernet 0.5 ICMP 0.5 IPv4 0.5 SLL 0.5 LLC/SNAP 0.5 TCP 0.5 UDP 0.5 ARP 0.5 FDDI 0.5 libnetdude feature plugins: -------------------------------------------------- BPF-Filter 0.5 Checksum-Fix 0.5 PHDL 0.1 TCP-Filter 0.1 TCP-State-Tracker 0.2 Trace-Set 0.1 Traffic-Analyzer 0.3 |
Figure 8: Running lndtool to obtain a list of installed plugins. |
lndtool also provides a command line interface for accessing the plugins, passing command line arguments through to the selected plugin. Examples of lndtool's usage are given in the following selection from feature plugins that have been developed so far:
We have designed a language, PHDL, for this purpose and provide an interpreter as a separate libnetdude plugin. When libnetdude is initialized, the PHDL plugin reads all installed protocol definition files and creates protocol data structures accordingly, equipping each new protocol with a header blueprint. When a packet's protocols are analyzed, the header blueprint is used to build an instance of the structured protocol data that can then be queried and manipulated. The complementing PHDL Netdude plugin then uses this information to visualize the protocol data in a standardized tree view similar to ethereal, but with the added functionality of being able to modify the header fields.
As an example, we show a PHDL definition for IPv4 in Figure 9.
# PHDL Definition for IPv4 based on RFC 791. # structure of the header common to many IP options. def "opthdr" { unsigned int "type" 8; unsigned int "length" 8; } # structure of an IPv4 address -- 32bit field, when output, # chunk into 8bit units and separate using a ".". def "ip4addr" { int "addr" 32 { unit = 8; sep = "."; } } # structure of IP options that contain a list of IPv4 addresses. def "addropt" { opthdr "header"; unsigned int "ptr" 8; chain "route" { ip4addr "addr"; } until length ((.header.length - 3) * 8); } # Now the main header definition: proto "IPv4" (net : 0x800) { block "fixed" { int "version" 4; int "hl" 4 { scale = 4; } block "tos" { enum "ecn" 2 { 0 : "-"; 1 : "ECT(0)2"; 2 : "ECT(1)"; 3 : "CE"; } enum "tos" 4{ 0x10 : "Low Delay"; 0x08 : "Reliability"; 0x04 : "Low Cost"; 0x00 : "None"; } } unsigned int "len" 16; unsigned int "id" 16; block "frag" { int "rf" 1; int "df" 1; int "mf" 1; unsigned int "off" 13; } int "ttl" 8; # The exclamation mark identifies this field as the # key to the selection of the next protocol header: ! enum "proto" 8 { 1 : "ICMP"; 2 : "IGMP"; 4 : "IPIP"; 6 : "TCP"; # rest treated as "other" } hex "checksum" 16; ip4addr "src"; ip4addr "dst"; } chain "options" { union "option" { int "noop" 8 if (.noop == 0); block "security" { opthdr "header"; unsigned int "s" 16; unsigned int "c" 16; unsigned int "h" 16; unsigned int "tcc" 16; } if (.header.type == 130); addropt "lsrr" if (.header.type == 131); addropt "ssrr" if (.header.type == 137); addropt "rr" if (.header.type == 7); block "streamid" { opthdr "header"; unsigned int "id" 16; } if (.header.type == 136); block "timestamp" { opthdr "header"; unsigned int "ptr" 8; unsigned int "oflw" 4; unsigned int "flag" 4; ip4addr "addr"; chain "ts" { unsigned int "tstamp" 32; } until length ((.header.length - 3) * 8); } if (.header.type == 68); } } until length (fixed.hl - 5) * 4 * 8; } |
Figure 9: PHDL code describing the IPv4 header layout. |
Other potential applications include traffic anonymizers, address mappers, import and export filters for other file formats, and interfaces to other software for more advanced functionality like visualization and mathematical analysis.
The original catalyst for the creation of Netdude was our work on TCP/IP network traffic normalization [HKP01]. This was a typical scenario for small-scale editing. In order to test our normalizations, we needed to create very specific packet constellations, for example specific values for the IP TTL field, the TCP flag bits, and IP fragments with valid and invalid fragment offsets. Using the Netdude GUI, we gave individual packets the desired features and replayed the manipulated trace files through the normalizer.
The second use case was in the domain of high-speed network monitoring equipment. The subject of study was Nprobe, a scalable multi-protocol network monitor [MHK+03]. The goal was to evaluate system performance under various traffic loads. We used libnetdude to create traffic patterns that triggered different hotspots in the system. We then wrote an IP address mapping plugin for libnetdude, that maps those traces to disjunct IP address ranges so that we could replay multiple instances of the traces in parallel to expose the probe to high volumes of traffic.
At the moment we are using Netdude in order to test intrusion detection system (IDS) signatures. The classic approach is to experiment with a signature for a network-based IDS [Pax98][Roe99], testing whether the IDS reacts correctly when replaying a trace file. It is often more straightforward to manipulate the traffic itself and not the signature, particularly when testing the resilience of a new signature against variation in traffic patterns and corresponding false positive rates. This approach was particularly useful in our work on the Honeycomb IDS signature generator[KC03], where the ability to make small-scale modifications to packet data was most helpful for testing the string-matching algorithm used by the system. Netdude's editing capabilities have worked very well in these scenarios.
In its current state, we find the framework useful for everyday work with sets of trace files of sizes ranging from a few kilobytes up to several gigabytes. The ability to access functionality through a command line interface is most valuable for scripting tasks for repeated execution. We mostly use the GUI application for the simpler editing tasks and for quick inspection of trace files. Netdude's ability to handle large trace files makes it a far better option than alternative tools like ethereal that lack this feature and that are restricted to files smaller than the system's physical memory capacity.
When using the command line, we have frequently found that it would be useful to be able to use the traditional UNIX approach of piping the output from one processing stage to the input of the next stage. Unfortunately this metaphor is not directly applicable to our problem setting: depending on the functionality provided, a stage may have to employ random access to various locations in the file, or scan a file repeatedly (as in the case of the TCP filtering plugin described in Section 3.3). Directly piping packet data from one stage into the next will not work here since the streams cannot be rewound. However, temporary files could be used transparently, and the piping could be kept within the lndtool command, for example using a syntax like lndtool '-r <stage1> -i <input> | -r <stage 2> | ... | -r <stage n> -o <output>'
Another useful feature would be the ability to use libnetdude in a scripting environment such as Python or Perl. Creating the necessary ``glue'' code using a tool like SWIG5 should prove fairly easy and is one of our next items for future work.
Netdude is a framework for inspection, visualization, and modification of tcpdump packet trace files. Its modular design allows users to interact with the framework at different abstraction levels: a low-level trace navigation wrapper for libpcap called libpcapnav, a high-level API with convenient types for performing common packet manipulation tasks in libnetdude, and a GUI application that allows both small- and large-scale editing previously impossible without writing code. The framework is readily extensible at the libnetdude and GUI levels through its plugin architecture, making it a workbench for the creation of new packet trace tools. A number of plugins have been developed so far and have already helped us in cutting down the development time for new features.
The system has been in development for three years. The use cases that allowed us to apply the framework so far have confirmed our goals of simplifying the development of packet manipulation code and encouraging the re-use of components developed in other projects. We have implemented a number of plugins for purposes such as IP address translation, TCP flow demultiplexing, and statistical analysis.
We hope that the authors of networking code consider using the Netdude framework for their future packet manipulation needs, and provide useful functionality in the form of plugins for libnetdude or the Netdude GUI as a benefit to the community. Netdude is provided with a BSD license, hosted on SourceForge, and can be obtained at https://netdude.sf.net.
Since October 2002, this work has been carried out in collaboration with Intel Research, Cambridge. We would like to thank Vern Paxson, Mark Handley, and Jon Crowcroft for inspiration and helpful feedback. We also thank the Netdude user community for valuable ideas, comments, and contributions, particularly Andrew Moore, Daniel Stodden, and Euan Harris, who also provided valuable comments on the paper. Thanks also to the Castle Pub in Cambridge for hosting our brainstorming sessions.
This document was generated using the LaTeX2HTML translator Version 2002 (1.62)
Copyright © 1993, 1994, 1995, 1996,
Nikos Drakos,
Computer Based Learning Unit, University of Leeds.
Copyright © 1997, 1998, 1999,
Ross Moore,
Mathematics Department, Macquarie University, Sydney.
The command line arguments were:
latex2html -split 0 -no_navigation paper.tex
The translation was initiated by Christian Kreibich on 2004-03-29
(and required too much tweaking afterwards to get this to look nice)
This paper was originally published in the
Proceedings of the 2004 USENIX Annual Technical Conference,
June 27-July 2, 2004, Boston, MA, USA Last changed: 10 June 2004 aw |
|