Skip to content

Comments

Add TcpSorter to support sorting TCP segments.#337

Closed
rickyzhang82 wants to merge 2 commits intoseladb:masterfrom
rickyzhang82:pr-tcp-sorter
Closed

Add TcpSorter to support sorting TCP segments.#337
rickyzhang82 wants to merge 2 commits intoseladb:masterfrom
rickyzhang82:pr-tcp-sorter

Conversation

@rickyzhang82
Copy link
Contributor

I have performed online capturing test and offline pcap file test. The result looks good.

Also, I ran valgrind to check memory leak for hours. The shared pointer makes my life easier. I did find pcap library itself has memory leak when lists NIC. I didn't look deeper into the issue since I can't fix libpcap here.

Regarding to the TCP sorter logic, the TCP packet is flushed to the user once another side sends ACK. You can find more technical detail in the Doxygen header.

Signed-off-by: Ricky Zhang <rickyzhang@gmail.com>
@seladb
Copy link
Owner

seladb commented Dec 20, 2019

Thanks for working on this!

Could you please check the CI failures? I had a quick look and it seems it's failing because you're using C++11 features. PcapPlusPlus is intended to support older C++ versions as well

@rickyzhang82
Copy link
Contributor Author

C++11 is fully supported by GCC 4.8.1. Any Linux distribution and FreeBSD which are NOT in end of life provide GCC version above 4.8.1.

I won't take away C++11 feature in this PR. It is up to you if you want to add compiler flag -std=c++11 or not. That can address most (if not all) compile problem.

@seladb
Copy link
Owner

seladb commented Dec 20, 2019

Unfortunately I don't think I'll add the -std=c++11 flag for now for the reason I mentioned before. Can I please understand which parts of your code depend on C++11 features? Maybe we can change those pieces of code to make it compatible with older versions of C++

@rickyzhang82
Copy link
Contributor Author

It is just two weeks away that we will start our next decade. I'm surprised that if we still say no to C++11 standard. Anyway, it is a choice you made. I respect that. But I won't patch it by myself. So please drop the PR if nobody is going to make it C++11 free.

