libraries/pcappp/include/pcapplusplus/IPReassembly.h

458 lines
20 KiB
C++

#ifndef PACKETPP_IP_REASSEMBLY
#define PACKETPP_IP_REASSEMBLY
#include "Packet.h"
#include "LRUList.h"
#include "IpAddress.h"
#include "PointerVector.h"
#include <map>
/**
* @file
* This file includes an implementation of IP reassembly mechanism (a.k.a IP de-fragmentation), which is the mechanism of assembling IPv4 or IPv6
* fragments back into one whole packet. As the previous sentence imply, this module supports both IPv4 and IPv6 reassembly which means
* the same pcpp#IPReassembly instance can reassemble both IPv4 and IPv6 fragments. You can read more about IP fragmentation here:
* https://en.wikipedia.org/wiki/IP_fragmentation.<BR>
* The API is rather simple and contains one main method: pcpp#IPReassembly#processPacket() which gets a fragment packet as a parameter, does the
* reassembly and returns a fully reassembled packet when done.<BR>
*
* The logic works as follows:
* - There is an internal map that stores the reassembly data for each packet. The key to this map, meaning the way to uniquely associate a
* fragment to a (reassembled) packet is the triplet of source IP, destination IP and IP ID (for IPv4) or Fragment ID (for IPv6)
* - When the first fragment arrives a new record is created in the map and the fragment data is copied
* - With each fragment arriving the fragment data is copied right after the previous fragment and the reassembled packet is gradually being built
* - When the last fragment arrives the packet is fully reassembled and returned to the user. Since all fragment data is copied, the packet pointer
* returned to the user has to be freed by the user when done using it
* - The logic supports out-of-order fragments, meaning that a fragment which arrives out-of-order, its data will be copied to a list of out-of-order
* fragments where it waits for its turn. This list is observed each time a new fragment arrives to see if the next fragment(s) wait(s) in this
* list
* - If a non-IP packet arrives it's returned as is to the user
* - If a non-fragment packet arrives it's returned as is to the user
*
* In order to limit the amount of memory used by this mechanism there is a limit to the number of concurrent packets being reassembled.
* The default limit is #PCPP_IP_REASSEMBLY_DEFAULT_MAX_PACKETS_TO_STORE but the user can set any value (determined in pcpp#IPReassembly
* c'tor). Once capacity (the number of concurrent reassembled packets) exceeds this number, the packet that was least recently used will be
* dropped from the map along with all the data that was reassembled so far. This means that if the next fragment from this packet suddenly
* appears it will be treated as a new reassembled packet (which will create another record in the map). The user can be notified when
* reassembled packets are removed from the map by registering to the pcpp#IPReassembly#OnFragmentsClean callback in pcpp#IPReassembly c'tor
*/
/**
* @namespace pcpp
* @brief The main namespace for the PcapPlusPlus lib
*/
namespace pcpp
{
/** IP reassembly mechanism default capacity. If concurrent packet volume exceeds this numbers, packets will start to be dropped in
* a LRU manner
*/
#define PCPP_IP_REASSEMBLY_DEFAULT_MAX_PACKETS_TO_STORE 500000
/**
* @class IPReassembly
* Contains the IP reassembly (a.k.a IP de-fragmentation) mechanism. Encapsulates both IPv4 and IPv6 reassembly.
* Please refer to the documentation at the top of IPReassembly.h
* to understand how this mechanism works. The main APIs are:
* - IPReassembly#processPacket() - process a fragment. This is the main method which should be called whenever a new fragment arrives.
* This method processes the fragment, runs the reassembly logic and returns the result packet when it's fully reassembled
* - IPReassembly#getCurrentPacket() - get the reassembled data that is currently available, even if reassembly process is not yet completed
* - IPReassembly#removePacket() - remove all data that is currently stored for a packet, including the reassembled data that was gathered
* so far
*/
class IPReassembly
{
public:
/**
* @class PacketKey
* An abstract class that represents a key that can uniquely identify an IP packet. This class cannot be instantiated or copied,
* only its derived classes can
*/
class PacketKey
{
public:
/**
* A default virtual d'tor
*/
virtual ~PacketKey() {}
/**
* @return A 4-byte hash value of the packet key
*/
virtual uint32_t getHashValue() const = 0;
/**
* @return The IP protocol this key represents (pcpp#IPv4 or pcpp#IPv6)
*/
virtual ProtocolType getProtocolType() const = 0;
/**
* @return A pointer to a new instance which is a clone of the current instance
*/
virtual PacketKey* clone() const = 0;
protected:
// private c'tor
PacketKey() {}
// private copy c'tor
PacketKey(const PacketKey& other) {}
};
/**
* @class IPv4PacketKey
* Represents a key that can uniquely identify IPv4 packets. The key comprises of source IPv4 address, dest IPv4 address and IP ID
*/
class IPv4PacketKey : public PacketKey
{
public:
/**
* A default c'tor which zeros all members
*/
IPv4PacketKey() : m_IpID(0), m_SrcIP(IPv4Address::Zero), m_DstIP(IPv4Address::Zero) { }
/**
* A c'tor that sets values in each one of the members
* @param[in] ipid IP ID value
* @param[in] srcip Source IPv4 address
* @param[in] dstip Dest IPv4 address
*/
IPv4PacketKey(uint16_t ipid, IPv4Address srcip, IPv4Address dstip) : m_IpID(ipid), m_SrcIP(srcip), m_DstIP(dstip) { }
/**
* A copy c'tor for this class
* @param[in] other The instance to copy from
*/
IPv4PacketKey(const IPv4PacketKey& other) : PacketKey(other), m_IpID(other.m_IpID), m_SrcIP(other.m_SrcIP), m_DstIP(other.m_DstIP) { }
/**
* Assignment operator for this class
* @param[in] other The instance to assign from
*/
IPv4PacketKey& operator=(const IPv4PacketKey& other)
{
m_IpID = other.m_IpID;
m_SrcIP = other.m_SrcIP;
m_DstIP = other.m_DstIP;
return *this;
}
/**
* @return IP ID value
*/
uint16_t getIpID() const { return m_IpID; }
/**
* @return Source IP address
*/
IPv4Address getSrcIP() const { return m_SrcIP; }
/**
* @return Dest IP address
*/
IPv4Address getDstIP() const { return m_DstIP; }
/**
* Set IP ID
* @param[in] ipID IP ID value to set
*/
void setIpID(uint16_t ipID) { m_IpID = ipID; }
/**
* Set source IPv4 address
* @param[in] srcIP Source IP to set
*/
void setSrcIP(const IPv4Address& srcIP) { m_SrcIP = srcIP; }
/**
* Set dest IPv4 address
* @param[in] dstIP Dest IP to set
*/
void setDstIP(const IPv4Address& dstIP) { m_DstIP = dstIP; }
// implement abstract methods
uint32_t getHashValue() const;
/**
* @return pcpp#IPv4 protocol
*/
ProtocolType getProtocolType() const { return IPv4; }
PacketKey* clone() const { return new IPv4PacketKey(*this); }
private:
uint16_t m_IpID;
IPv4Address m_SrcIP;
IPv4Address m_DstIP;
};
/**
* @class IPv6PacketKey
* Represents a key that can uniquely identify IPv6 fragment packets. The key comprises of source IPv6 address, dest IPv6 address
* and fragment ID (which resides in the IPv6 fragmentation extension)
*/
class IPv6PacketKey : public PacketKey
{
public:
/**
* A default c'tor which zeros all members
*/
IPv6PacketKey() : m_FragmentID(0), m_SrcIP(IPv6Address::Zero), m_DstIP(IPv6Address::Zero) { }
/**
* A c'tor that sets values in each one of the members
* @param[in] fragmentID Fragment ID value
* @param[in] srcip Source IPv6 address
* @param[in] dstip Dest IPv6 address
*/
IPv6PacketKey(uint32_t fragmentID, IPv6Address srcip, IPv6Address dstip) : m_FragmentID(fragmentID), m_SrcIP(srcip), m_DstIP(dstip) { }
/**
* A copy c'tor for this class
* @param[in] other The instance to copy from
*/
IPv6PacketKey(const IPv6PacketKey& other) : PacketKey(other), m_FragmentID(other.m_FragmentID), m_SrcIP(other.m_SrcIP), m_DstIP(other.m_DstIP) { }
/**
* Assignment operator for this class
* @param[in] other The instance to assign from
*/
IPv6PacketKey& operator=(const IPv6PacketKey& other)
{
m_FragmentID = other.m_FragmentID;
m_SrcIP = other.m_SrcIP;
m_SrcIP = other.m_DstIP;
return *this;
}
/**
* @return Fragment ID value
*/
uint32_t getFragmentID() const { return m_FragmentID; }
/**
* @return Source IP address
*/
IPv6Address getSrcIP() const { return m_SrcIP; }
/**
* @return Dest IP address
*/
IPv6Address getDstIP() const { return m_DstIP; }
/**
* Set fragment ID
* @param[in] fragID Fragment ID value to set
*/
void setFragmentID(uint32_t fragID) { m_FragmentID = fragID; }
/**
* Set source IPv6 address
* @param[in] srcIP Source IP to set
*/
void setSrcIP(const IPv6Address& srcIP) { m_SrcIP = srcIP; }
/**
* Set dest IPv6 address
* @param[in] dstIP Dest IP to set
*/
void setDstIP(const IPv6Address& dstIP) { m_DstIP = dstIP; }
// implement abstract methods
uint32_t getHashValue() const;
/**
* @return pcpp#IPv6 protocol
*/
ProtocolType getProtocolType() const { return IPv6; }
PacketKey* clone() const { return new IPv6PacketKey(*this); }
private:
uint32_t m_FragmentID;
IPv6Address m_SrcIP;
IPv6Address m_DstIP;
};
/**
* @typedef OnFragmentsClean
* The IP reassembly mechanism has a certain capacity of concurrent packets it can handle. This capacity is determined in its c'tor
* (default value is #PCPP_IP_REASSEMBLY_DEFAULT_MAX_PACKETS_TO_STORE). When traffic volume exceeds this capacity the mechanism starts
* dropping packets in a LRU manner (least recently used are dropped first). Whenever a packet is dropped this callback is fired
* @param[in] key A pointer to the identifier of the packet that is being dropped
* @param[in] userCookie A pointer to the cookie provided by the user in IPReassemby c'tor (or NULL if no cookie provided)
*/
typedef void (*OnFragmentsClean)(const PacketKey* key, void* userCookie);
/**
* An enum representing the status returned from processing a fragment
*/
enum ReassemblyStatus
{
/** The processed packet isn't of type IPv4 or IPv6 */
NON_IP_PACKET = 0x00,
/** The processed packet isn't a fragment */
NON_FRAGMENT = 0x01,
/** The processed fragment is the first fragment */
FIRST_FRAGMENT = 0x02,
/** The processed fragment is a fragment (but not the first one) */
FRAGMENT = 0x04,
/** The processed fragment is not the fragment that was expected at this time */
OUT_OF_ORDER_FRAGMENT = 0x08,
/** The processed fragment is malformed, meaning a fragment which has offset of zero but isn't the first fragment */
MALFORMED_FRAGMENT = 0x10,
/** Packet is now fully reassembled */
REASSEMBLED = 0x20
};
/**
* A c'tor for this class.
* @param[in] onFragmentsCleanCallback The callback to be called when packets are dropped due to capacity limit.
* Please read more about capacity limit in IPReassembly.h file description. This parameter is optional, default value is NULL (no callback)
* @param[in] callbackUserCookie A pointer to an object provided by the user. This pointer will be returned when invoking the
* onFragmentsCleanCallback. This parameter is optional, default cookie is NULL
* @param[in] maxPacketsToStore Set the capacity limit of the IP reassembly mechanism. Default capacity is #PCPP_IP_REASSEMBLY_DEFAULT_MAX_PACKETS_TO_STORE
*/
IPReassembly(OnFragmentsClean onFragmentsCleanCallback = NULL, void *callbackUserCookie = NULL, size_t maxPacketsToStore = PCPP_IP_REASSEMBLY_DEFAULT_MAX_PACKETS_TO_STORE)
: m_PacketLRU(maxPacketsToStore), m_OnFragmentsCleanCallback(onFragmentsCleanCallback), m_CallbackUserCookie(callbackUserCookie) {}
/**
* A d'tor for this class
*/
~IPReassembly();
/**
* The main API that drives IPReassembly. This method should be called whenever a fragment arrives. This method finds the relevant
* packet this fragment belongs to and runs the IP reassembly logic that is described in IPReassembly.h.
* @param[in] fragment The fragment to process (IPv4 or IPv6). Please notice that the reassembly logic doesn't change or manipulate
* this object in any way. All of its data is copied to internal structures and manipulated there
* @param[out] status An indication of the packet reassembly status following the processing of this fragment. Possible values are:
* - The input fragment is not a IPv4 or IPv6 packet
* - The input fragment is not a IPv4 or IPv6 fragment packet
* - The input fragment is the first fragment of the packet
* - The input fragment is not the first or last fragment
* - The input fragment came out-of-order, meaning that wasn't the fragment that was currently expected (it's data is copied to
* the out-of-order fragment list)
* - The input fragment is malformed and will be ignored
* - The input fragment is the last one and the packet is now fully reassembled. In this case the return value will contain
* a pointer to the reassebmled packet
* @param[in] parseUntil Optional parameter. Parse the reassembled packet until you reach a certain protocol (inclusive). Can be useful for cases when you need to parse only up to a
* certain layer and want to avoid the performance impact and memory consumption of parsing the whole packet. Default value is ::UnknownProtocol which means don't take this
* parameter into account
* @param[in] parseUntilLayer Optional parameter. Parse the reassembled packet until you reach a certain layer in the OSI model (inclusive). Can be useful for cases when you need to
* parse only up to a certain OSI layer (for example transport layer) and want to avoid the performance impact and memory consumption of parsing the whole packet.
* Default value is ::OsiModelLayerUnknown which means don't take this parameter into account
* @return
* - If the input fragment isn't an IPv4/IPv6 packet or if it isn't an IPv4/IPv6 fragment, the return value is a pointer to the input fragment
* - If the input fragment is the last one and the reassembled packet is ready - a pointer to the reassembled packet is
* returned. Notice it's the user's responsibility to free this pointer when done using it
* - If the reassembled packet isn't ready then NULL is returned
*/
Packet* processPacket(Packet* fragment, ReassemblyStatus& status, ProtocolType parseUntil = UnknownProtocol, OsiModelLayer parseUntilLayer = OsiModelLayerUnknown);
/**
* The main API that drives IPReassembly. This method should be called whenever a fragment arrives. This method finds the relevant
* packet this fragment belongs to and runs the IPv4 reassembly logic that is described in IPReassembly.h.
* @param[in] fragment The fragment to process (IPv4 or IPv6). Please notice that the reassembly logic doesn't change or manipulate
* this object in any way. All of its data is copied to internal structures and manipulated there
* @param[out] status An indication of the packet reassembly status following the processing of this fragment. Possible values are:
* - The input fragment is not a IPv4 or IPv6 packet
* - The input fragment is not a IPv4 or IPv6 fragment packet
* - The input fragment is the first fragment of the packet
* - The input fragment is not the first or last fragment
* - The input fragment came out-of-order, meaning that wasn't the fragment that was currently expected (it's data is copied to
* the out-of-order fragment list)
* - The input fragment is malformed and will be ignored
* - The input fragment is the last one and the packet is now fully reassembled. In this case the return value will contain
* a pointer to the reassebmled packet
* @param[in] parseUntil Optional parameter. Parse the raw and reassembled packets until you reach a certain protocol (inclusive). Can be useful for cases when you need to parse only up to a
* certain layer and want to avoid the performance impact and memory consumption of parsing the whole packet. Default value is ::UnknownProtocol which means don't take this
* parameter into account
* @param[in] parseUntilLayer Optional parameter. Parse the raw and reassembled packets until you reach a certain layer in the OSI model (inclusive). Can be useful for cases when you need to
* parse only up to a certain OSI layer (for example transport layer) and want to avoid the performance impact and memory consumption of parsing the whole packet.
* Default value is ::OsiModelLayerUnknown which means don't take this parameter into account
* @return
* - If the input fragment isn't an IPv4/IPv6 packet or if it isn't an IPv4/IPv6 fragment, the return value is a pointer to a Packet object
* wrapping the input fragment RawPacket object. It's the user responsibility to free this instance
* - If the input fragment is the last one and the reassembled packet is ready - a pointer to the reassembled packet is
* returned. Notice it's the user's responsibility to free this pointer when done using it
* - If the reassembled packet isn't ready then NULL is returned
*/
Packet* processPacket(RawPacket* fragment, ReassemblyStatus& status, ProtocolType parseUntil = UnknownProtocol, OsiModelLayer parseUntilLayer = OsiModelLayerUnknown);
/**
* Get a partially reassembled packet. This method returns all the reassembled data that was gathered so far which is obviously not
* a fully reassembled packet (otherwise it would have returned by processPacket()). Notice all data is being copied so the user is
* responsible to free the returned Packet object when done using it. Notice#2 - calling this method doesn't interfere with the
* reassembly of this packet - all internal structures and data remain
* @param[in] key The identifiers of the packet to return
* @return A pointer to a Packet object containing the partially reassembled packet. Notice the user is responsible to free this
* object when done using it
*/
Packet* getCurrentPacket(const PacketKey& key);
/**
* Remove a partially reassembled packet from all internal structures. That means that if another fragment of this packet appears
* it will be treated as a new packet
* @param[in] key The identifiers of the packet to remove
*/
void removePacket(const PacketKey& key);
/**
* Get the maximum capacity as determined in the c'tor
*/
size_t getMaxCapacity() const { return m_PacketLRU.getMaxSize(); }
/**
* Get the current number of packets being processed
*/
size_t getCurrentCapacity() const { return m_FragmentMap.size(); }
private:
struct IPFragment
{
uint16_t fragmentOffset;
bool lastFragment;
uint8_t* fragmentData;
size_t fragmentDataLen;
IPFragment() { fragmentOffset = 0; lastFragment = false; fragmentData = NULL; fragmentDataLen = 0; }
~IPFragment() { delete [] fragmentData; }
};
struct IPFragmentData
{
uint16_t currentOffset;
RawPacket* data;
bool deleteData;
uint32_t fragmentID;
PacketKey* packetKey;
PointerVector<IPFragment> outOfOrderFragments;
IPFragmentData(PacketKey* pktKey, uint32_t fragId) { currentOffset = 0; data = NULL; deleteData = true; fragmentID = fragId; packetKey = pktKey; }
~IPFragmentData() { delete packetKey; if (deleteData && data != NULL) { delete data; } }
};
LRUList<uint32_t> m_PacketLRU;
std::map<uint32_t, IPFragmentData*> m_FragmentMap;
OnFragmentsClean m_OnFragmentsCleanCallback;
void* m_CallbackUserCookie;
void addNewFragment(uint32_t hash, IPFragmentData* fragData);
bool matchOutOfOrderFragments(IPFragmentData* fragData);
};
} // namespace pcpp
#endif // PACKETPP_IP_REASSEMBLY