In this section we'll have a look at how libtins works, and how to perform packet crafting, sending and sniffing.
This section assumes you already know how to compile applications and link them with libtins.
So how does libtins work? libtins is conformed by PDU
classes,
sender and sniffer classes, classes that represent addresses, and some
helper functions which make your life easier.
We'll first have a look at what a PDU
object is. Every
PDU
implemented in the library(say IP
,
TCP
, UDP
, etc) is a class that inherits an
abstract class named PDU
.
This class contains methods which can retrieve the actual protocol data unit size and its type, among other things. It also contains a method called send which allows you to effectively send that packet through the network.
PDU
objects also support stacking. That means one
PDU
object(disregarding its actual type), can have 0 or 1
inner PDU
. This is a very logical way of imagining a
network packet. Suppose you create an Ethernet II
frame,
and then add an IP
datagram on top of it, followed by a
TCP
frame. That structure would look like this inside
libtins:
As you may imagine, a PDU
's inner pdu can be retrieved using
the method PDU::inner_pdu()
. Let's see an code example of
how this situation could be reproduced:
So what have we done here? The method PDU::inner_pdu(PDU*)
sets the given parameter, as the callee's inner PDU
. The
object passed as an argument must have been allocated using
operator new
,
and from that point on, that PDU
is now owned by its parent,
meaning that the destruction of that object will be handled by it. So
in the above example there is no actual memory leak. On eth's
destructor, both the allocated IP
and TCP
objects will be destroyed and their memory released.
Note that if you want to store a copy and not the actual pointer, you
can use the PDU::clone
function, which returns a copy of
that PDU
's concrete type, including all of its stacked
inner PDU
s.
There is a simpler way to nest PDU
s. For those who have used
scapy,
you may be used to creating a PDU
stack using the division
operator. libtins supports this as well!
The code above can be rewritten as the following:
Note that the IP
and TCP
temporary objects created in
the example above, are cloned using the PDU::clone()
method.
Both IP and hardware addresses are handled using the IPv4Address
,
IPv6Address
and HWAddress<>
classes. All of
these classes can be constructed from an std::string
or
c-string containing an appropriate representation(dotted-notation
for IPv4Address
, semicolon notation for IPv6Addresses
,
etc).
This addresses can be implicitly converted to an integral value, but
this is used inside the library, so you don't have to worry about it.
As you can notice from above, a default constructed IPv4Address
corresponds to the dotted-notation address 0.0.0.0
.
These classes also provide a constructor that takes an uint32_t
,
which is extremely useful when using default values for certain parameters
to functions/constructors. In the above example's last couple of lines,
both an IPv4 and an IPv6 addresses are written to stdout. This classes
define the output operator(operator<<
), so it's easier
to serialize them.
The HWAddress<>
class template is defined as follows:
Where the n non-type template parameter indicates the length of
the address(tipically 6 for network interfaces), and the Storage
template parameter indicates the type of each of those n elements
(this shouldn't normally be changed, uint8_t
should do).
HWAddress objects can be constructed from both std::string
s,
c-strings, const Storage*
and HWAddress of any length. They
can also be compared for equality, and provide some helper functions to
allow iteration over the address:
libtins also supports address ranges. This is very useful for several purposes, such as classifying traffic into different subnetworks.
Creating address ranges is very intuitive, using either a slash-dotation, or a netmask:
Now, what can you do with an address range? You can either iterate it, or ask it if a specific address is inside that network:
But wait, there's more. You can also create ranges of hardware addresses. Why is this useful? Using this, you can use the OUI specifiers to determine which is the vendor of a specific network device:
The last helper class reviewed here is NetworkInterface
. This
class represents the abstraction of a network interface. It can be
constructed from the interface's name(as string), and from an
IPv4Address. This last constructor creates the interface that would be
the gateway if some packet were to be sent to the given ip address:
You can also retrieve an interface's name using NetworkInterface::name()
.
Note that this function searches through the system's interfaces and
retrieves the name every time it is called, so you might want to call it
once and store the return value.
Writing packets to a pcap file is very simple as well. The
PacketWriter
class takes the name of the file in which you want
to store packets as its argument, and a data link type indicating which will be
the lowest layer written to the file. That means, if you're writing
EthernetII PDU
s, you should use the DataLinkType<EthernetII>
flag, while on wireless interfaces you should use DataLinkType<RadioTap>
or DataLinkType<Dot11>
, depending on the encapsulation used in
the device.
Once a PacketWriter
is created, you can write PDU
s to it
using the PacketWriter::write
method. This method contains 2
overloads: one takes a PDU&
, the other one takes two template
forward iterators, start
and end
. The latter will iterate
through the range [start, end) and write the PDU
s stored
in each position of the range. This will work both if *start
yields a PDU&
, or if dereferecing it several times leads to
a PDU&
. This means a
std::vector<std::unique_ptr<PDU>>::iterator
will
work as well.
This example creates a std::vector
containing one
EthernetII PDU
, and writes it to a pcap file using both
overloads:
Now we're going to use most of the classes listed above to create a packet and send it:
Note that the creation of that packet can be done in one line, using
operator/
rather than operator/=
:
The packet sending mechanism is addressed in the third section of this tutorial.