Extending the PDU class

As you should already know, every protocol must derive the PDU class. Therefore, if you wanted to add new protocols, then your class must do so. Now, what member functions should you implement to make it work? Here's a dummy PDU you could use as a template:

/*
 * This is a dummy PDU. It behaves very similarly to Tins::RawPDU.
 */
class DummyPDU : public PDU {
public:
    /* 
     * Unique protocol identifier. For user-defined PDUs, you **must**
     * use values greater or equal to PDU::USER_DEFINED_PDU;
     */
    static const PDU::PDUType pdu_flag;

    /*
     * Constructor from buffer. This constructor will be called while
     * sniffing packets, whenever a PDU of this type is found. 
     * 
     * The "data" parameter points to a buffer of length "sz". 
     */
    DummyPDU(const uint8_t* data, uint32_t sz)
    : buffer_(data, data + sz) {

    }
    
    /*
     * Clones the PDU. This method is used when copying PDUs.
     */
    DummyPDU* clone() const {
        return new DummyPDU(*this);
    }
    
    /*
     * Retrieves the size of this PDU. 
     */
    uint32_t header_size() const {
        return buffer_.size();
    }
    
    /*
     * This method must return pdu_flag.
     */
    PDUType pdu_type() const {
        return pdu_flag;
    }
    
    /*
     * Serializes the PDU. The serialization output should be written
     * to the buffer pointed to by "data", which is of size "sz". The
     * "sz" parameter will be equal to the value returned by 
     * DummyPDU::header_size. 
     *
     * Note that before libtins 4.0, there would be an extra
     * const PDU* parameter after "sz" which would contain the parent
     * PDU. On libtins 4.0 this parameter was removed as you can get
     * the parent PDU by calling PDU::parent_pdu()
     */
    void write_serialization(uint8_t *data, uint32_t sz) {
        std::memcpy(data, buffer_.data(), sz);
    }
    
    // This is just a getter to retrieve the buffer member.
    const std::vector<uint8_t>& get_buffer() const {
        return buffer_;
    }
private:
    std::vector<uint8_t> buffer_;
};

// Let's assign some value to the pdu_flag.
const PDU::PDUType DummyPDU::pdu_flag = PDU::USER_DEFINED_PDU;

Registering the new protocol

Okay, we've defined a new PDU, but now we need to register it, so that the layers below it can recognize it while sniffing and serializing.

Registering a protocol is very simple. Let's imagine our DummyPDU is a network layer protocol. Therefore, we'd like EthernetII, Dot3 and the rest of the link layer protocols to recognize it. In order to do so, the following line of code should be used:

// Allocators::register_allocator is defined in tins/pdu_allocator.h
// This registers it for every link layer protocol. 
Allocators::register_allocator<EthernetII, DummyPDU>(0x8ae);

// If we wanted a transport layer which can appear after an 
// IP or IPv6 PDUs, we'd call:
// 
// Allocators::register_allocator<IP, DummyPDU>(0x12);

You are probably wondering what is that 0x8ae constant used above. That is the identifier which link layer PDUs will use to identify our protocol, just like ARP is identified inside EthernetII using the 0x806 constant.

Now that our PDU is registered, the following will happen:

  • When sniffing a packet, if the sniffed link layer PDU finds that the network layer protocol identifier field is 0x8ae, it will construct a DummyPDU from the sniffed bytes using the DummyPDU::DummyPDU(const uint8_t*, uint32_t) constructor
  • When serializing a PDU, our constant will be used in the network layer's protocol identifier field.

Note that Allocators::register_allocator's first template parameter can only be one of the following types:

  • EthernetII
  • SNAP
  • SLL
  • Dot1Q
  • IP
  • IPv6

Example

As a final example, let's sniff a packet that contains a DummyPDU. This is the packet I've generated:

The packet shown in wireshark

As you can see, the network layer protocol type is 0x8ae. Now, let's sniff the packet and display the payload!

#include <iostream>
#include <tins/tins.h>
#include "dummy_pdu.h";

using namespace Tins;

int main() {
    Allocators::register_allocator<EthernetII, DummyPDU>(0x8ae);
    FileSniffer sniffer("/tmp/dummy.pcap");
    Packet pkt = sniffer.next_packet();
    if (!pkt) {
        std::cout << "Oops, didn't sniff anything.\n";
    }
    else {
        const DummyPDU& dummy = pkt.pdu()->rfind_pdu<DummyPDU>();
        // Convert the contents of the DummyPDU to a std::string
        std::string payload(dummy.get_buffer().begin(), dummy.get_buffer().end());
        std::cout << "Payload: " << payload << std::endl;
    }
}

After executing this example, this is the output I get:

matias@master:/tmp$ ./dummy_test
Payload: AAAAAAAAAAAAAAAAAAAAAAAAAA
Previous part: IEEE 802.11