#define LOG_MODULE PacketLogModuleTcpReassembly
#include "TcpReassembly.h"
#include "TcpLayer.h"
#include "IPLayer.h"
#include "PacketUtils.h"
#include "Logger.h"
#include <sstream>
#include <vector>
#include "EndianPortable.h"
#include "TimespecTimeval.h"
#ifdef _MSC_VER
#include <time.h>
#endif
#define PURGE_FREQ_SECS 1
#define SEQ_LT(a,b) ((int32_t)((a)-(b)) < 0)
#define SEQ_LEQ(a,b) ((int32_t)((a)-(b)) <= 0)
#define SEQ_GT(a,b) ((int32_t)((a)-(b)) > 0)
#define SEQ_GEQ(a,b) ((int32_t)((a)-(b)) >= 0)
namespace pcpp
{
static timeval timespecToTimeval(const timespec& in)
{
timeval out;
TIMESPEC_TO_TIMEVAL(&out, &in);
return out;
}
TcpReassembly::TcpReassembly(OnTcpMessageReady onMessageReadyCallback, void* userCookie, OnTcpConnectionStart onConnectionStartCallback, OnTcpConnectionEnd onConnectionEndCallback, const TcpReassemblyConfiguration &config)
{
m_OnMessageReadyCallback = onMessageReadyCallback;
m_UserCookie = userCookie;
m_OnConnStart = onConnectionStartCallback;
m_OnConnEnd = onConnectionEndCallback;
m_ClosedConnectionDelay = (config.closedConnectionDelay > 0) ? config.closedConnectionDelay : 5;
m_RemoveConnInfo = config.removeConnInfo;
m_MaxNumToClean = (config.removeConnInfo == true && config.maxNumToClean == 0) ? 30 : config.maxNumToClean;
m_MaxOutOfOrderFragments = config.maxOutOfOrderFragments;
m_PurgeTimepoint = time(nullptr) + PURGE_FREQ_SECS;
m_EnableBaseBufferClearCondition = config.enableBaseBufferClearCondition;
}
TcpReassembly::ReassemblyStatus TcpReassembly::reassemblePacket(Packet& tcpData)
{
if (m_RemoveConnInfo == true)
{
if (time(nullptr) >= m_PurgeTimepoint)
{
purgeClosedConnections();
m_PurgeTimepoint = time(nullptr) + PURGE_FREQ_SECS;
}
}
IPAddress srcIP, dstIP;
if (tcpData.isPacketOfType(IP))
{
const IPLayer* ipLayer = tcpData.getLayerOfType<IPLayer>();
srcIP = ipLayer->getSrcIPAddress();
dstIP = ipLayer->getDstIPAddress();
}
else
return NonIpPacket;
if (!srcIP.isValid() || !dstIP.isValid())
return NonIpPacket;
TcpLayer* tcpLayer = tcpData.getLayerOfType<TcpLayer>(true); if (tcpLayer == nullptr)
{
return NonTcpPacket;
}
if (tcpData.isPacketOfType(ICMP))
{
PCPP_LOG_DEBUG("Packet is of type ICMP so TCP data is probably part of the ICMP message. Ignoring this packet");
return NonTcpPacket;
}
ReassemblyStatus status = TcpMessageHandled;
size_t tcpPayloadSize = tcpLayer->getLayerPayloadSize();
bool isFin = (tcpLayer->getTcpHeader()->finFlag == 1);
bool isRst = (tcpLayer->getTcpHeader()->rstFlag == 1);
bool isFinOrRst = isFin || isRst;
if (tcpPayloadSize == 0 && tcpLayer->getTcpHeader()->synFlag == 0 && !isFinOrRst)
{
return Ignore_PacketWithNoData;
}
TcpReassemblyData* tcpReassemblyData = nullptr;
uint32_t flowKey = hash5Tuple(&tcpData);
timeval currTime = timespecToTimeval(tcpData.getRawPacket()->getPacketTimeStamp());
ConnectionList::iterator iter = m_ConnectionList.find(flowKey);
if (iter == m_ConnectionList.end())
{
std::pair<ConnectionList::iterator, bool> pair = m_ConnectionList.insert(std::make_pair(flowKey, TcpReassemblyData()));
tcpReassemblyData = &pair.first->second;
tcpReassemblyData->connData.srcIP = srcIP;
tcpReassemblyData->connData.dstIP = dstIP;
tcpReassemblyData->connData.srcPort = tcpLayer->getSrcPort();
tcpReassemblyData->connData.dstPort = tcpLayer->getDstPort();
tcpReassemblyData->connData.flowKey = flowKey;
tcpReassemblyData->connData.setStartTime(currTime);
m_ConnectionInfo[flowKey] = tcpReassemblyData->connData;
if (m_OnConnStart != nullptr)
m_OnConnStart(tcpReassemblyData->connData, m_UserCookie);
}
else {
if (iter->second.closed)
{
PCPP_LOG_DEBUG("Ignoring packet of already closed flow [0x" << std::hex << flowKey << "]");
return Ignore_PacketOfClosedFlow;
}
tcpReassemblyData = &iter->second;
if (currTime.tv_sec > tcpReassemblyData->connData.endTime.tv_sec)
{
tcpReassemblyData->connData.setEndTime(currTime);
m_ConnectionInfo[flowKey].setEndTime(currTime);
}
else if (currTime.tv_sec == tcpReassemblyData->connData.endTime.tv_sec)
{
if (currTime.tv_usec > tcpReassemblyData->connData.endTime.tv_usec)
{
tcpReassemblyData->connData.setEndTime(currTime);
m_ConnectionInfo[flowKey].setEndTime(currTime);
}
}
}
timeval timestampOfTheReceivedPacket = currTime;
int8_t sideIndex = -1;
bool first = false;
uint16_t srcPort = tcpLayer->getTcpHeader()->portSrc;
if (tcpReassemblyData->numOfSides == 0)
{
PCPP_LOG_DEBUG("Setting side for new connection");
sideIndex = 0;
tcpReassemblyData->twoSides[sideIndex].srcIP = srcIP;
tcpReassemblyData->twoSides[sideIndex].srcPort = srcPort;
tcpReassemblyData->numOfSides++;
first = true;
}
else if (tcpReassemblyData->numOfSides == 1)
{
if (tcpReassemblyData->twoSides[0].srcPort == srcPort && tcpReassemblyData->twoSides[0].srcIP == srcIP)
{
sideIndex = 0;
}
else
{
PCPP_LOG_DEBUG("Setting second side of a connection");
sideIndex = 1;
tcpReassemblyData->twoSides[sideIndex].srcIP = srcIP;
tcpReassemblyData->twoSides[sideIndex].srcPort = srcPort;
tcpReassemblyData->numOfSides++;
first = true;
}
}
else if (tcpReassemblyData->numOfSides == 2)
{
if (tcpReassemblyData->twoSides[0].srcPort == srcPort && tcpReassemblyData->twoSides[0].srcIP == srcIP)
{
sideIndex = 0;
}
else if (tcpReassemblyData->twoSides[1].srcPort == srcPort && tcpReassemblyData->twoSides[1].srcIP == srcIP)
{
sideIndex = 1;
}
else
{
PCPP_LOG_ERROR("Error occurred - packet doesn't match either side of the connection!!");
return Error_PacketDoesNotMatchFlow;
}
}
else
{
PCPP_LOG_ERROR("Error occurred - connection has more than 2 sides!!");
return Error_PacketDoesNotMatchFlow;
}
if (tcpReassemblyData->twoSides[sideIndex].gotFinOrRst)
{
PCPP_LOG_DEBUG("Got a packet after FIN or RST were already seen on this side (" << sideIndex << "). Ignoring this packet");
return Ignore_PacketOfClosedFlow;
}
if (isFinOrRst && tcpPayloadSize == 0)
{
PCPP_LOG_DEBUG("Got FIN or RST packet without data on side " << sideIndex);
handleFinOrRst(tcpReassemblyData, sideIndex, flowKey, isRst);
return FIN_RSTWithNoData;
}
if (m_EnableBaseBufferClearCondition && !first && tcpPayloadSize > 0 && tcpReassemblyData->prevSide != -1 && tcpReassemblyData->prevSide != sideIndex &&
tcpReassemblyData->twoSides[tcpReassemblyData->prevSide].tcpFragmentList.size() > 0)
{
PCPP_LOG_DEBUG("Seeing a first data packet from a different side. Previous side was " << tcpReassemblyData->prevSide << ", current side is " << sideIndex);
checkOutOfOrderFragments(tcpReassemblyData, tcpReassemblyData->prevSide, true);
}
tcpReassemblyData->prevSide = sideIndex;
uint32_t sequence = be32toh(tcpLayer->getTcpHeader()->sequenceNumber);
if (first)
{
PCPP_LOG_DEBUG("First data from this side of the connection");
tcpReassemblyData->twoSides[sideIndex].sequence = sequence + tcpPayloadSize;
if (tcpLayer->getTcpHeader()->synFlag != 0)
tcpReassemblyData->twoSides[sideIndex].sequence++;
if (tcpPayloadSize != 0 && m_OnMessageReadyCallback != nullptr)
{
TcpStreamData streamData(tcpLayer->getLayerPayload(), tcpPayloadSize, 0, tcpReassemblyData->connData, timestampOfTheReceivedPacket);
m_OnMessageReadyCallback(sideIndex, streamData, m_UserCookie);
}
status = TcpMessageHandled;
if (isFinOrRst)
handleFinOrRst(tcpReassemblyData, sideIndex, flowKey, isRst);
return status;
}
if (SEQ_LT(sequence, tcpReassemblyData->twoSides[sideIndex].sequence))
{
PCPP_LOG_DEBUG("Found new data with the sequence lower than expected");
uint32_t newSequence = sequence + tcpPayloadSize;
if (SEQ_GT(newSequence, tcpReassemblyData->twoSides[sideIndex].sequence))
{
uint32_t newLength = tcpReassemblyData->twoSides[sideIndex].sequence - sequence;
PCPP_LOG_DEBUG("Although sequence is lower than expected payload is long enough to contain new data. Calling the callback with the new data");
tcpReassemblyData->twoSides[sideIndex].sequence += tcpPayloadSize - newLength;
if (m_OnMessageReadyCallback != nullptr)
{
TcpStreamData streamData(tcpLayer->getLayerPayload() + newLength, tcpPayloadSize - newLength, 0, tcpReassemblyData->connData, timestampOfTheReceivedPacket);
m_OnMessageReadyCallback(sideIndex, streamData, m_UserCookie);
}
status = TcpMessageHandled;
}
else
{
status = Ignore_Retransimission;
}
if (isFinOrRst)
handleFinOrRst(tcpReassemblyData, sideIndex, flowKey, isRst);
return status;
}
else if (sequence == tcpReassemblyData->twoSides[sideIndex].sequence)
{
if (tcpPayloadSize == 0)
{
PCPP_LOG_DEBUG("Payload length is 0, doing nothing");
if (isFinOrRst)
{
handleFinOrRst(tcpReassemblyData, sideIndex, flowKey, isRst);
status = FIN_RSTWithNoData;
}
else
{
status = Ignore_PacketWithNoData;
}
return status;
}
PCPP_LOG_DEBUG("Found new data with expected sequence. Calling the callback");
tcpReassemblyData->twoSides[sideIndex].sequence += tcpPayloadSize;
if (tcpLayer->getTcpHeader()->synFlag != 0)
tcpReassemblyData->twoSides[sideIndex].sequence++;
if (m_OnMessageReadyCallback != nullptr)
{
TcpStreamData streamData(tcpLayer->getLayerPayload(), tcpPayloadSize, 0, tcpReassemblyData->connData,timestampOfTheReceivedPacket);
m_OnMessageReadyCallback(sideIndex, streamData, m_UserCookie);
}
status = TcpMessageHandled;
checkOutOfOrderFragments(tcpReassemblyData, sideIndex, false);
if (isFinOrRst)
handleFinOrRst(tcpReassemblyData, sideIndex, flowKey, isRst);
return status;
}
else
{
if (tcpPayloadSize == 0)
{
PCPP_LOG_DEBUG("Payload length is 0, doing nothing");
if (isFinOrRst)
{
handleFinOrRst(tcpReassemblyData, sideIndex, flowKey, isRst);
status = FIN_RSTWithNoData;
}
else
{
status = Ignore_PacketWithNoData;
}
return status;
}
TcpFragment* newTcpFrag = new TcpFragment();
newTcpFrag->data = new uint8_t[tcpPayloadSize];
newTcpFrag->dataLength = tcpPayloadSize;
newTcpFrag->sequence = sequence;
newTcpFrag->timestamp = timestampOfTheReceivedPacket;
memcpy(newTcpFrag->data, tcpLayer->getLayerPayload(), tcpPayloadSize);
tcpReassemblyData->twoSides[sideIndex].tcpFragmentList.pushBack(newTcpFrag);
PCPP_LOG_DEBUG("Found out-of-order packet and added a new TCP fragment with size " << tcpPayloadSize << " to the out-of-order list of side " << sideIndex);
status = OutOfOrderTcpMessageBuffered;
if (m_MaxOutOfOrderFragments > 0 && tcpReassemblyData->twoSides[sideIndex].tcpFragmentList.size() > m_MaxOutOfOrderFragments)
{
checkOutOfOrderFragments(tcpReassemblyData, sideIndex, false);
}
if (isFinOrRst)
{
handleFinOrRst(tcpReassemblyData, sideIndex, flowKey, isRst);
}
return status;
}
}
TcpReassembly::ReassemblyStatus TcpReassembly::reassemblePacket(RawPacket* tcpRawData)
{
Packet parsedPacket(tcpRawData, false);
return reassemblePacket(parsedPacket);
}
static std::string prepareMissingDataMessage(uint32_t missingDataLen)
{
std::stringstream missingDataTextStream;
missingDataTextStream << '[' << missingDataLen << " bytes missing]";
return missingDataTextStream.str();
}
void TcpReassembly::handleFinOrRst(TcpReassemblyData* tcpReassemblyData, int8_t sideIndex, uint32_t flowKey, bool isRst)
{
if (tcpReassemblyData->twoSides[sideIndex].gotFinOrRst)
return;
PCPP_LOG_DEBUG("Handling FIN or RST packet on side " << sideIndex);
tcpReassemblyData->twoSides[sideIndex].gotFinOrRst = true;
int otherSideIndex = 1 - sideIndex;
if (tcpReassemblyData->twoSides[otherSideIndex].gotFinOrRst)
{
closeConnectionInternal(flowKey, TcpReassembly::TcpReassemblyConnectionClosedByFIN_RST);
return;
}
else
checkOutOfOrderFragments(tcpReassemblyData, sideIndex, true);
if(isRst)
closeConnectionInternal(flowKey, TcpReassembly::TcpReassemblyConnectionClosedByFIN_RST);
}
void TcpReassembly::checkOutOfOrderFragments(TcpReassemblyData* tcpReassemblyData, int8_t sideIndex, bool cleanWholeFragList)
{
bool foundSomething = false;
do
{
PCPP_LOG_DEBUG("Starting first iteration of checkOutOfOrderFragments - looking for fragments that match the current sequence or have smaller sequence");
int index = 0;
foundSomething = false;
do
{
index = 0;
foundSomething = false;
while (index < (int)tcpReassemblyData->twoSides[sideIndex].tcpFragmentList.size())
{
TcpFragment* curTcpFrag = tcpReassemblyData->twoSides[sideIndex].tcpFragmentList.at(index);
if (curTcpFrag->sequence == tcpReassemblyData->twoSides[sideIndex].sequence)
{
tcpReassemblyData->twoSides[sideIndex].sequence += curTcpFrag->dataLength;
if (curTcpFrag->data != nullptr)
{
PCPP_LOG_DEBUG("Found an out-of-order packet matching to the current sequence with size " << curTcpFrag->dataLength << " on side " << sideIndex << ". Pulling it out of the list and sending the data to the callback");
if (m_OnMessageReadyCallback != nullptr)
{
TcpStreamData streamData(curTcpFrag->data, curTcpFrag->dataLength, 0, tcpReassemblyData->connData, curTcpFrag->timestamp);
m_OnMessageReadyCallback(sideIndex, streamData, m_UserCookie);
}
}
tcpReassemblyData->twoSides[sideIndex].tcpFragmentList.erase(tcpReassemblyData->twoSides[sideIndex].tcpFragmentList.begin() + index);
foundSomething = true;
continue;
}
if (SEQ_LT(curTcpFrag->sequence, tcpReassemblyData->twoSides[sideIndex].sequence))
{
uint32_t newSequence = curTcpFrag->sequence + curTcpFrag->dataLength;
if (SEQ_GT(newSequence, tcpReassemblyData->twoSides[sideIndex].sequence))
{
uint32_t newLength = tcpReassemblyData->twoSides[sideIndex].sequence - curTcpFrag->sequence;
PCPP_LOG_DEBUG("Found a fragment in the out-of-order list which its sequence is lower than expected but its payload is long enough to contain new data. "
"Calling the callback with the new data. Fragment size is " << curTcpFrag->dataLength << " on side " << sideIndex << ", new data size is " << (int)(curTcpFrag->dataLength - newLength));
tcpReassemblyData->twoSides[sideIndex].sequence += curTcpFrag->dataLength - newLength;
if (m_OnMessageReadyCallback != nullptr)
{
TcpStreamData streamData(curTcpFrag->data + newLength, curTcpFrag->dataLength - newLength, 0, tcpReassemblyData->connData, curTcpFrag->timestamp);
m_OnMessageReadyCallback(sideIndex, streamData, m_UserCookie);
}
foundSomething = true;
}
else
{
PCPP_LOG_DEBUG("Found a fragment in the out-of-order list which doesn't contain any new data, ignoring it. Fragment size is " << curTcpFrag->dataLength << " on side " << sideIndex);
}
tcpReassemblyData->twoSides[sideIndex].tcpFragmentList.erase(tcpReassemblyData->twoSides[sideIndex].tcpFragmentList.begin() + index);
continue;
}
index++;
}
} while (foundSomething);
if (!cleanWholeFragList && (m_MaxOutOfOrderFragments == 0 || tcpReassemblyData->twoSides[sideIndex].tcpFragmentList.size() <= m_MaxOutOfOrderFragments))
{
return;
}
PCPP_LOG_DEBUG("Starting second iteration of checkOutOfOrderFragments - handle missing data");
uint32_t closestSequence = 0xffffffff;
bool closestSequenceDefined = false;
int closestSequenceFragIndex = -1;
index = 0;
while (index < (int)tcpReassemblyData->twoSides[sideIndex].tcpFragmentList.size())
{
TcpFragment* curTcpFrag = tcpReassemblyData->twoSides[sideIndex].tcpFragmentList.at(index);
if (!closestSequenceDefined || SEQ_LT(curTcpFrag->sequence, closestSequence))
{
closestSequence = curTcpFrag->sequence;
closestSequenceFragIndex = index;
closestSequenceDefined = true;
}
index++;
}
if (closestSequenceFragIndex > -1)
{
TcpFragment* curTcpFrag = tcpReassemblyData->twoSides[sideIndex].tcpFragmentList.at(closestSequenceFragIndex);
uint32_t missingDataLen = curTcpFrag->sequence - tcpReassemblyData->twoSides[sideIndex].sequence;
tcpReassemblyData->twoSides[sideIndex].sequence = curTcpFrag->sequence + curTcpFrag->dataLength;
if (curTcpFrag->data != nullptr)
{
if (m_OnMessageReadyCallback != nullptr)
{
std::string missingDataTextStr = prepareMissingDataMessage(missingDataLen);
std::vector<uint8_t> dataWithMissingDataText;
dataWithMissingDataText.reserve(missingDataTextStr.length() + curTcpFrag->dataLength);
dataWithMissingDataText.insert(dataWithMissingDataText.end(), missingDataTextStr.begin(), missingDataTextStr.end());
dataWithMissingDataText.insert(dataWithMissingDataText.end(), curTcpFrag->data, curTcpFrag->data + curTcpFrag->dataLength);
TcpStreamData streamData(&dataWithMissingDataText[0], dataWithMissingDataText.size(), missingDataLen, tcpReassemblyData->connData, curTcpFrag->timestamp);
m_OnMessageReadyCallback(sideIndex, streamData, m_UserCookie);
PCPP_LOG_DEBUG("Found missing data on side " << sideIndex << ": " << missingDataLen << " byte are missing. Sending the closest fragment which is in size " << curTcpFrag->dataLength << " + missing text message which size is " << missingDataTextStr.length());
}
}
tcpReassemblyData->twoSides[sideIndex].tcpFragmentList.erase(tcpReassemblyData->twoSides[sideIndex].tcpFragmentList.begin() + closestSequenceFragIndex);
PCPP_LOG_DEBUG("Calling checkOutOfOrderFragments again from the start");
foundSomething = true;
}
} while (foundSomething);
}
void TcpReassembly::closeConnection(uint32_t flowKey)
{
closeConnectionInternal(flowKey, TcpReassembly::TcpReassemblyConnectionClosedManually);
}
void TcpReassembly::closeConnectionInternal(uint32_t flowKey, ConnectionEndReason reason)
{
ConnectionList::iterator iter = m_ConnectionList.find(flowKey);
if (iter == m_ConnectionList.end())
{
PCPP_LOG_ERROR("Cannot close flow with key 0x" << std::uppercase << std::hex << flowKey << ": cannot find flow");
return;
}
TcpReassemblyData& tcpReassemblyData = iter->second;
if (tcpReassemblyData.closed) return;
PCPP_LOG_DEBUG("Closing connection with flow key 0x" << std::hex << flowKey);
PCPP_LOG_DEBUG("Calling checkOutOfOrderFragments on side 0");
checkOutOfOrderFragments(&tcpReassemblyData, 0, true);
PCPP_LOG_DEBUG("Calling checkOutOfOrderFragments on side 1");
checkOutOfOrderFragments(&tcpReassemblyData, 1, true);
if (m_OnConnEnd != nullptr)
m_OnConnEnd(tcpReassemblyData.connData, reason, m_UserCookie);
tcpReassemblyData.closed = true; insertIntoCleanupList(flowKey);
PCPP_LOG_DEBUG("Connection with flow key 0x" << std::hex << flowKey << " is closed");
}
void TcpReassembly::closeAllConnections()
{
PCPP_LOG_DEBUG("Closing all flows");
ConnectionList::iterator iter = m_ConnectionList.begin(), iterEnd = m_ConnectionList.end();
for (; iter != iterEnd; ++iter)
{
TcpReassemblyData& tcpReassemblyData = iter->second;
if (tcpReassemblyData.closed) continue;
uint32_t flowKey = tcpReassemblyData.connData.flowKey;
PCPP_LOG_DEBUG("Closing connection with flow key 0x" << std::hex << flowKey);
PCPP_LOG_DEBUG("Calling checkOutOfOrderFragments on side 0");
checkOutOfOrderFragments(&tcpReassemblyData, 0, true);
PCPP_LOG_DEBUG("Calling checkOutOfOrderFragments on side 1");
checkOutOfOrderFragments(&tcpReassemblyData, 1, true);
if (m_OnConnEnd != nullptr)
m_OnConnEnd(tcpReassemblyData.connData, TcpReassemblyConnectionClosedManually, m_UserCookie);
tcpReassemblyData.closed = true; insertIntoCleanupList(flowKey);
PCPP_LOG_DEBUG("Connection with flow key 0x" << std::hex << flowKey << " is closed");
}
}
int TcpReassembly::isConnectionOpen(const ConnectionData& connection) const
{
ConnectionList::const_iterator iter = m_ConnectionList.find(connection.flowKey);
if (iter != m_ConnectionList.end())
return iter->second.closed == false;
return -1;
}
void TcpReassembly::insertIntoCleanupList(uint32_t flowKey)
{
std::pair<CleanupList::iterator, bool> pair = m_CleanupList.insert(std::make_pair(time(nullptr) + m_ClosedConnectionDelay, CleanupList::mapped_type()));
CleanupList::mapped_type& keysList = pair.first->second;
keysList.push_front(flowKey);
}
uint32_t TcpReassembly::purgeClosedConnections(uint32_t maxNumToClean)
{
uint32_t count = 0;
if (maxNumToClean == 0)
maxNumToClean = m_MaxNumToClean;
CleanupList::iterator iterTime = m_CleanupList.begin(), iterTimeEnd = m_CleanupList.upper_bound(time(nullptr));
while (iterTime != iterTimeEnd && count < maxNumToClean)
{
CleanupList::mapped_type& keysList = iterTime->second;
for (; !keysList.empty() && count < maxNumToClean; ++count)
{
CleanupList::mapped_type::const_reference key = keysList.front();
m_ConnectionInfo.erase(key);
m_ConnectionList.erase(key);
keysList.pop_front();
}
if (keysList.empty())
m_CleanupList.erase(iterTime++);
else
++iterTime;
}
return count;
}
}