Next Previous Top

4. A Means to an Ends

This section covers operational issues including how to employ the library in a useful manner as well noting some of its quirks.

4.1 The Order of Operations

In order to build and inject an arbitrary network packet, there is a standard order of operations to be followed. There are five easy steps to packet injection happiness:

1. Memory Initialization
2. Network Initialization
3. Packet Construction
4. Packet Checksums
5. Packet Injection

Each one of these is an important topic and is covered below.

4.1.1 Memory Allocation and Initialization

The first step in employing libnet in your application is to allocate memory for a packet. The conventional way to do this is via a call to libnet_init_packet(). You just need to make sure you specify enough memory for whatever packet you're going to build. This will also require some forethought as to which injection method you're going to use (see below for more information). If you're going to build a simple TCP packet (sans options) with a 30 byte payload using the IP-layer interface, you'll need 70 bytes (IP header + TCP header + payload). If you're going to build the same packet using the link-layer interface, you'll need 84 bytes (ethernet header + IP header + TCP header + payload). To be safe you could just allocate IP_MAXPACKET + ETH_H bytes (65549) and not worry about overwriting buffer boundaries. When finished with the memory, it should be released with a call to libnet_destroy_packet() (this can either be in a garbage collection function or at the end of the program).

Another method of memory allocation is via the arena interface. Arenas are basically memory pools that allocate large chunks of memory in one call, divvy out chunks as needed, then deallocate the whole pool when done. The libnet arena interface is useful when you want to preload different kinds of packets that you're potentially going to be writing in rapid succession. It is initialized with a call to libnet_init_packet_arena() and chunks are retrieved with libnet_next_packet_from_arena(). When finished with the memory it should be released with a call to libnet_destroy_packet_arena() (this can either be in a garbage collection function or at the end of the program).

An important note regarding memory management and packet construction: If you do not allocate enough memory for the type of packet you're building, your program will probably segfault on you. Libnet can detect when you haven't passed any memory, but not when you haven't passed enough. Take heed.

4.1.2 Network Initialization

The next step is to bring up the network injection interface. With the IP-layer interface, this is with a call to libnet_open_raw_sock() with the appropriate protocol (usually IPPROTO_RAW). This call will return a raw socket with IP_HDRINCL set on the socket telling the kernel you're going to build the IP header.

The link-layer interface is brought up with a call to libnet_open_link_interface() with the proper device argument. This will return a pointer to a ready to go link interface structure.

4.1.3 Packet Construction

Packets are constructed modularly. For each protocol layer, there should be a corresponding call to a libnet_build function. Depending on your end goal, different things may happen here. For the above IP-layer example, calls to libnet_build_ip() and libnet_build_tcp() will be made. For the link-layer example, an additional call to libnet_build_ethernet() will be made. It is important to note that the ordering of the packet constructor function calls is not significant, it is only necessary that the correct memory locations be passed to these functions. The functions need to build the packet headers inside the buffer as they would appear on the wire and be demultiplexed by the recipient, and this can be done in any order (figure 2).

libnet_build_ethernet() would be passed the beginning of the buffer with an offset of 0 (as it needs to build an ethernet header at the front of the packet). libnet_build_ip() would get the buffer at a 14 byte (ETH_H) offset to construct the IP header in the correct location, while libnet_build_tcp() would get the buffer 20 bytes beyond this (or at a 34 bytes offset from the beginning (ETH_H + IP_H)). This is easily apparent in the example code.

4.1.4 Packet Checksums

The next-to-last step is computing the packet checksums (assuming the packet is an IP packet of some sort). For the raw IP interface, we need only compute a transport layer checksum (assuming our packet has a transport layer protocol) as the kernel will handle our IP checksum. For the link-layer interface, the IP checksum must be explicitly computed. Checksums are calculated via libnet_do_checksum(), which will be expecting the buffer passed to point to the IP header of the packet.

4.1.5 Packet Injection

The last step is to write the packet to the network. Using the IP-layer interface this is accomplished with libnet_write_ip(), and with the link-layer interface it is accomplished with libnet_write_link_layer(). The functions return the number of bytes written (which should jive with the size of your packet) or a -1 on error.

4.2 Using the Configure Script

There has been some confusion on how to correctly implement the libnet-configure shell script. Since 0.99e, it has become mandatory to use this script. The library will not compile code without it. This is to avoid potential problems when user code is compiled with improper or missing CPP macros. The script also has provisions for specifying libraries and cflags. The library switch is useful on architectures that require additional libraries to compile network code (such as Solaris). The script is very simple to use. The following examples should dispell any confusion:

