3. Design Decisions
Libnet is very much an ongoing learning/research project. When I started it over a year and a half ago, I had no idea it would grow as it did, incorporating as much functionality as it does. Libnet's design has changed not so much in stages, but rather in evolutions. Many of these evolutionary changes I took from other successful libraries out there. Some of the changes are hard to pass and are still in progress, while some were just simple internal changes. Then there were some modifications to the library that unfortunately changed the interface and obsoleted older versions. In this section I hope enlighten the reader as to some of the design decisions that go into libnet; where it was, where it is, and where it's going.
3.1 Modularity (interfaces and implementations)
Big programs are made up of many modules [3]. These modules provide the user with functions and data structures that are to be used in a program. A module comes in two parts: its interface and its implementation. The interface specifies what a module does, while the implementation specifies how the module does it. The interface declares all of the data types, function prototypes, global information, macros, or whatever is required by the module. The implementation adheres to the specifications set forth by the interface. This is how libnet was and is designed. Each implementation, you'll find, has a corresponding interface (figure 3a).
There is a third piece of this puzzle: the client. The client is the piece of code that imports and employs the interface, without having to even see the implementation. Your code is the client.
For more information on interfaces and implementations in C, I urge the reader to check out
[3]. It's an excellent book that changed the way I wrote code.
Initially, the naming of files, functions and other tidbits didn't seem to be that important. They took on whatever names seemed appropriate at the time. In a stand-alone program, this is bad style. In a library, it's bad style AND potentially error-prone. Library code is intended to be used on different platforms and potentially with other libraries. If one of these other libraries (or potentially the user's code) contains an object with the same name, problems result. Therefore, naming has become an important issue to me. A strict naming convention helps in two major areas:
3.3 Error Handling and Reporting
Error handling and reporting is an essential part of any programming paradigm. Delicate handling of and recovery from error conditions is an absolute necessity, especially in a third party library. I believe Libnet now has decent error handling (see below for a dissertation on assertions). It can recover from most bad situations more or less gracefully. It checks for illegal conditions under most circumstances. Reporting, however, is a different story and is still progressing. Libnet needs to have a standard error reporting convention in place. As it stands now, some functions use errno (since they are basically system call wrappers), while some accept an additional buffer argument to hold potential error messages, and still others as yet have no provision for verbose error reporting. This needs to change and possibly might be accomplished using variable argument lists.
3.4 Assertions and Exit Points
assert(3) is a macro that accepts a single argument which it treats as an expression, evaluating it for truth. If the expression is evaluated to be false, the assert macro prints an error message and aborts (terminates) the program. Assertions are useful in the developmental stages of programs when verbose error handling is not in place or when a grievous error condition that normally should not happen occurs. Initially libnet was riddled with assertions. Libnet mainly employed assertions to catch NULL pointer dereferences before they occurred (many libnet functions accept pointer arguments expecting them to actually point somewhere). This seemed reasonable at the time because this is obviously a grievous error -- if you're passing a NULL pointer when you shouldn't, your program is probably going to crash. However, assertions also riddled the library with numerous potential unpredictable exit points. Exit points inside a supplementary library such as libnet are bad style, let alone unpredictable exit points. Library code should not cause or allow a program to exit. If a grievous error condition is detected, the library should return error codes to the main, and let it decide what to do. Code should be able to handle grievous errors well enough to be able to exit gracefully from the top level (if possible). In any event, the assertions were removed in version 0.99f in favor of error indicative return values. This preserves compatibility, while removing the exit points.
Libnet currently only supports IPv4. Support for IPv6 is definitely planned, however. The main consideration is nomenclature. Had I been mister-cool-smart guy in the beginning, I would have anticipated this and added IP version information to the function names and macros e.g.: ipv4_build_ip, IPV4_H. However at this point, I refuse to force users to adopt to yet another interface, so the IPv6 functions and macros will contain IPv6 in the name (much like the POSIX 1.g sockets interface [2]).
Early on in the development of libnet, it became clear that there was much OS and architecture dependent code that had to conditionally included and compiled. The autoconf configuration stuff (circa version 0.7) worked great to determine what needed to be included and excluded in order to build the library, but did nothing for post-install support. Many of these CPP macros were needed to conditionally include header information for user-based code. This was initially handled by relying on the user to define the proper macros, but this quickly proved inefficient.
Libnet now employs a simple configure script. This script is created during autoconf configuration and is installed when the library is installed. It handles all of the OS and architecture dependencies automatically - however, it is now mandatory to use it. You will not be able to compile libnet-based code without it. See the next section for details on how to invoke the script.