Skip to content

Commit

Permalink
Improve nanosecond precision support for pcap (#1368)
Browse files Browse the repository at this point in the history
* runtime nanosecond precision support

* minor fix for cppcheck

* this should fix winpcap?

* reorder

* remove unnecessary changes + add log line

* should fix winpcap

* update pcap write precision

* Fix write issues

* tab indent

* Just tiny update

* add method to get precision of file

* function to check supported precision from runtime

* add tests

* make function static + pre-commit fix

* Fix test case for windows

* for read it always nanosecond

* Remove unnecessary ternary

* oops :)

* fix for comments

* vs not likes constexpr

* Update naming + spaces to tabs

* update tests for timestamp precision before open

* improve tests

* one more space to tab

* Add tests for reader
  • Loading branch information
egecetin authored May 24, 2024
1 parent cde8089 commit ed6db69
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 5 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ Tests/Pcap++Test/cUrl/curl_output.txt
*.VC.db
*.vcxproj.user
**/.vs
CMakeSettings.json

#Visual Studio Projects
mk/vs2015/**
Expand Down
44 changes: 41 additions & 3 deletions Pcap++/header/PcapFileDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@ typedef struct pcap_dumper pcap_dumper_t;
*/
namespace pcpp
{
/**
* @enum FileTimestampPrecision
* An enumeration representing the precision of timestamps in a pcap file.
* The precision can be Unknown, Micro, or Nano.
*/
enum class FileTimestampPrecision : int8_t
{
/// Precision is unknown or not set/determined
Unknown = -1,
/// Precision is in microseconds.
Microseconds = 0,
/// Precision is in nanoseconds.
Nanoseconds = 1
};

/**
* @class IFileDevice
Expand All @@ -36,7 +50,6 @@ namespace pcpp
*/
std::string getFileName() const;


//override methods

/**
Expand Down Expand Up @@ -103,6 +116,7 @@ namespace pcpp
class PcapFileReaderDevice : public IFileReaderDevice
{
private:
FileTimestampPrecision m_Precision;
LinkLayerType m_PcapLinkLayerType;

// private copy c'tor
Expand All @@ -115,7 +129,7 @@ namespace pcpp
* isn't opened yet, so reading packets will fail. For opening the file call open()
* @param[in] fileName The full path of the file to read
*/
PcapFileReaderDevice(const std::string& fileName) : IFileReaderDevice(fileName), m_PcapLinkLayerType(LINKTYPE_ETHERNET) {}
PcapFileReaderDevice(const std::string& fileName) : IFileReaderDevice(fileName), m_Precision(FileTimestampPrecision::Unknown), m_PcapLinkLayerType(LINKTYPE_ETHERNET) {}

/**
* A destructor for this class
Expand All @@ -127,6 +141,17 @@ namespace pcpp
*/
LinkLayerType getLinkLayerType() const { return m_PcapLinkLayerType; }

/**
* @return The precision of the timestamps in the file. If the platform supports nanosecond precision, this method will return
* nanoseconds even if the file has microseconds since libpcap scales timestamps before supply. Otherwise, it will return microseconds.
*/
FileTimestampPrecision getTimestampPrecision() const { return m_Precision; }

/**
* A static method that checks if nano-second precision is supported in the current platform and environment
* @return True if nano-second precision is supported, false otherwise
*/
static bool isNanoSecondPrecisionSupported();

//overridden methods

Expand Down Expand Up @@ -384,6 +409,7 @@ namespace pcpp
pcap_dumper_t* m_PcapDumpHandler;
LinkLayerType m_PcapLinkLayerType;
bool m_AppendMode;
FileTimestampPrecision m_Precision;
FILE* m_File;

// private copy c'tor
Expand All @@ -398,8 +424,9 @@ namespace pcpp
* constructor the file isn't opened yet, so writing packets will fail. For opening the file call open()
* @param[in] fileName The full path of the file
* @param[in] linkLayerType The link layer type all packet in this file will be based on. The default is Ethernet
* @param[in] nanosecondsPrecision A boolean indicating whether to write timestamps in nano-precision. If set to false, timestamps will be written in micro-precision
*/
PcapFileWriterDevice(const std::string& fileName, LinkLayerType linkLayerType = LINKTYPE_ETHERNET);
PcapFileWriterDevice(const std::string& fileName, LinkLayerType linkLayerType = LINKTYPE_ETHERNET, bool nanosecondsPrecision = false);

/**
* A destructor for this class
Expand All @@ -425,6 +452,17 @@ namespace pcpp
*/
bool writePackets(const RawPacketVector& packets) override;

/**
* @return The precision of the timestamps in the file.
*/
FileTimestampPrecision getTimestampPrecision() const { return m_Precision; }

/**
* A static method that checks if nano-second precision is supported in the current platform and environment
* @return True if nano-second precision is supported, false otherwise
*/
static bool isNanoSecondPrecisionSupported();

//override methods

/**
Expand Down
60 changes: 58 additions & 2 deletions Pcap++/src/PcapFileDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ struct packet_header
uint32_t len;
};

static bool checkNanoSupport()
{
#if defined(PCAP_TSTAMP_PRECISION_NANO)
return true;
#else
PCPP_LOG_DEBUG(
"PcapPlusPlus was compiled without nano precision support which requires libpcap > 1.5.1. Please "
"recompile PcapPlusPlus with nano precision support to use this feature. Using default microsecond precision");
return false;
#endif
}

// ~~~~~~~~~~~~~~~~~~~
// IFileDevice members
// ~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -275,11 +287,23 @@ bool PcapFileReaderDevice::open()

m_PcapLinkLayerType = static_cast<LinkLayerType>(linkLayer);

PCPP_LOG_DEBUG("Successfully opened file reader device for filename '" << m_FileName << "'");
#if defined(PCAP_TSTAMP_PRECISION_NANO)
m_Precision = static_cast<FileTimestampPrecision>(pcap_get_tstamp_precision(m_PcapDescriptor));
std::string precisionStr = (m_Precision == FileTimestampPrecision::Nanoseconds) ? "nanoseconds" : "microseconds";
#else
m_Precision = FileTimestampPrecision::Microseconds;
std::string precisionStr = "microseconds";
#endif
PCPP_LOG_DEBUG("Successfully opened file reader device for filename '" << m_FileName << "' with precision " << precisionStr);
m_DeviceOpened = true;
return true;
}

bool PcapFileReaderDevice::isNanoSecondPrecisionSupported()
{
return checkNanoSupport();
}

void PcapFileReaderDevice::getStatistics(PcapStats& stats) const
{
stats.packetsRecv = m_NumOfPacketsRead;
Expand Down Expand Up @@ -528,13 +552,24 @@ IFileWriterDevice:: IFileWriterDevice(const std::string& fileName) : IFileDevice
// PcapFileWriterDevice members
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

PcapFileWriterDevice::PcapFileWriterDevice(const std::string& fileName, LinkLayerType linkLayerType) : IFileWriterDevice(fileName)
PcapFileWriterDevice::PcapFileWriterDevice(const std::string& fileName, LinkLayerType linkLayerType, bool nanosecondsPrecision) : IFileWriterDevice(fileName)
{
m_PcapDumpHandler = nullptr;
m_NumOfPacketsNotWritten = 0;
m_NumOfPacketsWritten = 0;
m_PcapLinkLayerType = linkLayerType;
m_AppendMode = false;
#if defined(PCAP_TSTAMP_PRECISION_NANO)
m_Precision = nanosecondsPrecision ? FileTimestampPrecision::Nanoseconds : FileTimestampPrecision::Microseconds;
#else
if (nanosecondsPrecision)
{
PCPP_LOG_ERROR(
"PcapPlusPlus was compiled without nano precision support which requires libpcap > 1.5.1. Please "
"recompile PcapPlusPlus with nano precision support to use this feature. Using default microsecond precision");
}
m_Precision = FileTimestampPrecision::Microseconds;
#endif
m_File = nullptr;
}

Expand Down Expand Up @@ -567,7 +602,19 @@ bool PcapFileWriterDevice::writePacket(RawPacket const& packet)
pktHdr.caplen = ((RawPacket&)packet).getRawDataLen();
pktHdr.len = ((RawPacket&)packet).getFrameLength();
timespec packet_timestamp = ((RawPacket&)packet).getPacketTimeStamp();
#if defined(PCAP_TSTAMP_PRECISION_NANO)
if (m_Precision != FileTimestampPrecision::Nanoseconds)
{
TIMESPEC_TO_TIMEVAL(&pktHdr.ts, &packet_timestamp);
}
else
{
pktHdr.ts.tv_sec = packet_timestamp.tv_sec;
pktHdr.ts.tv_usec = packet_timestamp.tv_nsec;
}
#else
TIMESPEC_TO_TIMEVAL(&pktHdr.ts, &packet_timestamp);
#endif
if (!m_AppendMode)
pcap_dump((uint8_t*)m_PcapDumpHandler, &pktHdr, ((RawPacket&)packet).getRawData());
else
Expand Down Expand Up @@ -604,6 +651,11 @@ bool PcapFileWriterDevice::writePackets(const RawPacketVector& packets)
return true;
}

bool PcapFileWriterDevice::isNanoSecondPrecisionSupported()
{
return checkNanoSupport();
}

bool PcapFileWriterDevice::open()
{
if (m_PcapDescriptor != nullptr)
Expand All @@ -625,7 +677,11 @@ bool PcapFileWriterDevice::open()
m_NumOfPacketsNotWritten = 0;
m_NumOfPacketsWritten = 0;

#if defined(PCAP_TSTAMP_PRECISION_NANO)
m_PcapDescriptor = pcap_open_dead_with_tstamp_precision(m_PcapLinkLayerType, PCPP_MAX_PACKET_SIZE, static_cast<int>(m_Precision));
#else
m_PcapDescriptor = pcap_open_dead(m_PcapLinkLayerType, PCPP_MAX_PACKET_SIZE);
#endif
if (m_PcapDescriptor == nullptr)
{
PCPP_LOG_ERROR("Error opening file writer device for file '" << m_FileName << "': pcap_open_dead returned NULL");
Expand Down
2 changes: 2 additions & 0 deletions Tests/Pcap++Test/Common/PcapFileNamesDef.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@
#define EXAMPLE_SOLARIS_SNOOP "PcapExamples/solaris.snoop"
#define SLL2_PCAP_PATH "PcapExamples/sll2.pcap"
#define SLL2_PCAP_WRITE_PATH "PcapExamples/sll2_copy.pcap"
#define EXAMPLE_PCAP_MICRO_PATH "PcapExamples/microsecs.pcap"
#define EXAMPLE_PCAP_NANO_PATH "PcapExamples/nanosecs.pcap"
1 change: 1 addition & 0 deletions Tests/Pcap++Test/TestDefinition.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ PTF_TEST_CASE(TestLoggerMultiThread);

// Implemented in FileTests.cpp
PTF_TEST_CASE(TestPcapFileReadWrite);
PTF_TEST_CASE(TestPcapFilePrecision);
PTF_TEST_CASE(TestPcapSllFileReadWrite);
PTF_TEST_CASE(TestPcapSll2FileReadWrite);
PTF_TEST_CASE(TestPcapRawIPFileReadWrite);
Expand Down
74 changes: 74 additions & 0 deletions Tests/Pcap++Test/Tests/FileTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "Packet.h"
#include "PcapFileDevice.h"
#include "../Common/PcapFileNamesDef.h"
#include <array>
#include <fstream>


Expand Down Expand Up @@ -103,6 +104,79 @@ PTF_TEST_CASE(TestPcapFileReadWrite)
PTF_ASSERT_FALSE(readerDev2.isOpened());
} // TestPcapFileReadWrite

PTF_TEST_CASE(TestPcapFilePrecision)
{
std::array<uint8_t, 16> testPayload = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
pcpp::RawPacket rawPacketNano(testPayload.data(), testPayload.size(), timespec({1, 1234}), false); // 1.000001234
pcpp::RawPacket rawPacketMicro(testPayload.data(), testPayload.size(), timeval({1, 2}), false); // 1.000002000

// Writer precision support should equal to reader precision support
PTF_ASSERT_EQUAL(pcpp::PcapFileWriterDevice::isNanoSecondPrecisionSupported(), pcpp::PcapFileReaderDevice::isNanoSecondPrecisionSupported());

// Write nano precision file
pcpp::PcapFileWriterDevice writerDevNano(EXAMPLE_PCAP_NANO_PATH, pcpp::LINKTYPE_ETHERNET, true);
PTF_ASSERT_EQUAL(writerDevNano.getTimestampPrecision(), pcpp::PcapFileWriterDevice::isNanoSecondPrecisionSupported()
? pcpp::FileTimestampPrecision::Nanoseconds
: pcpp::FileTimestampPrecision::Microseconds, enumclass);
PTF_ASSERT_TRUE(writerDevNano.open());
PTF_ASSERT_EQUAL(writerDevNano.getTimestampPrecision(), pcpp::PcapFileWriterDevice::isNanoSecondPrecisionSupported()
? pcpp::FileTimestampPrecision::Nanoseconds
: pcpp::FileTimestampPrecision::Microseconds, enumclass);
PTF_ASSERT_TRUE(writerDevNano.writePacket(rawPacketMicro));
PTF_ASSERT_TRUE(writerDevNano.writePacket(rawPacketNano));
writerDevNano.close();

// Write micro precision file
pcpp::PcapFileWriterDevice writerDevMicro(EXAMPLE_PCAP_MICRO_PATH, pcpp::LINKTYPE_ETHERNET, false);
PTF_ASSERT_EQUAL(writerDevMicro.getTimestampPrecision(), pcpp::FileTimestampPrecision::Microseconds, enumclass);
PTF_ASSERT_TRUE(writerDevMicro.open());
PTF_ASSERT_EQUAL(writerDevMicro.getTimestampPrecision(), pcpp::FileTimestampPrecision::Microseconds, enumclass);
PTF_ASSERT_TRUE(writerDevMicro.writePacket(rawPacketMicro));
PTF_ASSERT_TRUE(writerDevMicro.writePacket(rawPacketNano));
writerDevMicro.close();

// Read nano precision file
pcpp::PcapFileReaderDevice readerDevNano(EXAMPLE_PCAP_NANO_PATH);
PTF_ASSERT_EQUAL(readerDevNano.getTimestampPrecision(), pcpp::FileTimestampPrecision::Unknown, enumclass);
PTF_ASSERT_TRUE(readerDevNano.open());
PTF_ASSERT_EQUAL(readerDevNano.getTimestampPrecision(),
pcpp::PcapFileReaderDevice::isNanoSecondPrecisionSupported()
? pcpp::FileTimestampPrecision::Nanoseconds
: pcpp::FileTimestampPrecision::Microseconds,
enumclass);

pcpp::RawPacket readPacketNano, readPacketMicro;
PTF_ASSERT_TRUE(readerDevNano.getNextPacket(readPacketMicro));
PTF_ASSERT_EQUAL(readPacketMicro.getPacketTimeStamp().tv_sec, 1);
PTF_ASSERT_EQUAL(readPacketMicro.getPacketTimeStamp().tv_nsec, 2000);

PTF_ASSERT_TRUE(readerDevNano.getNextPacket(readPacketNano));
PTF_ASSERT_EQUAL(readPacketNano.getPacketTimeStamp().tv_sec, 1);
PTF_ASSERT_EQUAL(readPacketNano.getPacketTimeStamp().tv_nsec, pcpp::PcapFileReaderDevice::isNanoSecondPrecisionSupported() ? 1234 : 1000);

readerDevNano.close();

// Read micro precision file
pcpp::PcapFileReaderDevice readerDevMicro(EXAMPLE_PCAP_MICRO_PATH);
PTF_ASSERT_EQUAL(readerDevMicro.getTimestampPrecision(), pcpp::FileTimestampPrecision::Unknown, enumclass);
PTF_ASSERT_TRUE(readerDevMicro.open());
PTF_ASSERT_EQUAL(readerDevMicro.getTimestampPrecision(),
pcpp::PcapFileReaderDevice::isNanoSecondPrecisionSupported()
? pcpp::FileTimestampPrecision::Nanoseconds
: pcpp::FileTimestampPrecision::Microseconds,
enumclass);

pcpp::RawPacket readPacketNano2, readPacketMicro2;
PTF_ASSERT_TRUE(readerDevMicro.getNextPacket(readPacketMicro2));
PTF_ASSERT_EQUAL(readPacketMicro2.getPacketTimeStamp().tv_sec, 1);
PTF_ASSERT_EQUAL(readPacketMicro2.getPacketTimeStamp().tv_nsec, 2000);

PTF_ASSERT_TRUE(readerDevMicro.getNextPacket(readPacketNano2));
PTF_ASSERT_EQUAL(readPacketNano2.getPacketTimeStamp().tv_sec, 1);
PTF_ASSERT_EQUAL(readPacketNano2.getPacketTimeStamp().tv_nsec, 1000);

readerDevMicro.close();
} // TestPcapFilePrecision


PTF_TEST_CASE(TestPcapSllFileReadWrite)
Expand Down
1 change: 1 addition & 0 deletions Tests/Pcap++Test/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ int main(int argc, char* argv[])
PTF_RUN_TEST(TestLoggerMultiThread, "no_network;logger;skip_mem_leak_check");

PTF_RUN_TEST(TestPcapFileReadWrite, "no_network;pcap");
PTF_RUN_TEST(TestPcapFilePrecision, "no_network;pcap");
PTF_RUN_TEST(TestPcapSllFileReadWrite, "no_network;pcap");
PTF_RUN_TEST(TestPcapSll2FileReadWrite, "no_network;pcap");
PTF_RUN_TEST(TestPcapRawIPFileReadWrite, "no_network;pcap");
Expand Down

0 comments on commit ed6db69

Please sign in to comment.