In this section we'll have a look how you can implement your own protocols and integrate them with the library.
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;
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 PDU
s 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:
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
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:
As a final example, let's sniff a packet that contains a DummyPDU
.
This is the packet I've generated:
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