Packet Purgatory Tutorial
Part 1: Packet Purgatory Quick Start
Skip to Part 2
This is a tutorial designed to help coders get the most
out of Packet Purgatory. The current man pages are a not-bad reference
and all, but they don't really help you if you're coming in cold. So,
after reading this, hopefully you'll be mangling packets in no
time at all.
Packet Purgatory is a library providing a portable API to developers
that provides the fine-grained control of raw sockets, while still
being able to use helpful constructs like the TCP stack, and preexisting
IP software.
There are really only two core functions that need calling in
packet purgatory. They are packetp_init() and packetp_start().
packetp_init is a really simple function, with the following form:
struct packetp_ctx *packetp_init(void);
It just returns a packetp_ctx structure, or NULL on failure. By
default, you don't have to do anything with/to this context except
pass it into packetp_start(), although if you want to change the
default packet purgatory behaviors, you do so by modifying the
packetp_ctx structure before calling packetp_start().
For now, though, we'll just use the default behavior, and not modify
the context.
packetp_start() has the following form:
int packetp_start(struct packetp_ctx *ctx, packet_handler outbound,
packet_handler inbound, void *state);
The variable "ctx" we got from packetp_init(). The variable "state" is
user defined, can be NULL if you want. We'll cover it more later in the
tutorial, but for the beginning examples, we'll leave it at NULL.
The main concept to packet purgatory is the idea of packet handlers.
The packetp_start() function takes two of them, an inbound and
an outbound handler. These functions must be written by you, the programmer.
The structure of the packet handler functions are as follows:
typedef int (*packet_handler)(struct packetp_ctx *ctx,
uint8_t *packet, void *state);
The ctx and state variables are the variables passed into the
packetp_start() function. They are there for your convenience, you are
not required to make use of them. (Later in the tutorial, we will explore
ways that they can be useful if you want to use them).
The key variable is the variable "packet". This is a buffer that contains
the contents of the packet. If the packet handler function returns
zero, whatever is in the buffer at that time will be reinjected to
the network. So by modifying the buffer, you modify the packet that
will be sent/received.
The inbound packet handler will be called for each packet heading
out from the system running Packet Purgatory, before that packet
reaches the network, and the inbound packet handler will be
called for each packet inbound to the system, but before the system's
kernel starts processing it. Either function can be defined
as NULL, in which case packets will simply transit as they normally
would, with no modifications being made.
What would a useful packet handling function do? Well, the limit is
your imagination, but this is a tutorial, so I've got to come up with
a simple example for you. Let's modify a bit of low-level behavior.
The Type of Service (TOS) field in the IP header isn't often used.
However, what if your ISP was in fact paying attention to those bits,
and routing accordingly? Wouldn't it be neat to write a program to
set your priority, and have that program be portable across multiple
architectures?
Use the following as your outbound packet handler:
int
outbound(struct packetp_ctx *ctx, uint8_t *packet, void *unused)
{
struct ip_hdr *ip_head; /* Defined in dnet/ip.h */
ip_head = (struct ip_hdr *)packet;
ip_head->ip_tos = IP_TOS_PREC_FLASHOVERRIDE; /* Also defined in dnet/ip.h */
return(0);
}
Bang, that's it. Modify the packet buffer in place, and return 0. That's the
whole packet handler.
OK, let's finish off the whole program.
int
main()
{
struct packetp_ctx *pp_ctx;
pp_ctx = packetp_init();
packetp_start(pp_ctx, outbound, NULL, NULL);
return(0);
}
Note that the inbound packet handler is NULL in this case, so inbound
packets won't be tampered with.
That's it. Mostly. At the top, stick a
#include <packetp.h>
and now you're really done.
The whole program, with the required #include statements up top, can be
downloaded here.
Compile using
% gcc -o pp_tut1 pp_tut1.c -lpacketp -ldnet -lpcap -pthread
And you're golden! (Hopefully. For undefined symbols, and other
problems, check LD_LIBRARY_PATH to make sure that the requisite packetp
libraries are included. Send me email if there are errors that this
does not fix)
Note: When running this program, root priviledges are required. The
deep network juju Packet Purgatory performs involes raw socket access,
placing network cards in promiscuous mode, and updating routing tables,
all of which are only granted to root-level users.
That covers the 'hello world' of Packet Purgatory programming. There
are many more -- and more interesting -- uses that will be covered in
later chapters.
Small Print:
While I intend for Packet Purgatory to be portable -- and to a large extent
it is -- there exist platforms which cannot use the default settings, and
programs using Packet Purgatory on those systems must change said settings.
This is covered in the next chapter.
On to Part 2!