Overall Packet library Architecture

The packet library handles network packets of a large number of protocols. We work with a packet on three levels

1. The Packet handle

Whenever we are using a Packet, we are talking about a senf::Packet (or a senf::ConcretePacket). This class is a handle referencing an internally managed packet data structure. So even though we pass senf::Packet instances around by value, they work like references. The packet library automatically manages all required memory resources using reference counting.

Different Packet handles may really internally share one Packet data structure if they both point to the same packet.

2. The Packet as a 'bunch of bytes'

From the outside, a packet is just a bunch of bytes just as it is read from (or will be written to) the wire. At this low-level view, we can access the data in it's raw form but have no further information about what kind of packet we have.

The packet library provides a consistent container interface for this representation.

Packet p = ...;

// Change first byte of packet to 1
p.data()[0] = 1u;

// Copy packet data into a vector
std::vector<char> data (p.data().size());
std::copy(p.data().begin(), p.data().end(), data.begin());

This type of access is primarily needed when reading or writing packets (e.g. to/from the network).

See also:
senf::Packet::data()
senf::PacketData

3. The Interpreter Chain

On the next level, the packet is divided into a nested list of sub-packets (or headers) called interpreters. Each senf::Packet handle internally points to an interpreter or header. This allows us to access one and the same packet in different ways.

Consider an Ethernet Packet with an IP payload holding a UDP packet. We may reference either the Ethernet packet as a whole or we may reference the IP or UDP interpreters (sub-packets or headers). All handles really refer to the same data structure but provide access to a different (sub-)range of the data in the packet.

We can navigate around this chained structure using appropriate members:

// eth, ip and udp all reference the same internal packet data albeit at different data ranges
Packet eth = ...;
Packet ip = eth.next();
Packet udp = ip.next();

eth.next() == ip                   // true
eth.next().is<IPv4Packet>()        // true
eth.next().next() == udp           // true
eth.next().is<UDPPacket>()         // false
eth.find<UDPPacket>() == udp       // true

udp.find<EthernetPacket>()         // throws InvalidPacketChainException
udp.find<EthernetPacket>(senf::nothrow) // An in-valid() senf::Packet which tests as 'false'
udp.find<UDPPacket> == udp         // true
udp.first<IPv4Packet>()            // throws InvalidPacketChainException

udp.prev() == ip                   // true
udp.prev<EthernetPacket>()         // throws InvalidPacketChainException
See also:
Packet Handling

4. Parsing specific Protocols

On the next level, the packet library allows us to parse the individual protocols. This gives us access to the protocol specific data members of a packet and allows us to access or manipulate a packet in a protocol specific way.

To access this information, we need to use a protocol specific handle, the senf::ConcretePacket which takes as a template argument the specific type of packet to be interpreted. This allows us to easily interpret or create packets. Here an example on how to create a new Ethernet / IP / UDP / Payload packet interpreter chain:

// EthernetPacket, IPv4Packet, UDPPacket and DataPacket are typedefs for corresponding
// ConcretePacket instantiations
senf::EthernetPacket eth      (senf::EthernetPacket::create());
senf::IPv4Packet     ip       (senf::IPv4Packet    ::createAfter(eth));
senf::UDPPacket      udp      (senf::UDPPacket     ::createAfter(ip));
senf::DataPacket     payload  (senf::DataPacket    ::createAfter(udp,
                                                                 std::string("Hello, world!")));

udp->source()      = 2000u;
udp->destination() = 2001u;
ip->ttl()          = 255u;
ip->source()       = senf::INet4Address::from_string("192.168.0.1");
ip->destination()  = senf::INet4Address::from_string("192.168.0.2");
eth->source()      = senf::MACAddress::from_string("00:11:22:33:44:55");
eth->destination() = senf::MACAddress::from_string("00:11:22:33:44:66");

eth.finalizeAll();

Again, realize, that eth, ip, udp and payload share the same internal packet data structure (the respective data() members all provide access to the same underlying container however at different byte ranges): The complete packet can be accessed at eth.data() whereas payload.data() only holds UDP payload (in this case the string "Hello, world!").

See also:
The PacketParser facility
Supported packet types (protocols)