I used the following C++11 features to save me from typing and troubles:

  1. Use static_cast/dynamic_cast in casting. (This might not be not C++11, I'd guess)
  2. Use nullptr whenever possible to replace NULL pointer in C++.
  3. Use auto keyword to avoid typing a long string of actual type.
  4. Use smart pointer like shared_ptr whenever I allocate memory from heap.

From 1-3, you can replace it mechanically. The 4th bullet point involves more work. I consider it is a crime in 2020 to ask any programmers to free up memory manually.

@seladb
Copy link
Owner

seladb commented Dec 20, 2019

Thanks @rickyzhang82 for all your work and efforts. Since the rest of the library is not C++11 compatible I prefer not to use C++11 in just specific modules. I'll try to make those changes you suggest to make it C++98 compatible.

Thanks again for all of your contribution to this project

Signed-off-by: Ricky Zhang <rickyzhang@gmail.com>
@rickyzhang82
Copy link
Contributor Author

I patched one minor bug I found. The missing packet callback should pass in expected sequence number, instead.

It is a pity that you don't enable C++11 for older GCC. I have to fork your repository to maintain mine now.

@gx740
Copy link
Contributor

gx740 commented Dec 23, 2019

@rickyzhang82: let me to propose you to use a std::unordered_map instead of std::map in your local repository. The lookup in an unordered_map works in 10 times faster than in std::map.

@rickyzhang82
Copy link
Contributor Author

But I need the ordering of (seq + TCP payload size) when flushing the packets by other’s side ACK.

Does RB tree that slow? I’m curious about this. Because each TCP connection should not hold that many unacknowledged TCP packets at any given time.

I did find that the longer callback function execution, the higher probability the libpcap loses the packet in capturing. I’m thinking about using multithreaded solution. See my discussion issue #342

@gx740
Copy link
Contributor

gx740 commented Dec 23, 2019

The m_ConnectionList is a std::map. It can be replaced by a std::unordered_map.

@rickyzhang82
Copy link
Contributor Author

That’s true. Will change it.

@gx740
Copy link
Contributor

gx740 commented Dec 23, 2019

ConnectionData has two pointers to IPAddress that are expensive to copy. I have prepared the new classes but migration process is not completed.

Change the signature of callback from

typedef void (*OnTcpPacketReady)(int side, ConnectionData connData, SPRawPacket spRawPacket, void* userCookie);

to

typedef void (*OnTcpPacketReady)(int side, const ConnectionData& connData, SPRawPacket spRawPacket, void* userCookie);

@gx740
Copy link
Contributor

gx740 commented Dec 23, 2019

The following code is also heavyweight:

// get packet's source and dest IP address
IPAddress* srcIP = nullptr;
IPAddress* dstIP = nullptr;
IPv4Address srcIP4Addr = IPv4Address::Zero; // !!! mem alloc
IPv6Address srcIP6Addr = IPv6Address::Zero; // !!! mem alloc
IPv4Address dstIP4Addr = IPv4Address::Zero; // !!! mem alloc
IPv6Address dstIP6Addr = IPv6Address::Zero; // !!! mem alloc
if (ipLayer->getProtocol() == IPv4)
{
  srcIP4Addr = (dynamic_cast<IPv4Layer*>(ipLayer))->getSrcIpAddress(); // !!! mem alloc
  srcIP = &srcIP4Addr;
  dstIP4Addr = (dynamic_cast<IPv4Layer*>(ipLayer))->getDstIpAddress(); // !!! mem alloc
  dstIP = &dstIP4Addr;
}
else if (ipLayer->getProtocol() == IPv6)
{
  srcIP6Addr = (dynamic_cast<IPv6Layer*>(ipLayer))->getSrcIpAddress();
  srcIP = &srcIP6Addr;
  dstIP6Addr = (dynamic_cast<IPv6Layer*>(ipLayer))->getDstIpAddress();
  dstIP = &dstIP6Addr;
}

@gx740
Copy link
Contributor

gx740 commented Dec 23, 2019

Look into PR #308 in which I have proposed the performance improvements. You can use it in your code in place where a hash is calculated.

@seladb
Copy link
Owner

seladb commented Dec 23, 2019

@rickyzhang82 I'll review the code soon, but one thing that is missing is UTs. Could you please add some to either Packet++Test or Pcap++Test?

You can take a look at the UTs for TcpReassembly:

PTF_TEST_CASE(TestTcpReassemblySanity)
{
std::string errMsg;
std::vector<RawPacket> packetStream;
PTF_ASSERT(tcpReassemblyReadPcapIntoPacketVec("PcapExamples/one_tcp_stream.pcap", packetStream, errMsg) == true, "Error reading pcap file: %s", errMsg.c_str());
TcpReassemblyMultipleConnStats tcpReassemblyResults;
tcpReassemblyTest(packetStream, tcpReassemblyResults, true, true);
TcpReassemblyMultipleConnStats::Stats &stats = tcpReassemblyResults.stats;
PTF_ASSERT(stats.size() == 1, "Num of connections isn't 1");
PTF_ASSERT(stats.begin()->second.numOfDataPackets == 19, "Num of data packets isn't 19, it's %d", stats.begin()->second.numOfDataPackets);
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[0] == 2, "Num of messages from side 0 isn't 2");
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[1] == 2, "Num of messages from side 1 isn't 2");
PTF_ASSERT(stats.begin()->second.connectionsStarted == true, "Connections wasn't opened");
PTF_ASSERT(stats.begin()->second.connectionsEnded == false, "Connection was ended with FIN or RST");
PTF_ASSERT(stats.begin()->second.connectionsEndedManually == true, "Connection wasn't ended manually");
PTF_ASSERT(stats.begin()->second.connData.srcIP != NULL, "Source IP is NULL");
PTF_ASSERT(stats.begin()->second.connData.dstIP != NULL, "Source IP is NULL");
IPv4Address expectedSrcIP(std::string("10.0.0.1"));
IPv4Address expectedDstIP(std::string("81.218.72.15"));
PTF_ASSERT(stats.begin()->second.connData.srcIP->equals(&expectedSrcIP), "Source IP isn't 10.0.0.1");
PTF_ASSERT(stats.begin()->second.connData.dstIP->equals(&expectedDstIP), "Source IP isn't 81.218.72.15");
PTF_ASSERT(stats.begin()->second.connData.startTime.tv_sec == 1491516383, "Bad start time seconds, expected 1491516383");
PTF_ASSERT(stats.begin()->second.connData.startTime.tv_usec == 915793, "Bad start time microseconds, expected 915793");
PTF_ASSERT(stats.begin()->second.connData.endTime.tv_sec == 0, "Bad end time seconds, expected 0");
PTF_ASSERT(stats.begin()->second.connData.endTime.tv_usec == 0, "Bad end time microseconds, expected 0");
std::string expectedReassemblyData = readFileIntoString(std::string("PcapExamples/one_tcp_stream_output.txt"));
PTF_ASSERT(expectedReassemblyData == stats.begin()->second.reassembledData, "Reassembly data different than expected");
}
PTF_TEST_CASE(TestTcpReassemblyRetran)
{
std::string errMsg;
std::vector<RawPacket> packetStream;
PTF_ASSERT(tcpReassemblyReadPcapIntoPacketVec("PcapExamples/one_tcp_stream.pcap", packetStream, errMsg) == true, "Error reading pcap file: %s", errMsg.c_str());
// retransmission includes exact same data
RawPacket retPacket1 = tcpReassemblyAddRetransmissions(packetStream.at(4), 0, 0);
// retransmission includes 10 bytes less than original data (missing bytes are from the beginning)
RawPacket retPacket2 = tcpReassemblyAddRetransmissions(packetStream.at(10), 10, 0);
// retransmission includes 20 bytes less than original data (missing bytes are from the end)
RawPacket retPacket3 = tcpReassemblyAddRetransmissions(packetStream.at(13), 0, 1340);
// retransmission includes 10 bytes more than original data (original data + 10 bytes)
RawPacket retPacket4 = tcpReassemblyAddRetransmissions(packetStream.at(21), 0, 1430);
// retransmission includes 10 bytes less in the beginning and 20 bytes more at the end
RawPacket retPacket5 = tcpReassemblyAddRetransmissions(packetStream.at(28), 10, 1370);
// retransmission includes 10 bytes less in the beginning and 15 bytes less at the end
RawPacket retPacket6 = tcpReassemblyAddRetransmissions(packetStream.at(34), 10, 91);
packetStream.insert(packetStream.begin() + 5, retPacket1);
packetStream.insert(packetStream.begin() + 12, retPacket2);
packetStream.insert(packetStream.begin() + 16, retPacket3);
packetStream.insert(packetStream.begin() + 25, retPacket4);
packetStream.insert(packetStream.begin() + 33, retPacket5);
packetStream.insert(packetStream.begin() + 40, retPacket6);
TcpReassemblyMultipleConnStats tcpReassemblyResults;
tcpReassemblyTest(packetStream, tcpReassemblyResults, false, true);
TcpReassemblyMultipleConnStats::Stats &stats = tcpReassemblyResults.stats;
PTF_ASSERT(stats.size() == 1, "Num of connections isn't 1");
PTF_ASSERT(stats.begin()->second.numOfDataPackets == 21, "Num of data packets isn't 21, it's %d", stats.begin()->second.numOfDataPackets);
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[0] == 2, "Num of messages from side 0 isn't 2");
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[1] == 2, "Num of messages from side 1 isn't 2");
std::string expectedReassemblyData = readFileIntoString(std::string("PcapExamples/one_tcp_stream_retransmission_output.txt"));
PTF_ASSERT(expectedReassemblyData == stats.begin()->second.reassembledData, "Reassembly data different than expected");
}
PTF_TEST_CASE(TestTcpReassemblyMissingData)
{
std::string errMsg;
std::vector<RawPacket> packetStream;
PTF_ASSERT(tcpReassemblyReadPcapIntoPacketVec("PcapExamples/one_tcp_stream.pcap", packetStream, errMsg) == true, "Error reading pcap file: %s", errMsg.c_str());
// remove 20 bytes from the beginning
RawPacket missPacket1 = tcpReassemblyAddRetransmissions(packetStream.at(3), 20, 0);
packetStream.insert(packetStream.begin() + 4, missPacket1);
packetStream.erase(packetStream.begin() + 3);
// remove 30 bytes from the end
RawPacket missPacket2 = tcpReassemblyAddRetransmissions(packetStream.at(20), 0, 1390);
packetStream.insert(packetStream.begin() + 21, missPacket2);
packetStream.erase(packetStream.begin() + 20);
// remove whole packets
packetStream.erase(packetStream.begin() + 28);
packetStream.erase(packetStream.begin() + 30);
TcpReassemblyMultipleConnStats tcpReassemblyResults;
tcpReassemblyTest(packetStream, tcpReassemblyResults, false, true);
TcpReassemblyMultipleConnStats::Stats &stats = tcpReassemblyResults.stats;
PTF_ASSERT(stats.size() == 1, "Num of connections isn't 1");
PTF_ASSERT(stats.begin()->second.numOfDataPackets == 17, "Num of data packets isn't 21, it's %d", stats.begin()->second.numOfDataPackets);
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[0] == 2, "Num of messages from side 0 isn't 2");
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[1] == 2, "Num of messages from side 1 isn't 2");
std::string expectedReassemblyData = readFileIntoString(std::string("PcapExamples/one_tcp_stream_missing_data_output.txt"));
PTF_ASSERT(expectedReassemblyData == stats.begin()->second.reassembledData, "Reassembly data different than expected");
packetStream.clear();
tcpReassemblyResults.clear();
expectedReassemblyData = "";
// test flow without SYN packet
PTF_ASSERT(tcpReassemblyReadPcapIntoPacketVec("PcapExamples/one_tcp_stream.pcap", packetStream, errMsg) == true, "Error reading pcap file: %s", errMsg.c_str());
// remove SYN and SYN/ACK packets
packetStream.erase(packetStream.begin());
packetStream.erase(packetStream.begin());
tcpReassemblyTest(packetStream, tcpReassemblyResults, false, true);
PTF_ASSERT(stats.size() == 1, "Num of connections isn't 1");
PTF_ASSERT(stats.begin()->second.numOfDataPackets == 19, "Num of data packets isn't 19, it's %d", stats.begin()->second.numOfDataPackets);
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[0] == 2, "Num of messages from side 0 isn't 2");
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[1] == 2, "Num of messages from side 1 isn't 2");
expectedReassemblyData = readFileIntoString(std::string("PcapExamples/one_tcp_stream_output.txt"));
PTF_ASSERT(expectedReassemblyData == stats.begin()->second.reassembledData, "Reassembly data different than expected");
}
PTF_TEST_CASE(TestTcpReassemblyOutOfOrder)
{
std::string errMsg;
std::vector<RawPacket> packetStream;
PTF_ASSERT(tcpReassemblyReadPcapIntoPacketVec("PcapExamples/one_tcp_stream.pcap", packetStream, errMsg) == true, "Error reading pcap file: %s", errMsg.c_str());
// swap 2 consequent packets
std::swap(packetStream[9], packetStream[10]);
// swap 2 non-consequent packets
RawPacket oooPacket1 = packetStream[18];
packetStream.erase(packetStream.begin() + 18);
packetStream.insert(packetStream.begin() + 23, oooPacket1);
// reverse order of all packets in message
for (int i = 0; i < 12; i++)
{
RawPacket oooPacketTemp = packetStream[35];
packetStream.erase(packetStream.begin() + 35);
packetStream.insert(packetStream.begin() + 24 + i, oooPacketTemp);
}
TcpReassemblyMultipleConnStats tcpReassemblyResults;
tcpReassemblyTest(packetStream, tcpReassemblyResults, true, true);
TcpReassemblyMultipleConnStats::Stats &stats = tcpReassemblyResults.stats;
PTF_ASSERT(stats.size() == 1, "OOO test: Num of connections isn't 1");
PTF_ASSERT(stats.begin()->second.numOfDataPackets == 19, "OOO test: Num of data packets isn't 19, it's %d", stats.begin()->second.numOfDataPackets);
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[0] == 2, "OOO test: Num of messages from side 0 isn't 2");
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[1] == 2, "OOO test: Num of messages from side 1 isn't 2");
PTF_ASSERT(stats.begin()->second.connectionsStarted == true, "OOO test: Connection wasn't opened");
PTF_ASSERT(stats.begin()->second.connectionsEnded == false, "OOO test: Connection ended with FIN or RST");
PTF_ASSERT(stats.begin()->second.connectionsEndedManually == true, "OOO test: Connection wasn't ended manually");
std::string expectedReassemblyData = readFileIntoString(std::string("PcapExamples/one_tcp_stream_out_of_order_output.txt"));
PTF_ASSERT(expectedReassemblyData == stats.begin()->second.reassembledData, "OOO test: Reassembly data different than expected");
packetStream.clear();
tcpReassemblyResults.clear();
expectedReassemblyData = "";
// test out-of-order + missing data
PTF_ASSERT(tcpReassemblyReadPcapIntoPacketVec("PcapExamples/one_tcp_stream.pcap", packetStream, errMsg) == true, "Error reading pcap file: %s", errMsg.c_str());
// reverse order of all packets in message
for (int i = 0; i < 12; i++)
{
RawPacket oooPacketTemp = packetStream[35];
packetStream.erase(packetStream.begin() + 35);
packetStream.insert(packetStream.begin() + 24 + i, oooPacketTemp);
}
// remove one packet
packetStream.erase(packetStream.begin() + 29);
tcpReassemblyTest(packetStream, tcpReassemblyResults, true, true);
PTF_ASSERT(stats.size() == 1, "OOO + missing data test: Num of connections isn't 1");
PTF_ASSERT(stats.begin()->second.numOfDataPackets == 18, "OOO + missing data test: Num of data packets isn't 18, it's %d", stats.begin()->second.numOfDataPackets);
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[0] == 2, "OOO + missing data test: Num of messages from side 0 isn't 2");
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[1] == 2, "OOO + missing data test: Num of messages from side 1 isn't 2");
PTF_ASSERT(stats.begin()->second.connectionsStarted == true, "OOO + missing data test: Connection wasn't opened");
PTF_ASSERT(stats.begin()->second.connectionsEnded == false, "OOO + missing data test: Connection ended with FIN or RST");
PTF_ASSERT(stats.begin()->second.connectionsEndedManually == true, "OOO + missing data test: Connection wasn't ended manually");
expectedReassemblyData = readFileIntoString(std::string("PcapExamples/one_tcp_stream_missing_data_output_ooo.txt"));
PTF_ASSERT(expectedReassemblyData == stats.begin()->second.reassembledData, "OOO + missing data test: Reassembly data different than expected");
}
PTF_TEST_CASE(TestTcpReassemblyWithFIN_RST)
{
std::string errMsg;
std::vector<RawPacket> packetStream;
TcpReassemblyMultipleConnStats tcpReassemblyResults;
std::string expectedReassemblyData = "";
// test fin packet in end of connection
PTF_ASSERT(tcpReassemblyReadPcapIntoPacketVec("PcapExamples/one_http_stream_fin.pcap", packetStream, errMsg) == true, "Error reading pcap file: %s", errMsg.c_str());
tcpReassemblyTest(packetStream, tcpReassemblyResults, true, false);
TcpReassemblyMultipleConnStats::Stats &stats = tcpReassemblyResults.stats;
PTF_ASSERT(stats.size() == 1, "FIN test: Num of connections isn't 1");
PTF_ASSERT(stats.begin()->second.numOfDataPackets == 5, "FIN test: Num of data packets isn't 5, it's %d", stats.begin()->second.numOfDataPackets);
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[0] == 1, "FIN test: Num of messages from side 0 isn't 1");
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[1] == 1, "FIN test: Num of messages from side 1 isn't 1");
PTF_ASSERT(stats.begin()->second.connectionsStarted == true, "FIN test: Connection wasn't opened");
PTF_ASSERT(stats.begin()->second.connectionsEnded == true, "FIN test: Connection didn't end with FIN or RST");
PTF_ASSERT(stats.begin()->second.connectionsEndedManually == false, "FIN test: Connection wasn ended manually");
expectedReassemblyData = readFileIntoString(std::string("PcapExamples/one_http_stream_fin_output.txt"));
PTF_ASSERT(expectedReassemblyData == stats.begin()->second.reassembledData, "FIN test: Reassembly data different than expected");
packetStream.clear();
tcpReassemblyResults.clear();
expectedReassemblyData = "";
// test rst packet in end of connection
PTF_ASSERT(tcpReassemblyReadPcapIntoPacketVec("PcapExamples/one_http_stream_rst.pcap", packetStream, errMsg) == true, "Error reading pcap file: %s", errMsg.c_str());
tcpReassemblyTest(packetStream, tcpReassemblyResults, true, false);
PTF_ASSERT(stats.size() == 1, "RST test: Num of connections isn't 1");
PTF_ASSERT(stats.begin()->second.numOfDataPackets == 2, "RST test: Num of data packets isn't 2, it's %d", stats.begin()->second.numOfDataPackets);
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[0] == 1, "RST test: Num of messages from side 0 isn't 1");
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[1] == 1, "RST test: Num of messages from side 1 isn't 1");
PTF_ASSERT(stats.begin()->second.connectionsStarted == true, "RST test: Connection wasn't opened");
PTF_ASSERT(stats.begin()->second.connectionsEnded == true, "RST test: Connection didn't end with FIN or RST");
PTF_ASSERT(stats.begin()->second.connectionsEndedManually == false, "RST test: Connection wasn ended manually");
expectedReassemblyData = readFileIntoString(std::string("PcapExamples/one_http_stream_rst_output.txt"));
PTF_ASSERT(expectedReassemblyData == stats.begin()->second.reassembledData, "RST test: Reassembly data different than expected");
packetStream.clear();
tcpReassemblyResults.clear();
expectedReassemblyData = "";
//test fin packet in end of connection that has also data
PTF_ASSERT(tcpReassemblyReadPcapIntoPacketVec("PcapExamples/one_http_stream_fin2.pcap", packetStream, errMsg) == true, "Error reading pcap file: %s", errMsg.c_str());
tcpReassemblyTest(packetStream, tcpReassemblyResults, true, false);
PTF_ASSERT(stats.size() == 1, "FIN with data test: Num of connections isn't 1");
PTF_ASSERT(stats.begin()->second.numOfDataPackets == 6, "FIN with data test: Num of data packets isn't 6, it's %d", stats.begin()->second.numOfDataPackets);
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[0] == 1, "FIN with data test: Num of messages from side 0 isn't 1");
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[1] == 1, "FIN with data test: Num of messages from side 1 isn't 1");
PTF_ASSERT(stats.begin()->second.connectionsStarted == true, "FIN with data test: Connection wasn't opened");
PTF_ASSERT(stats.begin()->second.connectionsEnded == true, "FIN with data test: Connection didn't end with FIN or RST");
PTF_ASSERT(stats.begin()->second.connectionsEndedManually == false, "FIN with data test: Connection wasn ended manually");
expectedReassemblyData = readFileIntoString(std::string("PcapExamples/one_http_stream_fin2_output.txt"));
PTF_ASSERT(expectedReassemblyData == stats.begin()->second.reassembledData, "FIN with data test: Reassembly data different than expected");
packetStream.clear();
tcpReassemblyResults.clear();
expectedReassemblyData = "";
// test missing data before fin
PTF_ASSERT(tcpReassemblyReadPcapIntoPacketVec("PcapExamples/one_http_stream_fin2.pcap", packetStream, errMsg) == true, "Error reading pcap file: %s", errMsg.c_str());
// move second packet of server->client message to the end of the message (after FIN)
RawPacket oooPacketTemp = packetStream[6];
packetStream.erase(packetStream.begin() + 6);
packetStream.insert(packetStream.begin() + 12, oooPacketTemp);
tcpReassemblyTest(packetStream, tcpReassemblyResults, true, false);
PTF_ASSERT(stats.size() == 1, "Missing data before FIN test: Num of connections isn't 1");
PTF_ASSERT(stats.begin()->second.numOfDataPackets == 5, "Missing data before FIN test: Num of data packets isn't 5, it's %d", stats.begin()->second.numOfDataPackets);
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[0] == 1, "Missing data before FIN test: Num of messages from side 0 isn't 1");
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[1] == 1, "Missing data before FIN test: Num of messages from side 1 isn't 1");
PTF_ASSERT(stats.begin()->second.connectionsStarted == true, "Missing data before FIN test: Connection wasn't opened");
PTF_ASSERT(stats.begin()->second.connectionsEnded == true, "Missing data before FIN test: Connection didn't end with FIN or RST");
PTF_ASSERT(stats.begin()->second.connectionsEndedManually == false, "Missing data before FIN test: Connection wasn ended manually");
expectedReassemblyData = readFileIntoString(std::string("PcapExamples/one_http_stream_fin2_output2.txt"));
PTF_ASSERT(expectedReassemblyData == stats.begin()->second.reassembledData, "Missing data before FIN test: Reassembly data different than expected");
}
PTF_TEST_CASE(TestTcpReassemblyMalformedPkts)
{
std::string errMsg;
std::vector<RawPacket> packetStream;
TcpReassemblyMultipleConnStats tcpReassemblyResults;
std::string expectedReassemblyData = "";
// test retransmission with new data but payload doesn't really contain all the new data
PTF_ASSERT(tcpReassemblyReadPcapIntoPacketVec("PcapExamples/one_http_stream_fin2.pcap", packetStream, errMsg) == true, "Error reading pcap file: %s", errMsg.c_str());
// take one of the packets and increase the IPv4 total length field
Packet malPacket(&packetStream.at(8));
IPv4Layer* ipLayer = malPacket.getLayerOfType<IPv4Layer>();
PTF_ASSERT(ipLayer != NULL, "Cannot find the IPv4 layer of the packet");
ipLayer->getIPv4Header()->totalLength = ntohs(htons(ipLayer->getIPv4Header()->totalLength) + 40);
// PcapFileWriterDevice writer("pasdasda.pcap");
// writer.open();
//
// for (std::vector<RawPacket>::iterator iter = packetStream.begin(); iter != packetStream.end(); iter++)
// {
// writer.writePacket(*iter);
// }
//
// writer.close();
tcpReassemblyTest(packetStream, tcpReassemblyResults, true, false);
TcpReassemblyMultipleConnStats::Stats &stats = tcpReassemblyResults.stats;
PTF_ASSERT(stats.size() == 1, "Num of connections isn't 1");
PTF_ASSERT(stats.begin()->second.numOfDataPackets == 6, "Num of data packets isn't 6, it's %d", stats.begin()->second.numOfDataPackets);
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[0] == 1, "Num of messages from side 0 isn't 1");
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[1] == 1, "Num of messages from side 1 isn't 1");
PTF_ASSERT(stats.begin()->second.connectionsStarted == true, "Connection wasn't opened");
PTF_ASSERT(stats.begin()->second.connectionsEnded == true, "Connection didn't end with FIN or RST");
PTF_ASSERT(stats.begin()->second.connectionsEndedManually == false, "Connection wasn ended manually");
expectedReassemblyData = readFileIntoString(std::string("PcapExamples/one_http_stream_fin2_output.txt"));
PTF_ASSERT(expectedReassemblyData == stats.begin()->second.reassembledData, "Reassembly data different than expected");
}
PTF_TEST_CASE(TestTcpReassemblyMultipleConns)
{
TcpReassemblyMultipleConnStats results;
std::string errMsg;
std::string expectedReassemblyData = "";
TcpReassembly tcpReassembly(tcpReassemblyMsgReadyCallback, &results, tcpReassemblyConnectionStartCallback, tcpReassemblyConnectionEndCallback);
std::vector<RawPacket> packetStream;
PTF_ASSERT(tcpReassemblyReadPcapIntoPacketVec("PcapExamples/three_http_streams.pcap", packetStream, errMsg) == true, "Error reading pcap file: %s", errMsg.c_str());
RawPacket finPacket1 = packetStream.at(13);
RawPacket finPacket2 = packetStream.at(15);
packetStream.erase(packetStream.begin() + 13);
packetStream.erase(packetStream.begin() + 14);
for (std::vector<RawPacket>::iterator iter = packetStream.begin(); iter != packetStream.end(); iter++)
{
Packet packet(&(*iter));
tcpReassembly.reassemblePacket(packet);
}
TcpReassemblyMultipleConnStats::Stats &stats = results.stats;
PTF_ASSERT(stats.size() == 3, "Num of connections isn't 3");
PTF_ASSERT(results.flowKeysList.size() == 3, "Num of flow keys isn't 3");
TcpReassemblyMultipleConnStats::Stats::iterator iter = stats.begin();
PTF_ASSERT(iter->second.numOfDataPackets == 2, "Conn #1: Num of data packets isn't 2, it's %d", iter->second.numOfDataPackets);
PTF_ASSERT(iter->second.numOfMessagesFromSide[0] == 1, "Conn #1: Num of messages from side 0 isn't 1");
PTF_ASSERT(iter->second.numOfMessagesFromSide[1] == 1, "Conn #1: Num of messages from side 1 isn't 1");
PTF_ASSERT(iter->second.connectionsStarted == true, "Conn #1: Connection wasn't opened");
PTF_ASSERT(iter->second.connectionsEnded == true, "Conn #1: Connection didn't end with FIN or RST");
PTF_ASSERT(iter->second.connectionsEndedManually == false, "Conn #1: Connections ended manually");
expectedReassemblyData = readFileIntoString(std::string("PcapExamples/three_http_streams_conn_1_output.txt"));
PTF_ASSERT(expectedReassemblyData == iter->second.reassembledData, "Conn #1: Reassembly data different than expected");
iter++;
PTF_ASSERT(iter->second.numOfDataPackets == 2, "Conn #2: Num of data packets isn't 2, it's %d", iter->second.numOfDataPackets);
PTF_ASSERT(iter->second.numOfMessagesFromSide[0] == 1, "Conn #2: Num of messages from side 0 isn't 1");
PTF_ASSERT(iter->second.numOfMessagesFromSide[1] == 1, "Conn #2: Num of messages from side 1 isn't 1");
PTF_ASSERT(iter->second.connectionsStarted == true, "Conn #2: Connection wasn't opened");
PTF_ASSERT(iter->second.connectionsEnded == true, "Conn #2: Connection didn't end with FIN or RST");
PTF_ASSERT(iter->second.connectionsEndedManually == false, "Conn #2: Connections ended manually");
expectedReassemblyData = readFileIntoString(std::string("PcapExamples/three_http_streams_conn_2_output.txt"));
PTF_ASSERT(expectedReassemblyData == iter->second.reassembledData, "Conn #2: Reassembly data different than expected");
iter++;
PTF_ASSERT(iter->second.numOfDataPackets == 2, "Conn #3: Num of data packets isn't 2, it's %d", iter->second.numOfDataPackets);
PTF_ASSERT(iter->second.numOfMessagesFromSide[0] == 1, "Conn #3: Num of messages from side 0 isn't 1");
PTF_ASSERT(iter->second.numOfMessagesFromSide[1] == 1, "Conn #3: Num of messages from side 1 isn't 1");
PTF_ASSERT(iter->second.connectionsStarted == true, "Conn #3: Connection wasn't opened");
PTF_ASSERT(iter->second.connectionsEnded == false, "Conn #3: Connection ended with FIN or RST");
PTF_ASSERT(iter->second.connectionsEndedManually == false, "Conn #3: Connections ended manually");
expectedReassemblyData = readFileIntoString(std::string("PcapExamples/three_http_streams_conn_3_output.txt"));
PTF_ASSERT(expectedReassemblyData == iter->second.reassembledData, "Conn #3: Reassembly data different than expected");
// test getConnectionInformation and isConnectionOpen
const TcpReassembly::ConnectionInfoList &managedConnections = tcpReassembly.getConnectionInformation();
PTF_ASSERT(managedConnections.size() == 3, "Size of managed connection list isn't 3");
TcpReassembly::ConnectionInfoList::const_iterator iterConn1 = managedConnections.find(results.flowKeysList[0]);
TcpReassembly::ConnectionInfoList::const_iterator iterConn2 = managedConnections.find(results.flowKeysList[1]);
TcpReassembly::ConnectionInfoList::const_iterator iterConn3 = managedConnections.find(results.flowKeysList[2]);
PTF_ASSERT(iterConn1 != managedConnections.end(), "Connection #1 not found");
PTF_ASSERT(iterConn2 != managedConnections.end(), "Connection #2 not found");
PTF_ASSERT(iterConn3 != managedConnections.end(), "Connection #3 not found");
PTF_ASSERT(tcpReassembly.isConnectionOpen(iterConn1->second) > 0, "Connection #1 is closed");
PTF_ASSERT(tcpReassembly.isConnectionOpen(iterConn2->second) == 0, "Connection #2 is still open");
PTF_ASSERT(tcpReassembly.isConnectionOpen(iterConn3->second) == 0, "Connection #3 is still open");
ConnectionData dummyConn;
dummyConn.flowKey = 0x12345678;
PTF_ASSERT(tcpReassembly.isConnectionOpen(dummyConn) < 0, "Dummy connection exists");
// close flow manually and verify it's closed
tcpReassembly.closeConnection(iter->first);
PTF_ASSERT(iter->second.connectionsEnded == false, "Conn #3: Connection ended supposedly ended with FIN or RST although ended manually");
PTF_ASSERT(iter->second.connectionsEndedManually == true, "Conn #3: Connections still isn't ended even though ended manually");
// now send FIN packets of conn 3 and verify they are igonred
tcpReassembly.reassemblePacket(&finPacket1);
tcpReassembly.reassemblePacket(&finPacket2);
PTF_ASSERT(iter->second.connectionsEnded == false, "Conn #3: Connection ended supposedly ended with FIN or RST after FIN packets sent although ended manually before");
PTF_ASSERT(iter->second.connectionsEndedManually == true, "Conn #3: Connections isn't ended after FIN packets sent even though ended manually before");
}
PTF_TEST_CASE(TestTcpReassemblyIPv6)
{
std::string errMsg;
std::vector<RawPacket> packetStream;
PTF_ASSERT(tcpReassemblyReadPcapIntoPacketVec("PcapExamples/one_ipv6_http_stream.pcap", packetStream, errMsg) == true, "Error reading pcap file: %s", errMsg.c_str());
TcpReassemblyMultipleConnStats tcpReassemblyResults;
tcpReassemblyTest(packetStream, tcpReassemblyResults, true, true);
TcpReassemblyMultipleConnStats::Stats &stats = tcpReassemblyResults.stats;
PTF_ASSERT(stats.size() == 1, "Num of connections isn't 1");
PTF_ASSERT(stats.begin()->second.numOfDataPackets == 10, "Num of data packets isn't 10, it's %d", stats.begin()->second.numOfDataPackets);
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[0] == 3, "Num of messages from side 0 isn't 3");
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[1] == 3, "Num of messages from side 1 isn't 3");
PTF_ASSERT(stats.begin()->second.connectionsStarted == true, "Connections wasn't opened");
PTF_ASSERT(stats.begin()->second.connectionsEnded == false, "Connection was ended with FIN or RST");
PTF_ASSERT(stats.begin()->second.connectionsEndedManually == true, "Connection wasn't ended manually");
PTF_ASSERT(stats.begin()->second.connData.srcIP != NULL, "Source IP is NULL");
PTF_ASSERT(stats.begin()->second.connData.dstIP != NULL, "Source IP is NULL");
IPv6Address expectedSrcIP(std::string("2001:618:400::5199:cc70"));
IPv6Address expectedDstIP(std::string("2001:618:1:8000::5"));
PTF_ASSERT(stats.begin()->second.connData.srcIP->equals(&expectedSrcIP), "Source IP isn't 2001:618:400::5199:cc70");
PTF_ASSERT(stats.begin()->second.connData.dstIP->equals(&expectedDstIP), "Source IP isn't 2001:618:1:8000::5");
PTF_ASSERT(stats.begin()->second.connData.startTime.tv_sec == 1147551796, "Bad start time seconds, expected 1147551796");
PTF_ASSERT(stats.begin()->second.connData.startTime.tv_usec == 702602, "Bad start time microseconds, expected 702602");
PTF_ASSERT(stats.begin()->second.connData.endTime.tv_sec == 0, "Bad end time seconds, expected 0");
PTF_ASSERT(stats.begin()->second.connData.endTime.tv_usec == 0, "Bad end time microseconds, expected 0");
std::string expectedReassemblyData = readFileIntoString(std::string("PcapExamples/one_ipv6_http_stream.txt"));
PTF_ASSERT(expectedReassemblyData == stats.begin()->second.reassembledData, "Reassembly data different than expected");
}
PTF_TEST_CASE(TestTcpReassemblyIPv6MultConns)
{
std::string errMsg;
std::vector<RawPacket> packetStream;
std::string expectedReassemblyData = "";
PTF_ASSERT(tcpReassemblyReadPcapIntoPacketVec("PcapExamples/four_ipv6_http_streams.pcap", packetStream, errMsg) == true, "Error reading pcap file: %s", errMsg.c_str());
TcpReassemblyMultipleConnStats tcpReassemblyResults;
tcpReassemblyTest(packetStream, tcpReassemblyResults, true, true);
TcpReassemblyMultipleConnStats::Stats &stats = tcpReassemblyResults.stats;
PTF_ASSERT(stats.size() == 4, "Num of connections isn't 4");
TcpReassemblyMultipleConnStats::Stats::iterator iter = stats.begin();
IPv6Address expectedSrcIP(std::string("2001:618:400::5199:cc70"));
IPv6Address expectedDstIP1(std::string("2001:618:1:8000::5"));
IPv6Address expectedDstIP2(std::string("2001:638:902:1:202:b3ff:feee:5dc2"));
PTF_ASSERT(iter->second.numOfDataPackets == 14, "Conn #1: Num of data packets isn't 14, it's %d", iter->second.numOfDataPackets);
PTF_ASSERT(iter->second.numOfMessagesFromSide[0] == 3, "Conn #1: Num of messages from side 0 isn't 3");
PTF_ASSERT(iter->second.numOfMessagesFromSide[1] == 3, "Conn #1: Num of messages from side 1 isn't 3");
PTF_ASSERT(iter->second.connectionsStarted == true, "Conn #1: Connection wasn't opened");
PTF_ASSERT(iter->second.connectionsEnded == false, "Conn #1: Connection ended with FIN or RST");
PTF_ASSERT(iter->second.connectionsEndedManually == true, "Conn #1: Connections wasn't ended manually");
PTF_ASSERT(iter->second.connData.srcIP != NULL, "Conn #1: Source IP is NULL");
PTF_ASSERT(iter->second.connData.dstIP != NULL, "Conn #1: Source IP is NULL");
PTF_ASSERT(iter->second.connData.srcIP->equals(&expectedSrcIP), "Conn #1: Source IP isn't 2001:618:400::5199:cc70");
PTF_ASSERT(iter->second.connData.dstIP->equals(&expectedDstIP1), "Conn #1: Source IP isn't 2001:618:1:8000::5");
PTF_ASSERT(iter->second.connData.srcPort == 35995, "Conn #1: source port isn't 35995");
PTF_ASSERT(stats.begin()->second.connData.startTime.tv_sec == 1147551795, "Bad start time seconds, expected 1147551795");
PTF_ASSERT(stats.begin()->second.connData.startTime.tv_usec == 526632, "Bad start time microseconds, expected 526632");
PTF_ASSERT(stats.begin()->second.connData.endTime.tv_sec == 0, "Bad end time seconds, expected 0");
PTF_ASSERT(stats.begin()->second.connData.endTime.tv_usec == 0, "Bad end time microseconds, expected 0");
expectedReassemblyData = readFileIntoString(std::string("PcapExamples/one_ipv6_http_stream4.txt"));
PTF_ASSERT(expectedReassemblyData == iter->second.reassembledData, "Conn #1: Reassembly data different than expected");
iter++;
PTF_ASSERT(iter->second.numOfDataPackets == 10, "Conn #2: Num of data packets isn't 10, it's %d", iter->second.numOfDataPackets);
PTF_ASSERT(iter->second.numOfMessagesFromSide[0] == 1, "Conn #2: Num of messages from side 0 isn't 1");
PTF_ASSERT(iter->second.numOfMessagesFromSide[1] == 1, "Conn #2: Num of messages from side 1 isn't 1");
PTF_ASSERT(iter->second.connectionsStarted == true, "Conn #2: Connection wasn't opened");
PTF_ASSERT(iter->second.connectionsEnded == false, "Conn #2: Connection ended with FIN or RST");
PTF_ASSERT(iter->second.connectionsEndedManually == true, "Conn #2: Connections wasn't ended manually");
PTF_ASSERT(iter->second.connData.srcIP != NULL, "Conn #2: Source IP is NULL");
PTF_ASSERT(iter->second.connData.dstIP != NULL, "Conn #2: Source IP is NULL");
PTF_ASSERT(iter->second.connData.srcIP->equals(&expectedSrcIP), "Conn #2: Source IP isn't 2001:618:400::5199:cc70");
PTF_ASSERT(iter->second.connData.dstIP->equals(&expectedDstIP1), "Conn #2: Source IP isn't 2001:618:1:8000::5");
PTF_ASSERT(iter->second.connData.srcPort == 35999, "Conn #2: source port isn't 35999");
PTF_ASSERT(stats.begin()->second.connData.startTime.tv_sec == 1147551795, "Bad start time seconds, expected 1147551795");
PTF_ASSERT(stats.begin()->second.connData.startTime.tv_usec == 526632, "Bad start time microseconds, expected 526632");
PTF_ASSERT(stats.begin()->second.connData.endTime.tv_sec == 0, "Bad end time seconds, expected 0");
PTF_ASSERT(stats.begin()->second.connData.endTime.tv_usec == 0, "Bad end time microseconds, expected 0");
iter++;
PTF_ASSERT(iter->second.numOfDataPackets == 2, "Conn #3: Num of data packets isn't 2, it's %d", iter->second.numOfDataPackets);
PTF_ASSERT(iter->second.numOfMessagesFromSide[0] == 1, "Conn #3: Num of messages from side 0 isn't 1");
PTF_ASSERT(iter->second.numOfMessagesFromSide[1] == 1, "Conn #3: Num of messages from side 1 isn't 1");
PTF_ASSERT(iter->second.connectionsStarted == true, "Conn #3: Connection wasn't opened");
PTF_ASSERT(iter->second.connectionsEnded == false, "Conn #3: Connection ended with FIN or RST");
PTF_ASSERT(iter->second.connectionsEndedManually == true, "Conn #3: Connections wasn't ended manually");
PTF_ASSERT(iter->second.connData.srcIP != NULL, "Conn #3: Source IP is NULL");
PTF_ASSERT(iter->second.connData.dstIP != NULL, "Conn #3: Source IP is NULL");
PTF_ASSERT(iter->second.connData.srcIP->equals(&expectedSrcIP), "Conn #3: Source IP isn't 2001:618:400::5199:cc70");
PTF_ASSERT(iter->second.connData.dstIP->equals(&expectedDstIP2), "Conn #3: Source IP isn't 2001:638:902:1:202:b3ff:feee:5dc2");
PTF_ASSERT(iter->second.connData.srcPort == 40426, "Conn #3: source port isn't 40426");
PTF_ASSERT(stats.begin()->second.connData.startTime.tv_sec == 1147551795, "Bad start time seconds, expected 1147551795");
PTF_ASSERT(stats.begin()->second.connData.startTime.tv_usec == 526632, "Bad start time microseconds, expected 526632");
PTF_ASSERT(stats.begin()->second.connData.endTime.tv_sec == 0, "Bad end time seconds, expected 0");
PTF_ASSERT(stats.begin()->second.connData.endTime.tv_usec == 0, "Bad end time microseconds, expected 0");
expectedReassemblyData = readFileIntoString(std::string("PcapExamples/one_ipv6_http_stream3.txt"));
PTF_ASSERT(expectedReassemblyData == iter->second.reassembledData, "Conn #3: Reassembly data different than expected");
iter++;
PTF_ASSERT(iter->second.numOfDataPackets == 13, "Conn #4: Num of data packets isn't 13, it's %d", iter->second.numOfDataPackets);
PTF_ASSERT(iter->second.numOfMessagesFromSide[0] == 4, "Conn #4: Num of messages from side 0 isn't 4");
PTF_ASSERT(iter->second.numOfMessagesFromSide[1] == 4, "Conn #4: Num of messages from side 1 isn't 4");
PTF_ASSERT(iter->second.connectionsStarted == true, "Conn #4: Connection wasn't opened");
PTF_ASSERT(iter->second.connectionsEnded == false, "Conn #4: Connection ended with FIN or RST");
PTF_ASSERT(iter->second.connectionsEndedManually == true, "Conn #4: Connections wasn't ended manually");
PTF_ASSERT(iter->second.connData.srcIP != NULL, "Conn #4: Source IP is NULL");
PTF_ASSERT(iter->second.connData.dstIP != NULL, "Conn #4: Source IP is NULL");
PTF_ASSERT(iter->second.connData.srcIP->equals(&expectedSrcIP), "Conn #4: Source IP isn't 2001:618:400::5199:cc70");
PTF_ASSERT(iter->second.connData.dstIP->equals(&expectedDstIP1), "Conn #4: Source IP isn't 2001:618:1:8000::5");
PTF_ASSERT(iter->second.connData.srcPort == 35997, "Conn #4: source port isn't 35997");
PTF_ASSERT(stats.begin()->second.connData.startTime.tv_sec == 1147551795, "Bad start time seconds, expected 1147551795");
PTF_ASSERT(stats.begin()->second.connData.startTime.tv_usec == 526632, "Bad start time microseconds, expected 526632");
PTF_ASSERT(stats.begin()->second.connData.endTime.tv_sec == 0, "Bad end time seconds, expected 0");
PTF_ASSERT(stats.begin()->second.connData.endTime.tv_usec == 0, "Bad end time microseconds, expected 0");
expectedReassemblyData = readFileIntoString(std::string("PcapExamples/one_ipv6_http_stream2.txt"));
PTF_ASSERT(expectedReassemblyData == iter->second.reassembledData, "Conn #4: Reassembly data different than expected");
}
PTF_TEST_CASE(TestTcpReassemblyIPv6_OOO)
{
std::string errMsg;
std::vector<RawPacket> packetStream;
PTF_ASSERT(tcpReassemblyReadPcapIntoPacketVec("PcapExamples/one_ipv6_http_stream.pcap", packetStream, errMsg) == true, "Error reading pcap file: %s", errMsg.c_str());
// swap 2 non-consequent packets
RawPacket oooPacket1 = packetStream[10];
packetStream.erase(packetStream.begin() + 10);
packetStream.insert(packetStream.begin() + 12, oooPacket1);
// swap additional 2 non-consequent packets
oooPacket1 = packetStream[15];
packetStream.erase(packetStream.begin() + 15);
packetStream.insert(packetStream.begin() + 17, oooPacket1);
TcpReassemblyMultipleConnStats tcpReassemblyResults;
tcpReassemblyTest(packetStream, tcpReassemblyResults, true, true);
TcpReassemblyMultipleConnStats::Stats &stats = tcpReassemblyResults.stats;
PTF_ASSERT(stats.size() == 1, "Num of connections isn't 1");
PTF_ASSERT(stats.begin()->second.numOfDataPackets == 10, "Num of data packets isn't 10, it's %d", stats.begin()->second.numOfDataPackets);
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[0] == 3, "Num of messages from side 0 isn't 3");
PTF_ASSERT(stats.begin()->second.numOfMessagesFromSide[1] == 3, "Num of messages from side 1 isn't 3");
PTF_ASSERT(stats.begin()->second.connectionsStarted == true, "Connections wasn't opened");
PTF_ASSERT(stats.begin()->second.connectionsEnded == false, "Connection was ended with FIN or RST");
PTF_ASSERT(stats.begin()->second.connectionsEndedManually == true, "Connection wasn't ended manually");
PTF_ASSERT(stats.begin()->second.connData.srcIP != NULL, "Source IP is NULL");
PTF_ASSERT(stats.begin()->second.connData.dstIP != NULL, "Source IP is NULL");
IPv6Address expectedSrcIP(std::string("2001:618:400::5199:cc70"));
IPv6Address expectedDstIP(std::string("2001:618:1:8000::5"));
PTF_ASSERT(stats.begin()->second.connData.srcIP->equals(&expectedSrcIP), "Source IP isn't 2001:618:400::5199:cc70");
PTF_ASSERT(stats.begin()->second.connData.dstIP->equals(&expectedDstIP), "Source IP isn't 2001:618:1:8000::5");
PTF_ASSERT(stats.begin()->second.connData.startTime.tv_sec == 1147551796, "Bad start time seconds, expected 1147551796");
PTF_ASSERT(stats.begin()->second.connData.startTime.tv_usec == 702602, "Bad start time microseconds, expected 702602");
PTF_ASSERT(stats.begin()->second.connData.endTime.tv_sec == 0, "Bad end time seconds, expected 0");
PTF_ASSERT(stats.begin()->second.connData.endTime.tv_usec == 0, "Bad end time microseconds, expected 0");
std::string expectedReassemblyData = readFileIntoString(std::string("PcapExamples/one_ipv6_http_stream.txt"));
PTF_ASSERT(expectedReassemblyData == stats.begin()->second.reassembledData, "Reassembly data different than expected");
}
PTF_TEST_CASE(TestTcpReassemblyCleanup)
{
TcpReassemblyMultipleConnStats results;
std::string errMsg;
TcpReassemblyConfiguration config(true, 2, 1);
TcpReassembly tcpReassembly(tcpReassemblyMsgReadyCallback, &results, tcpReassemblyConnectionStartCallback, tcpReassemblyConnectionEndCallback, config);
std::vector<RawPacket> packetStream;
PTF_ASSERT_TRUE(tcpReassemblyReadPcapIntoPacketVec("PcapExamples/three_http_streams.pcap", packetStream, errMsg));
RawPacket lastPacket = packetStream.back();
packetStream.pop_back();
for(std::vector<RawPacket>::iterator iter = packetStream.begin(); iter != packetStream.end(); iter++)
{
Packet packet(&(*iter));
tcpReassembly.reassemblePacket(packet);
}
TcpReassembly::ConnectionInfoList managedConnections = tcpReassembly.getConnectionInformation(); // make a copy of list
PTF_ASSERT_EQUAL(managedConnections.size(), 3, size);
PTF_ASSERT_EQUAL(results.flowKeysList.size(), 3, size);
TcpReassembly::ConnectionInfoList::const_iterator iterConn1 = managedConnections.find(results.flowKeysList[0]);
TcpReassembly::ConnectionInfoList::const_iterator iterConn2 = managedConnections.find(results.flowKeysList[1]);
TcpReassembly::ConnectionInfoList::const_iterator iterConn3 = managedConnections.find(results.flowKeysList[2]);
PTF_ASSERT(iterConn1 != managedConnections.end(), "Connection #1 not found");
PTF_ASSERT(iterConn2 != managedConnections.end(), "Connection #2 not found");
PTF_ASSERT(iterConn3 != managedConnections.end(), "Connection #3 not found");
PTF_ASSERT_EQUAL(tcpReassembly.isConnectionOpen(iterConn1->second), 0, int);
PTF_ASSERT_EQUAL(tcpReassembly.isConnectionOpen(iterConn2->second), 0, int);
PTF_ASSERT_EQUAL(tcpReassembly.isConnectionOpen(iterConn3->second), 0, int);
PCAP_SLEEP(3);
tcpReassembly.reassemblePacket(&lastPacket); // automatic cleanup of 1 item
PTF_ASSERT_EQUAL(tcpReassembly.getConnectionInformation().size(), 2, size);
tcpReassembly.purgeClosedConnections(); // manually initiated cleanup of 1 item
PTF_ASSERT_EQUAL(tcpReassembly.getConnectionInformation().size(), 1, size);
tcpReassembly.purgeClosedConnections(0xFFFFFFFF); // manually initiated cleanup of all items
PTF_ASSERT_EQUAL(tcpReassembly.getConnectionInformation().size(), 0, size);
const TcpReassemblyMultipleConnStats::FlowKeysList& flowKeys = results.flowKeysList;
iterConn1 = managedConnections.find(flowKeys[0]);
iterConn2 = managedConnections.find(flowKeys[1]);
iterConn3 = managedConnections.find(flowKeys[2]);
PTF_ASSERT(iterConn1 != managedConnections.end(), "Connection #1 not found in flow keys list");
PTF_ASSERT(iterConn2 != managedConnections.end(), "Connection #2 not found in flow keys list");
PTF_ASSERT(iterConn3 != managedConnections.end(), "Connection #3 not found in flow keys list");
PTF_ASSERT_EQUAL(tcpReassembly.isConnectionOpen(iterConn1->second), -1, int);
PTF_ASSERT_EQUAL(tcpReassembly.isConnectionOpen(iterConn2->second), -1, int);
PTF_ASSERT_EQUAL(tcpReassembly.isConnectionOpen(iterConn3->second), -1, int);
} // TestTcpReassemblyCleanup

