From 660843ed2d392ad5ced3d4c70c73f1620b76dbb9 Mon Sep 17 00:00:00 2001 From: "Liu, An-Chi" Date: Wed, 13 Dec 2023 16:14:36 +0900 Subject: [PATCH] implement poll() for `startCaptureBlockingMode` (#1245) --- Pcap++/header/PcapLiveDevice.h | 20 ++- Pcap++/src/PcapLiveDevice.cpp | 100 ++++++++++---- Tests/Pcap++Test/Tests/LiveDeviceTests.cpp | 149 +++++++++++---------- 3 files changed, 172 insertions(+), 97 deletions(-) diff --git a/Pcap++/header/PcapLiveDevice.h b/Pcap++/header/PcapLiveDevice.h index 06fb03c6f1..f957dc450c 100644 --- a/Pcap++/header/PcapLiveDevice.h +++ b/Pcap++/header/PcapLiveDevice.h @@ -87,6 +87,7 @@ namespace pcpp // 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 pcap_t* m_PcapSendDescriptor; + int m_PcapSelectableFd; std::string m_Name; std::string m_Description; bool m_IsLoopback; @@ -109,6 +110,7 @@ namespace pcpp RawPacketVector* m_CapturedPackets; bool m_CaptureCallbackMode; LinkLayerType m_LinkType; + bool m_UsePoll; // c'tor is not public, there should be only one for every interface (created by PcapLiveDeviceList) PcapLiveDevice(pcap_if_t* pInterface, bool calculateMTU, bool calculateMacAddress, bool calculateDefaultGateway); @@ -217,6 +219,11 @@ namespace pcpp */ unsigned int nflogGroup; + + /// In Unix-like system, use poll() for blocking mode. + bool usePoll; + + /** * A c'tor for this struct * @param[in] mode The mode to open the device: promiscuous or non-promiscuous. Default value is promiscuous @@ -231,9 +238,10 @@ namespace pcpp * captured with USBPcap (> 131072, < 262144). A snapshot length of 65535 should be sufficient, on most if not all networks, * to capture all the data available from the packet. * @param[in] nflogGroup NFLOG group for NFLOG devices. Default value is 0. + * @param[in] usePoll use `poll()` when capturing packets in blocking more (`startCaptureBlockingMode()`) on Unix-like system. Default value is false. */ explicit DeviceConfiguration(DeviceMode mode = Promiscuous, int packetBufferTimeoutMs = 0, int packetBufferSize = 0, - PcapDirection direction = PCPP_INOUT, int snapshotLength = 0, unsigned int nflogGroup = 0) + PcapDirection direction = PCPP_INOUT, int snapshotLength = 0, unsigned int nflogGroup = 0, bool usePoll = false) { this->mode = mode; this->packetBufferTimeoutMs = packetBufferTimeoutMs; @@ -241,6 +249,7 @@ namespace pcpp this->direction = direction; this->snapshotLength = snapshotLength; this->nflogGroup = nflogGroup; + this->usePoll = usePoll; } }; @@ -402,13 +411,14 @@ namespace pcpp * @param[in] userCookie A pointer to a user provided object. This object will be transferred to the onPacketArrives callback * each time it is called. This cookie is very useful for transferring objects that give context to the capture callback, for example: * objects that counts packets, manages flow state or manages the application state according to the packet that was captured - * @param[in] timeout A timeout in seconds for the blocking to stop even if the user didn't return "true" in the onPacketArrives callback - * If this timeout is set to 0 or less the timeout will be ignored, meaning the method will keep blocking until the user frees it via - * the onPacketArrives callback + * @param[in] timeout A timeout in seconds for the blocking to stop even if the user didn't return "true" in the onPacketArrives callback. + * The precision of `timeout` is millisecond, e.g. 2.345 seconds means 2345 milliseconds. + * If this timeout is set to 0 or less the timeout will be ignored, meaning the method will keep handling packets until the `onPacketArrives` + * callback returns `true`. * @return -1 if timeout expired, 1 if blocking was stopped via onPacketArrives callback or 0 if an error occurred (such as device * not open etc.). When returning 0 an appropriate error message is printed to log */ - virtual int startCaptureBlockingMode(OnPacketArrivesStopBlocking onPacketArrives, void* userCookie, int timeout); + virtual int startCaptureBlockingMode(OnPacketArrivesStopBlocking onPacketArrives, void* userCookie, const double timeout); /** * Stop a currently running packet capture. This method terminates gracefully both packet capture thread and periodic stats collection diff --git a/Pcap++/src/PcapLiveDevice.cpp b/Pcap++/src/PcapLiveDevice.cpp index c799dc9aea..de7f50ca39 100644 --- a/Pcap++/src/PcapLiveDevice.cpp +++ b/Pcap++/src/PcapLiveDevice.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #if defined(_WIN32) // The definition of BPF_MAJOR_VERSION is required to support Npcap. In Npcap there are @@ -30,6 +31,8 @@ #include #include #include +#include +#include #endif // if defined(_WIN32) #if defined(__APPLE__) || defined(__FreeBSD__) #include @@ -64,8 +67,8 @@ static pcap_direction_t directionTypeMap(PcapLiveDevice::PcapDirection direction -PcapLiveDevice::PcapLiveDevice(pcap_if_t* pInterface, bool calculateMTU, bool calculateMacAddress, bool calculateDefaultGateway) : IPcapDevice(), - m_MacAddress(""), m_DefaultGateway(IPv4Address::Zero) +PcapLiveDevice::PcapLiveDevice(pcap_if_t* pInterface, bool calculateMTU, bool calculateMacAddress, bool calculateDefaultGateway) : + IPcapDevice(), m_PcapSelectableFd(-1), m_MacAddress(""), m_DefaultGateway(IPv4Address::Zero), m_UsePoll(false) { m_DeviceMtu = 0; m_LinkType = LINKTYPE_ETHERNET; @@ -346,6 +349,21 @@ bool PcapLiveDevice::open(const DeviceConfiguration& config) m_DeviceOpened = true; + if(!config.usePoll) + { + m_UsePoll = false; + m_PcapSelectableFd = -1; + } + else + { +#if !defined(_WIN32) + m_UsePoll = true; + m_PcapSelectableFd = pcap_get_selectable_fd(m_PcapSendDescriptor); +#else + PCPP_LOG_ERROR("Windows doesn't support poll(), ignoring the `usePoll` parameter"); +#endif + } + return true; } @@ -478,7 +496,7 @@ bool PcapLiveDevice::startCapture(RawPacketVector& capturedPacketsVector) } -int PcapLiveDevice::startCaptureBlockingMode(OnPacketArrivesStopBlocking onPacketArrives, void* userCookie, int timeout) +int PcapLiveDevice::startCaptureBlockingMode(OnPacketArrivesStopBlocking onPacketArrives, void* userCookie, const double timeout) { if (!m_DeviceOpened || m_PcapDescriptor == nullptr) { @@ -500,58 +518,94 @@ int PcapLiveDevice::startCaptureBlockingMode(OnPacketArrivesStopBlocking onPacke m_cbOnPacketArrivesBlockingMode = onPacketArrives; m_cbOnPacketArrivesBlockingModeUserCookie = userCookie; - long startTimeSec = 0, startTimeNSec = 0; - clockGetTime(startTimeSec, startTimeNSec); - - long curTimeSec = 0; - m_CaptureThreadStarted = true; m_StopThread = false; - bool pcapDispatchError = false; + const int64_t timeoutMs = timeout * 1000; // timeout unit is seconds, let's change it to milliseconds + auto startTime = std::chrono::steady_clock::now(); + auto currentTime = startTime; + +#if !defined(_WIN32) + struct pollfd pcapPollFd; + memset(&pcapPollFd, 0, sizeof(pcapPollFd)); + pcapPollFd.fd = m_PcapSelectableFd; + pcapPollFd.events = POLLIN; +#endif + + bool shouldReturnError = false; - if (timeout <= 0) + if(timeoutMs <= 0) { while (!m_StopThread) { - if (pcap_dispatch(m_PcapDescriptor, -1, onPacketArrivesBlockingMode, reinterpret_cast(this)) == -1) + if(pcap_dispatch(m_PcapDescriptor, -1, onPacketArrivesBlockingMode, reinterpret_cast(this)) == -1) { PCPP_LOG_ERROR("pcap_dispatch returned an error: " << pcap_geterr(m_PcapDescriptor)); - pcapDispatchError = true; + shouldReturnError = true; m_StopThread = true; } } - curTimeSec = startTimeSec + timeout; } else { - while (!m_StopThread && curTimeSec <= (startTimeSec + timeout)) + while (!m_StopThread && std::chrono::duration_cast(currentTime - startTime).count() < timeoutMs ) { - long curTimeNSec = 0; - if (pcap_dispatch(m_PcapDescriptor, -1, onPacketArrivesBlockingMode, reinterpret_cast(this)) == -1) + if(m_UsePoll) { - PCPP_LOG_ERROR("pcap_dispatch returned an error: " << pcap_geterr(m_PcapDescriptor)); - pcapDispatchError = true; +#if !defined(_WIN32) + int64_t pollTimeoutMs = timeoutMs - std::chrono::duration_cast(currentTime - startTime).count(); + pollTimeoutMs = std::max(pollTimeoutMs, (int64_t)0); // poll will be in blocking mode if negative value + + int ready = poll(&pcapPollFd, 1, pollTimeoutMs); // wait the packets until timeout + + if(ready > 0) + { + if(pcap_dispatch(m_PcapDescriptor, -1, onPacketArrivesBlockingMode, reinterpret_cast(this)) == -1) + { + PCPP_LOG_ERROR("pcap_dispatch returned an error: " << pcap_geterr(m_PcapDescriptor)); + shouldReturnError = true; + m_StopThread = true; + } + } + else if(ready < 0) + { + PCPP_LOG_ERROR("poll() got error '" << strerror(errno) << "'"); + shouldReturnError = true; + m_StopThread = true; + } +#else + PCPP_LOG_ERROR("Windows doesn't support poll()"); + shouldReturnError = true; m_StopThread = true; +#endif } - clockGetTime(curTimeSec, curTimeNSec); + else + { + if(pcap_dispatch(m_PcapDescriptor, -1, onPacketArrivesBlockingMode, reinterpret_cast(this)) == -1) + { + PCPP_LOG_ERROR("pcap_dispatch returned an error: " << pcap_geterr(m_PcapDescriptor)); + shouldReturnError = true; + m_StopThread = true; + } + } + currentTime = std::chrono::steady_clock::now(); } } m_CaptureThreadStarted = false; - m_StopThread = false; - m_cbOnPacketArrivesBlockingMode = nullptr; m_cbOnPacketArrivesBlockingModeUserCookie = nullptr; - if (pcapDispatchError) + if (shouldReturnError) { return 0; } - if (curTimeSec > (startTimeSec + timeout)) + if (std::chrono::duration_cast(currentTime - startTime).count() >= timeoutMs ) + { return -1; + } return 1; } diff --git a/Tests/Pcap++Test/Tests/LiveDeviceTests.cpp b/Tests/Pcap++Test/Tests/LiveDeviceTests.cpp index 39be1ba281..337c209fc2 100644 --- a/Tests/Pcap++Test/Tests/LiveDeviceTests.cpp +++ b/Tests/Pcap++Test/Tests/LiveDeviceTests.cpp @@ -379,91 +379,102 @@ PTF_TEST_CASE(TestPcapLiveDeviceStatsMode) PTF_TEST_CASE(TestPcapLiveDeviceBlockingMode) { - // open device - pcpp::PcapLiveDevice* liveDev = pcpp::PcapLiveDeviceList::getInstance().getPcapLiveDeviceByIp(PcapTestGlobalArgs.ipToSendReceivePackets.c_str()); - PTF_ASSERT_NOT_NULL(liveDev); - PTF_ASSERT_TRUE(liveDev->open()); - DeviceTeardown devTeardown(liveDev); - - // sanity - test blocking mode returns with timeout - PTF_ASSERT_EQUAL(liveDev->startCaptureBlockingMode(packetArrivesBlockingModeTimeout, nullptr, 5), -1); - - // sanity - test blocking mode returns before timeout - int packetCount = 0; - PTF_ASSERT_EQUAL(liveDev->startCaptureBlockingMode(packetArrivesBlockingModeNoTimeout, &packetCount, 30), 1); - PTF_ASSERT_EQUAL(packetCount, 5); - - // verify stop capture doesn't do any effect on blocking mode - liveDev->stopCapture(); - PTF_ASSERT_EQUAL(liveDev->startCaptureBlockingMode(packetArrivesBlockingModeTimeout, nullptr, 1), -1); - packetCount = 0; - PTF_ASSERT_EQUAL(liveDev->startCaptureBlockingMode(packetArrivesBlockingModeNoTimeout, &packetCount, 30), 1); - PTF_ASSERT_EQUAL(packetCount, 5); + std::vector configs; + configs.emplace_back(); // the default config - // verify it's possible to capture non-blocking mode after blocking mode - packetCount = 0; - PTF_ASSERT_TRUE(liveDev->startCapture(packetArrives, &packetCount)); +#if !defined(_WIN32) + configs.emplace_back(); // the config used poll + configs[1].usePoll = true; +#endif - int totalSleepTime = 0; - while (totalSleepTime <= 5) + // test the common behaviour for all configs + for(const auto & config: configs) { - pcpp::multiPlatformSleep(1); - totalSleepTime += 1; - if (packetCount > 0) - break; - } + // open device + pcpp::PcapLiveDevice* liveDev = pcpp::PcapLiveDeviceList::getInstance().getPcapLiveDeviceByIp(PcapTestGlobalArgs.ipToSendReceivePackets.c_str()); + PTF_ASSERT_NOT_NULL(liveDev); + PTF_ASSERT_TRUE(liveDev->open(config)); + DeviceTeardown devTeardown(liveDev); + + // sanity - test blocking mode returns with timeout + PTF_ASSERT_EQUAL(liveDev->startCaptureBlockingMode(packetArrivesBlockingModeTimeout, nullptr, 5), -1); + + // sanity - test blocking mode returns before timeout + int packetCount = 0; + PTF_ASSERT_EQUAL(liveDev->startCaptureBlockingMode(packetArrivesBlockingModeNoTimeout, &packetCount, 30), 1); + PTF_ASSERT_EQUAL(packetCount, 5); + + // verify stop capture doesn't do any effect on blocking mode + liveDev->stopCapture(); + PTF_ASSERT_EQUAL(liveDev->startCaptureBlockingMode(packetArrivesBlockingModeTimeout, nullptr, 1), -1); + packetCount = 0; + PTF_ASSERT_EQUAL(liveDev->startCaptureBlockingMode(packetArrivesBlockingModeNoTimeout, &packetCount, 30), 1); + PTF_ASSERT_EQUAL(packetCount, 5); + + // verify it's possible to capture non-blocking mode after blocking mode + packetCount = 0; + PTF_ASSERT_TRUE(liveDev->startCapture(packetArrives, &packetCount)); + + int totalSleepTime = 0; + while (totalSleepTime <= 5) + { + pcpp::multiPlatformSleep(1); + totalSleepTime += 1; + if (packetCount > 0) + break; + } - liveDev->stopCapture(); + liveDev->stopCapture(); - PTF_PRINT_VERBOSE("Total sleep time: " << totalSleepTime << " secs"); + PTF_PRINT_VERBOSE("Total sleep time: " << totalSleepTime << " secs"); - PTF_ASSERT_GREATER_THAN(packetCount, 0); + PTF_ASSERT_GREATER_THAN(packetCount, 0); - // verify it's possible to capture blocking mode after non-blocking mode - packetCount = 0; - PTF_ASSERT_EQUAL(liveDev->startCaptureBlockingMode(packetArrivesBlockingModeNoTimeout, &packetCount, 30), 1); - PTF_ASSERT_EQUAL(packetCount, 5); + // verify it's possible to capture blocking mode after non-blocking mode + packetCount = 0; + PTF_ASSERT_EQUAL(liveDev->startCaptureBlockingMode(packetArrivesBlockingModeNoTimeout, &packetCount, 30), 1); + PTF_ASSERT_EQUAL(packetCount, 5); - // try to start capture from within the callback, verify no error - packetCount = 0; - PTF_ASSERT_EQUAL(liveDev->startCaptureBlockingMode(packetArrivesBlockingModeStartCapture, &packetCount, 30), 1); - PTF_ASSERT_EQUAL(packetCount, 5); + // try to start capture from within the callback, verify no error + packetCount = 0; + PTF_ASSERT_EQUAL(liveDev->startCaptureBlockingMode(packetArrivesBlockingModeStartCapture, &packetCount, 30), 1); + PTF_ASSERT_EQUAL(packetCount, 5); - // try to stop capture from within the callback, verify no impact on capturing - packetCount = 0; - PTF_ASSERT_EQUAL(liveDev->startCaptureBlockingMode(packetArrivesBlockingModeStopCapture, &packetCount, 10), 1); - PTF_ASSERT_EQUAL(packetCount, 5); + // try to stop capture from within the callback, verify no impact on capturing + packetCount = 0; + PTF_ASSERT_EQUAL(liveDev->startCaptureBlockingMode(packetArrivesBlockingModeStopCapture, &packetCount, 10), 1); + PTF_ASSERT_EQUAL(packetCount, 5); - // verify it's possible to capture non-blocking after the mess done in previous lines - packetCount = 0; - PTF_ASSERT_TRUE(liveDev->startCapture(packetArrives, &packetCount)); + // verify it's possible to capture non-blocking after the mess done in previous lines + packetCount = 0; + PTF_ASSERT_TRUE(liveDev->startCapture(packetArrives, &packetCount)); - // verify an error returns if trying capture blocking while non-blocking is running - pcpp::Logger::getInstance().suppressLogs(); - PTF_ASSERT_EQUAL(liveDev->startCaptureBlockingMode(packetArrivesBlockingModeTimeout, nullptr, 1), 0); - pcpp::Logger::getInstance().enableLogs(); + // verify an error returns if trying capture blocking while non-blocking is running + pcpp::Logger::getInstance().suppressLogs(); + PTF_ASSERT_EQUAL(liveDev->startCaptureBlockingMode(packetArrivesBlockingModeTimeout, nullptr, 1), 0); + pcpp::Logger::getInstance().enableLogs(); - totalSleepTime = 0; - while (totalSleepTime <= 5) - { - pcpp::multiPlatformSleep(1); - totalSleepTime += 1; - if (packetCount > 0) - break; - } + totalSleepTime = 0; + while (totalSleepTime <= 5) + { + pcpp::multiPlatformSleep(1); + totalSleepTime += 1; + if (packetCount > 0) + break; + } - PTF_PRINT_VERBOSE("Total sleep time: " << totalSleepTime << " secs"); + PTF_PRINT_VERBOSE("Total sleep time: " << totalSleepTime << " secs"); - liveDev->stopCapture(); - PTF_ASSERT_GREATER_THAN(packetCount, 0); + liveDev->stopCapture(); + PTF_ASSERT_GREATER_THAN(packetCount, 0); - liveDev->close(); - PTF_ASSERT_FALSE(liveDev->isOpened()); + liveDev->close(); - // a negative test - pcpp::Logger::getInstance().suppressLogs(); - PTF_ASSERT_FALSE(liveDev->startCapture(packetArrives, &packetCount)); - pcpp::Logger::getInstance().enableLogs(); + // a negative test + pcpp::Logger::getInstance().suppressLogs(); + PTF_ASSERT_FALSE(liveDev->startCapture(packetArrives, &packetCount)); + pcpp::Logger::getInstance().enableLogs(); + } } // TestPcapLiveDeviceBlockingMode