Chapter 2

Overview

2.1  Transmitting UDP Packets

int udp_send( const char* host, uint16_t port, uint8_t* msg, uint16_t msg_len ){ 
    ipaf_packet packet; 
    ipaf_ipv4* ipv4; 
    ipaf_udp* udp; 
    struct addr da; 
 
    packet = ipaf_create_udp_packet( msg_len ); 
    ipv4 = ipaf_locate_ipv4( packet ); 
    udp = ipaf_locate_udp( packet ); 
 
    if( addr_pton( host, &da ) == -1 ){ 
        ipaf_decref_cell( (ipaf_cell)packet ); 
        return 1; 
    }; 
 
    IPAF_IP_SET_DESTADDR( 
        ipv4, da.addr_data8[ 0 ], da.addr_data8[ 1 ],  
        da.addr_data8[ 2 ], da.addr_data8[ 3 ] 
    ); 
 
    ipaf_set_udp_srcport( udp, rand() ); 
    ipaf_set_udp_dstport( udp, port ); 
 
    memcpy( ipaf_locate_udp_data( packet ), msg, msg_len ); 
 
    ipaf_finish_packet( packet ); 
    ipaf_transmit( NULL, packet ); 
 
    ipaf_decref_cell( (ipaf_cell)packet ); 
 
    return 0; 
} 

Transmitting a packet in IPAF at its simplest consists of allocating a packet, setting segment field values, "finishing" the packet, and passing the packet to the OS for transmission.

    packet = ipaf_create_udp_packet( msg_len ); 
    ipv4 = ipaf_locate_ipv4( packet ); 
    udp = ipaf_locate_udp( packet ); 

First, we allocate a UDP packet with sufficient room to carry msg_len bytes of data. ipaf_create_udp_packet does a lot of grunt work, here, initializing the new packet with sane default values, and configuring both the IP and Ethernet headers as appropriate for a UDP packet.

Since the packet has been reasonably laid out for us, we can use IPAF's packet analysis API's to locate the IPv4 and UDP header segments in the packet for later use. Unlike some packet generation libraries, IPAF works on packets in place, instead of manipulating a complicated structure that will later be compiled to create the packet. This simplifies modifying and rebroadcasting packets, but can introduce problems when packets are resized.

    if( addr_pton( host, &da ) == -1 ){ 
        ipaf_decref_cell( (ipaf_cell)packet ); 
        return 1; 
    }; 

libdnet's address parsing function, addr_pton, does a serviceable job of parsing dotted quad notation addresses. For more information on addr_pton, and the addr structure, see dnet's manual page.

    IPAF_IP_SET_DESTADDR( 
        ipv4, da.addr_data8[ 0 ], da.addr_data8[ 1 ],  
        da.addr_data8[ 2 ], da.addr_data8[ 3 ] 
    ); 
 
    ipaf_set_udp_srcport( udp, rand() ); 
    ipaf_set_udp_dstport( udp, port ); 

The da structure now contains the parsed address from addr_pton. We use the IPAF_IP_SET_DESTADDR macro supplied from ipaf/generator_api.h here to assign the IPv4 destination address in our packet, then assign a source and destination port for our packet. IPAF's packet manipulation functions quietly do conversions between network byte order and host byte order, with the obvious exception of the data locator and range locator functions.

    memcpy( ipaf_locate_udp_data( packet ), msg, msg_len ); 

Since IPAF's analysis tools operate on the packet in place, we can use the pointer returned from the UDP data locator to copy in our message data. Our allocation, earlier, has ensured that the packet has sufficient space to contain the message, and has initialized the UDP header's length field.

    ipaf_finish_packet( packet ); 
    ipaf_transmit( NULL, packet ); 

Before we can transmit the packet, we need to perform the IP and UDP checksums. ipaf_finish_packet will determine which checksums need to be performed on the packet prior to transmission. The ipaf_transmit routine actually sends the packet to the kernel for immediate transmission on the appropriate interface. We do not bother creating a Generator for this, because we are uninterested in scheduling the packet for later transmission, or knowing exactly when the packet has been transmitted.

    ipaf_decref_cell( (ipaf_cell)packet ); 