@rickyzhang82 rickyzhang82 deleted the pr-tcp-sorter branch December 24, 2019 02:49
@rickyzhang82
Copy link
Contributor Author

rickyzhang82 commented Dec 24, 2019

@seladb Please let me add UT and send another PR later.

@rickyzhang82
Copy link
Contributor Author

@gx740

Do you have a empirical test to verify your claim that

The lookup in an unordered_map works in 10 times faster than in std::map

Also, how about insertion and deletion?

@rickyzhang82
Copy link
Contributor Author

@gx740

Regarding to the copy cost, it makes sense to squeeze the performance by passing as const reference. I also change all methods that pass in shared pointer into pass in as const reference as well.

@gx740
Copy link
Contributor

gx740 commented Dec 24, 2019

@gx740

Do you have a empirical test to verify your claim that

The lookup in an unordered_map works in 10 times faster than in std::map

Also, how about insertion and deletion?

Yes, I did the tests but did not test the deletion. You can make the tests in your environment.

Test results: Win10x64, VS2019, Core i7-9700K

Fill the map: duration(ms): 22, count: 100.000
Lookup in the map: duration(ms): 2469, iterations: 15.000.000
Insuccessfull lookup in the map: duration(ms): 2622, iterations: 15.000.000

Fill the unordered map: duration(ms): 7, count: 100.000
Lookup in the unordered map: duration(ms): 133, iterations: 15.000.000
Insuccessfull lookup in the unordered map: duration(ms): 154, iterations: 15.000.000

