diff --git a/Pcap++/header/PcapLiveDevice.h b/Pcap++/header/PcapLiveDevice.h index d7782ad6b2..b2d570bc1f 100644 --- a/Pcap++/header/PcapLiveDevice.h +++ b/Pcap++/header/PcapLiveDevice.h @@ -271,6 +271,22 @@ namespace pcpp Nanoseconds, }; + /// Controls whether libpcap's "immediate mode" is used for packet delivery. + /// PcapPlusPlus enables immediate mode by default (where supported) for minimal packet delivery latency. + /// On Linux, immediate mode also forces a less efficient capture path (TPACKET_V2 with non-blocking + /// polling, instead of TPACKET_V3 with blocking, batched reads), which can hurt throughput and CPU usage + /// at high packet rates. + enum class BufferingMode + { + /// Let the device decide, currently equivalent to Immediate. Default value. + Default = 0, + /// Use immediate mode, trading throughput/CPU for minimal packet delivery latency + Immediate, + /// Disable immediate mode, trading some added latency (up to packetBufferTimeoutMs, or 100 ms on + /// Linux/Windows if not set) for the throughput and CPU benefits described above + Buffered + }; + /// @struct DeviceConfiguration /// A struct that contains user configurable parameters for opening a device. All parameters have default values /// so the user isn't expected to set all parameters or understand exactly how they work @@ -318,6 +334,10 @@ namespace pcpp /// Depending on the capture device and the software on the host, different precision can be used TimestampPrecision timestampPrecision; + /// Controls whether libpcap's immediate mode is used. See BufferingMode for details. + /// Default value is BufferingMode::Default. + BufferingMode bufferingMode; + /// A c'tor for this struct /// @param[in] mode The mode to open the device: promiscuous or non-promiscuous. Default value is /// promiscuous @@ -338,11 +358,14 @@ namespace pcpp /// for each packet (not all platforms support this). Default provider is Host. /// @param[in] timestampPrecision The timestamp precision (not all platforms support this). /// Default precision is Microseconds. + /// @param[in] bufferingMode Controls whether libpcap's immediate mode is used. See BufferingMode for + /// details. Default value is BufferingMode::Default. explicit DeviceConfiguration(DeviceMode mode = Promiscuous, int packetBufferTimeoutMs = 0, int packetBufferSize = 0, PcapDirection direction = PCPP_INOUT, int snapshotLength = 0, unsigned int nflogGroup = 0, bool usePoll = false, TimestampProvider timestampProvider = TimestampProvider::Host, - TimestampPrecision timestampPrecision = TimestampPrecision::Microseconds) + TimestampPrecision timestampPrecision = TimestampPrecision::Microseconds, + BufferingMode bufferingMode = BufferingMode::Default) { this->mode = mode; this->packetBufferTimeoutMs = packetBufferTimeoutMs; @@ -353,6 +376,7 @@ namespace pcpp this->usePoll = usePoll; this->timestampProvider = timestampProvider; this->timestampPrecision = timestampPrecision; + this->bufferingMode = bufferingMode; } }; diff --git a/Pcap++/src/PcapLiveDevice.cpp b/Pcap++/src/PcapLiveDevice.cpp index deeb4c25e5..4e3e42d9eb 100644 --- a/Pcap++/src/PcapLiveDevice.cpp +++ b/Pcap++/src/PcapLiveDevice.cpp @@ -65,6 +65,10 @@ # define LIBPCAP_OPEN_LIVE_TIMEOUT -1 #endif +// Fallback used on Linux/Windows (where LIBPCAP_OPEN_LIVE_TIMEOUT is -1) when immediate mode is disabled and +// packetBufferTimeoutMs isn't set, since libpcap's timeout behavior is unpredictable without immediate mode +static const int NON_IMMEDIATE_MODE_DEFAULT_TIMEOUT = 100; + static const char* NFLOG_IFACE = "nflog"; static const int DEFAULT_SNAPLEN = 9000; @@ -660,7 +664,16 @@ namespace pcpp throw std::runtime_error("Cannot set promiscuous mode, error was: " + std::string(pcap.getLastError())); } - int timeout = (config.packetBufferTimeoutMs <= 0 ? LIBPCAP_OPEN_LIVE_TIMEOUT : config.packetBufferTimeoutMs); + bool useBufferedMode = config.bufferingMode == BufferingMode::Buffered; + + int timeout = config.packetBufferTimeoutMs; + if (timeout <= 0) + { + // FreeBSD/macOS already have a positive LIBPCAP_OPEN_LIVE_TIMEOUT for the pcap_breakloop() workaround + bool useNonImmediateModeDefault = useBufferedMode && LIBPCAP_OPEN_LIVE_TIMEOUT <= 0; + // cppcheck-suppress knownConditionTrueFalse + timeout = useNonImmediateModeDefault ? NON_IMMEDIATE_MODE_DEFAULT_TIMEOUT : LIBPCAP_OPEN_LIVE_TIMEOUT; + } ret = pcap_set_timeout(pcap.get(), timeout); if (ret != 0) { @@ -677,10 +690,13 @@ namespace pcpp } #ifdef HAS_PCAP_IMMEDIATE_MODE - ret = pcap_set_immediate_mode(pcap.get(), 1); - if (ret != 0) + if (!useBufferedMode) { - throw std::runtime_error("Cannot set immediate mode, error was: " + std::string(pcap.getLastError())); + ret = pcap_set_immediate_mode(pcap.get(), 1); + if (ret != 0) + { + throw std::runtime_error("Cannot set immediate mode, error was: " + std::string(pcap.getLastError())); + } } #endif diff --git a/Tests/Pcap++Test/Tests/LiveDeviceTests.cpp b/Tests/Pcap++Test/Tests/LiveDeviceTests.cpp index 0c2300f66f..fe4800ed0f 100644 --- a/Tests/Pcap++Test/Tests/LiveDeviceTests.cpp +++ b/Tests/Pcap++Test/Tests/LiveDeviceTests.cpp @@ -510,6 +510,9 @@ PTF_TEST_CASE(TestPcapLiveDeviceBlockingMode) configs[1].usePoll = true; # endif + configs.emplace_back(); // disables immediate mode so libpcap can use TPACKET_V3 batching on Linux + configs.back().bufferingMode = pcpp::PcapLiveDevice::BufferingMode::Buffered; + // test the common behaviour for all configs for (const auto& config : configs) {