Since ipaf_transmit transmits the packet immediately, we are responsible for disposing of the packet. Most data types in IPAF are derived from ipaf_cell, so we can use ipaf_decref_cell to signal that we are about to lose a reference to the packet. This will cause IPAF's memory management system to free the packet's memory, or scavenge it for later reuse.

2.2  Packet Type Monitor

unsigned int column_count; 
 
IPAF_DEFINE_CALLBACK( packet_type_monitor ){ 
    ipaf_packet packet = (ipaf_packet)ipaf_read( queue ); 
 
    if( ipaf_locate_udp( packet ) ){ 
        if( ipaf_udp_data_length( packet ) ){ 
            printf( "U" ); 
        }else{ 
            printf( "u" ); 
        }; 
    }else if( ipaf_locate_tcp( packet ) ){  
        if( ipaf_tcp_data_length( packet ) ){ 
            printf( "T" ); 
        }else{ 
            printf( "t" ); 
        }; 
    }else if( ipaf_locate_ipv4( packet ) ){ 
        printf( "i" ); 
    }else{ 
        printf( "." ); 
    }; 
 
    if( column_count++ == 77 ){ 
        printf( "\n" ); 
        column_count = 0; 
    } 
     
    ipaf_decref_cell( (ipaf_cell)packet ); 
} 
 
void monitor_iface( char* iface ){ 
    ipaf_initialize_queue_module(); 
 
    ipaf_collector mon = ipaf_create_collector( iface, 1, NULL, NULL ); 
    ipaf_set_collector_reader( mon, packet_type_monitor ); 
 
    column_count = 0; 
         
    while( 1 ){ 
        ipaf_collect( 1 ); ipaf_step( 0 ); 
    }; 
} 

Like transmitting a packet, collecting network packets in IPAF is relatively easy. A collector is initialized, assigned a reader and, optionally, a BPF filter program, then periodically checked. Decoding packet segments, segment fields and segment data is managed with a comprehensive analysis API which was mentioned in the previous example.

IPAF_DEFINE_CALLBACK( packet_type_monitor ){ 
    ipaf_packet packet = (ipaf_packet)ipaf_read( queue ); 
    : 
} 

This is the common preamble in IPAF for defining a queue reader callback - when a message is placed in a queue, if that queue has a reader associated with it, the queue will later invoke the reader callback, supplying it with a pointer to the queue. The reader is then expected to read the pending message or packet, and take appropriate action. IPAF's message, queue and reader system provides a way to break complicated network analysis tasks into discrete steps using asynchronous channels. For trivial tasks, it is possible to bypass using a queue reader by simply checking and reading messages from the queue as necessary.

    if( ipaf_locate_udp( packet ) ){ 
        if( ipaf_udp_data_length( packet ) ){ 
            printf( "U" ); 
        }else{ 
            printf( "u" ); 
        }; 
    }else if( ipaf_locate_tcp( packet ) ){  
        if( ipaf_tcp_data_length( packet ) ){ 
            printf( "T" ); 
        }else{ 
            printf( "t" ); 
        }; 
    }else if( ipaf_locate_ipv4( packet ) ){ 
        printf( "i" ); 
    }else{ 
        printf( "." ); 
    }; 

Once again, IPAF's packet analysis API's are used here to discover TCP, UDP and IPv4 segments in the packet. Since the locator functions return NULL if they fail to discover a segment in the packet, they can be used to reliably identify the packet type. Since the resulting segment pointers exist within the packet data buffer itself, we do not use IPAF's reference management API here.

    ipaf_decref_cell( (ipaf_cell)packet ); 

It is important that a queue reader ensure that it decrements the reference counter on the read packet prior to exiting. This will ensure that packets that are not referenced elsewhere are properly freed or reused.