Memory usage (valgrind result): CentOS 7, g++ 8.3.1

key: uint32_t, value: uint64_t, size: 500 000

map:
500,000 allocs, 500,000 frees, 24,000,000 bytes allocated

unordered_map:
500,018 allocs, 500,018 frees, 20,204,296 bytes allocated
500,001 allocs, 500,001 frees, 16,161,928 bytes allocated (pre-alloc)

#include <vector>
#include <map>
#include <cstdint>
#include <chrono>
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <ctime>
#include <unordered_map>
#include <random>


void testHashMapAndMap()
{
  constexpr auto tryCount = 15000000U;
  constexpr uint32_t numToSearch = 1U;
  constexpr auto maxSize = 100000U;

  // create a shuffled unique set
  std::unordered_map<uint32_t, uint32_t> shuffledMap, shuffledErrMap;
  shuffledMap.reserve(maxSize);
  shuffledErrMap.reserve(maxSize);

  using RandomGenerator = std::mt19937;
  std::random_device dev;
  RandomGenerator randomGenerator(dev());
  std::uniform_int_distribution<RandomGenerator::result_type> dist(1, maxSize * 3);

  while(shuffledMap.size() < maxSize)
  {
    auto rndValue = dist(randomGenerator);
    if(shuffledMap.find(rndValue) == shuffledMap.cend())
      shuffledMap.insert(std::make_pair(rndValue, rndValue));
  }
  
  while(shuffledErrMap.size() < maxSize)
  {
    auto rndValue = dist(randomGenerator);
    if(shuffledMap.find(rndValue) == shuffledMap.cend() && shuffledErrMap.find(rndValue) == shuffledErrMap.cend())
      shuffledErrMap.insert(std::make_pair(rndValue, rndValue));
  }

  std::vector<uint32_t> keys, errKeys;
  keys.reserve(shuffledMap.size());
  errKeys.reserve(shuffledErrMap.size());

  for(auto const &pair : shuffledMap)
    keys.push_back(pair.first);
  
  for(auto const &pair : shuffledErrMap)
    errKeys.push_back(pair.first);

  shuffledMap.clear();
  shuffledErrMap.clear();

  {
    // fill the map
    std::map<uint32_t, uint64_t> map;

    auto startTime = std::chrono::steady_clock::now();

    for(auto key : keys)
      map.insert(std::make_pair(key, key));

    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - startTime);
    std::cout << "Fill the map: duration(ms): " << duration.count() << ", count: " << map.size() << '\n';

    // lookup in the map
    auto count = 0U;

    startTime = std::chrono::steady_clock::now();

    for(auto i = 0U; i < tryCount; ++i)
      if(map.find(keys[i % maxSize]) != map.cend())
        ++count;

    duration = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - startTime);
    std::cout << "Lookup in the map: duration(ms): " << duration.count() << ", iterations: " << count << '\n';

    // insuccessfull lookup in the map
    count = 0U;

    startTime = std::chrono::steady_clock::now();

    for(auto i = 0U; i < tryCount; ++i)
      if(map.find(errKeys[i % maxSize]) == map.cend())
        ++count;

    duration = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - startTime);
    std::cout << "Insuccessfull lookup in the map: duration(ms): " << duration.count() << ", iterations: " << count << '\n';

    std::cout << '\n';
  }

  {
    // fill the unordered map

    std::unordered_map<uint32_t, uint64_t> unorderedMap;
    //hashMap.reserve(keys.size());

    auto startTime = std::chrono::steady_clock::now();

    for(auto key : keys)
      unorderedMap.insert(std::make_pair(key, key));

    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - startTime);
    std::cout << "Fill the unordered map: duration(ms): " << duration.count() << ", count: " << unorderedMap.size() << '\n';

    // lookup in the hash map
    auto count = 0U;

    startTime = std::chrono::steady_clock::now();

    for(auto i = 0U; i < tryCount; ++i)
      if(unorderedMap.find(keys[i % maxSize]) != unorderedMap.cend())
        ++count;

    duration = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - startTime);
    std::cout << "Lookup in the unordered map: duration(ms): " << duration.count() << ", iterations: " << count << '\n';

    // insuccessfull lookup
    count = 0U;

    startTime = std::chrono::steady_clock::now();

    for(auto i = 0U; i < tryCount; ++i)
      if(unorderedMap.find(errKeys[i % maxSize]) == unorderedMap.cend())
        ++count;

    duration = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - startTime);
    std::cout << "Insuccessfull lookup in the unordered map: duration(ms): " << duration.count() << ", iterations: " << count << '\n';

    std::cout << '\n';
  }
}


