logo
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!