void monitor_iface( char* iface ){ 
    ipaf_initialize_queue_module(); 
 
    ipaf_collector mon = ipaf_create_collector( iface, 1, NULL, NULL ); 
    ipaf_set_collector_reader( mon, packet_type_monitor ); 
    : 
} 

The first step in any IPAF application using Queues or Collectors should be to initialize the queue module. This will initialize the queue scheduler, and certain memory management details that are necessary for many queue API operations.

iface, here, is the name of a network interface to monitor - it is important to use the interface name, for example, "eth0" or "en0", instead of the device path. A Collector in IPAF is managed by the cell memory system, just like packets, queues, generators and messages, and can be manipulated with the same memory management API's.

monitor_iface starts off by creating a collector assigned to the specified interface, in promiscuous mode, with no assigned BPF filter or collection queue. Since a queue hasn't been assigned, one will be created; monitor_iface then assigns the reader implemented earlier by packet_type_monitor to the collector, causing a collection queue to be quietly created for our use.

    while( 1 ){ 
        ipaf_collect( 1 ); ipaf_step( 0 ); 
    }; 

monitor_iface's event loop is brutally simple. The ipaf_collect function causes IPAF to collect one packet before returning, then ipaf_step iterates through any active message queues with readers until there ceases to be further activity. In many real world situations, it may be necessary to specify the number of queues that ipaf_step should process before yielding.

2.3  Transmitting UDP Packets Revisited in Python

A key design requirement for IPAF was the ability to support various high level scripting languages, ideally through the use of foreign function interfaces. Currently, IPAF comes with support for only one high level language, Python, but can easily be expanded to support other languages. While speed and efficiency is sacrificed by using the Python wrapper, it is extremely easy to write tools using the Python API.

def udp_send( addr, port, msg ): 
    u = UdpPacket( len( msg ) ) 
    u.ipv4.srcaddr = addr 
    u.ipv4.dstaddr = addr 
    u.udp.srcport = port 
    u.udp.dstport = port 
    u.udp_data = msg 
    u.emit() 

This is the Python version of the UDP packet transmission example. Of note, the IPAF's packet type has been wrapped in a set of Python classes, TcpPacket, UdpPacket, etc, and the locators are wrapped into packet properties. It is important, when generating packets, to consider using the correct base class. This will simplify emitting the packet, since the wrapper will automatically know how to calculate the correct packet size and calculate checksums.

2.4  Packet Type Monitor Revisited in Python

column_count = 0 
 
def packet_type_monitor( packet ): 
    global column_count 
     
    if packet.udp: 
 
        if len( packet.udp_data ) > 0: 
            print "U", 
        else: 
            print "u", 
 
    elif packet.tcp: 
 
        if len( packet.tcp_data ) > 0: 
            print "T", 
        else: 
            print "t" 
 
    elif packet.ipv4: 
 
        print "i", 
 
    else: 
 
        print ".", 
 
 
    if column_count == 77: 
        print 
        column_count = 0 
    else: 
        column_count += 1  
 
collector = Collector( iface, None ) 
collector.setReader( packet_type_monitor ) 
 
while true: 
    collect( 1 ); step( 0 ) 

Python callable objects can be used as reader callbacks by IPAF, thanks to the ctypes module. Since Python provides its own memory management system, IPAF's python wrapper leverages that to manage cell memory, removing the need to decrement the reference count for a read packet.

To further simplify writing readers in Python, the reader is furnished with a packet, instead of a reference to the queue itself. IPAF permits you to override methods on the Queue class if more contextual information is required.

Since every call to the IPAF API requires accessing several python methods, and often some redundant type conversion at the foreign function interface level, it is important to note that it is often best to use the Python interface for tools that do not have extremely high performance requirements, or to prototype tools. A future design requirement for IPAF includes a way to load specialized "filter" queues to perform repetitive tasks at the C level, avoiding the FFI overhead penalty.