2024-02-21 14:52:47 +03:00

721 lines
21 KiB
C++

/*
* Copyright (c) 2017, Matias Fontanini
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#ifndef TINS_SNIFFER_H
#define TINS_SNIFFER_H
#include <string>
#include <memory>
#include <iterator>
#include <tins/pdu.h>
#include <tins/packet.h>
#include <tins/cxxstd.h>
#include <tins/macros.h>
#include <tins/exceptions.h>
#include <tins/detail/type_traits.h>
#ifdef TINS_HAVE_PCAP
#include <pcap.h>
namespace Tins {
class SnifferIterator;
class SnifferConfiguration;
/**
* \class BaseSniffer
* \brief Base class for sniffers.
*
* This class implements the basic sniffing operations. Subclasses
* should only initialize this object using a pcap_t pointer, which
* will be used to extract packets.
*
* Initialization must be done using the BaseSniffer::init method.
*/
class TINS_API BaseSniffer {
public:
/**
* The iterator type.
*/
typedef SnifferIterator iterator;
#if TINS_IS_CXX11
/**
* \brief Move constructor.
* This constructor is available only in C++11.
*/
BaseSniffer(BaseSniffer &&rhs) TINS_NOEXCEPT
: handle_(0), mask_(), extract_raw_(false),
pcap_sniffing_method_(pcap_loop) {
*this = std::move(rhs);
}
/**
* \brief Move assignment operator.
* This operator is available only in C++11.
*/
BaseSniffer& operator=(BaseSniffer &&rhs) TINS_NOEXCEPT {
using std::swap;
swap(handle_, rhs.handle_);
swap(mask_, rhs.mask_);
swap(extract_raw_, rhs.extract_raw_);
swap(pcap_sniffing_method_, rhs.pcap_sniffing_method_);
return* this;
}
#endif
/**
* \brief Sniffer destructor.
* This frees all memory used by the pcap handle.
*/
virtual ~BaseSniffer();
/**
* \brief Compiles a filter and uses it to capture one packet.
*
* This method returns the first valid sniffed packet that matches the
* sniffer's filter, or the first sniffed packet if no filter has
* been set.
*
* The return type is a thin wrapper over a PDU* and a Timestamp
* object. This wrapper can be both implicitly converted to a
* PDU* and a Packet object. So doing this:
*
* \code
* Sniffer s(...);
* std::unique_ptr<PDU> pdu(s.next_packet());
* // Packet takes care of the PDU*.
* Packet packet(s.next_packet());
* \endcode
*
* Is fine, but this:
*
* \code
* // bad!!
* PtrPacket p = s.next_packet();
* \endcode
*
* Is not, since PtrPacket can't be copy constructed.
*
* \sa Packet::release_pdu
*
* \return A captured packet. If an error occured, PtrPacket::pdu
* will return 0. Caller takes ownership of the PDU pointer stored in
* the PtrPacket.
*/
PtrPacket next_packet();
/**
* \brief Starts a sniffing loop, using a callback functor for every
* sniffed packet.
*
* The functor must implement an operator with one of the
* following signatures:
*
* \code
* bool(PDU&);
* bool(const PDU&);
*
* // These two are only allowed when compiling in C++11 mode
* bool(Packet&);
* bool(const Packet&);
* \endcode
*
* This functor will be called using the each of the sniffed packets
* as its argument. Using PDU member functions that modify the PDU,
* such as PDU::release_inner_pdu, is perfectly valid.
*
* Note that if you're using a functor object, it will be copied using
* its copy constructor, so it should be some kind of proxy to
* another object which will process the packets(e.g. std::bind).
*
* Sniffing will stop when either max_packets are sniffed(if it is != 0),
* or when the functor returns false.
*
* Note that the pcap handle stored in a BaseSniffer will always be the
* same. This means that if you start sniffing using sniff_loop, then stop
* and at some point in the future you call sniff_loop again, you will keep
* iterating over the same handle. If the handle points to a pcap file, then
* you will continue processing packets from it. If the handle points to
* a network device, you will keep sniffing from it.
*
* This method catches both malformed_packet and pdu_not_found exceptions,
* which allows writing much cleaner code, since you can call PDU::rfind_pdu
* without worrying about catching the exception that can be thrown. This
* allows writing code such as the following:
*
* \code
* bool callback(const PDU& pdu) {
* // If either RawPDU is not found, or construction of the DNS
* // object fails, the BaseSniffer object will trap the exceptions,
* // so we don't need to worry about it.
* DNS dns = pdu.rfind_pdu<RawPDU>().to<DNS>();
* return true;
* }
* \endcode
*
* \param function The callback handler object which should process packets.
* \param max_packets The maximum amount of packets to sniff. 0 == infinite.
*/
template <typename Functor>
void sniff_loop(Functor function, uint32_t max_packets = 0);
/**
* \brief Sets a filter on this sniffer.
* \param filter The filter to be set.
* \return True iif it was possible to apply the filter.
*/
bool set_filter(const std::string& filter);
/**
* \brief Stops sniffing loops.
*
* This method must be called from the same thread from which
* BaseSniffer::sniff_loop was called.
*/
void stop_sniff();
/**
* \brief Gets the file descriptor associated with the sniffer.
*/
int get_fd();
/**
* \brief Sets direction for the sniffer.
*
* This calls pcap_setdirection using the provided parameter.
* \param d The direction for the sniffer.
*/
bool set_direction(pcap_direction_t d);
/**
* \brief Sets the read timeout for this sniffer.
*
* This calls pcap_set_timeout using the provided parameter.
* \param ms The amount of milliseconds.
*/
void set_timeout(int ms);
/**
* \brief Sets whether to extract RawPDUs or fully parsed packets.
*
* By default, packets will be parsed starting from link layer.
* However, if you're parsing a lot of traffic, then you might
* want to extract packets and push them into a queue,
* so a consumer can parse them when they're popped.
*
* This method allows doing that. If the parameter is true,
* then packets taken from this BaseSniffer will only contain
* a RawPDU which will have to entire contents of the packet.
*
* \param value Whether to extract RawPDUs or not.
*/
void set_extract_raw_pdus(bool value);
/**
* \brief function pointer for the sniffing method
*
* By default, libtins uses `pcap_loop` to sniff packets. With
* `set_pcap_sniffing_method` it is possible to specify an alternative
* sniffing method, for example `pcap_dispatch`, or a custom function.
* This function pointer has the same interface as `pcap_loop` and
* `pcap_dispatch`.
*
* \sa set_pcap_sniffing_method
*/
typedef int(*PcapSniffingMethod)(pcap_t*, int, pcap_handler, u_char*);
/**
* \brief set sniffing method to either pcap_loop or pcap_dispatch.
*
* By default, packets are sniffed with `pcap_loop`, which only returns if
* a packet is received, thus ignoring timeout expiration, if any is set.
* With this method it is possible to pass an alternative sniffer function,
* e.g. `pcap_dispatch`, that honors timeouts, or a custom function with
* the same signature.
*
* See the relevant manual pages for pcap_loop and pcap_dispatch for more
* information on their behavior
*
* \sa PcapSniffingMethod
*/
void set_pcap_sniffing_method(PcapSniffingMethod method);
/**
* \brief Retrieves this sniffer's link type.
*
* This calls pcap_datalink on the stored pcap handle and
* returns its result.
*/
int link_type() const;
/**
* Retrieves an iterator to the next packet in this sniffer.
*/
iterator begin();
/**
* Retrieves an end iterator.
*/
iterator end();
/**
* Retrieves the pcap handle used by this sniffer.
*/
pcap_t* get_pcap_handle();
/**
* Retrieves the pcap handle used by this sniffer.
*/
const pcap_t* get_pcap_handle() const;
protected:
/**
* Default constructor.
*/
BaseSniffer();
void set_pcap_handle(pcap_t* pcap_handle);
void set_if_mask(bpf_u_int32 if_mask);
bpf_u_int32 get_if_mask() const;
private:
BaseSniffer(const BaseSniffer&);
BaseSniffer& operator=(const BaseSniffer&);
pcap_t* handle_;
bpf_u_int32 mask_;
bool extract_raw_;
PcapSniffingMethod pcap_sniffing_method_;
};
/**
* \class Sniffer
* \brief Sniffs packets from a network interface.
*/
class TINS_API Sniffer : public BaseSniffer {
public:
/**
* \deprecated This enum is no longer necessary. You should use the
* Sniffer(const std::string&, const SnifferConfiguration&) constructor.
*/
enum promisc_type {
NON_PROMISC,
PROMISC
};
/**
* \brief Constructs an instance of Sniffer
*
* \param device The device from which to capture packets
*/
Sniffer(const std::string& device);
/**
* \brief Constructs an instance of Sniffer using the provided configuration.
*
* Use the SnifferConfiguration object to specify how you want to configure
* the properties of this sniffer
*
* \sa SnifferConfiguration
*
* \param device The device which will be sniffed.
* \param configuration The configuration object to use to setup the sniffer.
*/
Sniffer(const std::string& device, const SnifferConfiguration& configuration);
/**
* \brief Constructs an instance of Sniffer.
*
* By default the interface won't be put into promiscuous mode, and won't
* be put into monitor mode.
*
* \deprecated Use the Sniffer(const std::string&, const SnifferConfiguration&)
* constructor.
* \param device The device which will be sniffed.
* \param max_packet_size The maximum packet size to be read.
* \param promisc bool indicating whether to put the interface in promiscuous mode.(optional)
* \param filter A capture filter to be used on the sniffing session.(optional);
* \param rfmon Indicates if the interface should be put in monitor mode.(optional);
*/
TINS_DEPRECATED(Sniffer(const std::string& device, unsigned max_packet_size,
bool promisc = false, const std::string& filter = "", bool rfmon = false));
/**
* \brief Constructs an instance of Sniffer.
*
* The maximum capture size is set to 65535. By default the interface won't
* be put into promiscuous mode, and won't be put into monitor mode.
*
* \deprecated Use the Sniffer(const std::string&, const SnifferConfiguration&)
* constructor.
* \param device The device which will be sniffed.
* \param promisc Indicates if the interface should be put in promiscuous mode.
* \param filter A capture filter to be used on the sniffing session.(optional);
* \param rfmon Indicates if the interface should be put in monitor mode.(optional);
*/
TINS_DEPRECATED(Sniffer(const std::string& device, promisc_type promisc,
const std::string& filter = "", bool rfmon = false));
private:
friend class SnifferConfiguration;
void init(const std::string& device, const SnifferConfiguration& configuration);
void set_snap_len(unsigned snap_len);
void set_buffer_size(unsigned buffer_size);
void set_promisc_mode(bool promisc_enabled);
void set_rfmon(bool rfmon_enabled);
void set_immediate_mode(bool enabled);
void set_timestamp_precision(int value);
};
/**
* \class FileSniffer
* \brief Reads pcap files and interprets the packets in it.
*
* This class acts exactly in the same way that Sniffer, but reads
* packets from a pcap file instead of an interface.
*/
class TINS_API FileSniffer : public BaseSniffer {
public:
/**
* \brief Constructs an instance of FileSniffer.
* \param fp The pcap file which will be parsed.
* \param configuration A SnifferConfiguration to be used on the file.
*/
FileSniffer(FILE *fp, const SnifferConfiguration& configuration);
/**
* \brief Constructs an instance of FileSniffer.
* \param file_name The pcap file which will be parsed.
* \param configuration A SnifferConfiguration to be used on the file.
*/
FileSniffer(const std::string& file_name, const SnifferConfiguration& configuration);
/**
* \deprecated Use the constructor that takes a SnifferConfiguration instead.
*
* \brief Constructs an instance of FileSniffer.
* \param file_name The pcap file which will be parsed.
* \param filter A capture filter to be used on the file. (optional)
*/
FileSniffer(const std::string& file_name, const std::string& filter = "");
/**
* \deprecated Use the constructor that takes a SnifferConfiguration instead.
*
* \brief Constructs an instance of FileSniffer.
* \param fp The pcap file which will be parsed.
* \param filter A capture filter to be used on the file. (optional)
*/
FileSniffer(FILE *fp, const std::string& filter = "");
};
template <typename T>
class HandlerProxy {
public:
typedef T* ptr_type;
typedef bool (T::*fun_type)(PDU&) ;
HandlerProxy(ptr_type ptr, fun_type function)
: object_(ptr), fun_(function) {}
bool operator()(PDU& pdu) {
return (object_->*fun_)(pdu);
}
private:
ptr_type object_;
fun_type fun_;
};
template <typename T>
HandlerProxy<T> make_sniffer_handler(T* ptr,
typename HandlerProxy<T>::fun_type function) {
return HandlerProxy<T>(ptr, function);
}
/**
* \brief Iterates over packets sniffed by a BaseSniffer.
*/
class SnifferIterator {
public:
typedef std::forward_iterator_tag iterator_category;
typedef Packet value_type;
typedef std::ptrdiff_t difference_type;
typedef Packet* pointer;
typedef Packet& reference;
/**
* Constructs a SnifferIterator.
* \param sniffer The sniffer to iterate.
*/
SnifferIterator(BaseSniffer* sniffer = 0)
: sniffer_(sniffer) {
if (sniffer_) {
advance();
}
}
/**
* Advances the iterator.
*/
SnifferIterator& operator++() {
advance();
return* this;
}
/**
* Advances the iterator.
*/
SnifferIterator operator++(int) {
SnifferIterator other(*this);
advance();
return other;
}
/**
* Dereferences the iterator.
* \return reference to the current packet.
*/
Packet& operator*() {
return pkt_;
}
/**
* Dereferences the iterator.
* \return pointer to the current packet.
*/
Packet* operator->() {
return &(**this);
}
/**
* Compares this iterator for equality.
* \param rhs The iterator to be compared to.
*/
bool operator==(const SnifferIterator& rhs) const {
return sniffer_ == rhs.sniffer_;
}
/**
* Compares this iterator for in-equality.
* \param rhs The iterator to be compared to.
*/
bool operator!=(const SnifferIterator& rhs) const {
return !(*this == rhs);
}
private:
void advance() {
pkt_ = sniffer_->next_packet();
if (!pkt_) {
sniffer_ = 0;
}
}
BaseSniffer* sniffer_;
Packet pkt_;
};
/**
* \class SnifferConfiguration
* \brief Represents the configuration of a BaseSniffer object.
*
* This class can be used as an easy way to configure a Sniffer
* or FileSniffer object.
*
* It can be used by constructing an object of this type,
* setting the desired values and then passing it to the
* Sniffer or FileSniffer object's constructor. This sets
* default values for some attributes:
*
* - Snapshot length: 65535 bytes (64 KB).
* - Timeout: 1000 milliseconds.
* - Promiscuous mode: false.
*
* For any of the attributes not listed above, the associated
* pcap function which is used to set them on a pcap handle
* won't be called at all.
*
* This class can be used to configure a Sniffer object,
* like this:
*
* \code
* // Initialize the configuration.
* SnifferConfiguration config;
* config.set_filter("ip and port 80");
* config.set_promisc_mode(true);
*
* // Use it on a Sniffer object.
* Sniffer sniffer("eth0", config);
* \endcode
*/
class TINS_API SnifferConfiguration {
public:
/**
* \brief The default snapshot length.
*
* This is 65535 by default.
*/
static const unsigned DEFAULT_SNAP_LEN;
/**
* \brief The default timeout.
*
* This is 1000 by default.
*/
static const unsigned DEFAULT_TIMEOUT;
/**
* Default constructs a SnifferConfiguration.
*/
SnifferConfiguration();
/**
* Sets the snapshot length option.
* \param snap_len The snapshot length to be set.
*/
void set_snap_len(unsigned snap_len);
/**
* Sets the buffer size option.
* \param buffer_size The buffer size to be set.
*/
void set_buffer_size(unsigned buffer_size);
/**
* Sets the promiscuous mode option.
* \param enabled The promiscuous mode value.
*/
void set_promisc_mode(bool enabled);
/**
* Sets a pcap filter to use on the sniffer.
* \param filter The pcap filter to be used.
*/
void set_filter(const std::string& filter);
/**
* Sets the pcap sniffing method to use.
* \param method The sniffing method to be used.
*/
void set_pcap_sniffing_method(BaseSniffer::PcapSniffingMethod method);
/**
* Sets the rfmon option.
* \param enabled The rfmon option value.
*/
void set_rfmon(bool enabled);
/**
* Sets the timeout option.
* \param timeout The timeout to be set.
*/
void set_timeout(unsigned timeout);
/**
* Sets the direction option.
* \param direction The direction to be set.
*/
void set_direction(pcap_direction_t direction);
/**
* Sets the immediate mode option.
* \param enabled The immediate mode option value.
*/
void set_immediate_mode(bool enabled);
/**
* Sets the timestamp precision value
* \param value The timestamp option value.
*/
void set_timestamp_precision(int value);
protected:
friend class Sniffer;
friend class FileSniffer;
enum Flags {
BUFFER_SIZE = 1,
PROMISCUOUS = 2,
RFMON = 4,
PACKET_FILTER = 8,
IMMEDIATE_MODE = 16,
DIRECTION = 32,
TIMESTAMP_PRECISION = 64,
PCAP_SNIFFING_METHOD = 128,
};
void configure_sniffer_pre_activation(Sniffer& sniffer) const;
void configure_sniffer_pre_activation(FileSniffer& sniffer) const;
void configure_sniffer_post_activation(Sniffer& sniffer) const;
uint32_t flags_;
unsigned snap_len_;
unsigned buffer_size_;
std::string filter_;
BaseSniffer::PcapSniffingMethod pcap_sniffing_method_;
unsigned timeout_;
bool promisc_;
bool rfmon_;
bool immediate_mode_;
pcap_direction_t direction_;
int timestamp_precision_;
};
template <typename Functor>
void Tins::BaseSniffer::sniff_loop(Functor function, uint32_t max_packets) {
for(iterator it = begin(); it != end(); ++it) {
try {
// If the functor returns false, we're done
#if TINS_IS_CXX11 && !defined(_MSC_VER)
if (!Tins::Internals::invoke_loop_cb(function, *it)) {
return;
}
#else
if (!function(*it->pdu())) {
return;
}
#endif
}
catch(malformed_packet&) { }
catch(pdu_not_found&) { }
if (max_packets && --max_packets == 0) {
return;
}
}
}
} // Tins
#endif // TINS_HAVE_PCAP
#endif // TINS_SNIFFER_H