- At the command line you can run the script to see what defines are used for that system:

`shattered:~> libnet-config --defines`
`-D_BSD_SOURCE -D__BSD_SOURCE -D__FAVOR_BSD -DHAVE_NET_ETHERNET_H`
`-DLIBNET_LIL_ENDIAN`

- At the command line to compile a simple program:

`shattered:~> gcc -Wall `libnet-config --defines` foo.c -o foo `libnet-config --libs``

- In a Makefile:

`DEFINES = `libnet-config --defines``

- In a Makefile.in (also employing autoheader):

`DEFINES = `libnet-config --defines` @DEFS@`

4.3 IP-layer vs. Link-layer

People often wonder when to use the link-layer interface in place of the IP-layer interface. It's mainly trading of power and complexity for ease of use. The link-layer interface is slightly more complex and requires a bit more coding. A standard invocation of either interface might include the following:

Raw IP | Link-layer
libnet_init_packet libnet_open_raw_sock libnet_build_ip libnet_build_icmp libnet_do_checksum libnet_write_ip | libnet_init_packet libnet_open_link_interface libnet_build_ethernet libnet_build_ip libnet_build_icmp libnet_do_checksum (IP header checksum) libnet_do_checksum (transport layer header checksum) libnet_write_link_layer

Visually:

The link-layer interface is also considerably more powerful and portable (if you want to build ARP/RARP/ethernet frames it's the only way to go) than the raw IP interface (see below).

One major issue with the link-layer interface, however, is that in order to send packets to arbitrary remote Internet hosts, it needs to know the MAC address of the first hop router. This is accomplished via ARP packets, but if proxy ARP isn't being done, you run into all kinds of problems determining whose MAC address to request. Code to portably alleviate this problem is being developed.

4.4 Spoofing Ethernet Addresses

Certain operating systems (specifically ones that use the Berkeley Packet Filter for link-layer access) do not allow for arbitrary specification of source ethernet addresses. This is not so much a bug as it is an oversight in the protocol. The way around this is to patch the kernel. There are two ways to patch a kernel, either statically, with kernel diffs (which requires the individual to have the kernel sources, and know how to rebuild and install a new kernel) or dynamically, with loadable kernel modules (lkms). Since it's a bit overzealous to assume people will want to patch their kernel for a library, included with the libnet distribution is lkm code to seamlessly bypass the bpf restriction.

In order to spoof ethernet packets on bpf-based systems (currently supported are FreeBSD and OpenBSD) do the following: cd to the proper support/bpf-lkm/ directory, build the module, and modload it.

The module works as per the following description:

The 4.4BSD machine-independent ethernet driver does not allow upper layers to forge the ethernet source address; all ethernet outputs cause the output routine to build a new ethernet header, and the process that does this explicitly copies the MAC address registered to the interface into this header.

This is odd, because the bpf writing convention asserts that writes to bpf must include a link-layer header; it's intuitive to assume that this header is, along with the rest of the packet data, written to the wire.

This is not the case. The link-layer header is used solely by the bpf code in order to build a sockaddr structure that is passed to the generic ethernet output routine; the header is then effectively stripped off the
packet. The ethernet output routine consults this sockaddr to obtain the ethernet type and destination address, but not the source address.

The Libnet lkm simply replaces the standard ethernet output routine with a slightly modified one. This modified version retrieves the source ethernet address from the sockaddr and uses it as the source address for the header written the wire. This allows bpf to be used to seamlessly forge ethernet packets in their entirety, which has applications in address management.

The modload glue provided traverses the global list of system interfaces, and replaces any pointer to the original ethernet output routine with the new one we've provided. The unload glue undoes this. The effect of loading this module will be that all ethernet interfaces on the system will support source address forging.

Thomas H. Ptacek wrote the first version of this lkm in 1997.

4.5 Raw Sockets Limitations

Raw sockets are horribly non-standard across different platforms.

- Under some x86 BSD implementations the IP header length and fragmentation bits need to be in host byte order, and under others, network byte order.
- Solaris does not allow you to set many IP header related bits including the length, fragmentation flags, or IP options.
- Linux requires SO_BROADCAST to be set on the raw socket for the injection of broadcast IP datagrams (which libnet now does).

Because of these quirks, unless your code isn't designed to be multi-platform, you might want to consider employing libnet's link-layer interface instead.

Next Previous Top