constexpr auto maxSize = 500000U;

void mapTestMem()
{
  std::map<uint32_t, uint64_t> map;

  for(auto i = 0U; i < maxSize; ++i)
    map.emplace(i, i);
}

void unorderedMapTestMem()
{
  std::unordered_map<uint32_t, uint64_t> map;
  map.reserve(maxSize);

  for(auto i = 0U; i < maxSize; ++i)
    map.emplace(i, i);
}

int main(int argc, char *argv[])
{
  testHashMapAndMap();
  return 0;
}

return;
}
// parse the packet
Packet packet(spRawPacket.get(), false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can make a small optimization if it is applicable: Packet packet(spRawPacket.get(), false, TCP)

Comment on lines +72 to +85
// get IP layer
Layer* ipLayer = nullptr;
if (packet.isPacketOfType(IPv4))
ipLayer = dynamic_cast<Layer*>(packet.getLayerOfType<IPv4Layer>());
else if (packet.isPacketOfType(IPv6))
ipLayer = dynamic_cast<Layer*>(packet.getLayerOfType<IPv6Layer>());

if (ipLayer == nullptr)
return;

// Ignore non-TCP packets
TcpLayer* tcpLayer = packet.getLayerOfType<TcpLayer>();
if (tcpLayer == nullptr)
return;
Copy link
Contributor

@gx740 gx740 Dec 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possible optimizations:

  // get IP layer
  Layer* ipLayer;
  if (packet.isPacketOfType(IPv4))
    ipLayer = dynamic_cast<Layer*>(packet.getLayerOfType<IPv4Layer>());
  else if (packet.isPacketOfType(IPv6))
    ipLayer = dynamic_cast<Layer*>(packet.getLayerOfType<IPv6Layer>());
  else
    return;

  // Ignore non-TCP packets
  TcpLayer* tcpLayer = packet.getNextLayerOfType<TcpLayer>(ipLayer);
  if (tcpLayer == nullptr)
    return;

return;
}
// create a TcpSorterData object and add it to the active connection list
tcpSorterData = std::make_shared<TcpSorterData>();
Copy link
Contributor

