In this section we'll have a deeper look at how to perform sniffing using libtins.
Sniffing is done through the Sniffer
class. This class accepts
a libpcap string filter, and lets you sniff on some network device,
interpreting the packets sent through it, and giving youPDU
objects so you can easily work with them.
Once you've set the filter, there are two functions which allow to
retrieve the sniffed packets. One of them is Sniffer::next_packet
.
This member function lets you retrieve a packet using the provided
filter:
Since version 3.2, there's a class that represents the
different parameters that can be given to the sniffer in order to affect
the sniffing sessions. They are all wrappers over the different
libpcap functions, such as pcap_setfilter,
pcap_set_promisc, etc. It's an improvement over the many
parameters that the other Sniffer
constructors' take.
For example, if you wanted to capture packets on port 80, sniff on promiscuous mode and set a snapshot length of 400 bytes, you would do it this way:
Note: if you notice sniffed packets come in bursts or there's
a delay in their capture (e.g. 1 second), this is very likely due to
libpcap >= v1.5 using a buffered mode by default.
If you want to get packets as fast as possible, make sure to use
immediate mode by using
SnifferConfiguration::set_immediate_mode
.
There is another way to extract packets from a Sniffer
object, apart from Sniffer::next_packet
. It's very common
that you want to sniff lots packets until some certain condition is
met. In that case its better to use Sniffer::sniff_loop
.
This method takes a template functor as an argument, which must define an operator with one of the following signatures:
The call to Sniffer::sniff_loop
will make the sniffer start
processing packets. The functor will be called using each processed
packet as its argument. If at some point, you want to stop sniffing,
then your functor should return false
. Otherwise return
true
and the Sniffer
object will keep looping.
The functor object will be copy constructed, so it must implement copy
semantics. There is a helper template function which takes a pointer
to an object of a template parameter type, and a member function,
and returns a HandlerProxy
. That object implements the
required operator, in which it forwards the call to the member function pointer
provided, using the object pointer given:
As you can see, sniffing using Sniffer::sniff_loop
can not
only be an easy way to process several packets, but also can make your
code a lot tidier when using classes.
Now the interesting part. In the above example we know we are sniffing
IP PDU
s sent by the ip address 192.168.0.100, but our
function takes a PDU&
. We want to search the IP PDU
stored inside the parameter(which will probably be of type EthernetII
).
Luckily for us, you can ask a PDU
to search for a certain
PDU
type inside its whole stack of PDUs(including
itself), and return a reference to it. If no such PDU
is
found in the packet, a pdu_not_found
exception is thrown:
Another thing that makes the loop-sniffing mechanism better
than fetching packets one by one, is exception handling.
Sniffer::sniff_loop
catches both pdu_not_found
and
malformed_packet
exceptions thrown in the functor body. This
means you can use PDU::rfind_pdu
and don't even care if such
PDU
is not found, since the exception will be caught by
the Sniffer
, and the sniffing session will continue.
Note to Windows users: you may want to check out the sniffing on Windows extra section of this tutorial to make sure you know what you need to before starting a packet capture on that platform.
There is yet another way to retrieve packets from a Sniffer object.
This class defines two methods, begin()
and end()
, which
return forward iterators. These can be used to retrieve packets while
they're being sniffed:
If you require to store a PDU
along with the timestamp object,
then you should use the Packet
class. Packet
s
contain a PDU
and Timestamp
, can be copyed and moved.
Let's see an example in which we'll store 10 packets read from the wire into a vector:
As you may have noticed Packet
objects can also be used along
with Sniffer::next_packet
:
Packet
s can also be accepted on the functor object used
on Sniffer::sniff_loop
, but only when you are compiling
in C++11 mode.
Reading files in pcap format is very straightforward. The
FileSniffer
class takes the name of the file to be opened as
argument, and lets you process the packets in it. Both Sniffer
and FileSniffer
inherit from BaseSniffer
, which is
the class that actually implements next_packet
and sniff_loop
.
Therefore, we can use the FileSniffer
class in the same way
we used Sniffer
in the examples above:
Now that we've seen the ways in which you can read pcap files and sniff from network interfaces, we'll have a look at how packet interpretation is performed.
Every time a packet is read from one of those sources, an object of that
source's link layer type is created(EthernetII
, RadioTap
,
etc). Each of these types of object detects which is the type of the next
PDU
based on its internal flags, creates it, adds it as its
child, and propagates the same action.
This action is performed by every instantiated PDU
, except for
transport-layer protocols. This means that, for example, if a DNS
packet is sniffed off an ethernet interface, you'll get the following
structure:
You can then interpret that DNS
packet constructing a DNS
object using that RawPDU
's payload:
The same mechanism should be used for other protocols such as DHCP
.
In case you're wondering why application-layer protocols aren't interpreted
automatically by transport-layer PDU
s, the reason is efficiency.
Application layer protocols, such as DNS
, require much more processing
in order to parse them than lower layer protocols. In addition, some
applications might not even require to use those protocols, so making them
pay for that extra processing is undesirable.