From 80b3f8b33d6cfd50d798c77045ca4b93208ecaac Mon Sep 17 00:00:00 2001 From: Dimitar Krastev Date: Wed, 23 Apr 2025 13:23:17 +0300 Subject: [PATCH 1/5] Added getStatistics method to PcapHandle to allow encapsulate simple fetching of statistics from PcapHandle via pcap_stats. --- Pcap++/header/PcapDevice.h | 32 +++++++++++++++++++++----------- Pcap++/src/PcapDevice.cpp | 21 +++++++++++++++++++++ Pcap++/src/PcapLiveDevice.cpp | 7 +------ 3 files changed, 43 insertions(+), 17 deletions(-) diff --git a/Pcap++/header/PcapDevice.h b/Pcap++/header/PcapDevice.h index 7352ef9c2..ff0957cec 100644 --- a/Pcap++/header/PcapDevice.h +++ b/Pcap++/header/PcapDevice.h @@ -18,6 +18,18 @@ namespace pcpp namespace internal { + /// @struct PcapStats + /// A container for pcap device statistics + struct PcapStats + { + /// Number of packets received + uint64_t packetsRecv; + /// Number of packets dropped + uint64_t packetsDrop; + /// number of packets dropped by interface (not supported on all platforms) + uint64_t packetsDropByInterface; + }; + /// @class PcapHandle /// @brief A wrapper class for pcap_t* which is the libpcap packet capture descriptor. /// This class is used to manage the lifecycle of the pcap_t* object @@ -77,6 +89,14 @@ namespace pcpp /// @return True if the filter was removed successfully or if no filter was set, false otherwise. bool clearFilter(); + /// @brief Retrieves statistics from the pcap handle. + /// + /// The function internally calls pcap_stats() to retrieve the statistics and only works on live devices. + /// + /// @param stats Structure to store the statistics. + /// @return True if the statistics were retrieved successfully, false otherwise. + bool getStatistics(PcapStats& stats) const; + /// @return True if the handle is not null, false otherwise. explicit operator bool() const noexcept { @@ -110,17 +130,7 @@ namespace pcpp {} public: - /// @struct PcapStats - /// A container for pcap device statistics - struct PcapStats - { - /// Number of packets received - uint64_t packetsRecv; - /// Number of packets dropped - uint64_t packetsDrop; - /// number of packets dropped by interface (not supported on all platforms) - uint64_t packetsDropByInterface; - }; + using PcapStats = internal::PcapStats; virtual ~IPcapDevice(); diff --git a/Pcap++/src/PcapDevice.cpp b/Pcap++/src/PcapDevice.cpp index 6f68092cc..9d7747ee2 100644 --- a/Pcap++/src/PcapDevice.cpp +++ b/Pcap++/src/PcapDevice.cpp @@ -103,6 +103,27 @@ namespace pcpp { return setFilter(""); } + + bool PcapHandle::getStatistics(PcapStats& stats) const + { + if (!isValid()) + { + PCPP_LOG_ERROR("Cannot get stats from invalid handle"); + return false; + } + + pcap_stat pcapStats; + if (pcap_stats(m_PcapDescriptor, &pcapStats) < 0) + { + PCPP_LOG_ERROR("Error getting stats. Error message is: " << getLastError()); + return false; + } + + stats.packetsRecv = pcapStats.ps_recv; + stats.packetsDrop = pcapStats.ps_drop; + stats.packetsDropByInterface = pcapStats.ps_ifdrop; + return true; + } } // namespace internal IPcapDevice::~IPcapDevice() diff --git a/Pcap++/src/PcapLiveDevice.cpp b/Pcap++/src/PcapLiveDevice.cpp index ccf0b2fd2..7074d578e 100644 --- a/Pcap++/src/PcapLiveDevice.cpp +++ b/Pcap++/src/PcapLiveDevice.cpp @@ -823,15 +823,10 @@ namespace pcpp void PcapLiveDevice::getStatistics(PcapStats& stats) const { - pcap_stat pcapStats; - if (pcap_stats(m_PcapDescriptor.get(), &pcapStats) < 0) + if (!m_PcapDescriptor.getStatistics(stats)) { PCPP_LOG_ERROR("Error getting statistics from live device '" << m_InterfaceDetails.name << "'"); } - - stats.packetsRecv = pcapStats.ps_recv; - stats.packetsDrop = pcapStats.ps_drop; - stats.packetsDropByInterface = pcapStats.ps_ifdrop; } bool PcapLiveDevice::doMtuCheck(int packetPayloadLength) const From 8230c92ed08ee507a7e5abc1af1436c6ac32a4f5 Mon Sep 17 00:00:00 2001 From: Dimitar Krastev Date: Wed, 23 Apr 2025 13:28:42 +0300 Subject: [PATCH 2/5] Encapsulated logic for running the statistics update worker into its own class. --- Pcap++/header/PcapLiveDevice.h | 53 +++++++++++++++-- Pcap++/src/PcapLiveDevice.cpp | 103 +++++++++++++++++++++++---------- 2 files changed, 119 insertions(+), 37 deletions(-) diff --git a/Pcap++/header/PcapLiveDevice.h b/Pcap++/header/PcapLiveDevice.h index 8b3754a50..cbb87a3bc 100644 --- a/Pcap++/header/PcapLiveDevice.h +++ b/Pcap++/header/PcapLiveDevice.h @@ -82,6 +82,50 @@ namespace pcpp bool isLoopback; }; + /// @brief A worker thread that periodically calls the provided callback with updated statistics. + class StatisticsUpdateWorker + { + public: + /// @brief Constructs and starts a worker thread that periodically calls the provided callback with updated + /// statistics. + /// @param pcapHandle A pcap handle to the device to be monitored. + /// @param onStatsUpdateCallback A callback function to be called with updated statistics. + /// @param m_cbOnStatsUpdateUserCookie A user-defined pointer that is passed to the callback function. + /// @param updateIntervalMs The interval in milliseconds between each callback invocation. + StatisticsUpdateWorker(PcapLiveDevice const& pcapDevice, OnStatsUpdateCallback onStatsUpdateCallback, + void* m_cbOnStatsUpdateUserCookie = nullptr, unsigned int updateIntervalMs = 1000); + + bool isRunning() const + { + // TODO: A thread that has finished executing will be joinable, before join is called. + return m_WorkerThread.joinable(); + } + + /// @brief Stops the worker thread. + void stopWorker(); + + private: + struct ThreadData + { + PcapLiveDevice const* m_PcapDevice; + OnStatsUpdateCallback m_cbOnStatsUpdate; + void* m_cbOnStatsUpdateUserCookie = nullptr; + unsigned int m_updateIntervalMs = 1000; // Default update interval is 1 second + }; + + struct SharedThreadData + { + std::atomic_bool m_stopRequested = false; + }; + + /// @brief Main function for the worker thread. + /// @remarks This function is static to allow the worker class to be movable. + static void workerMain(std::shared_ptr sharedThreadData, ThreadData threadData); + + std::shared_ptr m_sharedThreadData; + std::thread m_WorkerThread; + }; + // This is a second descriptor for the same device. It is needed because of a bug // that occurs in libpcap on Linux (on Windows using WinPcap/Npcap it works well): // It's impossible to capture packets sent by the same descriptor @@ -94,8 +138,9 @@ namespace pcpp MacAddress m_MacAddress; IPv4Address m_DefaultGateway; std::thread m_CaptureThread; - std::thread m_StatsThread; - bool m_StatsThreadStarted; + + // TODO: Cpp17 Using std::optional might be better here + std::unique_ptr m_StatisticsUpdateWorker; // Should be set to true by the Caller for the Callee std::atomic m_StopThread; @@ -104,11 +149,8 @@ namespace pcpp OnPacketArrivesCallback m_cbOnPacketArrives; void* m_cbOnPacketArrivesUserCookie; - OnStatsUpdateCallback m_cbOnStatsUpdate; - void* m_cbOnStatsUpdateUserCookie; OnPacketArrivesStopBlocking m_cbOnPacketArrivesBlockingMode; void* m_cbOnPacketArrivesBlockingModeUserCookie; - int m_IntervalToUpdateStats; RawPacketVector* m_CapturedPackets; bool m_CaptureCallbackMode; LinkLayerType m_LinkType; @@ -128,7 +170,6 @@ namespace pcpp // threads void captureThreadMain(); - void statsThreadMain(); static void onPacketArrives(uint8_t* user, const struct pcap_pkthdr* pkthdr, const uint8_t* packet); static void onPacketArrivesNoCallback(uint8_t* user, const struct pcap_pkthdr* pkthdr, const uint8_t* packet); diff --git a/Pcap++/src/PcapLiveDevice.cpp b/Pcap++/src/PcapLiveDevice.cpp index 7074d578e..315158dff 100644 --- a/Pcap++/src/PcapLiveDevice.cpp +++ b/Pcap++/src/PcapLiveDevice.cpp @@ -233,6 +233,68 @@ namespace pcpp } } + PcapLiveDevice::StatisticsUpdateWorker::StatisticsUpdateWorker(PcapLiveDevice const& pcapDevice, + OnStatsUpdateCallback onStatsUpdateCallback, + void* m_cbOnStatsUpdateUserCookie, + unsigned int updateIntervalMs) + { + // Setup thread data + m_sharedThreadData = std::make_shared(); + + ThreadData threadData; + threadData.m_PcapDevice = &pcapDevice; + threadData.m_cbOnStatsUpdate = onStatsUpdateCallback; + threadData.m_cbOnStatsUpdateUserCookie = m_cbOnStatsUpdateUserCookie; + threadData.m_updateIntervalMs = updateIntervalMs; + + // Start the thread + m_WorkerThread = std::thread(&StatisticsUpdateWorker::workerMain, m_sharedThreadData, std::move(threadData)); + } + + void PcapLiveDevice::StatisticsUpdateWorker::stopWorker() + { + m_sharedThreadData->m_stopRequested = true; + if (m_WorkerThread.joinable()) + { + m_WorkerThread.join(); + } + } + + void PcapLiveDevice::StatisticsUpdateWorker::workerMain(std::shared_ptr sharedThreadData, + ThreadData threadData) + { + if (sharedThreadData == nullptr) + { + PCPP_LOG_ERROR("Shared thread data is null"); + return; + } + + if (threadData.m_PcapDevice == nullptr) + { + PCPP_LOG_ERROR("Pcap device is null"); + return; + } + + if (threadData.m_cbOnStatsUpdate == nullptr) + { + PCPP_LOG_ERROR("Statistics Callback is null"); + return; + } + + PCPP_LOG_DEBUG("Started statistics thread"); + + PcapStats stats; + auto sleepDuration = std::chrono::milliseconds(threadData.m_updateIntervalMs); + while (!sharedThreadData->m_stopRequested) + { + threadData.m_PcapDevice->getStatistics(stats); + threadData.m_cbOnStatsUpdate(stats, threadData.m_cbOnStatsUpdateUserCookie); + std::this_thread::sleep_for(sleepDuration); + } + + PCPP_LOG_DEBUG("Stopped statistics thread"); + } + PcapLiveDevice::PcapLiveDevice(DeviceInterfaceDetails interfaceDetails, bool calculateMTU, bool calculateMacAddress, bool calculateDefaultGateway) : IPcapDevice(), m_PcapSendDescriptor(nullptr), m_PcapSelectableFd(-1), @@ -266,17 +328,12 @@ namespace pcpp // init all other members m_CaptureThreadStarted = false; - m_StatsThreadStarted = false; m_StopThread = false; m_CaptureThread = {}; - m_StatsThread = {}; m_cbOnPacketArrives = nullptr; - m_cbOnStatsUpdate = nullptr; m_cbOnPacketArrivesBlockingMode = nullptr; m_cbOnPacketArrivesBlockingModeUserCookie = nullptr; - m_IntervalToUpdateStats = 0; m_cbOnPacketArrivesUserCookie = nullptr; - m_cbOnStatsUpdateUserCookie = nullptr; m_CaptureCallbackMode = true; m_CapturedPackets = nullptr; if (calculateMacAddress) @@ -366,19 +423,6 @@ namespace pcpp PCPP_LOG_DEBUG("Ended capture thread for device '" << m_InterfaceDetails.name << "'"); } - void PcapLiveDevice::statsThreadMain() - { - PCPP_LOG_DEBUG("Started stats thread for device '" << m_InterfaceDetails.name << "'"); - while (!m_StopThread) - { - PcapStats stats; - getStatistics(stats); - m_cbOnStatsUpdate(stats, m_cbOnStatsUpdateUserCookie); - std::this_thread::sleep_for(std::chrono::seconds(m_IntervalToUpdateStats)); - } - PCPP_LOG_DEBUG("Ended stats thread for device '" << m_InterfaceDetails.name << "'"); - } - pcap_t* PcapLiveDevice::doOpen(const DeviceConfiguration& config) { char errbuf[PCAP_ERRBUF_SIZE] = { '\0' }; @@ -606,8 +650,6 @@ namespace pcpp return false; } - m_IntervalToUpdateStats = intervalInSecondsToUpdateStats; - m_CaptureCallbackMode = true; m_cbOnPacketArrives = std::move(onPacketArrives); m_cbOnPacketArrivesUserCookie = onPacketArrivesUserCookie; @@ -625,12 +667,12 @@ namespace pcpp if (onStatsUpdate != nullptr && intervalInSecondsToUpdateStats > 0) { - m_cbOnStatsUpdate = std::move(onStatsUpdate); - m_cbOnStatsUpdateUserCookie = onStatsUpdateUserCookie; - m_StatsThread = std::thread(&pcpp::PcapLiveDevice::statsThreadMain, this); - m_StatsThreadStarted = true; - PCPP_LOG_DEBUG("Successfully created stats thread for device '" - << m_InterfaceDetails.name << "'. Thread id: " << m_StatsThread.get_id()); + // Due to passing a this pointer, the current device object shouldn't be relocated, while toe worker is + // active. + m_StatisticsUpdateWorker = std::unique_ptr(new StatisticsUpdateWorker( + *this, std::move(onStatsUpdate), onStatsUpdateUserCookie, intervalInSecondsToUpdateStats * 1000)); + + PCPP_LOG_DEBUG("Successfully created stats thread for device '" << m_InterfaceDetails.name << "'."); } return true; @@ -684,9 +726,7 @@ namespace pcpp } m_cbOnPacketArrives = nullptr; - m_cbOnStatsUpdate = nullptr; m_cbOnPacketArrivesUserCookie = nullptr; - m_cbOnStatsUpdateUserCookie = nullptr; m_cbOnPacketArrivesBlockingMode = std::move(onPacketArrives); m_cbOnPacketArrivesBlockingModeUserCookie = userCookie; @@ -805,11 +845,12 @@ namespace pcpp PCPP_LOG_DEBUG("Capture thread stopped for device '" << m_InterfaceDetails.name << "'"); } PCPP_LOG_DEBUG("Capture thread stopped for device '" << m_InterfaceDetails.name << "'"); - if (m_StatsThreadStarted) + + if (m_StatisticsUpdateWorker != nullptr) { PCPP_LOG_DEBUG("Stopping stats thread, waiting for it to join..."); - m_StatsThread.join(); - m_StatsThreadStarted = false; + m_StatisticsUpdateWorker->stopWorker(); + m_StatisticsUpdateWorker.reset(); PCPP_LOG_DEBUG("Stats thread stopped for device '" << m_InterfaceDetails.name << "'"); } From 581d54bcbb2763f981542ac2e8e7f50685ee40a4 Mon Sep 17 00:00:00 2001 From: Dimitar Krastev Date: Wed, 23 Apr 2025 13:40:38 +0300 Subject: [PATCH 3/5] Fixed atomic bool initialization. --- Pcap++/header/PcapLiveDevice.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pcap++/header/PcapLiveDevice.h b/Pcap++/header/PcapLiveDevice.h index cbb87a3bc..b1bc854ad 100644 --- a/Pcap++/header/PcapLiveDevice.h +++ b/Pcap++/header/PcapLiveDevice.h @@ -115,7 +115,7 @@ namespace pcpp struct SharedThreadData { - std::atomic_bool m_stopRequested = false; + std::atomic_bool m_stopRequested{ false }; }; /// @brief Main function for the worker thread. From 6b891c543c7707610cd5fa9aada55e1bf491232f Mon Sep 17 00:00:00 2001 From: Dimitar Krastev Date: Wed, 23 Apr 2025 13:41:46 +0300 Subject: [PATCH 4/5] Fixed documentation. --- Pcap++/header/PcapLiveDevice.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pcap++/header/PcapLiveDevice.h b/Pcap++/header/PcapLiveDevice.h index b1bc854ad..1f25d94db 100644 --- a/Pcap++/header/PcapLiveDevice.h +++ b/Pcap++/header/PcapLiveDevice.h @@ -88,7 +88,7 @@ namespace pcpp public: /// @brief Constructs and starts a worker thread that periodically calls the provided callback with updated /// statistics. - /// @param pcapHandle A pcap handle to the device to be monitored. + /// @param pcapDevice A pointer to the PcapLiveDevice instance to be monitored. /// @param onStatsUpdateCallback A callback function to be called with updated statistics. /// @param m_cbOnStatsUpdateUserCookie A user-defined pointer that is passed to the callback function. /// @param updateIntervalMs The interval in milliseconds between each callback invocation. From c8bd08f55ca02729c5b8c25859a32db89276a92d Mon Sep 17 00:00:00 2001 From: Dimitar Krastev Date: Wed, 23 Apr 2025 13:47:04 +0300 Subject: [PATCH 5/5] Defaulted ptr to nullptr. --- Pcap++/header/PcapLiveDevice.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pcap++/header/PcapLiveDevice.h b/Pcap++/header/PcapLiveDevice.h index 1f25d94db..34fec6e5d 100644 --- a/Pcap++/header/PcapLiveDevice.h +++ b/Pcap++/header/PcapLiveDevice.h @@ -107,7 +107,7 @@ namespace pcpp private: struct ThreadData { - PcapLiveDevice const* m_PcapDevice; + PcapLiveDevice const* m_PcapDevice = nullptr; OnStatsUpdateCallback m_cbOnStatsUpdate; void* m_cbOnStatsUpdateUserCookie = nullptr; unsigned int m_updateIntervalMs = 1000; // Default update interval is 1 second