@gx740 gx740 Dec 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a unique_ptr is acceptable in this case you should use it instead of shared_ptr

else if (tcpSorterData->numOfSides == 2)
{
// check if packet matches side 0
if (tcpSorterData->twoSides[0].srcIP->equals(srcIP) && tcpSorterData->twoSides[0].srcPort == srcPort)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small optimization: change the order of the checks

sndIdx = 0;
}
// check if packet matches side 1
else if (tcpSorterData->twoSides[1].srcIP->equals(srcIP) && tcpSorterData->twoSides[1].srcPort == srcPort)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small optimization: change the order of the checks


// collect flowKeys from TCP connection list
auto iterKey = m_ConnectionList.begin(), iterKeyEnd = m_ConnectionList.end();
std::vector<uint32_t> accessKeys;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Append the following: accessKeys.reserve(m_ConnectionList.size());

@seladb
Copy link
Owner

seladb commented Jan 29, 2020

hi @rickyzhang82 I'm wondering if we can revive this PR. I think you almost completed the work, why shouldn't we complete the remaining work and merge it?

@rickyzhang82
Copy link
Contributor Author

There are 3 road blocks:

  1. No unit test
  2. C++ 11
  3. missing capturing packet at rate 0.73% by libpcap

If you can live with all 3, I will send a PR again.

