#ifndef PACKETPP_IP_REASSEMBLY #define PACKETPP_IP_REASSEMBLY #include "Packet.h" #include "LRUList.h" #include "IpAddress.h" #include "PointerVector.h" #include /** * @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.
* 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.
* * 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() = default; /** * @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() = default; // private copy c'tor PacketKey(const PacketKey& other) = default; }; /** * @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_DstIP = 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 */ explicit 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 reassembled 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. Note that setting this to a protocol which doesn't * include the IP-Layer will result in IPReassembly not finding the IP-Layer and thus failing to work properly. 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. * Note that setting this value to OsiModelPhysicalLayer will result in IPReassembly not finding the IP-layer and thus failing to work properly. * 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 reassembled 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. Note that setting this to a protocol which doesn't * include the IP-Layer will result in IPReassembly not finding the IP-Layer and thus failing to work properly. 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. * Note that setting this value to OsiModelPhysicalLayer will result in IPReassembly not finding the IP-layer and thus failing to work properly. *Default value is ::UnknownProtocol which means don't take this parameter into account * 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 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 m_PacketLRU; std::map 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