@seladb
Copy link
Owner

seladb commented Jan 30, 2020

with number 3 I can live and we can work together on numbers 1 and 2.

Specifically for number 2 - I can help if you'd like me to.

@rickyzhang82
Copy link
Contributor Author

C++ 11 is a deal breaker to me. The app PacketSorter has ran for almost two months in my router. I don't want to mess with memory leak in Valgrind because of taking away smart pointer.

@seladb
Copy link
Owner

seladb commented Jan 31, 2020

You can still run your own branch of TcpSorter with the C++11 features and at the same merge to master a version that doesn't include these feature

@rickyzhang82
Copy link
Contributor Author

I prefer to have one single code base. Otherwise, neither of us share the benefit of the merging.

I may find bugs and patch it in future. But the fix may not be in your repository and vice versa.

@seladb
Copy link
Owner

seladb commented Feb 5, 2020

the issue itself is still open (#100) so at some point someone else might want to implement the TCP reorder/sort functionality. That will be a pity because you already implemented it, and we'll end up having 2 implementations of the same thing.

Can we find any way to bridge this gap and get your code merged?

If I remember correctly there aren't a lot of places you're using C++11 specific features so maybe we can work something out

@rickyzhang82
Copy link
Contributor Author

I'm not convinced that removing smart pointer is a good idea in year 2020. So I can't compromise on C++ 11.

One of the callback function makes a copy of packet and passes it down to the end user application. It is better leave it to the smart pointer to do clean up. I have no interest to exercise memory leak detection again in Valgrind.

@seladb
Copy link
Owner

seladb commented Feb 6, 2020

Ok I give up. If you think of any way we can bridge the gaps and get your PR merged please let me know. Otherwise let's keep it as is

@rickyzhang82
Copy link
Contributor Author

Please let me know when you allow C++ 11 in PcapPlusPlus.

All latest Linux and FreeBSD comes with GCC and Clang supports C++ 11.

Thanks!

@seladb
Copy link
Owner

seladb commented Mar 15, 2020

hi @rickyzhang82 do you mind if I use your code and make some necessary changes to support older versions of C++?

@rickyzhang82
Copy link
Contributor Author

@seladb Please feel free to use it from my my-master branch:

https://github.com/rickyzhang82/PcapPlusPlus

Thanks!

@seladb
Copy link
Owner

seladb commented Mar 16, 2020

Thanks! since the branch you created the PR from is gone, the only way I see is to copy the code into my branch and then do the necessary changes. However that way your name won't be in it. Are you ok with this or do you want to create another PR that I'll later modify?

@seladb
Copy link
Owner

seladb commented Mar 18, 2020

@rickyzhang82 did you get my previous comment?

@rickyzhang82
Copy link
Contributor Author

No problem. Feel free to do whatever you want!

Thanks!

@seladb
Copy link
Owner

seladb commented Mar 19, 2020

Thank you @rickyzhang82 , I'll keep you posted

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants