From 0ec61ac2f48150263dbcd1eb724472266fdadffd Mon Sep 17 00:00:00 2001 From: seladb Date: Sun, 19 Oct 2025 00:05:16 -0700 Subject: [PATCH 01/17] Add WinDivert device --- CMakeLists.txt | 9 + Pcap++/CMakeLists.txt | 6 + Pcap++/header/WinDivertDevice.h | 243 ++++++++ Pcap++/src/WinDivertDevice.cpp | 725 ++++++++++++++++++++++ Tests/Pcap++Test/CMakeLists.txt | 1 + Tests/Pcap++Test/TestDefinition.h | 6 + Tests/Pcap++Test/Tests/WinDivertTests.cpp | 431 +++++++++++++ Tests/Pcap++Test/main.cpp | 5 + cmake/modules/FindWinDivert.cmake | 127 ++++ 9 files changed, 1553 insertions(+) create mode 100644 Pcap++/header/WinDivertDevice.h create mode 100644 Pcap++/src/WinDivertDevice.cpp create mode 100644 Tests/Pcap++Test/Tests/WinDivertTests.cpp create mode 100644 cmake/modules/FindWinDivert.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 7337d95568..20b7a8252a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -165,6 +165,7 @@ cmake_dependent_option( ) option(PCAPPP_USE_PF_RING "Setup PcapPlusPlus with PF_RING. In this case you must also set PF_RING_ROOT") option(PCAPPP_USE_XDP "Setup PcapPlusPlus with XDP") +option(PCAPPP_USE_WINDIVERT "Setup PcapPlusPlus with WinDivert") option(PCAPPP_INSTALL "Install Pcap++" ${PCAPPP_MAIN_PROJECT}) option(PCAPPP_PACKAGE "Package Pcap++ could require a recent version of CMake" OFF) @@ -284,6 +285,14 @@ if(PCAPPP_BUILD_PCAPPP) endif() add_definitions(-DUSE_XDP) endif() + + if(PCAPPP_USE_WINDIVERT) + find_package(WinDivert REQUIRED) + if(NOT WinDivert_FOUND) + message(FATAL_ERROR "WinDivert not found!") + endif() + add_definitions(-DUSE_WINDIVERT) + endif() endif() if(NOT CMAKE_BUILD_TYPE) diff --git a/Pcap++/CMakeLists.txt b/Pcap++/CMakeLists.txt index f97a93653a..f1e5997cbe 100644 --- a/Pcap++/CMakeLists.txt +++ b/Pcap++/CMakeLists.txt @@ -21,6 +21,7 @@ add_library( $<$:src/XdpDevice.cpp> src/RawSocketDevice.cpp $<$:src/WinPcapLiveDevice.cpp> + $<$:src/WinDivertDevice.cpp> # Force light pcapng to be link fully static $ ) @@ -54,6 +55,10 @@ if(PCAPPP_USE_XDP) list(APPEND public_headers header/XdpDevice.h) endif() +if(PCAPPP_USE_WINDIVERT) + list(APPEND public_headers header/WinDivertDevice.h) +endif() + if(LINUX) list(APPEND public_headers header/LinuxNicInformationSocket.h) endif() @@ -95,6 +100,7 @@ target_link_libraries( $<$:PF_RING::PF_RING> $<$:DPDK::DPDK> $<$:BPF::BPF> + $<$:WinDivert::WinDivert> PCAP::PCAP Threads::Threads ) diff --git a/Pcap++/header/WinDivertDevice.h b/Pcap++/header/WinDivertDevice.h new file mode 100644 index 0000000000..a45ab6dbc5 --- /dev/null +++ b/Pcap++/header/WinDivertDevice.h @@ -0,0 +1,243 @@ +#pragma once + +#include +#include +#include +#include +#include "Device.h" + +/// @file + +namespace pcpp +{ + namespace internal + { + class IWinDivertHandle + { + public: + virtual ~IWinDivertHandle() = default; + }; + + class IOverlappedWrapper + { + public: + struct WaitResult + { + enum class Status + { + Completed, + Timeout, + Failed + }; + + Status status; + uint32_t errorCode = 0; + }; + + struct OverlappedResult + { + enum class Status + { + Success, + Failed + }; + + Status status; + uint32_t packetLen = 0; + uint32_t errorCode = 0; + }; + + virtual WaitResult wait(uint32_t timeout) = 0; + virtual void reset() = 0; + virtual OverlappedResult getOverlappedResult(const IWinDivertHandle* handle) = 0; + virtual ~IOverlappedWrapper() = default; + }; + + struct WinDivertAddress + { + bool isIPv6; + uint32_t interfaceIndex; + uint64_t timestamp; + }; + + class IWinDivertImplementation + { + public: + enum class WinDivertParam + { + QueueLength = 0, + QueueTime = 1, + QueueSize = 2, + VersionMajor = 3, + VersionMinor = 4 + }; + + struct NetworkInterface + { + uint32_t index; + std::wstring name; + std::wstring description; + bool isLoopback; + bool isUp; + }; + + static constexpr uint32_t SuccessResult = 0; + static constexpr uint32_t ErrorIoPending = 997; + + virtual ~IWinDivertImplementation() = default; + + virtual std::unique_ptr open(const std::string& filter, int layer, int16_t priority, + uint64_t flags) = 0; + virtual uint32_t close(const IWinDivertHandle* handle) = 0; + virtual uint32_t recvEx(const IWinDivertHandle* handle, uint8_t* buffer, uint32_t bufferLen, + size_t addressesSize, IOverlappedWrapper* overlapped) = 0; + virtual std::vector recvExComplete() = 0; + virtual uint32_t sendEx(const IWinDivertHandle* handle, uint8_t* buffer, uint32_t bufferLen, + size_t addressesSize) = 0; + virtual std::unique_ptr createOverlapped() = 0; + virtual bool getParam(const IWinDivertHandle* handle, WinDivertParam param, uint64_t& value) = 0; + virtual bool setParam(const IWinDivertHandle* handle, WinDivertParam param, uint64_t value) = 0; + virtual std::vector getNetworkInterfaces() const = 0; + }; + } // namespace internal + + class WinDivertRawPacket : public RawPacket + { + public: + WinDivertRawPacket(const uint8_t* pRawData, int rawDataLen, timespec timestamp, bool deleteRawDataAtDestructor, + LinkLayerType layerType, uint32_t interfaceIndex, uint64_t winDivertTimestamp) + : RawPacket(pRawData, rawDataLen, timestamp, deleteRawDataAtDestructor, layerType), + m_InterfaceIndex(interfaceIndex), m_WinDivertTimestamp(winDivertTimestamp) + {} + + uint32_t getInterfaceIndex() const + { + return m_InterfaceIndex; + } + + uint64_t getWinDivertTimestamp() const + { + return m_WinDivertTimestamp; + } + + private: + uint32_t m_InterfaceIndex; + uint64_t m_WinDivertTimestamp; + }; + + class WinDivertDevice : public IDevice + { + public: + struct ReceiveResult + { + enum class Status + { + Completed, + Timeout, + Failed + }; + + Status status; + std::string error; + uint32_t errorCode = 0; + }; + + struct SendResult + { + enum class Status + { + Completed, + Failed + }; + + Status status; + size_t packetsSent; + std::string error; + uint32_t errorCode = 0; + }; + + struct WinDivertVersion + { + uint64_t major; + uint64_t minor; + + std::string toString() const + { + return std::to_string(major) + "." + std::to_string(minor); + } + }; + + struct NetworkInterface + { + uint32_t index; + std::wstring name; + std::wstring description; + bool isLoopback; + bool isUp; + }; + + enum class QueueParam + { + QueueLength, + QueueTime, + QueueSize + }; + + using WinDivertRawPacketVector = PointerVector; + using ReceivePacketCallback = std::function; + using QueueParams = std::unordered_map; + + WinDivertDevice(); + + bool open() override; + bool open(const std::string& filter); + void close() override; + + ReceiveResult receivePackets(WinDivertRawPacketVector& packetVec, uint32_t timeout = 5000, + uint32_t maxPackets = 0, uint8_t batchSize = 64); + ReceiveResult receivePackets(const ReceivePacketCallback& callback, uint32_t timeout = 5000, + uint8_t batchSize = 64); + void stopReceive(); + + SendResult sendPackets(const RawPacketVector& packetVec, uint8_t batchSize = 64) const; + + QueueParams getPacketQueueParams() const; + void setPacketQueueParams(const QueueParams& params) const; + + WinDivertVersion getVersion() const; + + const NetworkInterface* getNetworkInterface(uint32_t interfaceIndex) const; + std::vector getNetworkInterfaces() const; + + void setImplementation(std::unique_ptr implementation); + + private: + std::unique_ptr m_Impl; + std::unique_ptr m_Handle; + std::atomic m_IsReceiving{ false }; + mutable std::unordered_map m_NetworkInterfaces; + mutable bool m_NetworkInterfacesInitialized = false; + + struct ReceiveResultInternal : ReceiveResult + { + uint32_t capturedDataLength = 0; + std::vector addresses; + + ReceiveResultInternal(Status status, const std::string& error = "", uint32_t errorCode = 0) + : ReceiveResult{ status, error, errorCode } + {} + + ReceiveResultInternal(uint32_t capturedDataLength, const std::vector& addresses) + : ReceiveResult{ Status::Completed, "", 0 }, capturedDataLength(capturedDataLength), + addresses(addresses) + {} + }; + + ReceiveResultInternal receivePacketsInternal(uint32_t timeout, uint8_t batchSize, std::vector& buffer, + internal::IOverlappedWrapper* overlapped); + static std::tuple getPacketInfo(uint8_t* buffer, uint32_t bufferLen, + const internal::WinDivertAddress& address); + void setNetworkInterfaces() const; + static std::string getErrorString(uint32_t errorCode); + }; +} // namespace pcpp diff --git a/Pcap++/src/WinDivertDevice.cpp b/Pcap++/src/WinDivertDevice.cpp new file mode 100644 index 0000000000..ea3a39041f --- /dev/null +++ b/Pcap++/src/WinDivertDevice.cpp @@ -0,0 +1,725 @@ +#include "WinDivertDevice.h" +#include "Logger.h" +#include "Packet.h" +#include "windivert.h" +#include "IPv4Layer.h" +#include "IPv6Layer.h" +#include "EndianPortable.h" +#include +#include +#include +#include + +namespace pcpp +{ + namespace internal + { + class WinDivertHandle : public IWinDivertHandle + { + public: + explicit WinDivertHandle(const HANDLE handle) : m_Handle(handle) + {} + + HANDLE get() const + { + return m_Handle; + } + + private: + HANDLE m_Handle; + }; + + class WinDivertOverlappedWrapper : public IOverlappedWrapper + { + public: + WinDivertOverlappedWrapper() + { + ZeroMemory(&m_Overlapped, sizeof(m_Overlapped)); + + m_Event = CreateEvent(nullptr, TRUE, FALSE, nullptr); + if (!m_Event) + { + throw std::runtime_error("Failed to create event"); + } + m_Overlapped.hEvent = m_Event; + } + + // Non-copyable + WinDivertOverlappedWrapper(const WinDivertOverlappedWrapper&) = delete; + WinDivertOverlappedWrapper& operator=(const WinDivertOverlappedWrapper&) = delete; + + ~WinDivertOverlappedWrapper() override + { + CloseHandle(m_Event); + } + + LPOVERLAPPED get() + { + return &m_Overlapped; + } + + WaitResult wait(uint32_t timeout) override + { + auto waitResult = WaitForSingleObject(m_Overlapped.hEvent, timeout); + if (waitResult == WAIT_OBJECT_0) + { + return { WaitResult::Status::Completed, 0 }; + } + + if (waitResult == WAIT_TIMEOUT) + { + return { WaitResult::Status::Timeout, 0 }; + } + + return { WaitResult::Status::Failed, static_cast(GetLastError()) }; + } + + void reset() override + { + if (!ResetEvent(m_Event)) + { + throw std::runtime_error("Failed to reset overlapped event"); + } + + // Zero everything except hEvent + auto event = m_Overlapped.hEvent; + ZeroMemory(&m_Overlapped, sizeof(m_Overlapped)); + m_Overlapped.hEvent = event; + } + + OverlappedResult getOverlappedResult(const IWinDivertHandle* handle) override + { + auto winDivertHandle = dynamic_cast(handle); + if (winDivertHandle == nullptr) + { + throw std::runtime_error("Failed to get WinDivertHandle"); + } + + DWORD packetLen = 0; + if (GetOverlappedResult(winDivertHandle->get(), &m_Overlapped, &packetLen, FALSE)) + { + return { OverlappedResult::Status::Success, static_cast(packetLen), 0 }; + } + + return { OverlappedResult::Status::Failed, 0, static_cast(GetLastError()) }; + } + + private: + HANDLE m_Event; + OVERLAPPED m_Overlapped = {}; + }; + + class WinDivertImplementation : public IWinDivertImplementation + { + public: + std::unique_ptr open(const std::string& filter, int layer, int16_t priority, + uint64_t flags) override + { + auto handle = WinDivertOpen(filter.c_str(), static_cast(layer), priority, flags); + if (handle == INVALID_HANDLE_VALUE) + { + PCPP_LOG_ERROR("Failed to open WinDivertHandle, error was: " << GetLastError()); + return nullptr; + } + return std::make_unique(handle); + } + + uint32_t close(const IWinDivertHandle* handle) override + { + auto winDivertHandle = getHandle(handle); + + auto result = WinDivertClose(winDivertHandle->get()); + if (!result) + { + return GetLastError(); + } + return SuccessResult; + } + + uint32_t recvEx(const IWinDivertHandle* handle, uint8_t* buffer, uint32_t bufferLen, size_t addressesSize, + IOverlappedWrapper* overlapped) override + { + auto winDivertOverlapped = dynamic_cast(overlapped); + if (winDivertOverlapped == nullptr) + { + throw std::runtime_error("Failed to get WinDivertOverlapped"); + } + + auto winDivertHandle = getHandle(handle); + + m_WinDivertAddresses.resize(addressesSize); + m_WinDivertAddressesSize = sizeof(WINDIVERT_ADDRESS) * addressesSize; + + uint32_t recvLen; + auto result = + WinDivertRecvEx(winDivertHandle->get(), buffer, bufferLen, &recvLen, 0, m_WinDivertAddresses.data(), + &m_WinDivertAddressesSize, winDivertOverlapped->get()); + + if (!result) + { + return GetLastError(); + } + + return SuccessResult; + } + + std::vector recvExComplete() override + { + uint32_t numOfAddressesReceived = m_WinDivertAddressesSize / sizeof(WINDIVERT_ADDRESS); + std::vector result(numOfAddressesReceived); + + for (uint32_t i = 0; i < numOfAddressesReceived; i++) + { + result[i].isIPv6 = m_WinDivertAddresses[i].IPv6 == 1; + result[i].interfaceIndex = m_WinDivertAddresses[i].Network.IfIdx; + result[i].timestamp = m_WinDivertAddresses[i].Timestamp; + } + + return result; + } + + uint32_t sendEx(const IWinDivertHandle* handle, uint8_t* buffer, uint32_t bufferLen, + size_t addressesSize) override + { + auto winDivertHandle = getHandle(handle); + + std::vector winDivertAddresses; + for (size_t i = 0; i < addressesSize; i++) + { + WINDIVERT_ADDRESS addr = {}; + addr.Outbound = 1; + winDivertAddresses.push_back(addr); + } + + auto result = + WinDivertSendEx(winDivertHandle->get(), buffer, bufferLen, nullptr, 0, winDivertAddresses.data(), + addressesSize * sizeof(WINDIVERT_ADDRESS), nullptr); + if (!result) + { + return GetLastError(); + } + + return SuccessResult; + } + + std::unique_ptr createOverlapped() override + { + return std::make_unique(); + } + + bool getParam(const IWinDivertHandle* handle, WinDivertParam param, uint64_t& value) override + { + auto winDivertHandle = getHandle(handle); + return WinDivertGetParam(winDivertHandle->get(), static_cast(param), &value); + } + + bool setParam(const IWinDivertHandle* handle, WinDivertParam param, uint64_t value) override + { + auto winDivertHandle = getHandle(handle); + return WinDivertSetParam(winDivertHandle->get(), static_cast(param), value); + } + + std::vector getNetworkInterfaces() const override + { + std::vector networkInterfaces; + + ULONG bufferSize = 15000; + std::vector buffer(bufferSize); + + auto result = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, + reinterpret_cast(buffer.data()), &bufferSize); + + if (result == ERROR_BUFFER_OVERFLOW) + { + buffer.resize(bufferSize); + result = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, + reinterpret_cast(buffer.data()), &bufferSize); + } + + if (result != NO_ERROR) + { + throw std::runtime_error("Error while getting network interfaces"); + } + + auto adapter = reinterpret_cast(buffer.data()); + + while (adapter) + { + NetworkInterface networkInterface; + networkInterface.index = adapter->IfIndex; + networkInterface.name = adapter->FriendlyName; + networkInterface.description = adapter->Description; + networkInterface.isLoopback = (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK); + networkInterface.isUp = (adapter->OperStatus == IfOperStatusUp); + + networkInterfaces.push_back(networkInterface); + adapter = adapter->Next; + } + + return networkInterfaces; + } + + private: + std::vector m_WinDivertAddresses; + uint32_t m_WinDivertAddressesSize = 0; + + static const WinDivertHandle* getHandle(const IWinDivertHandle* handle) + { + auto winDivertHandle = dynamic_cast(handle); + if (winDivertHandle == nullptr) + { + throw std::runtime_error("Failed to get WinDivertHandle"); + } + + return winDivertHandle; + } + }; + + } // namespace internal + +#define WINDIVERT_BUFFER_LEN 65536 + + WinDivertDevice::WinDivertDevice() : m_Impl(std::make_unique()) + {} + + bool WinDivertDevice::open() + { + return open("inbound or outbound"); + } + + bool WinDivertDevice::open(const std::string& filter) + { + m_Handle = m_Impl->open(filter, WINDIVERT_LAYER_NETWORK, 0, WINDIVERT_FLAG_SNIFF | WINDIVERT_FLAG_FRAGMENTS); + if (!m_Handle) + { + return false; + } + + setNetworkInterfaces(); + + m_DeviceOpened = true; + return true; + } + + void WinDivertDevice::close() + { + auto result = m_Impl->close(m_Handle.get()); + if (result != internal::IWinDivertImplementation::SuccessResult) + { + PCPP_LOG_ERROR("Couldn't receive packet, status: " << getErrorString(static_cast(result)) << "(" + << static_cast(result) << ")"); + } + } + + WinDivertDevice::ReceiveResult WinDivertDevice::receivePackets(WinDivertRawPacketVector& packetVec, + uint32_t timeout, uint32_t maxPackets, + uint8_t batchSize) + { + if (!isOpened()) + { + return { ReceiveResult::Status::Failed, "Device is not open" }; + } + + if (m_IsReceiving) + { + return { ReceiveResult::Status::Failed, "Already receiving packets, please call stopReceive() first" }; + } + + if (batchSize == 0) + { + return { ReceiveResult::Status::Failed, "Batch size has to be a positive number" }; + } + + if (timeout == 0 && maxPackets == 0) + { + return { ReceiveResult::Status::Failed, + "At least one of timeout and maxPackets must be a positive number" }; + } + + auto overlapped = m_Impl->createOverlapped(); + uint32_t bufferSize = WINDIVERT_BUFFER_LEN * batchSize; + std::vector buffer(bufferSize); + + uint32_t receivedPacketCount = 0; + while (maxPackets == 0 || receivedPacketCount < maxPackets) + { + auto result = receivePacketsInternal(timeout, batchSize, buffer, overlapped.get()); + + if (result.status != ReceiveResult::Status::Completed) + { + return { result.status, result.error, result.errorCode }; + } + + uint32_t packetCountInCurrentBatch = receivedPacketCount + result.addresses.size() > maxPackets + ? maxPackets - receivedPacketCount + : result.addresses.size(); + receivedPacketCount += packetCountInCurrentBatch; + + uint8_t* curPacketPtr = buffer.data(); + size_t remainingBytes = result.capturedDataLength; + + for (uint32_t i = 0; i < packetCountInCurrentBatch; i++) + { + auto packetInfo = getPacketInfo(curPacketPtr, remainingBytes, result.addresses[i]); + + auto linkType = std::get<0>(packetInfo); + if (linkType == LINKTYPE_INVALID) + { + continue; + } + + auto packetLength = std::get<1>(packetInfo); + auto packetData = std::make_unique(packetLength); + memcpy(packetData.get(), curPacketPtr, packetLength); + packetVec.pushBack(new WinDivertRawPacket( + packetData.release(), static_cast(packetLength), std::get<2>(packetInfo), true, linkType, + result.addresses[i].interfaceIndex, result.addresses[i].timestamp)); + curPacketPtr += packetLength; + remainingBytes -= packetLength; + } + + overlapped->reset(); + } + + return { ReceiveResult::Status::Completed }; + } + + WinDivertDevice::ReceiveResult WinDivertDevice::receivePackets(const ReceivePacketCallback& callback, + uint32_t timeout, uint8_t batchSize) + { + if (!isOpened()) + { + return { ReceiveResult::Status::Failed, "Device is not open" }; + } + + if (m_IsReceiving) + { + return { ReceiveResult::Status::Failed, "Already receiving packets, please call stopReceive() first" }; + } + + if (batchSize == 0) + { + return { ReceiveResult::Status::Failed, "Batch size has to be a positive number" }; + } + + auto overlapped = m_Impl->createOverlapped(); + uint32_t bufferSize = WINDIVERT_BUFFER_LEN * batchSize; + std::vector buffer(bufferSize); + + m_IsReceiving = true; + while (m_IsReceiving) + { + auto result = receivePacketsInternal(timeout, batchSize, buffer, overlapped.get()); + + if (result.status != ReceiveResult::Status::Completed) + { + m_IsReceiving = false; + return { result.status, result.error, result.errorCode }; + } + + uint8_t* curPacketPtr = buffer.data(); + size_t remainingBytes = result.capturedDataLength; + + WinDivertRawPacketVector receivedPackets; + for (auto& address : result.addresses) + { + auto packetInfo = getPacketInfo(curPacketPtr, remainingBytes, address); + + auto linkType = std::get<0>(packetInfo); + if (linkType == LINKTYPE_INVALID) + { + continue; + } + + auto packetLength = std::get<1>(packetInfo); + receivedPackets.pushBack(new WinDivertRawPacket(curPacketPtr, static_cast(packetLength), + std::get<2>(packetInfo), false, linkType, + address.interfaceIndex, address.timestamp)); + curPacketPtr += packetLength; + remainingBytes -= packetLength; + } + + callback(receivedPackets); + + overlapped->reset(); + } + + return { ReceiveResult::Status::Completed }; + } + + void WinDivertDevice::stopReceive() + { + m_IsReceiving = false; + } + + WinDivertDevice::SendResult WinDivertDevice::sendPackets(const RawPacketVector& packetVec, uint8_t batchSize) const + { + if (!m_DeviceOpened) + { + return { SendResult::Status::Failed, 0, "Device is not open" }; + } + + if (batchSize == 0) + { + return { SendResult::Status::Failed, 0, "Batch size has to be a positive number" }; + } + + uint8_t buffer[WINDIVERT_BUFFER_LEN]; + auto curBufferPtr = buffer; + + uint8_t packetsInCurrentBatch = 0; + size_t packetsSent = 0; + size_t packetsToSend = packetVec.size(); + for (auto packetIndex = 0; packetIndex < packetVec.size(); packetIndex++) + { + memcpy(curBufferPtr, packetVec.at(packetIndex)->getRawData(), packetVec.at(packetIndex)->getRawDataLen()); + curBufferPtr += packetVec.at(packetIndex)->getRawDataLen(); + packetsInCurrentBatch++; + + if (packetsInCurrentBatch >= batchSize || packetIndex >= packetsToSend - 1) + { + auto result = m_Impl->sendEx(m_Handle.get(), buffer, WINDIVERT_BUFFER_LEN, packetsInCurrentBatch); + if (result != internal::IWinDivertImplementation::SuccessResult) + { + return { SendResult::Status::Failed, packetsSent, + "Sending packets failed: " + getErrorString(result), result }; + } + packetsSent += packetsInCurrentBatch; + + packetsInCurrentBatch = 0; + memset(buffer, 0, sizeof(buffer)); + curBufferPtr = buffer; + } + } + + return { SendResult::Status::Completed, packetsSent }; + } + + WinDivertDevice::QueueParams WinDivertDevice::getPacketQueueParams() const + { + if (!m_DeviceOpened) + { + throw std::runtime_error("Device is not open"); + } + + uint64_t queueLength, queueTime, queueSize; + + auto getParamResult = true; + getParamResult |= m_Impl->getParam( + m_Handle.get(), internal::IWinDivertImplementation::WinDivertParam::QueueLength, queueLength); + getParamResult |= + m_Impl->getParam(m_Handle.get(), internal::IWinDivertImplementation::WinDivertParam::QueueTime, queueTime); + getParamResult |= + m_Impl->getParam(m_Handle.get(), internal::IWinDivertImplementation::WinDivertParam::QueueSize, queueSize); + + if (!getParamResult) + { + throw std::runtime_error("Failed to retrieve queue parameters"); + } + + return { + { QueueParam::QueueLength, queueLength }, + { QueueParam::QueueTime, queueTime }, + { QueueParam::QueueSize, queueSize } + }; + } + + void WinDivertDevice::setPacketQueueParams(const QueueParams& params) const + { + if (!m_DeviceOpened) + { + throw std::runtime_error("Device is not open"); + } + + for (auto& param : params) + { + switch (param.first) + { + case QueueParam::QueueLength: + { + m_Impl->setParam(m_Handle.get(), internal::IWinDivertImplementation::WinDivertParam::QueueLength, + param.second); + break; + } + case QueueParam::QueueTime: + { + m_Impl->setParam(m_Handle.get(), internal::IWinDivertImplementation::WinDivertParam::QueueTime, + param.second); + break; + } + case QueueParam::QueueSize: + { + m_Impl->setParam(m_Handle.get(), internal::IWinDivertImplementation::WinDivertParam::QueueSize, + param.second); + break; + } + } + } + } + + WinDivertDevice::WinDivertVersion WinDivertDevice::getVersion() const + { + if (!m_DeviceOpened) + { + throw std::runtime_error("Device is not open"); + } + + uint64_t versionMajor, versionMinor; + + auto getParamResult = true; + getParamResult |= m_Impl->getParam( + m_Handle.get(), internal::IWinDivertImplementation::WinDivertParam::VersionMajor, versionMajor); + getParamResult |= m_Impl->getParam( + m_Handle.get(), internal::IWinDivertImplementation::WinDivertParam::VersionMajor, versionMinor); + + if (!getParamResult) + { + throw std::runtime_error("Failed to retrieve WinDivert version"); + } + + return { versionMajor, versionMinor }; + } + + const WinDivertDevice::NetworkInterface* WinDivertDevice::getNetworkInterface(uint32_t interfaceIndex) const + { + auto it = m_NetworkInterfaces.find(interfaceIndex); + if (it != m_NetworkInterfaces.end()) + { + return &it->second; + } + + return nullptr; + } + + std::vector WinDivertDevice::getNetworkInterfaces() const + { + setNetworkInterfaces(); + + std::vector interfaces; + interfaces.reserve(m_NetworkInterfaces.size()); + + for (const auto& entry : m_NetworkInterfaces) + { + interfaces.push_back(entry.second); + } + + return interfaces; + } + + void WinDivertDevice::setImplementation(std::unique_ptr implementation) + { + m_Impl = std::move(implementation); + } + + WinDivertDevice::ReceiveResultInternal WinDivertDevice::receivePacketsInternal( + uint32_t timeout, uint8_t batchSize, std::vector& buffer, internal::IOverlappedWrapper* overlapped) + { + auto result = m_Impl->recvEx(m_Handle.get(), buffer.data(), buffer.size(), batchSize, overlapped); + if (result != internal::IWinDivertImplementation::ErrorIoPending) + { + return { ReceiveResult::Status::Failed, "Error receiving packets: " + getErrorString(result), result }; + } + + if (timeout == 0) + { + timeout = INFINITE; + } + auto waitResult = overlapped->wait(timeout); + + switch (waitResult.status) + { + case internal::IOverlappedWrapper::WaitResult::Status::Completed: + { + auto overlappedResult = overlapped->getOverlappedResult(m_Handle.get()); + if (overlappedResult.status != internal::IOverlappedWrapper::OverlappedResult::Status::Success) + { + return { ReceiveResult::Status::Failed, + "Error fetching overlapped result: " + getErrorString(overlappedResult.errorCode), + overlappedResult.errorCode }; + } + + return { overlappedResult.packetLen, m_Impl->recvExComplete() }; + } + case internal::IOverlappedWrapper::WaitResult::Status::Timeout: + { + return { ReceiveResult::Status::Timeout }; + } + default: + { + return { ReceiveResult::Status::Failed, + "Error while waiting for packets: " + getErrorString(waitResult.errorCode), waitResult.errorCode }; + } + } + } + + std::tuple WinDivertDevice::getPacketInfo( + uint8_t* buffer, uint32_t bufferLen, const internal::WinDivertAddress& address) + { + uint16_t packetLength = 0; + LinkLayerType linkType = LINKTYPE_INVALID; + if (address.isIPv6 && IPv6Layer::isDataValid(buffer, bufferLen)) + { + packetLength = sizeof(ip6_hdr) + be16toh(reinterpret_cast(buffer)->payloadLength); + linkType = LINKTYPE_IPV6; + } + else if (IPv4Layer::isDataValid(buffer, bufferLen)) + { + packetLength = be16toh(reinterpret_cast(buffer)->totalLength); + linkType = LINKTYPE_IPV4; + } + else + { + return { linkType, 0, {} }; + } + + auto now = std::chrono::system_clock::now(); + auto duration = now.time_since_epoch(); + auto nanoSecs = std::chrono::duration_cast(duration).count(); + timespec ts = { nanoSecs / 1'000'000'000, nanoSecs % 1'000'000'000 }; + + return { linkType, packetLength, ts }; + } + + void WinDivertDevice::setNetworkInterfaces() const + { + if (m_NetworkInterfacesInitialized) + { + return; + } + + auto networkInterfaces = m_Impl->getNetworkInterfaces(); + for (const auto& networkInterface : networkInterfaces) + { + m_NetworkInterfaces[networkInterface.index] = { networkInterface.index, networkInterface.name, + networkInterface.description, networkInterface.isLoopback, + networkInterface.isUp }; + } + + m_NetworkInterfacesInitialized = true; + } + + std::string WinDivertDevice::getErrorString(uint32_t errorCode) + { + LPSTR messageBuffer = nullptr; + DWORD size = FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, + errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast(&messageBuffer), 0, nullptr); + + if (size == 0) + { + return "Unknown error"; + } + + std::string message(messageBuffer, size); + + // Remove trailing newlines + while (!message.empty() && (message.back() == '\n' || message.back() == '\r')) + { + message.pop_back(); + } + + LocalFree(messageBuffer); + + return message; + } +} // namespace pcpp diff --git a/Tests/Pcap++Test/CMakeLists.txt b/Tests/Pcap++Test/CMakeLists.txt index 1727a4d15b..1ab51a9f90 100644 --- a/Tests/Pcap++Test/CMakeLists.txt +++ b/Tests/Pcap++Test/CMakeLists.txt @@ -16,6 +16,7 @@ add_executable( Tests/RawSocketTests.cpp Tests/SystemUtilsTests.cpp Tests/TcpReassemblyTests.cpp + Tests/WinDivertTests.cpp Tests/XdpTests.cpp ) diff --git a/Tests/Pcap++Test/TestDefinition.h b/Tests/Pcap++Test/TestDefinition.h index a5cb6c46d8..c9da93ec16 100644 --- a/Tests/Pcap++Test/TestDefinition.h +++ b/Tests/Pcap++Test/TestDefinition.h @@ -127,3 +127,9 @@ PTF_TEST_CASE(TestXdpDeviceReceivePackets); PTF_TEST_CASE(TestXdpDeviceSendPackets); PTF_TEST_CASE(TestXdpDeviceNonDefaultConfig); PTF_TEST_CASE(TestXdpDeviceInvalidConfig); + +// Implemented in WinDivertTests.cpp +PTF_TEST_CASE(TestWinDivertReceivePackets); +PTF_TEST_CASE(TestWinDivertSendPackets); +PTF_TEST_CASE(TestWinDivertParams); +PTF_TEST_CASE(TestWinDivertNetworkInterfaces); diff --git a/Tests/Pcap++Test/Tests/WinDivertTests.cpp b/Tests/Pcap++Test/Tests/WinDivertTests.cpp new file mode 100644 index 0000000000..29f54828c2 --- /dev/null +++ b/Tests/Pcap++Test/Tests/WinDivertTests.cpp @@ -0,0 +1,431 @@ +#include + +#include "../TestDefinition.h" +#include "../Common/GlobalTestArgs.h" +#include "../Common/TestUtils.h" +#include "WinDivertDevice.h" +#include "PcapFileDevice.h" +#include "Packet.h" + +extern PcapTestArgs PcapTestGlobalArgs; + +PTF_TEST_CASE(TestWinDivertReceivePackets) +{ +#ifdef USE_WINDIVERT + // Receive with packet vector + { + pcpp::WinDivertDevice device; + PTF_ASSERT_TRUE(device.open()); + + DeviceTeardown devTeardown(&device); + + pcpp::WinDivertDevice::WinDivertRawPacketVector rawPackets; + uint32_t expectedPacketCount = 10; + auto result = device.receivePackets(rawPackets, 10000, expectedPacketCount); + PTF_ASSERT_EQUAL(result.status, pcpp::WinDivertDevice::ReceiveResult::Status::Completed, enumclass); + PTF_ASSERT_EQUAL(result.error, ""); + PTF_ASSERT_EQUAL(result.errorCode, 0); + + PTF_ASSERT_EQUAL(rawPackets.size(), expectedPacketCount); + + for (const auto& rawPacket : rawPackets) + { + PTF_ASSERT_NOT_NULL(device.getNetworkInterface(rawPacket->getInterfaceIndex())); + pcpp::Packet packet(rawPacket); + PTF_ASSERT_TRUE(packet.getFirstLayer()->isMemberOfProtocolFamily(pcpp::IP)); + } + } + + // Receive with callback + { + pcpp::WinDivertDevice device; + PTF_ASSERT_TRUE(device.open()); + + DeviceTeardown devTeardown(&device); + + uint32_t expectedPacketCount = 10; + uint16_t packetCounter = 0; + bool allPacketsHaveInterface = true; + bool allPacketsOfTypeIP = true; + bool isTimestampIncreasing = true; + uint64_t currentTimestamp = 0; + + auto result = device.receivePackets([&](const pcpp::WinDivertDevice::WinDivertRawPacketVector& packetVec) { + for (auto& rawPacket : packetVec) + { + allPacketsHaveInterface &= device.getNetworkInterface(rawPacket->getInterfaceIndex()) != nullptr; + pcpp::Packet packet(rawPacket); + allPacketsOfTypeIP &= packet.getFirstLayer()->isMemberOfProtocolFamily(pcpp::IP); + isTimestampIncreasing &= (rawPacket->getWinDivertTimestamp() > currentTimestamp); + currentTimestamp = rawPacket->getWinDivertTimestamp(); + } + + packetCounter += packetVec.size(); + if (packetCounter >= expectedPacketCount) + { + device.stopReceive(); + } + }); + + PTF_ASSERT_EQUAL(result.status, pcpp::WinDivertDevice::ReceiveResult::Status::Completed, enumclass); + PTF_ASSERT_EQUAL(result.error, ""); + PTF_ASSERT_EQUAL(result.errorCode, 0); + PTF_ASSERT_GREATER_OR_EQUAL_THAN(packetCounter, expectedPacketCount); + PTF_ASSERT_TRUE(allPacketsHaveInterface); + PTF_ASSERT_TRUE(allPacketsOfTypeIP); + PTF_ASSERT_TRUE(isTimestampIncreasing); + } + + // Receive timeout + { + pcpp::WinDivertDevice device; + + auto networkInterfaces = device.getNetworkInterfaces(); + uint32_t invalidInterfaceIndex = 0; + while (true) + { + auto it = std::find_if(networkInterfaces.begin(), networkInterfaces.end(), + [&](const auto& item) { return item.index == invalidInterfaceIndex; }); + + if (it == networkInterfaces.end()) + { + break; + } + + invalidInterfaceIndex++; + } + + PTF_ASSERT_TRUE(device.open("ifIdx == " + std::to_string(invalidInterfaceIndex))); + + DeviceTeardown devTeardown(&device); + + pcpp::WinDivertDevice::WinDivertRawPacketVector rawPackets; + auto result = device.receivePackets(rawPackets, 500); + + PTF_ASSERT_EQUAL(result.status, pcpp::WinDivertDevice::ReceiveResult::Status::Timeout, enumclass); + } + + // TODO: consider adding stats for number of batches + + // Receive with non-default batch size + { + pcpp::WinDivertDevice device; + PTF_ASSERT_TRUE(device.open()); + + DeviceTeardown devTeardown(&device); + + pcpp::WinDivertDevice::WinDivertRawPacketVector rawPackets; + uint32_t expectedPacketCount = 13; + auto result = device.receivePackets(rawPackets, 10000, expectedPacketCount, 3); + PTF_ASSERT_EQUAL(result.status, pcpp::WinDivertDevice::ReceiveResult::Status::Completed, enumclass); + + PTF_ASSERT_EQUAL(rawPackets.size(), expectedPacketCount); + } + + // Receive with a filter + { + pcpp::WinDivertDevice device; + pcpp::IPAddress ipAddress(PcapTestGlobalArgs.ipToSendReceivePackets); + std::string filter; + if (ipAddress.isIPv4()) + { + filter = "ip.SrcAddr == " + ipAddress.toString(); + } + else + { + filter = "ipv6.SrcAddr == " + ipAddress.toString(); + } + PTF_ASSERT_TRUE(device.open(filter)); + + pcpp::WinDivertDevice::WinDivertRawPacketVector rawPackets; + auto result = device.receivePackets(rawPackets, 10000, 10); + PTF_ASSERT_EQUAL(result.status, pcpp::WinDivertDevice::ReceiveResult::Status::Completed, enumclass); + + for (const auto& rawPacket : rawPackets) + { + pcpp::Packet packet(rawPacket); + PTF_ASSERT_EQUAL(packet.getLayerOfType()->getSrcIPAddress(), ipAddress); + } + } + + // Receive when device not open + { + pcpp::WinDivertDevice device; + + pcpp::WinDivertDevice::WinDivertRawPacketVector rawPackets; + auto result1 = device.receivePackets(rawPackets); + auto result2 = device.receivePackets(nullptr); + + for (const auto& result : { result1, result2 }) + { + PTF_ASSERT_EQUAL(result.status, pcpp::WinDivertDevice::ReceiveResult::Status::Failed, enumclass); + PTF_ASSERT_EQUAL(result.error, "Device is not open"); + PTF_ASSERT_EQUAL(result.errorCode, 0); + } + } + + // Receive when batch size is 0 + { + pcpp::WinDivertDevice device; + PTF_ASSERT_TRUE(device.open()); + + DeviceTeardown devTeardown(&device); + + pcpp::WinDivertDevice::WinDivertRawPacketVector rawPackets; + auto result1 = device.receivePackets(rawPackets, 5000, 0, 0); + auto result2 = device.receivePackets(nullptr, 5000, 0); + + for (const auto& result : { result1, result2 }) + { + PTF_ASSERT_EQUAL(result.status, pcpp::WinDivertDevice::ReceiveResult::Status::Failed, enumclass); + PTF_ASSERT_EQUAL(result.error, "Batch size has to be a positive number"); + PTF_ASSERT_EQUAL(result.errorCode, 0); + } + } + + // Receive when timeout and maxPackets are 0 + { + pcpp::WinDivertDevice device; + PTF_ASSERT_TRUE(device.open()); + + DeviceTeardown devTeardown(&device); + + pcpp::WinDivertDevice::WinDivertRawPacketVector rawPackets; + auto result = device.receivePackets(rawPackets, 0, 0); + + PTF_ASSERT_EQUAL(result.status, pcpp::WinDivertDevice::ReceiveResult::Status::Failed, enumclass); + PTF_ASSERT_EQUAL(result.error, "At least one of timeout and maxPackets must be a positive number"); + PTF_ASSERT_EQUAL(result.errorCode, 0); + } + + // Receive when already receiving + { + pcpp::WinDivertDevice device; + PTF_ASSERT_TRUE(device.open()); + + DeviceTeardown devTeardown(&device); + + bool failedReceiveWhileReceiving = false; + + device.receivePackets([&](const pcpp::WinDivertDevice::WinDivertRawPacketVector&) { + auto result1 = device.receivePackets(nullptr); + pcpp::WinDivertDevice::WinDivertRawPacketVector rawPackets; + auto result2 = device.receivePackets(rawPackets); + + auto expectedStatus = pcpp::WinDivertDevice::ReceiveResult::Status::Failed; + auto expectedError = "Already receiving packets, please call stopReceive() first"; + if (result1.status == expectedStatus && result2.status == expectedStatus && + result1.error == expectedError && result2.error == expectedError) + { + failedReceiveWhileReceiving = true; + } + + device.stopReceive(); + }); + + PTF_ASSERT_TRUE(failedReceiveWhileReceiving); + } +#else + PTF_SKIP_TEST("WinDivert is not configured"); +#endif +} // TestWinDivertReceivePackets + +PTF_TEST_CASE(TestWinDivertSendPackets) +{ +#ifdef USE_WINDIVERT + // Send packets + { + pcpp::RawPacketVector packetVec; + + pcpp::PcapFileReaderDevice ipv4Reader("PcapExamples/linktype_ipv4.pcap"); + PTF_ASSERT_TRUE(ipv4Reader.open()); + PTF_ASSERT_EQUAL(ipv4Reader.getNextPackets(packetVec, 2), 2); + + pcpp::PcapFileReaderDevice ipv6Reader("PcapExamples/linktype_ipv6.pcap"); + PTF_ASSERT_TRUE(ipv6Reader.open()); + PTF_ASSERT_EQUAL(ipv6Reader.getNextPackets(packetVec, 1), 1); + + pcpp::WinDivertDevice device; + PTF_ASSERT_TRUE(device.open()); + + DeviceTeardown devTeardown(&device); + + auto sendResult = device.sendPackets(packetVec); + PTF_ASSERT_EQUAL(sendResult.status, pcpp::WinDivertDevice::SendResult::Status::Completed, enumclass); + PTF_ASSERT_EQUAL(sendResult.error, ""); + PTF_ASSERT_EQUAL(sendResult.packetsSent, 3); + } + + // Send packets with batch size + { + pcpp::RawPacketVector packetVec; + + for (int i = 0; i < 10; i++) + { + pcpp::PcapFileReaderDevice ipv4Reader("PcapExamples/linktype_ipv4.pcap"); + PTF_ASSERT_TRUE(ipv4Reader.open()); + PTF_ASSERT_EQUAL(ipv4Reader.getNextPackets(packetVec, 2), 2); + } + + pcpp::WinDivertDevice device; + PTF_ASSERT_TRUE(device.open()); + + DeviceTeardown devTeardown(&device); + + auto sendResult = device.sendPackets(packetVec, 6); + PTF_ASSERT_EQUAL(sendResult.status, pcpp::WinDivertDevice::SendResult::Status::Completed, enumclass); + PTF_ASSERT_EQUAL(sendResult.error, ""); + PTF_ASSERT_EQUAL(sendResult.packetsSent, 20); + } + + // Send packets with link layer which is not IPv4/6 + { + pcpp::RawPacketVector packetVec; + + pcpp::PcapFileReaderDevice ethReader("PcapExamples/one_tcp_stream.pcap"); + PTF_ASSERT_TRUE(ethReader.open()); + PTF_ASSERT_EQUAL(ethReader.getNextPackets(packetVec, 2), 2); + + pcpp::WinDivertDevice device; + PTF_ASSERT_TRUE(device.open()); + + DeviceTeardown devTeardown(&device); + + auto sendResult = device.sendPackets(packetVec); + PTF_ASSERT_EQUAL(sendResult.status, pcpp::WinDivertDevice::SendResult::Status::Failed, enumclass); + PTF_ASSERT_EQUAL(sendResult.errorCode, 87); + PTF_ASSERT_EQUAL(sendResult.error, "Sending packets failed: The parameter is incorrect."); + PTF_ASSERT_EQUAL(sendResult.packetsSent, 0); + } + + // Send partially succeeds + { + pcpp::RawPacketVector packetVec; + + pcpp::PcapFileReaderDevice ipv4Reader("PcapExamples/linktype_ipv4.pcap"); + PTF_ASSERT_TRUE(ipv4Reader.open()); + PTF_ASSERT_EQUAL(ipv4Reader.getNextPackets(packetVec, 2), 2); + + pcpp::PcapFileReaderDevice ethReader("PcapExamples/one_tcp_stream.pcap"); + PTF_ASSERT_TRUE(ethReader.open()); + PTF_ASSERT_EQUAL(ethReader.getNextPackets(packetVec, 2), 2); + + pcpp::WinDivertDevice device; + PTF_ASSERT_TRUE(device.open()); + + DeviceTeardown devTeardown(&device); + + auto sendResult = device.sendPackets(packetVec, 1); + PTF_ASSERT_EQUAL(sendResult.status, pcpp::WinDivertDevice::SendResult::Status::Failed, enumclass); + PTF_ASSERT_EQUAL(sendResult.errorCode, 87); + PTF_ASSERT_EQUAL(sendResult.error, "Sending packets failed: The parameter is incorrect."); + PTF_ASSERT_EQUAL(sendResult.packetsSent, 2); + } + + // Send when device not open + { + pcpp::RawPacketVector packetVec; + + pcpp::PcapFileReaderDevice ipv4Reader("PcapExamples/linktype_ipv4.pcap"); + PTF_ASSERT_TRUE(ipv4Reader.open()); + PTF_ASSERT_EQUAL(ipv4Reader.getNextPackets(packetVec, 2), 2); + + pcpp::WinDivertDevice device; + auto sendResult = device.sendPackets(packetVec); + + PTF_ASSERT_EQUAL(sendResult.status, pcpp::WinDivertDevice::SendResult::Status::Failed, enumclass); + PTF_ASSERT_EQUAL(sendResult.error, "Device is not open"); + PTF_ASSERT_EQUAL(sendResult.errorCode, 0); + PTF_ASSERT_EQUAL(sendResult.packetsSent, 0); + } + + // Send when batch size is 0 + { + pcpp::RawPacketVector packetVec; + + pcpp::PcapFileReaderDevice ipv4Reader("PcapExamples/linktype_ipv4.pcap"); + PTF_ASSERT_TRUE(ipv4Reader.open()); + PTF_ASSERT_EQUAL(ipv4Reader.getNextPackets(packetVec, 2), 2); + + pcpp::WinDivertDevice device; + PTF_ASSERT_TRUE(device.open()); + + DeviceTeardown devTeardown(&device); + + pcpp::WinDivertDevice::WinDivertRawPacketVector rawPackets; + auto sendResult = device.sendPackets(packetVec, 0); + + PTF_ASSERT_EQUAL(sendResult.status, pcpp::WinDivertDevice::SendResult::Status::Failed, enumclass); + PTF_ASSERT_EQUAL(sendResult.error, "Batch size has to be a positive number"); + PTF_ASSERT_EQUAL(sendResult.errorCode, 0); + PTF_ASSERT_EQUAL(sendResult.packetsSent, 0); + } +#else + PTF_SKIP_TEST("WinDivert is not configured"); +#endif +} // TestWinDivertSendPackets + +PTF_TEST_CASE(TestWinDivertParams) +{ +#ifdef USE_WINDIVERT + { + pcpp::WinDivertDevice device; + PTF_ASSERT_TRUE(device.open()); + + DeviceTeardown devTeardown(&device); + + PTF_ASSERT_EQUAL(device.getVersion().toString(), "2.2"); + + auto queueParams = device.getPacketQueueParams(); + for (const auto& keyValuePair : queueParams) + { + PTF_ASSERT_GREATER_THAN(keyValuePair.second, 0); + } + + pcpp::WinDivertDevice::QueueParams clonedQueueParams(queueParams); + for (const auto& keyValuePair : clonedQueueParams) + { + clonedQueueParams[keyValuePair.first] = keyValuePair.second + 1; + } + device.setPacketQueueParams(clonedQueueParams); + + queueParams = device.getPacketQueueParams(); + PTF_ASSERT_TRUE(queueParams == clonedQueueParams); + } + + // Device is not open + { + pcpp::WinDivertDevice device; + + PTF_ASSERT_RAISES(device.getVersion(), std::runtime_error, "Device is not open"); + PTF_ASSERT_RAISES(device.getPacketQueueParams(), std::runtime_error, "Device is not open"); + PTF_ASSERT_RAISES(device.setPacketQueueParams({}), std::runtime_error, "Device is not open"); + } +#else + PTF_SKIP_TEST("WinDivert is not configured"); +#endif +} // TestWinDivertParams + +PTF_TEST_CASE(TestWinDivertNetworkInterfaces) +{ +#ifdef USE_WINDIVERT + pcpp::WinDivertDevice device; + + auto networkInterfaces = device.getNetworkInterfaces(); + bool atLeastOneInterfaceIsUp = false; + bool atLeastOneLoopbackInterface = false; + for (const auto& networkInterface : networkInterfaces) + { + PTF_ASSERT_GREATER_THAN(networkInterface.index, 0); + PTF_ASSERT_FALSE(networkInterface.name.empty()); + PTF_ASSERT_FALSE(networkInterface.description.empty()); + atLeastOneInterfaceIsUp |= networkInterface.isUp; + atLeastOneLoopbackInterface |= networkInterface.isLoopback; + } + + PTF_ASSERT_TRUE(atLeastOneInterfaceIsUp); + PTF_ASSERT_TRUE(atLeastOneLoopbackInterface); +#else + PTF_SKIP_TEST("WinDivert is not configured"); +#endif +} // TestWinDivertNetworkInterfaces diff --git a/Tests/Pcap++Test/main.cpp b/Tests/Pcap++Test/main.cpp index f37f34b761..4d0a99bc66 100644 --- a/Tests/Pcap++Test/main.cpp +++ b/Tests/Pcap++Test/main.cpp @@ -312,6 +312,11 @@ int main(int argc, char* argv[]) PTF_RUN_TEST(TestXdpDeviceNonDefaultConfig, "xdp"); PTF_RUN_TEST(TestXdpDeviceInvalidConfig, "xdp"); + PTF_RUN_TEST(TestWinDivertReceivePackets, "windivert"); + PTF_RUN_TEST(TestWinDivertSendPackets, "windivert"); + PTF_RUN_TEST(TestWinDivertParams, "windivert"); + PTF_RUN_TEST(TestWinDivertNetworkInterfaces, "windivert"); + PTF_END_RUNNING_TESTS; } diff --git a/cmake/modules/FindWinDivert.cmake b/cmake/modules/FindWinDivert.cmake new file mode 100644 index 0000000000..6d6b8d9247 --- /dev/null +++ b/cmake/modules/FindWinDivert.cmake @@ -0,0 +1,127 @@ +# FindWinDivert.cmake +# +# Module to locate the WinDivert library, header, and driver files. +# +# This module defines the following variables: +# +# WinDivert_FOUND - TRUE if both the library and header were found +# WinDivert_INCLUDE_DIR - Directory containing windivert.h +# WinDivert_INCLUDE_DIRS - Same as above (for compatibility with other modules) +# WinDivert_LIBRARY - Path to WinDivert.lib +# WinDivert_LIBRARIES - Same as above (for compatibility with target_link_libraries) +# WinDivert_SYS_FILE - (Optional) Path to the WinDivertXX.sys driver file +# WinDivert_DLL_FILE - (Optional) Path to WinDivert.dll (for dynamic linking or redistribution) +# +# You can provide a hint to the search location using either: +# - The CMake variable WINDIVERT_ROOT +# - The environment variable WINDIVERT_ROOT +# +# Expected directory structure: +# +# WinDivert/ +# ├── include/ +# │ └── windivert.h +# ├── x64/ +# │ ├── WinDivert.lib +# │ ├── WinDivert.dll +# │ └── WinDivert64.sys +# └── x86/ +# ├── WinDivert.lib +# ├── WinDivert.dll +# └── WinDivert32.sys +# +# Usage in CMakeLists.txt: +# +# find_package(WinDivert REQUIRED) +# target_include_directories(MyTarget PRIVATE ${WinDivert_INCLUDE_DIRS}) +# target_link_libraries(MyTarget PRIVATE ${WinDivert_LIBRARIES}) +# # Optionally copy ${WinDivert_SYS_FILE} or ${WinDivert_DLL_FILE} after build + +# Check if running on Windows +if(NOT WIN32) + if(NOT WinDivert_FIND_QUIETLY) + message(FATAL_ERROR "WinDivert is only available on Windows") + endif() + return() +endif() + +# Detect 64-bit vs 32-bit +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(_WinDivert_ARCH_DIR "x64") + set(_WinDivert_SYS_NAME "WinDivert64.sys") +else() + set(_WinDivert_ARCH_DIR "x86") + set(_WinDivert_SYS_NAME "WinDivert32.sys") +endif() + +# Normalize user-provided root path +if(DEFINED WINDIVERT_ROOT) + file(TO_CMAKE_PATH "${WINDIVERT_ROOT}" _WinDivert_ROOT_HINT) +elseif(DEFINED ENV{WINDIVERT_ROOT}) + file(TO_CMAKE_PATH "$ENV{WINDIVERT_ROOT}" _WinDivert_ROOT_HINT) +else() + set(_WinDivert_ROOT_HINT "") +endif() + +message(STATUS "WinDivert root hint: ${_WinDivert_ROOT_HINT}") +message(STATUS "Looking in arch dir: ${_WinDivert_ARCH_DIR}") + +# Look for header +find_path(WinDivert_INCLUDE_DIR + NAMES windivert.h + PATHS + "${_WinDivert_ROOT_HINT}/include" + "C:/Program Files/WinDivert/include" + "C:/WinDivert/include" +) + +include(FindPackageHandleStandardArgs) + +# For compatibility +set(WinDivert_INCLUDE_DIRS ${WinDivert_INCLUDE_DIR}) +set(WinDivert_LIBRARIES ${WinDivert_LIBRARY}) +set(WinDivert_SYS_FILE ${WinDivert_SYS}) +set(WinDivert_DLL_FILE ${WinDivert_DLL}) + +# Look for library +find_library(WinDivert_LIBRARY + NAMES WinDivert + PATHS + "${_WinDivert_ROOT_HINT}/${_WinDivert_ARCH_DIR}" + "C:/Program Files/WinDivert/${_WinDivert_ARCH_DIR}" + "C:/WinDivert/${_WinDivert_ARCH_DIR}" +) + +message(STATUS "WinDivert_INCLUDE_DIR: ${WinDivert_INCLUDE_DIR}") +message(STATUS "WinDivert_LIBRARY: ${WinDivert_LIBRARY}") + +# Look for .sys file +find_file(WinDivert_SYS + NAMES ${_WinDivert_SYS_NAME} + PATHS + "${_WinDivert_ROOT_HINT}/${_WinDivert_ARCH_DIR}" + "C:/Program Files/WinDivert/${_WinDivert_ARCH_DIR}" + "C:/WinDivert/${_WinDivert_ARCH_DIR}" +) + +# Look for DLL (optional) +find_file(WinDivert_DLL + NAMES WinDivert.dll + PATHS + "${_WinDivert_ROOT_HINT}/${_WinDivert_ARCH_DIR}" + "C:/Program Files/WinDivert/${_WinDivert_ARCH_DIR}" + "C:/WinDivert/${_WinDivert_ARCH_DIR}" +) + +find_package_handle_standard_args(WinDivert + REQUIRED_VARS WinDivert_INCLUDE_DIR WinDivert_LIBRARY + VERSION_VAR WinDivert_VERSION # Optional, only if you parse a version +) + +if(WinDivert_FOUND AND NOT TARGET WinDivert::WinDivert) + add_library(WinDivert::WinDivert STATIC IMPORTED) + set_target_properties(WinDivert::WinDivert PROPERTIES + IMPORTED_LOCATION "${WinDivert_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${WinDivert_INCLUDE_DIR}" + ) +endif() From 90c3831351a87fcbdbe77814610e6c67bbb9e172 Mon Sep 17 00:00:00 2001 From: seladb Date: Sun, 19 Oct 2025 00:16:37 -0700 Subject: [PATCH 02/17] Update `FindWinDivert.cmake` --- cmake/modules/FindWinDivert.cmake | 75 ++++++++++++++++--------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/cmake/modules/FindWinDivert.cmake b/cmake/modules/FindWinDivert.cmake index 6d6b8d9247..ea50c4b6e0 100644 --- a/cmake/modules/FindWinDivert.cmake +++ b/cmake/modules/FindWinDivert.cmake @@ -29,15 +29,7 @@ # ├── WinDivert.lib # ├── WinDivert.dll # └── WinDivert32.sys -# -# Usage in CMakeLists.txt: -# -# find_package(WinDivert REQUIRED) -# target_include_directories(MyTarget PRIVATE ${WinDivert_INCLUDE_DIRS}) -# target_link_libraries(MyTarget PRIVATE ${WinDivert_LIBRARIES}) -# # Optionally copy ${WinDivert_SYS_FILE} or ${WinDivert_DLL_FILE} after build -# Check if running on Windows if(NOT WIN32) if(NOT WinDivert_FIND_QUIETLY) message(FATAL_ERROR "WinDivert is only available on Windows") @@ -63,61 +55,72 @@ else() set(_WinDivert_ROOT_HINT "") endif() -message(STATUS "WinDivert root hint: ${_WinDivert_ROOT_HINT}") -message(STATUS "Looking in arch dir: ${_WinDivert_ARCH_DIR}") +if(NOT WinDivert_FIND_QUIETLY) + message(STATUS "WinDivert root hint: ${_WinDivert_ROOT_HINT}") + message(STATUS "Looking in arch dir: ${_WinDivert_ARCH_DIR}") +endif() # Look for header find_path(WinDivert_INCLUDE_DIR NAMES windivert.h PATHS - "${_WinDivert_ROOT_HINT}/include" - "C:/Program Files/WinDivert/include" - "C:/WinDivert/include" + "${_WinDivert_ROOT_HINT}" + "C:/Program Files/WinDivert" + "C:/WinDivert" + PATH_SUFFIXES include ) -include(FindPackageHandleStandardArgs) - -# For compatibility -set(WinDivert_INCLUDE_DIRS ${WinDivert_INCLUDE_DIR}) -set(WinDivert_LIBRARIES ${WinDivert_LIBRARY}) -set(WinDivert_SYS_FILE ${WinDivert_SYS}) -set(WinDivert_DLL_FILE ${WinDivert_DLL}) - # Look for library find_library(WinDivert_LIBRARY NAMES WinDivert PATHS - "${_WinDivert_ROOT_HINT}/${_WinDivert_ARCH_DIR}" - "C:/Program Files/WinDivert/${_WinDivert_ARCH_DIR}" - "C:/WinDivert/${_WinDivert_ARCH_DIR}" + "${_WinDivert_ROOT_HINT}" + "C:/Program Files/WinDivert" + "C:/WinDivert" + PATH_SUFFIXES ${_WinDivert_ARCH_DIR} ) -message(STATUS "WinDivert_INCLUDE_DIR: ${WinDivert_INCLUDE_DIR}") -message(STATUS "WinDivert_LIBRARY: ${WinDivert_LIBRARY}") - -# Look for .sys file +# Look for .sys file (optional) find_file(WinDivert_SYS NAMES ${_WinDivert_SYS_NAME} PATHS - "${_WinDivert_ROOT_HINT}/${_WinDivert_ARCH_DIR}" - "C:/Program Files/WinDivert/${_WinDivert_ARCH_DIR}" - "C:/WinDivert/${_WinDivert_ARCH_DIR}" + "${_WinDivert_ROOT_HINT}" + "C:/Program Files/WinDivert" + "C:/WinDivert" + PATH_SUFFIXES ${_WinDivert_ARCH_DIR} ) -# Look for DLL (optional) +# Look for .dll file (optional) find_file(WinDivert_DLL NAMES WinDivert.dll PATHS - "${_WinDivert_ROOT_HINT}/${_WinDivert_ARCH_DIR}" - "C:/Program Files/WinDivert/${_WinDivert_ARCH_DIR}" - "C:/WinDivert/${_WinDivert_ARCH_DIR}" + "${_WinDivert_ROOT_HINT}" + "C:/Program Files/WinDivert" + "C:/WinDivert" + PATH_SUFFIXES ${_WinDivert_ARCH_DIR} ) +# Handle REQUIRED + FOUND logic +include(FindPackageHandleStandardArgs) find_package_handle_standard_args(WinDivert REQUIRED_VARS WinDivert_INCLUDE_DIR WinDivert_LIBRARY - VERSION_VAR WinDivert_VERSION # Optional, only if you parse a version ) +# Print results if not quiet +if(NOT WinDivert_FIND_QUIETLY) + message(STATUS "WinDivert_INCLUDE_DIR: ${WinDivert_INCLUDE_DIR}") + message(STATUS "WinDivert_LIBRARY: ${WinDivert_LIBRARY}") + message(STATUS "WinDivert_SYS: ${WinDivert_SYS}") + message(STATUS "WinDivert_DLL: ${WinDivert_DLL}") +endif() + +# Compatibility variables (set AFTER discovery) +set(WinDivert_INCLUDE_DIRS ${WinDivert_INCLUDE_DIR}) +set(WinDivert_LIBRARIES ${WinDivert_LIBRARY}) +set(WinDivert_SYS_FILE ${WinDivert_SYS}) +set(WinDivert_DLL_FILE ${WinDivert_DLL}) + +# Create imported target if(WinDivert_FOUND AND NOT TARGET WinDivert::WinDivert) add_library(WinDivert::WinDivert STATIC IMPORTED) set_target_properties(WinDivert::WinDivert PROPERTIES From b0e1d2e7513d35284b4e746f4db1bde191cf0dd7 Mon Sep 17 00:00:00 2001 From: seladb Date: Sat, 25 Oct 2025 01:07:34 -0700 Subject: [PATCH 03/17] Add doxygen documentation --- Pcap++/header/WinDivertDevice.h | 161 ++++++++++++++++++++++++++++---- 1 file changed, 143 insertions(+), 18 deletions(-) diff --git a/Pcap++/header/WinDivertDevice.h b/Pcap++/header/WinDivertDevice.h index a45ab6dbc5..5a049ca682 100644 --- a/Pcap++/header/WinDivertDevice.h +++ b/Pcap++/header/WinDivertDevice.h @@ -7,7 +7,19 @@ #include "Device.h" /// @file - +/// @brief WinDivert-based device (Windows-only) for capturing and sending packets at the network layer. +/// +/// This header exposes a device wrapper around the WinDivert driver that lets applications: +/// - Open a WinDivert handle with a filter +/// - Capture inbound/outbound IPv4/IPv6 packets in batches or via a callback +/// - Send batches of raw packets +/// - Inspect and configure queue parameters (length, time, size) +/// - Query WinDivert runtime version and available network interfaces +/// +/// For filter syntax and semantics please refer to the WinDivert documentation. +/// +/// @namespace pcpp +/// @brief The main namespace for the PcapPlusPlus library namespace pcpp { namespace internal @@ -101,6 +113,13 @@ namespace pcpp }; } // namespace internal + /// @class WinDivertRawPacket + /// @brief A RawPacket specialization used by WinDivertDevice. + /// + /// In addition to the base RawPacket data (raw buffer, timestamp and link-layer type), + /// WinDivert also provides the Windows network interface index and the original + /// WinDivert timestamp. These can be retrieved with getInterfaceIndex() and + /// getWinDivertTimestamp() respectively. class WinDivertRawPacket : public RawPacket { public: @@ -110,11 +129,15 @@ namespace pcpp m_InterfaceIndex(interfaceIndex), m_WinDivertTimestamp(winDivertTimestamp) {} + /// @brief Get the Windows interface index the packet was captured on. + /// @return The interface index as reported by WinDivert. uint32_t getInterfaceIndex() const { return m_InterfaceIndex; } + /// @brief Get the original WinDivert timestamp captured for this packet. + /// @return A 64-bit timestamp value as returned by WinDivert (see WinDivert docs for units and origin). uint64_t getWinDivertTimestamp() const { return m_WinDivertTimestamp; @@ -125,48 +148,75 @@ namespace pcpp uint64_t m_WinDivertTimestamp; }; + /// @class WinDivertDevice + /// @brief A device wrapper around the WinDivert driver for Windows. + /// + /// WinDivert is a kernel driver for packet interception and injection on Windows. + /// WinDivertDevice opens a WinDivert handle on the WINDIVERT_LAYER_NETWORK layer using a filter + /// and provides methods to receive and send packets in batches, query/set queue parameters, + /// retrieve the WinDivert runtime version, and enumerate Windows network interfaces. + /// + /// Notes: + /// - The default open() uses the filter "inbound or outbound", capturing both directions. + /// - The device is opened in sniffing mode and supports fragmented packets. + /// - Receive can be done into a user-provided vector or via a callback loop that can be stopped with stopReceive(). + /// - Send batches multiple packets at once for efficiency. + /// - Queue parameters map to WinDivert queue configuration (length in packets, time in milliseconds, size in + /// bytes). + /// + /// For WinDivert filter syntax, layer semantics, timestamps and error codes please refer to the WinDivert + /// documentation. class WinDivertDevice : public IDevice { public: + /// @struct ReceiveResult + /// @brief Result object returned by receive operations. struct ReceiveResult { enum class Status { - Completed, - Timeout, - Failed + Completed, ///< Receive completed successfully + Timeout, ///< Receive timed out before completing the requested operation + Failed ///< Receive failed due to an error (see error and errorCode) }; - Status status; - std::string error; - uint32_t errorCode = 0; + Status status; ///< Operation status (Completed/Timeout/Failed) + std::string error; ///< Error message when status is Failed; empty otherwise + uint32_t errorCode = 0; ///< Platform-specific error code associated with the failure (0 if none) }; + /// @struct SendResult + /// @brief Result object returned by send operations. struct SendResult { enum class Status { - Completed, - Failed + Completed, ///< Send operation completed successfully + Failed ///< Send operation failed (see error and errorCode) }; - Status status; - size_t packetsSent; - std::string error; - uint32_t errorCode = 0; + Status status; ///< Operation status (Completed/Failed) + size_t packetsSent; ///< Number of packets successfully sent when status is Completed + std::string error; ///< Error message when status is Failed; empty otherwise + uint32_t errorCode = 0; ///< Platform-specific error code associated with the failure (0 if none) }; + /// @struct WinDivertVersion + /// @brief The WinDivert runtime version as reported by the driver. struct WinDivertVersion { - uint64_t major; - uint64_t minor; + uint64_t major; ///< Major version number reported by WinDivert + uint64_t minor; ///< Minor version number reported by WinDivert + /// @brief Convert to "major.minor" string representation. std::string toString() const { return std::to_string(major) + "." + std::to_string(minor); } }; + /// @struct NetworkInterface + /// @brief A Windows network interface entry returned by getNetworkInterfaces(). struct NetworkInterface { uint32_t index; @@ -176,39 +226,114 @@ namespace pcpp bool isUp; }; + /// @enum QueueParam + /// @brief Queue tuning parameters supported by WinDivert. + /// + /// These map to WinDivert queue configuration parameters: + /// - QueueLength – maximum number of packets in the internal queue (packets) + /// - QueueTime – maximum time packets may sit in the internal queue (milliseconds) + /// - QueueSize – maximum memory size for the internal queue (bytes) enum class QueueParam { - QueueLength, - QueueTime, - QueueSize + QueueLength, ///< Maximum number of packets in the packet queue (packets) + QueueTime, ///< Maximum residence time of packets in the queue (milliseconds) + QueueSize ///< Maximum memory allocated for the queue (bytes) }; + /// @typedef WinDivertRawPacketVector + /// @brief Convenience alias for a vector of WinDivertRawPacket pointers with ownership semantics. using WinDivertRawPacketVector = PointerVector; + /// @typedef ReceivePacketCallback + /// @brief Callback invoked with a batch of received packets when using the callback receive API. + /// The callback is called from the receiving loop until stopReceive() is invoked or an error/timeout occurs. using ReceivePacketCallback = std::function; + /// @typedef QueueParams + /// @brief A map of QueueParam keys to their values. Units are per QueueParam description above. using QueueParams = std::unordered_map; + /// @brief Construct a WinDivertDevice with the default WinDivert implementation. WinDivertDevice(); + /// @brief Open the device with a default filter capturing both directions. + /// @return true on success, false on failure (see logs for details). + /// @note This calls open("inbound or outbound") on WINDIVERT_LAYER_NETWORK with sniffing/fragments flags. bool open() override; + + /// @brief Open the device with a custom WinDivert filter. + /// @param[in] filter A WinDivert filter string (e.g. "ip and tcp.DstPort == 80"). + /// @return true on success, false on failure (see logs for details). + /// @note The device is opened on WINDIVERT_LAYER_NETWORK with sniffing and fragment support. bool open(const std::string& filter); + + /// @brief Close the device and release the underlying WinDivert handle. void close() override; + /// @brief Receive packets into a vector owned by the caller. + /// + /// This method receives up to maxPackets packets (0 means unlimited) in batches of batchSize. + /// It returns when either enough packets were captured or timeout milliseconds elapsed without completion. + /// + /// @param[out] packetVec Destination vector for received packets. Each entry is a WinDivertRawPacket that owns + /// its data. + /// @param[in] timeout Receive timeout in milliseconds. Use 0 with a positive maxPackets to wait until quota is + /// reached. + /// @param[in] maxPackets Maximum packets to receive before returning. Use 0 for no limit (subject to timeout). + /// @param[in] batchSize Number of packets to read per WinDivert call (must be > 0). Default is 64. + /// @return A ReceiveResult describing the outcome. On failure, see error and errorCode. ReceiveResult receivePackets(WinDivertRawPacketVector& packetVec, uint32_t timeout = 5000, uint32_t maxPackets = 0, uint8_t batchSize = 64); + + /// @brief Receive packets using a callback invoked for each received batch. + /// + /// The method runs a receive loop and invokes callback with each batch. The loop ends when stopReceive() + /// is called from another thread, on timeout, or if an error occurs. Packet memory is valid during the callback + /// and is released when the callback returns. + /// + /// @param[in] callback A callback receiving a vector view of the current batch. + /// @param[in] timeout Receive timeout in milliseconds per wait cycle. Default is 5000ms. + /// @param[in] batchSize Number of packets to read per WinDivert call (must be > 0). Default is 64. + /// @return A ReceiveResult describing the final outcome. ReceiveResult receivePackets(const ReceivePacketCallback& callback, uint32_t timeout = 5000, uint8_t batchSize = 64); + + /// @brief Request to stop an ongoing receivePackets(callback, ...) loop. + /// @note This is thread-safe and can be called from a thread other than the receiving thread. void stopReceive(); + /// @brief Send a vector of raw packets in batches. + /// + /// The method copies packet data into an internal buffer and calls WinDivert send in batches of batchSize. + /// + /// @param[in] packetVec A vector of raw packets to send. + /// @param[in] batchSize Number of packets to send per WinDivert call (must be > 0). Default is 64. + /// @return A SendResult describing the outcome and number of packets sent. SendResult sendPackets(const RawPacketVector& packetVec, uint8_t batchSize = 64) const; + /// @brief Get the current WinDivert queue parameters. + /// @return A map from QueueParam to the configured value. QueueParams getPacketQueueParams() const; + + /// @brief Set WinDivert queue parameters. + /// @param[in] params A map of queue parameters to set. Absent keys are left unchanged. + /// @note Values units are: length (packets), time (milliseconds), size (bytes). void setPacketQueueParams(const QueueParams& params) const; + /// @brief Get the WinDivert runtime version loaded on the system. + /// @return A WinDivertVersion with major and minor components. WinDivertVersion getVersion() const; + /// @brief Get a pointer to a specific Windows network interface by index. + /// @param[in] interfaceIndex The Windows interface index. + /// @return A pointer to an internal NetworkInterface entry or nullptr if not found. + /// @warning The returned pointer may become invalid after subsequent calls that refresh interfaces. const NetworkInterface* getNetworkInterface(uint32_t interfaceIndex) const; + + /// @brief Enumerate Windows network interfaces. + /// @return A vector of NetworkInterface entries. std::vector getNetworkInterfaces() const; + /// @brief Replace the underlying implementation (intended for testing/mocking). + /// @param[in] implementation An implementation of the WinDivert backend APIs. void setImplementation(std::unique_ptr implementation); private: From 6bad9a72fceb409b04fd2329c4e0aa3b27ce2bd2 Mon Sep 17 00:00:00 2001 From: seladb Date: Sun, 9 Nov 2025 13:45:04 -0800 Subject: [PATCH 04/17] - Format CMake - Add missing doxygen documentation --- Pcap++/header/WinDivertDevice.h | 4 ++ cmake/modules/FindWinDivert.cmake | 96 ++++++++++++++----------------- 2 files changed, 47 insertions(+), 53 deletions(-) diff --git a/Pcap++/header/WinDivertDevice.h b/Pcap++/header/WinDivertDevice.h index 5a049ca682..e65066aac0 100644 --- a/Pcap++/header/WinDivertDevice.h +++ b/Pcap++/header/WinDivertDevice.h @@ -173,6 +173,8 @@ namespace pcpp /// @brief Result object returned by receive operations. struct ReceiveResult { + /// @enum Status + /// @brief Status codes for receive operations. enum class Status { Completed, ///< Receive completed successfully @@ -189,6 +191,8 @@ namespace pcpp /// @brief Result object returned by send operations. struct SendResult { + /// @enum Status + /// @brief Status codes for send operations. enum class Status { Completed, ///< Send operation completed successfully diff --git a/cmake/modules/FindWinDivert.cmake b/cmake/modules/FindWinDivert.cmake index ea50c4b6e0..6f5ba59f76 100644 --- a/cmake/modules/FindWinDivert.cmake +++ b/cmake/modules/FindWinDivert.cmake @@ -31,87 +31,77 @@ # └── WinDivert32.sys if(NOT WIN32) - if(NOT WinDivert_FIND_QUIETLY) - message(FATAL_ERROR "WinDivert is only available on Windows") - endif() - return() + if(NOT WinDivert_FIND_QUIETLY) + message(FATAL_ERROR "WinDivert is only available on Windows") + endif() + return() endif() # Detect 64-bit vs 32-bit if(CMAKE_SIZEOF_VOID_P EQUAL 8) - set(_WinDivert_ARCH_DIR "x64") - set(_WinDivert_SYS_NAME "WinDivert64.sys") + set(_WinDivert_ARCH_DIR "x64") + set(_WinDivert_SYS_NAME "WinDivert64.sys") else() - set(_WinDivert_ARCH_DIR "x86") - set(_WinDivert_SYS_NAME "WinDivert32.sys") + set(_WinDivert_ARCH_DIR "x86") + set(_WinDivert_SYS_NAME "WinDivert32.sys") endif() # Normalize user-provided root path if(DEFINED WINDIVERT_ROOT) - file(TO_CMAKE_PATH "${WINDIVERT_ROOT}" _WinDivert_ROOT_HINT) + file(TO_CMAKE_PATH "${WINDIVERT_ROOT}" _WinDivert_ROOT_HINT) elseif(DEFINED ENV{WINDIVERT_ROOT}) - file(TO_CMAKE_PATH "$ENV{WINDIVERT_ROOT}" _WinDivert_ROOT_HINT) + file(TO_CMAKE_PATH "$ENV{WINDIVERT_ROOT}" _WinDivert_ROOT_HINT) else() - set(_WinDivert_ROOT_HINT "") + set(_WinDivert_ROOT_HINT "") endif() if(NOT WinDivert_FIND_QUIETLY) - message(STATUS "WinDivert root hint: ${_WinDivert_ROOT_HINT}") - message(STATUS "Looking in arch dir: ${_WinDivert_ARCH_DIR}") + message(STATUS "WinDivert root hint: ${_WinDivert_ROOT_HINT}") + message(STATUS "Looking in arch dir: ${_WinDivert_ARCH_DIR}") endif() # Look for header -find_path(WinDivert_INCLUDE_DIR - NAMES windivert.h - PATHS - "${_WinDivert_ROOT_HINT}" - "C:/Program Files/WinDivert" - "C:/WinDivert" - PATH_SUFFIXES include +find_path( + WinDivert_INCLUDE_DIR + NAMES windivert.h + PATHS "${_WinDivert_ROOT_HINT}" "C:/Program Files/WinDivert" "C:/WinDivert" + PATH_SUFFIXES include ) # Look for library -find_library(WinDivert_LIBRARY - NAMES WinDivert - PATHS - "${_WinDivert_ROOT_HINT}" - "C:/Program Files/WinDivert" - "C:/WinDivert" - PATH_SUFFIXES ${_WinDivert_ARCH_DIR} +find_library( + WinDivert_LIBRARY + NAMES WinDivert + PATHS "${_WinDivert_ROOT_HINT}" "C:/Program Files/WinDivert" "C:/WinDivert" + PATH_SUFFIXES ${_WinDivert_ARCH_DIR} ) # Look for .sys file (optional) -find_file(WinDivert_SYS - NAMES ${_WinDivert_SYS_NAME} - PATHS - "${_WinDivert_ROOT_HINT}" - "C:/Program Files/WinDivert" - "C:/WinDivert" - PATH_SUFFIXES ${_WinDivert_ARCH_DIR} +find_file( + WinDivert_SYS + NAMES ${_WinDivert_SYS_NAME} + PATHS "${_WinDivert_ROOT_HINT}" "C:/Program Files/WinDivert" "C:/WinDivert" + PATH_SUFFIXES ${_WinDivert_ARCH_DIR} ) # Look for .dll file (optional) -find_file(WinDivert_DLL - NAMES WinDivert.dll - PATHS - "${_WinDivert_ROOT_HINT}" - "C:/Program Files/WinDivert" - "C:/WinDivert" - PATH_SUFFIXES ${_WinDivert_ARCH_DIR} +find_file( + WinDivert_DLL + NAMES WinDivert.dll + PATHS "${_WinDivert_ROOT_HINT}" "C:/Program Files/WinDivert" "C:/WinDivert" + PATH_SUFFIXES ${_WinDivert_ARCH_DIR} ) # Handle REQUIRED + FOUND logic include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(WinDivert - REQUIRED_VARS WinDivert_INCLUDE_DIR WinDivert_LIBRARY -) +find_package_handle_standard_args(WinDivert REQUIRED_VARS WinDivert_INCLUDE_DIR WinDivert_LIBRARY) # Print results if not quiet if(NOT WinDivert_FIND_QUIETLY) - message(STATUS "WinDivert_INCLUDE_DIR: ${WinDivert_INCLUDE_DIR}") - message(STATUS "WinDivert_LIBRARY: ${WinDivert_LIBRARY}") - message(STATUS "WinDivert_SYS: ${WinDivert_SYS}") - message(STATUS "WinDivert_DLL: ${WinDivert_DLL}") + message(STATUS "WinDivert_INCLUDE_DIR: ${WinDivert_INCLUDE_DIR}") + message(STATUS "WinDivert_LIBRARY: ${WinDivert_LIBRARY}") + message(STATUS "WinDivert_SYS: ${WinDivert_SYS}") + message(STATUS "WinDivert_DLL: ${WinDivert_DLL}") endif() # Compatibility variables (set AFTER discovery) @@ -122,9 +112,9 @@ set(WinDivert_DLL_FILE ${WinDivert_DLL}) # Create imported target if(WinDivert_FOUND AND NOT TARGET WinDivert::WinDivert) - add_library(WinDivert::WinDivert STATIC IMPORTED) - set_target_properties(WinDivert::WinDivert PROPERTIES - IMPORTED_LOCATION "${WinDivert_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${WinDivert_INCLUDE_DIR}" - ) + add_library(WinDivert::WinDivert STATIC IMPORTED) + set_target_properties( + WinDivert::WinDivert + PROPERTIES IMPORTED_LOCATION "${WinDivert_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${WinDivert_INCLUDE_DIR}" + ) endif() From 2f1c3ee5525c0b4d4407049072a065382bb3e15f Mon Sep 17 00:00:00 2001 From: seladb Date: Sun, 9 Nov 2025 14:42:09 -0800 Subject: [PATCH 05/17] Add CI --- .github/workflows/build_and_test.yml | 26 +++++++++++++++++++ ci/install_windivert.bat | 37 ++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 ci/install_windivert.bat diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 5930776830..e642987127 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -705,6 +705,32 @@ jobs: env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + windivert: + runs-on: windows-latest + steps: + - name: Checkout code + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Add MSBuild to PATH + uses: microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce # v2.0.0 + + - name: Install WinPcap + run: | + ci\install_winpcap.bat + echo "PCAP_SDK_DIR=C:\\WpdPack" >> $env:GITHUB_ENV + + - name: Set WinDivert root + run: echo "WINDIVERT_DIR=C:\\WinDivert" >> $env:GITHUB_ENV + + - name: Install WinDivert + run: ci\install_windivert.bat $env:WINDIVERT_DIR + + - name: Configure PcapPlusPlus (WinDivert) + run: cmake -A x64 -G "Visual Studio 17 2022" -DPCAP_ROOT=${{ env.PCAP_SDK_DIR }} -DPCAPPP_USE_WINDIVERT=ON -DWINDIVERT_ROOT=${{ env.WINDIVERT_DIR }} -S . -B "$env:BUILD_DIR" + + - name: Build PcapPlusPlus + run: cmake --build $env:BUILD_DIR -j + freebsd: runs-on: ubuntu-latest strategy: diff --git a/ci/install_windivert.bat b/ci/install_windivert.bat new file mode 100644 index 0000000000..79c7ff278b --- /dev/null +++ b/ci/install_windivert.bat @@ -0,0 +1,37 @@ +@echo off +setlocal enableextensions enabledelayedexpansion + +REM Install WinDivert SDK and set environment hints for subsequent CI steps +REM - Downloads WinDivert v2.2.0 +REM - Extracts and arranges it under the provided WINDIVERT_ROOT (include/, x64/, optional x86/) +REM - Appends \x64 to GITHUB_PATH for runtime DLL discovery +REM - Sets WINDIVERT_ROOT in GITHUB_ENV so CMake's FindWinDivert can locate it + +REM Accept optional first argument as WINDIVERT_ROOT. Default to C:\WinDivert if not provided +set "TARGET=C:\WinDivert" +if not "%~1"=="" ( + set "TARGET=%~1" +) + +set "URL=https://github.com/basil00/Divert/releases/download/v2.2.0/WinDivert-2.2.0-A.zip" +set "ZIP=%RUNNER_TEMP%\WinDivert.zip" +set "DEST=%RUNNER_TEMP%\WinDivertTmp" + +REM Use PowerShell for download and extraction +powershell -NoProfile -ExecutionPolicy Bypass -Command ^ + "$ErrorActionPreference='Stop'; $url='%URL%'; $zip='%ZIP%'; $dest='%DEST%'; $target='%TARGET%';" ^ + "Invoke-WebRequest -Uri $url -OutFile $zip;" ^ + "if (Test-Path $dest) { Remove-Item -Recurse -Force $dest };" ^ + "Expand-Archive -Path $zip -DestinationPath $dest -Force;" ^ + "$root = Get-ChildItem -Path $dest -Directory | Select-Object -First 1;" ^ + "if (Test-Path $target) { Remove-Item -Recurse -Force $target };" ^ + "New-Item -ItemType Directory -Path $target -Force | Out-Null;" ^ + "foreach($d in 'include','x64','x86'){ $p = Join-Path $root.FullName $d; if (Test-Path $p) { Copy-Item -Recurse -Force $p $target } }" + +IF ERRORLEVEL 1 ( + echo Failed to install WinDivert SDK + exit /b 1 +) + +echo WinDivert installation completed successfully. +exit /b 0 From 2e1c7e14e0710fa9aebcfacf245449b65b53b045 Mon Sep 17 00:00:00 2001 From: seladb Date: Sun, 9 Nov 2025 17:37:34 -0800 Subject: [PATCH 06/17] Fix doxygen warnings --- Pcap++/header/WinDivertDevice.h | 80 ++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 25 deletions(-) diff --git a/Pcap++/header/WinDivertDevice.h b/Pcap++/header/WinDivertDevice.h index e65066aac0..551ca8316b 100644 --- a/Pcap++/header/WinDivertDevice.h +++ b/Pcap++/header/WinDivertDevice.h @@ -24,39 +24,58 @@ namespace pcpp { namespace internal { + /// @brief Opaque handle wrapper for an opened WinDivert driver instance. + /// + /// Implementations encapsulate the native WinDivert handle and its lifetime. class IWinDivertHandle { public: virtual ~IWinDivertHandle() = default; }; + /// @brief Abstract helper that wraps Windows OVERLAPPED I/O used by WinDivert operations. + /// + /// Implementations provide waiting/resetting primitives and a way to fetch + /// the result of an asynchronous I/O tied to a specific WinDivert handle. class IOverlappedWrapper { public: + /// @brief Result of waiting on an OVERLAPPED I/O operation. + /// + /// Indicates whether the asynchronous operation completed, timed out or failed, + /// and carries an optional Windows error code. struct WaitResult { + /// @enum Status + /// @brief Status codes for wait result. enum class Status { - Completed, - Timeout, - Failed + Completed, ///< The wait completed successfully + Timeout, ///< The wait timed out before completion + Failed ///< The wait failed; see errorCode }; - - Status status; - uint32_t errorCode = 0; + + Status status; ///< Final wait status + uint32_t errorCode = 0; ///< Windows error code (when relevant) }; + /// @brief Result of completing an OVERLAPPED I/O operation. + /// + /// Contains the final status, the number of bytes/packet length produced by the + /// operation (when applicable), and a Windows error code on failure. struct OverlappedResult { + /// @enum Status + /// @brief Status codes for overlapped result. enum class Status { - Success, - Failed + Success, ///< Operation completed successfully + Failed ///< Operation failed; see errorCode }; - - Status status; - uint32_t packetLen = 0; - uint32_t errorCode = 0; + + Status status; ///< Completion status + uint32_t packetLen = 0; ///< Number of bytes read/written (when applicable) + uint32_t errorCode = 0; ///< Windows error code (when relevant) }; virtual WaitResult wait(uint32_t timeout) = 0; @@ -65,32 +84,43 @@ namespace pcpp virtual ~IOverlappedWrapper() = default; }; + /// @brief Minimal address/metadata returned by WinDivert for a captured packet. + /// + /// This structure mirrors the subset of fields PcapPlusPlus needs from WinDivert's + /// WINDIVERT_ADDRESS: whether the packet is IPv6, the Windows interface index and + /// the original WinDivert timestamp. struct WinDivertAddress { - bool isIPv6; - uint32_t interfaceIndex; - uint64_t timestamp; + bool isIPv6; ///< True if the packet is IPv6, false for IPv4 + uint32_t interfaceIndex; ///< Windows network interface index + uint64_t timestamp; ///< WinDivert timestamp associated with the packet }; + /// @brief Abstraction over the concrete WinDivert API used by WinDivertDevice. + /// + /// This interface allows providing different backends (e.g., real WinDivert DLL + /// or a test double) while keeping the device logic independent from the API. class IWinDivertImplementation { public: + /// @brief WinDivert runtime parameters that can be queried or configured. enum class WinDivertParam { - QueueLength = 0, - QueueTime = 1, - QueueSize = 2, - VersionMajor = 3, - VersionMinor = 4 + QueueLength = 0, ///< Maximum number of packets in the queue + QueueTime = 1, ///< Maximum time (ms) a packet may stay in the queue + QueueSize = 2, ///< Maximum total queue size (bytes) + VersionMajor= 3, ///< WinDivert major version + VersionMinor= 4 ///< WinDivert minor version }; + /// @brief Information about a Windows network interface as reported by WinDivert. struct NetworkInterface { - uint32_t index; - std::wstring name; - std::wstring description; - bool isLoopback; - bool isUp; + uint32_t index; ///< Interface index + std::wstring name; ///< Interface GUID or system name + std::wstring description; ///< Human-readable description + bool isLoopback; ///< True if the interface is loopback + bool isUp; ///< True if the interface is up/running }; static constexpr uint32_t SuccessResult = 0; From eb647f1b9ecad9919c5f8c1180c629c613f78072 Mon Sep 17 00:00:00 2001 From: seladb Date: Sun, 9 Nov 2025 17:54:52 -0800 Subject: [PATCH 07/17] Fix a few issues --- Pcap++/header/WinDivertDevice.h | 6 ++++++ Pcap++/src/WinDivertDevice.cpp | 1 + 2 files changed, 7 insertions(+) diff --git a/Pcap++/header/WinDivertDevice.h b/Pcap++/header/WinDivertDevice.h index 551ca8316b..474b83e458 100644 --- a/Pcap++/header/WinDivertDevice.h +++ b/Pcap++/header/WinDivertDevice.h @@ -302,6 +302,11 @@ namespace pcpp /// @brief Close the device and release the underlying WinDivert handle. void close() override; + bool isOpened() const override + { + return m_DeviceOpened; + } + /// @brief Receive packets into a vector owned by the caller. /// /// This method receives up to maxPackets packets (0 means unlimited) in batches of batchSize. @@ -376,6 +381,7 @@ namespace pcpp std::atomic m_IsReceiving{ false }; mutable std::unordered_map m_NetworkInterfaces; mutable bool m_NetworkInterfacesInitialized = false; + bool m_DeviceOpened = false; struct ReceiveResultInternal : ReceiveResult { diff --git a/Pcap++/src/WinDivertDevice.cpp b/Pcap++/src/WinDivertDevice.cpp index ea3a39041f..280ada70d6 100644 --- a/Pcap++/src/WinDivertDevice.cpp +++ b/Pcap++/src/WinDivertDevice.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace pcpp { From 1f7c0d00b93429988f48e5381d65c93ad7ed5337 Mon Sep 17 00:00:00 2001 From: seladb Date: Sun, 9 Nov 2025 18:03:24 -0800 Subject: [PATCH 08/17] Fix formatting --- Pcap++/header/WinDivertDevice.h | 38 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/Pcap++/header/WinDivertDevice.h b/Pcap++/header/WinDivertDevice.h index 474b83e458..c4543824ac 100644 --- a/Pcap++/header/WinDivertDevice.h +++ b/Pcap++/header/WinDivertDevice.h @@ -54,9 +54,9 @@ namespace pcpp Timeout, ///< The wait timed out before completion Failed ///< The wait failed; see errorCode }; - - Status status; ///< Final wait status - uint32_t errorCode = 0; ///< Windows error code (when relevant) + + Status status; ///< Final wait status + uint32_t errorCode = 0; ///< Windows error code (when relevant) }; /// @brief Result of completing an OVERLAPPED I/O operation. @@ -69,10 +69,10 @@ namespace pcpp /// @brief Status codes for overlapped result. enum class Status { - Success, ///< Operation completed successfully - Failed ///< Operation failed; see errorCode + Success, ///< Operation completed successfully + Failed ///< Operation failed; see errorCode }; - + Status status; ///< Completion status uint32_t packetLen = 0; ///< Number of bytes read/written (when applicable) uint32_t errorCode = 0; ///< Windows error code (when relevant) @@ -91,9 +91,9 @@ namespace pcpp /// the original WinDivert timestamp. struct WinDivertAddress { - bool isIPv6; ///< True if the packet is IPv6, false for IPv4 - uint32_t interfaceIndex; ///< Windows network interface index - uint64_t timestamp; ///< WinDivert timestamp associated with the packet + bool isIPv6; ///< True if the packet is IPv6, false for IPv4 + uint32_t interfaceIndex; ///< Windows network interface index + uint64_t timestamp; ///< WinDivert timestamp associated with the packet }; /// @brief Abstraction over the concrete WinDivert API used by WinDivertDevice. @@ -106,21 +106,21 @@ namespace pcpp /// @brief WinDivert runtime parameters that can be queried or configured. enum class WinDivertParam { - QueueLength = 0, ///< Maximum number of packets in the queue - QueueTime = 1, ///< Maximum time (ms) a packet may stay in the queue - QueueSize = 2, ///< Maximum total queue size (bytes) - VersionMajor= 3, ///< WinDivert major version - VersionMinor= 4 ///< WinDivert minor version + QueueLength = 0, ///< Maximum number of packets in the queue + QueueTime = 1, ///< Maximum time (ms) a packet may stay in the queue + QueueSize = 2, ///< Maximum total queue size (bytes) + VersionMajor = 3, ///< WinDivert major version + VersionMinor = 4 ///< WinDivert minor version }; /// @brief Information about a Windows network interface as reported by WinDivert. struct NetworkInterface { - uint32_t index; ///< Interface index - std::wstring name; ///< Interface GUID or system name - std::wstring description; ///< Human-readable description - bool isLoopback; ///< True if the interface is loopback - bool isUp; ///< True if the interface is up/running + uint32_t index; ///< Interface index + std::wstring name; ///< Interface GUID or system name + std::wstring description; ///< Human-readable description + bool isLoopback; ///< True if the interface is loopback + bool isUp; ///< True if the interface is up/running }; static constexpr uint32_t SuccessResult = 0; From 67c444427a5e3615b75b2b235c768fd84c38f2af Mon Sep 17 00:00:00 2001 From: seladb Date: Sun, 9 Nov 2025 18:23:47 -0800 Subject: [PATCH 09/17] Fix clang-tidy-all.sh --- ci/clang-tidy-all.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/clang-tidy-all.sh b/ci/clang-tidy-all.sh index c2cd4031c6..ec4e5ec844 100755 --- a/ci/clang-tidy-all.sh +++ b/ci/clang-tidy-all.sh @@ -1,7 +1,7 @@ #!/bin/sh set -e -IGNORE_LIST=".*dirent.* .*DpdkDevice* .*KniDevice* .*MBufRawPacket* .*PfRingDevice* .*RemoteDevice* .*XdpDevice* .*WinPcap*" +IGNORE_LIST=".*dirent.* .*DpdkDevice* .*KniDevice* .*MBufRawPacket* .*PfRingDevice* .*RemoteDevice* .*XdpDevice* .*WinPcap* .*WinDivert*" SCRIPT=$(readlink -f "$0") SCRIPTPATH=$(dirname "${SCRIPT}") From 60575ddc7ba0da0f4a5aa3a94f4fe6387a9ef824 Mon Sep 17 00:00:00 2001 From: seladb Date: Wed, 12 Nov 2025 23:24:19 -0800 Subject: [PATCH 10/17] Add tests --- .github/workflows/build_and_test.yml | 19 ++++++++++++------- Tests/Pcap++Test/Tests/FilterTests.cpp | 4 ++-- Tests/Pcap++Test/Tests/WinDivertTests.cpp | 2 +- ci/run_tests/run_tests_windows.py | 13 +++++++++++++ 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index e642987127..92abc2b25c 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -289,12 +289,6 @@ jobs: cd Tests/Packet++Test Bin/Packet++Test - - name: Test Pcap++ - if: env.avx512 == 'true' - run: | - cd Tests/Pcap++Test - Bin/Pcap++Test - - name: Tests skipped (no AVX-512 hardware support) if: env.avx512 == 'false' run: echo "Skipping tests since AVX-512 is not supported on the current runner" @@ -718,7 +712,6 @@ jobs: run: | ci\install_winpcap.bat echo "PCAP_SDK_DIR=C:\\WpdPack" >> $env:GITHUB_ENV - - name: Set WinDivert root run: echo "WINDIVERT_DIR=C:\\WinDivert" >> $env:GITHUB_ENV @@ -731,6 +724,18 @@ jobs: - name: Build PcapPlusPlus run: cmake --build $env:BUILD_DIR -j + - name: Install tcpreplay + run: ci\install_tcpreplay.bat + + - name: Test PcapPlusPlus + shell: pwsh + run: | + Copy-Item "${{ env.WINDIVERT_DIR }}\x64\WinDivert.dll" "Tests\Pcap++Test\Bin\WinDivert.dll" -Force + Copy-Item "${{ env.WINDIVERT_DIR }}\x64\WinDivert64.sys" "Tests\Pcap++Test\Bin\WinDivert64.sys" -Force + + python -m pip install -r ci\run_tests\requirements.txt + python ci\run_tests\run_tests_windows.py --include-tests windivert + freebsd: runs-on: ubuntu-latest strategy: diff --git a/Tests/Pcap++Test/Tests/FilterTests.cpp b/Tests/Pcap++Test/Tests/FilterTests.cpp index 8c46a50003..9e6948de4c 100644 --- a/Tests/Pcap++Test/Tests/FilterTests.cpp +++ b/Tests/Pcap++Test/Tests/FilterTests.cpp @@ -106,9 +106,9 @@ PTF_TEST_CASE(TestPcapFiltersLive) andFilter.parseToString(filterAsString); PTF_ASSERT_TRUE(liveDev->setFilter(andFilter)); PTF_ASSERT_TRUE(liveDev->startCapture(capturedPackets)); - PTF_ASSERT_TRUE(sendURLRequest("www.walla.co.il")); + PTF_ASSERT_TRUE(sendURLRequest("www.google.com")); // let the capture work for couple of seconds - totalSleepTime = incSleep(capturedPackets, 2, 7); + totalSleepTime = incSleep(capturedPackets, 2, 20); PTF_PRINT_VERBOSE("Total sleep time: " << totalSleepTime); liveDev->stopCapture(); PTF_ASSERT_GREATER_OR_EQUAL_THAN(capturedPackets.size(), 2); diff --git a/Tests/Pcap++Test/Tests/WinDivertTests.cpp b/Tests/Pcap++Test/Tests/WinDivertTests.cpp index 29f54828c2..97b73845b9 100644 --- a/Tests/Pcap++Test/Tests/WinDivertTests.cpp +++ b/Tests/Pcap++Test/Tests/WinDivertTests.cpp @@ -56,7 +56,7 @@ PTF_TEST_CASE(TestWinDivertReceivePackets) allPacketsHaveInterface &= device.getNetworkInterface(rawPacket->getInterfaceIndex()) != nullptr; pcpp::Packet packet(rawPacket); allPacketsOfTypeIP &= packet.getFirstLayer()->isMemberOfProtocolFamily(pcpp::IP); - isTimestampIncreasing &= (rawPacket->getWinDivertTimestamp() > currentTimestamp); + isTimestampIncreasing &= (rawPacket->getWinDivertTimestamp() >= currentTimestamp); currentTimestamp = rawPacket->getWinDivertTimestamp(); } diff --git a/ci/run_tests/run_tests_windows.py b/ci/run_tests/run_tests_windows.py index 532241bfeb..b2c9c3374d 100644 --- a/ci/run_tests/run_tests_windows.py +++ b/ci/run_tests/run_tests_windows.py @@ -72,6 +72,14 @@ def main(): default=[], help="Pcap++ tests to skip", ) + parser.add_argument( + "--include-tests", + "-t", + type=str, + nargs="+", + default=[], + help="Pcap++ tests to include", + ) parser.add_argument( "--coverage", "-c", @@ -125,6 +133,9 @@ def main(): exit(completed_process.returncode) skip_tests = ["TestRemoteCapture"] + args.skip_tests + include_tests = ( + ["-t", ";".join(args.include_tests)] if args.include_tests else [] + ) if args.coverage: completed_process = subprocess.run( [ @@ -146,6 +157,7 @@ def main(): ip_address, "-x", ";".join(skip_tests), + *include_tests, ], cwd=os.path.join("Tests", "Pcap++Test"), shell=True, @@ -158,6 +170,7 @@ def main(): ip_address, "-x", ";".join(skip_tests), + *include_tests, ], cwd=os.path.join("Tests", "Pcap++Test"), shell=True, From 1fb59c74e7f4fa21c501e5aa886f86af0826d8d0 Mon Sep 17 00:00:00 2001 From: seladb Date: Wed, 12 Nov 2025 23:37:19 -0800 Subject: [PATCH 11/17] Change default filter to `true` --- Pcap++/header/WinDivertDevice.h | 4 ++-- Pcap++/src/WinDivertDevice.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Pcap++/header/WinDivertDevice.h b/Pcap++/header/WinDivertDevice.h index c4543824ac..e26dd21f96 100644 --- a/Pcap++/header/WinDivertDevice.h +++ b/Pcap++/header/WinDivertDevice.h @@ -187,7 +187,7 @@ namespace pcpp /// retrieve the WinDivert runtime version, and enumerate Windows network interfaces. /// /// Notes: - /// - The default open() uses the filter "inbound or outbound", capturing both directions. + /// - The default open() uses the filter "true", capturing both directions. /// - The device is opened in sniffing mode and supports fragmented packets. /// - Receive can be done into a user-provided vector or via a callback loop that can be stopped with stopReceive(). /// - Send batches multiple packets at once for efficiency. @@ -290,7 +290,7 @@ namespace pcpp /// @brief Open the device with a default filter capturing both directions. /// @return true on success, false on failure (see logs for details). - /// @note This calls open("inbound or outbound") on WINDIVERT_LAYER_NETWORK with sniffing/fragments flags. + /// @note This calls open("true") on WINDIVERT_LAYER_NETWORK with sniffing/fragments flags. bool open() override; /// @brief Open the device with a custom WinDivert filter. diff --git a/Pcap++/src/WinDivertDevice.cpp b/Pcap++/src/WinDivertDevice.cpp index 280ada70d6..48949f2fff 100644 --- a/Pcap++/src/WinDivertDevice.cpp +++ b/Pcap++/src/WinDivertDevice.cpp @@ -285,7 +285,7 @@ namespace pcpp bool WinDivertDevice::open() { - return open("inbound or outbound"); + return open("true"); } bool WinDivertDevice::open(const std::string& filter) From ba511c5b18fad417cfd48f1ffb3b7952b243188b Mon Sep 17 00:00:00 2001 From: seladb Date: Thu, 13 Nov 2025 00:18:12 -0800 Subject: [PATCH 12/17] Add WinDivert to README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d7525c09ba..5e88c1f9b4 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ [PcapPlusPlus](https://pcapplusplus.github.io/) is a multiplatform C++ library for capturing, parsing and crafting of network packets. It is designed to be efficient, powerful and easy to use. -PcapPlusPlus enables decoding and forging capabilities for a large variety of network protocols. It also provides easy to use C++ wrappers for the most popular packet processing engines such as [libpcap](https://www.tcpdump.org/), [WinPcap](https://www.winpcap.org/), [Npcap](https://nmap.org/npcap/), [DPDK](https://www.dpdk.org/), [eBPF AF_XDP](https://www.kernel.org/doc/html/next/networking/af_xdp.html) and [PF_RING](https://www.ntop.org/products/packet-capture/pf_ring/). +PcapPlusPlus enables decoding and forging capabilities for a large variety of network protocols. It also provides easy to use C++ wrappers for the most popular packet processing engines such as [libpcap](https://www.tcpdump.org/), [WinPcap](https://www.winpcap.org/), [Npcap](https://nmap.org/npcap/), [DPDK](https://www.dpdk.org/), [eBPF AF_XDP](https://www.kernel.org/doc/html/next/networking/af_xdp.html), [WinDivert](https://reqrypt.org/windivert.html) and [PF_RING](https://www.ntop.org/products/packet-capture/pf_ring/). Translations: English · [正體中文](./translation/README-zh-tw.md) · [한국어](./translation/README-kor.md) @@ -115,7 +115,7 @@ and you should see the following output in your terminal: ## Feature Overview -- __Packet capture__ through an easy to use C++ wrapper for popular packet capture engines such as [libpcap](https://www.tcpdump.org/), [WinPcap](https://www.winpcap.org/), [Npcap](https://nmap.org/npcap/), [Intel DPDK](https://www.dpdk.org/), [eBPF AF_XDP](https://www.kernel.org/doc/html/next/networking/af_xdp.html), [ntop’s PF_RING](https://www.ntop.org/products/packet-capture/pf_ring/) and [raw sockets](https://en.wikipedia.org/wiki/Network_socket#Raw_socket) [[Learn more](https://pcapplusplus.github.io/docs/features#packet-capture)] +- __Packet capture__ through an easy to use C++ wrapper for popular packet capture engines such as [libpcap](https://www.tcpdump.org/), [WinPcap](https://www.winpcap.org/), [Npcap](https://nmap.org/npcap/), [Intel DPDK](https://www.dpdk.org/), [eBPF AF_XDP](https://www.kernel.org/doc/html/next/networking/af_xdp.html), [WinDivert](https://reqrypt.org/windivert.html), [ntop’s PF_RING](https://www.ntop.org/products/packet-capture/pf_ring/) and [raw sockets](https://en.wikipedia.org/wiki/Network_socket#Raw_socket) [[Learn more](https://pcapplusplus.github.io/docs/features#packet-capture)] - __Packet parsing and crafting__ including detailed analysis of protocols and layers, packet generation and packet edit for a large variety of [network protocols](https://pcapplusplus.github.io/docs/features#supported-network-protocols) [[Learn more](https://pcapplusplus.github.io/docs/features#packet-parsing-and-crafting)] - __Read and write packets from/to files__ in both __PCAP__ and __PCAPNG__ formats [[Learn more](https://pcapplusplus.github.io/docs/features#read-and-write-packets-fromto-files)] - __Packet processing in line rate__ through an efficient and easy to use C++ wrapper for [DPDK](https://www.dpdk.org/), [eBPF AF_XDP](https://www.kernel.org/doc/html/next/networking/af_xdp.html) and [PF_RING](https://www.ntop.org/products/packet-capture/pf_ring/) [[Learn more](https://pcapplusplus.github.io/docs/features#dpdk-support)] @@ -180,7 +180,7 @@ You can find much more information in the [Getting Started](https://pcapplusplus PcapPlusPlus consists of 3 libraries: 1. __Packet++__ - a library for parsing, creating and editing network packets -2. __Pcap++__ - a library for intercepting and sending packets, providing network and NIC info, stats, etc. It is actually a C++ wrapper for packet capturing engines such as libpcap, WinPcap, Npcap, DPDK and PF_RING +2. __Pcap++__ - a library for intercepting and sending packets, providing network and NIC info, stats, etc. It is actually a C++ wrapper for packet capturing engines such as libpcap, WinPcap, Npcap, DPDK, AF_XDP, WinDivert and PF_RING 3. __Common++__ - a library with some common code utilities used by both Packet++ and Pcap++ You can find an extensive API documentation in the [API documentation section](https://pcapplusplus.github.io/docs/api) in PcapPlusPlus web-site. From f42df08e0dc6f5f22a2845b5f80cc02f77b60c29 Mon Sep 17 00:00:00 2001 From: seladb Date: Thu, 13 Nov 2025 23:36:26 -0800 Subject: [PATCH 13/17] Add a d'tor to `WinDivertRawPacket` --- Pcap++/header/WinDivertDevice.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Pcap++/header/WinDivertDevice.h b/Pcap++/header/WinDivertDevice.h index e26dd21f96..3cfcfd2088 100644 --- a/Pcap++/header/WinDivertDevice.h +++ b/Pcap++/header/WinDivertDevice.h @@ -159,6 +159,8 @@ namespace pcpp m_InterfaceIndex(interfaceIndex), m_WinDivertTimestamp(winDivertTimestamp) {} + ~WinDivertRawPacket() override = default; + /// @brief Get the Windows interface index the packet was captured on. /// @return The interface index as reported by WinDivert. uint32_t getInterfaceIndex() const From 72403879b4af3c3ea1ac64a88268375488c256f8 Mon Sep 17 00:00:00 2001 From: seladb Date: Fri, 14 Nov 2025 19:36:42 -0800 Subject: [PATCH 14/17] Update the interfaces so IWinDivertHandle represents and open device and IWinDivertImplementation creates it --- Pcap++/header/WinDivertDevice.h | 124 +++++++++++++++++------- Pcap++/src/WinDivertDevice.cpp | 161 ++++++++++++-------------------- 2 files changed, 150 insertions(+), 135 deletions(-) diff --git a/Pcap++/header/WinDivertDevice.h b/Pcap++/header/WinDivertDevice.h index 3cfcfd2088..faa7232255 100644 --- a/Pcap++/header/WinDivertDevice.h +++ b/Pcap++/header/WinDivertDevice.h @@ -24,15 +24,6 @@ namespace pcpp { namespace internal { - /// @brief Opaque handle wrapper for an opened WinDivert driver instance. - /// - /// Implementations encapsulate the native WinDivert handle and its lifetime. - class IWinDivertHandle - { - public: - virtual ~IWinDivertHandle() = default; - }; - /// @brief Abstract helper that wraps Windows OVERLAPPED I/O used by WinDivert operations. /// /// Implementations provide waiting/resetting primitives and a way to fetch @@ -80,7 +71,7 @@ namespace pcpp virtual WaitResult wait(uint32_t timeout) = 0; virtual void reset() = 0; - virtual OverlappedResult getOverlappedResult(const IWinDivertHandle* handle) = 0; + virtual OverlappedResult getOverlappedResult() = 0; virtual ~IOverlappedWrapper() = default; }; @@ -96,11 +87,14 @@ namespace pcpp uint64_t timestamp; ///< WinDivert timestamp associated with the packet }; - /// @brief Abstraction over the concrete WinDivert API used by WinDivertDevice. + /// @class IWinDivertHandle + /// @brief An abstract handle for interacting with the WinDivert device. /// - /// This interface allows providing different backends (e.g., real WinDivert DLL - /// or a test double) while keeping the device logic independent from the API. - class IWinDivertImplementation + /// This interface represents an opened WinDivert handle and provides the minimal + /// set of operations used by WinDivertDevice: asynchronous receive, batched send, + /// querying/setting queue parameters and handle closure. Concrete implementations + /// wrap the corresponding WinDivert C APIs and Windows OVERLAPPED I/O. + class IWinDivertHandle { public: /// @brief WinDivert runtime parameters that can be queried or configured. @@ -113,33 +107,95 @@ namespace pcpp VersionMinor = 4 ///< WinDivert minor version }; - /// @brief Information about a Windows network interface as reported by WinDivert. - struct NetworkInterface - { - uint32_t index; ///< Interface index - std::wstring name; ///< Interface GUID or system name - std::wstring description; ///< Human-readable description - bool isLoopback; ///< True if the interface is loopback - bool isUp; ///< True if the interface is up/running - }; - + /// @brief Generic success code returned by most operations. static constexpr uint32_t SuccessResult = 0; + /// @brief Windows ERROR_IO_PENDING (997) reported when an async operation is in flight. static constexpr uint32_t ErrorIoPending = 997; - virtual ~IWinDivertImplementation() = default; + virtual ~IWinDivertHandle() = default; - virtual std::unique_ptr open(const std::string& filter, int layer, int16_t priority, - uint64_t flags) = 0; - virtual uint32_t close(const IWinDivertHandle* handle) = 0; - virtual uint32_t recvEx(const IWinDivertHandle* handle, uint8_t* buffer, uint32_t bufferLen, - size_t addressesSize, IOverlappedWrapper* overlapped) = 0; + /// @brief Close the underlying WinDivert handle. + /// @return Windows error code-style result. 0 indicates success. + virtual uint32_t close() = 0; + + /// @brief Begin or perform an overlapped receive of raw packet data. + /// + /// If an overlapped object is provided, the call initiates an asynchronous read + /// and typically returns ErrorIoPending. Completion status and size should be + /// obtained via the provided IOverlappedWrapper. + /// + /// @param[in] buffer Destination buffer for packet data. + /// @param[in] bufferLen Size of the destination buffer in bytes. + /// @param[in] addressesSize Number of address entries the implementation may capture for a batch. + /// @param[in] overlapped Wrapper around Windows OVERLAPPED used for async I/O. Must not be null for + /// async. + /// @return 0 on success, ErrorIoPending if async operation started, or a Windows error code on failure. + virtual uint32_t recvEx(uint8_t* buffer, uint32_t bufferLen, size_t addressesSize, + IOverlappedWrapper* overlapped) = 0; + + /// @brief Finalize a previous overlapped receive and fetch per-packet address metadata. + /// @return A vector of WinDivertAddress entries, one per packet captured in the last receive. virtual std::vector recvExComplete() = 0; - virtual uint32_t sendEx(const IWinDivertHandle* handle, uint8_t* buffer, uint32_t bufferLen, - size_t addressesSize) = 0; + + /// @brief Send a batch of raw packets. + /// @param[in] buffer Buffer containing one or more consecutive packets. + /// @param[in] bufferLen Total size in bytes of the packets contained in buffer. + /// @param[in] addressesSize Number of address entries accompanying the send batch. + /// @return 0 on success, otherwise a Windows error code. + virtual uint32_t sendEx(uint8_t* buffer, uint32_t bufferLen, size_t addressesSize) = 0; + + /// @brief Create a new overlapped wrapper bound to this handle. + /// @return A unique_ptr to a fresh IOverlappedWrapper for async operations. virtual std::unique_ptr createOverlapped() = 0; - virtual bool getParam(const IWinDivertHandle* handle, WinDivertParam param, uint64_t& value) = 0; - virtual bool setParam(const IWinDivertHandle* handle, WinDivertParam param, uint64_t value) = 0; + + /// @brief Query a WinDivert runtime/queue parameter. + /// @param[in] param The parameter to query. + /// @param[out] value The retrieved value. + /// @return True on success, false on failure. + virtual bool getParam(WinDivertParam param, uint64_t& value) = 0; + + /// @brief Set a WinDivert runtime/queue parameter. + /// @param[in] param The parameter to set. + /// @param[in] value The value to set. + /// @return True on success, false on failure. + virtual bool setParam(WinDivertParam param, uint64_t value) = 0; + }; + + /// @class IWinDivertImplementation + /// @brief Factory and system-query abstraction used by WinDivertDevice. + /// + /// The sole responsibilities of this interface are: + /// - Creating IWinDivertHandle instances (which expose the WinDivert API surface). + /// - Enumerating relevant Windows network interfaces. + /// Keeping these responsibilities here keeps WinDivertDevice decoupled from concrete + /// system/driver calls and enables unit testing and alternative implementations. + class IWinDivertImplementation + { + public: + /// @brief Information about a Windows network interface as reported by WinDivert/Windows APIs. + struct NetworkInterface + { + uint32_t index; ///< Interface index as provided by Windows + std::wstring name; ///< Interface name (GUID or friendly/system name) + std::wstring description; ///< Human-readable description from the OS + bool isLoopback; ///< True if the interface type is software loopback + bool isUp; ///< True when the interface operational status is up + }; + + /// @brief Open a WinDivert handle with the given filter and settings. + /// @param[in] filter WinDivert filter string (see WinDivert documentation). + /// @param[in] layer WinDivert layer value (typically WINDIVERT_LAYER_NETWORK). + /// @param[in] priority Injection/capture priority (lower is higher priority). + /// @param[in] flags WinDivert open flags (sniff mode, fragments, etc.). + /// @return A unique_ptr to an IWinDivertHandle on success, or nullptr on failure. + virtual std::unique_ptr open(const std::string& filter, int layer, int16_t priority, + uint64_t flags) = 0; + + /// @brief Enumerate Windows network interfaces relevant to WinDivert. + /// @return A vector of NetworkInterface objects with index, name, description and status. virtual std::vector getNetworkInterfaces() const = 0; + + virtual ~IWinDivertImplementation() = default; }; } // namespace internal diff --git a/Pcap++/src/WinDivertDevice.cpp b/Pcap++/src/WinDivertDevice.cpp index 48949f2fff..60c5e19592 100644 --- a/Pcap++/src/WinDivertDevice.cpp +++ b/Pcap++/src/WinDivertDevice.cpp @@ -15,26 +15,12 @@ namespace pcpp { namespace internal { - class WinDivertHandle : public IWinDivertHandle - { - public: - explicit WinDivertHandle(const HANDLE handle) : m_Handle(handle) - {} - - HANDLE get() const - { - return m_Handle; - } - - private: - HANDLE m_Handle; - }; - class WinDivertOverlappedWrapper : public IOverlappedWrapper { public: - WinDivertOverlappedWrapper() + explicit WinDivertOverlappedWrapper(const HANDLE handle) { + m_Handle = handle; ZeroMemory(&m_Overlapped, sizeof(m_Overlapped)); m_Event = CreateEvent(nullptr, TRUE, FALSE, nullptr); @@ -88,16 +74,10 @@ namespace pcpp m_Overlapped.hEvent = event; } - OverlappedResult getOverlappedResult(const IWinDivertHandle* handle) override + OverlappedResult getOverlappedResult() override { - auto winDivertHandle = dynamic_cast(handle); - if (winDivertHandle == nullptr) - { - throw std::runtime_error("Failed to get WinDivertHandle"); - } - DWORD packetLen = 0; - if (GetOverlappedResult(winDivertHandle->get(), &m_Overlapped, &packetLen, FALSE)) + if (GetOverlappedResult(m_Handle, &m_Overlapped, &packetLen, FALSE)) { return { OverlappedResult::Status::Success, static_cast(packetLen), 0 }; } @@ -107,29 +87,19 @@ namespace pcpp private: HANDLE m_Event; + HANDLE m_Handle; OVERLAPPED m_Overlapped = {}; }; - class WinDivertImplementation : public IWinDivertImplementation + class WinDivertHandle : public IWinDivertHandle { public: - std::unique_ptr open(const std::string& filter, int layer, int16_t priority, - uint64_t flags) override - { - auto handle = WinDivertOpen(filter.c_str(), static_cast(layer), priority, flags); - if (handle == INVALID_HANDLE_VALUE) - { - PCPP_LOG_ERROR("Failed to open WinDivertHandle, error was: " << GetLastError()); - return nullptr; - } - return std::make_unique(handle); - } + explicit WinDivertHandle(const HANDLE handle) : m_Handle(handle) + {} - uint32_t close(const IWinDivertHandle* handle) override + uint32_t close() override { - auto winDivertHandle = getHandle(handle); - - auto result = WinDivertClose(winDivertHandle->get()); + auto result = WinDivertClose(m_Handle); if (!result) { return GetLastError(); @@ -137,7 +107,7 @@ namespace pcpp return SuccessResult; } - uint32_t recvEx(const IWinDivertHandle* handle, uint8_t* buffer, uint32_t bufferLen, size_t addressesSize, + uint32_t recvEx(uint8_t* buffer, uint32_t bufferLen, size_t addressesSize, IOverlappedWrapper* overlapped) override { auto winDivertOverlapped = dynamic_cast(overlapped); @@ -146,15 +116,12 @@ namespace pcpp throw std::runtime_error("Failed to get WinDivertOverlapped"); } - auto winDivertHandle = getHandle(handle); - m_WinDivertAddresses.resize(addressesSize); m_WinDivertAddressesSize = sizeof(WINDIVERT_ADDRESS) * addressesSize; uint32_t recvLen; - auto result = - WinDivertRecvEx(winDivertHandle->get(), buffer, bufferLen, &recvLen, 0, m_WinDivertAddresses.data(), - &m_WinDivertAddressesSize, winDivertOverlapped->get()); + auto result = WinDivertRecvEx(m_Handle, buffer, bufferLen, &recvLen, 0, m_WinDivertAddresses.data(), + &m_WinDivertAddressesSize, winDivertOverlapped->get()); if (!result) { @@ -179,11 +146,8 @@ namespace pcpp return result; } - uint32_t sendEx(const IWinDivertHandle* handle, uint8_t* buffer, uint32_t bufferLen, - size_t addressesSize) override + uint32_t sendEx(uint8_t* buffer, uint32_t bufferLen, size_t addressesSize) override { - auto winDivertHandle = getHandle(handle); - std::vector winDivertAddresses; for (size_t i = 0; i < addressesSize; i++) { @@ -192,9 +156,8 @@ namespace pcpp winDivertAddresses.push_back(addr); } - auto result = - WinDivertSendEx(winDivertHandle->get(), buffer, bufferLen, nullptr, 0, winDivertAddresses.data(), - addressesSize * sizeof(WINDIVERT_ADDRESS), nullptr); + auto result = WinDivertSendEx(m_Handle, buffer, bufferLen, nullptr, 0, winDivertAddresses.data(), + addressesSize * sizeof(WINDIVERT_ADDRESS), nullptr); if (!result) { return GetLastError(); @@ -205,19 +168,38 @@ namespace pcpp std::unique_ptr createOverlapped() override { - return std::make_unique(); + return std::make_unique(m_Handle); } - bool getParam(const IWinDivertHandle* handle, WinDivertParam param, uint64_t& value) override + bool getParam(WinDivertParam param, uint64_t& value) override { - auto winDivertHandle = getHandle(handle); - return WinDivertGetParam(winDivertHandle->get(), static_cast(param), &value); + return WinDivertGetParam(m_Handle, static_cast(param), &value); } - bool setParam(const IWinDivertHandle* handle, WinDivertParam param, uint64_t value) override + bool setParam(WinDivertParam param, uint64_t value) override { - auto winDivertHandle = getHandle(handle); - return WinDivertSetParam(winDivertHandle->get(), static_cast(param), value); + return WinDivertSetParam(m_Handle, static_cast(param), value); + } + + private: + HANDLE m_Handle; + std::vector m_WinDivertAddresses; + uint32_t m_WinDivertAddressesSize = 0; + }; + + class WinDivertImplementation : public IWinDivertImplementation + { + public: + std::unique_ptr open(const std::string& filter, int layer, int16_t priority, + uint64_t flags) override + { + auto handle = WinDivertOpen(filter.c_str(), static_cast(layer), priority, flags); + if (handle == INVALID_HANDLE_VALUE) + { + PCPP_LOG_ERROR("Failed to open WinDivertHandle, error was: " << GetLastError()); + return nullptr; + } + return std::make_unique(handle); } std::vector getNetworkInterfaces() const override @@ -259,21 +241,6 @@ namespace pcpp return networkInterfaces; } - - private: - std::vector m_WinDivertAddresses; - uint32_t m_WinDivertAddressesSize = 0; - - static const WinDivertHandle* getHandle(const IWinDivertHandle* handle) - { - auto winDivertHandle = dynamic_cast(handle); - if (winDivertHandle == nullptr) - { - throw std::runtime_error("Failed to get WinDivertHandle"); - } - - return winDivertHandle; - } }; } // namespace internal @@ -304,8 +271,8 @@ namespace pcpp void WinDivertDevice::close() { - auto result = m_Impl->close(m_Handle.get()); - if (result != internal::IWinDivertImplementation::SuccessResult) + auto result = m_Handle->close(); + if (result != internal::IWinDivertHandle::SuccessResult) { PCPP_LOG_ERROR("Couldn't receive packet, status: " << getErrorString(static_cast(result)) << "(" << static_cast(result) << ")"); @@ -337,7 +304,7 @@ namespace pcpp "At least one of timeout and maxPackets must be a positive number" }; } - auto overlapped = m_Impl->createOverlapped(); + auto overlapped = m_Handle->createOverlapped(); uint32_t bufferSize = WINDIVERT_BUFFER_LEN * batchSize; std::vector buffer(bufferSize); @@ -403,7 +370,7 @@ namespace pcpp return { ReceiveResult::Status::Failed, "Batch size has to be a positive number" }; } - auto overlapped = m_Impl->createOverlapped(); + auto overlapped = m_Handle->createOverlapped(); uint32_t bufferSize = WINDIVERT_BUFFER_LEN * batchSize; std::vector buffer(bufferSize); @@ -479,8 +446,8 @@ namespace pcpp if (packetsInCurrentBatch >= batchSize || packetIndex >= packetsToSend - 1) { - auto result = m_Impl->sendEx(m_Handle.get(), buffer, WINDIVERT_BUFFER_LEN, packetsInCurrentBatch); - if (result != internal::IWinDivertImplementation::SuccessResult) + auto result = m_Handle->sendEx(buffer, WINDIVERT_BUFFER_LEN, packetsInCurrentBatch); + if (result != internal::IWinDivertHandle::SuccessResult) { return { SendResult::Status::Failed, packetsSent, "Sending packets failed: " + getErrorString(result), result }; @@ -506,12 +473,9 @@ namespace pcpp uint64_t queueLength, queueTime, queueSize; auto getParamResult = true; - getParamResult |= m_Impl->getParam( - m_Handle.get(), internal::IWinDivertImplementation::WinDivertParam::QueueLength, queueLength); - getParamResult |= - m_Impl->getParam(m_Handle.get(), internal::IWinDivertImplementation::WinDivertParam::QueueTime, queueTime); - getParamResult |= - m_Impl->getParam(m_Handle.get(), internal::IWinDivertImplementation::WinDivertParam::QueueSize, queueSize); + getParamResult |= m_Handle->getParam(internal::IWinDivertHandle::WinDivertParam::QueueLength, queueLength); + getParamResult |= m_Handle->getParam(internal::IWinDivertHandle::WinDivertParam::QueueTime, queueTime); + getParamResult |= m_Handle->getParam(internal::IWinDivertHandle::WinDivertParam::QueueSize, queueSize); if (!getParamResult) { @@ -538,20 +502,17 @@ namespace pcpp { case QueueParam::QueueLength: { - m_Impl->setParam(m_Handle.get(), internal::IWinDivertImplementation::WinDivertParam::QueueLength, - param.second); + m_Handle->setParam(internal::IWinDivertHandle::WinDivertParam::QueueLength, param.second); break; } case QueueParam::QueueTime: { - m_Impl->setParam(m_Handle.get(), internal::IWinDivertImplementation::WinDivertParam::QueueTime, - param.second); + m_Handle->setParam(internal::IWinDivertHandle::WinDivertParam::QueueTime, param.second); break; } case QueueParam::QueueSize: { - m_Impl->setParam(m_Handle.get(), internal::IWinDivertImplementation::WinDivertParam::QueueSize, - param.second); + m_Handle->setParam(internal::IWinDivertHandle::WinDivertParam::QueueSize, param.second); break; } } @@ -568,10 +529,8 @@ namespace pcpp uint64_t versionMajor, versionMinor; auto getParamResult = true; - getParamResult |= m_Impl->getParam( - m_Handle.get(), internal::IWinDivertImplementation::WinDivertParam::VersionMajor, versionMajor); - getParamResult |= m_Impl->getParam( - m_Handle.get(), internal::IWinDivertImplementation::WinDivertParam::VersionMajor, versionMinor); + getParamResult |= m_Handle->getParam(internal::IWinDivertHandle::WinDivertParam::VersionMajor, versionMajor); + getParamResult |= m_Handle->getParam(internal::IWinDivertHandle::WinDivertParam::VersionMajor, versionMinor); if (!getParamResult) { @@ -615,8 +574,8 @@ namespace pcpp WinDivertDevice::ReceiveResultInternal WinDivertDevice::receivePacketsInternal( uint32_t timeout, uint8_t batchSize, std::vector& buffer, internal::IOverlappedWrapper* overlapped) { - auto result = m_Impl->recvEx(m_Handle.get(), buffer.data(), buffer.size(), batchSize, overlapped); - if (result != internal::IWinDivertImplementation::ErrorIoPending) + auto result = m_Handle->recvEx(buffer.data(), buffer.size(), batchSize, overlapped); + if (result != internal::IWinDivertHandle::ErrorIoPending) { return { ReceiveResult::Status::Failed, "Error receiving packets: " + getErrorString(result), result }; } @@ -631,7 +590,7 @@ namespace pcpp { case internal::IOverlappedWrapper::WaitResult::Status::Completed: { - auto overlappedResult = overlapped->getOverlappedResult(m_Handle.get()); + auto overlappedResult = overlapped->getOverlappedResult(); if (overlappedResult.status != internal::IOverlappedWrapper::OverlappedResult::Status::Success) { return { ReceiveResult::Status::Failed, @@ -639,7 +598,7 @@ namespace pcpp overlappedResult.errorCode }; } - return { overlappedResult.packetLen, m_Impl->recvExComplete() }; + return { overlappedResult.packetLen, m_Handle->recvExComplete() }; } case internal::IOverlappedWrapper::WaitResult::Status::Timeout: { From b31b8694ae77803bdbc34e0acf4aa6c92d0594b4 Mon Sep 17 00:00:00 2001 From: seladb Date: Fri, 14 Nov 2025 19:59:24 -0800 Subject: [PATCH 15/17] Fix cppcheck warning --- Pcap++/src/WinDivertDevice.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Pcap++/src/WinDivertDevice.cpp b/Pcap++/src/WinDivertDevice.cpp index 60c5e19592..f7911dcf04 100644 --- a/Pcap++/src/WinDivertDevice.cpp +++ b/Pcap++/src/WinDivertDevice.cpp @@ -18,9 +18,8 @@ namespace pcpp class WinDivertOverlappedWrapper : public IOverlappedWrapper { public: - explicit WinDivertOverlappedWrapper(const HANDLE handle) + explicit WinDivertOverlappedWrapper(const HANDLE handle) : m_Handle(handle) { - m_Handle = handle; ZeroMemory(&m_Overlapped, sizeof(m_Overlapped)); m_Event = CreateEvent(nullptr, TRUE, FALSE, nullptr); From bbd692f7f0446d51374bcd3e961e423dc792ae35 Mon Sep 17 00:00:00 2001 From: seladb Date: Sat, 15 Nov 2025 23:57:18 -0800 Subject: [PATCH 16/17] Address PR comments --- .github/workflows/build_and_test.yml | 3 ++- CMakeLists.txt | 3 --- Pcap++/src/WinDivertDevice.cpp | 10 ++++++++ ci/install_windivert.bat | 37 ---------------------------- ci/install_windivert.ps1 | 35 ++++++++++++++++++++++++++ 5 files changed, 47 insertions(+), 41 deletions(-) delete mode 100644 ci/install_windivert.bat create mode 100644 ci/install_windivert.ps1 diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 92abc2b25c..c6bb3002e0 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -716,7 +716,8 @@ jobs: run: echo "WINDIVERT_DIR=C:\\WinDivert" >> $env:GITHUB_ENV - name: Install WinDivert - run: ci\install_windivert.bat $env:WINDIVERT_DIR + shell: pwsh + run: .\ci\install_windivert.ps1 -Target "$env:WINDIVERT_DIR" - name: Configure PcapPlusPlus (WinDivert) run: cmake -A x64 -G "Visual Studio 17 2022" -DPCAP_ROOT=${{ env.PCAP_SDK_DIR }} -DPCAPPP_USE_WINDIVERT=ON -DWINDIVERT_ROOT=${{ env.WINDIVERT_DIR }} -S . -B "$env:BUILD_DIR" diff --git a/CMakeLists.txt b/CMakeLists.txt index 20b7a8252a..0be5a0ddd7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -288,9 +288,6 @@ if(PCAPPP_BUILD_PCAPPP) if(PCAPPP_USE_WINDIVERT) find_package(WinDivert REQUIRED) - if(NOT WinDivert_FOUND) - message(FATAL_ERROR "WinDivert not found!") - endif() add_definitions(-DUSE_WINDIVERT) endif() endif() diff --git a/Pcap++/src/WinDivertDevice.cpp b/Pcap++/src/WinDivertDevice.cpp index f7911dcf04..d3689a444e 100644 --- a/Pcap++/src/WinDivertDevice.cpp +++ b/Pcap++/src/WinDivertDevice.cpp @@ -96,9 +96,18 @@ namespace pcpp explicit WinDivertHandle(const HANDLE handle) : m_Handle(handle) {} + ~WinDivertHandle() override + { + if (m_IsOpened) + { + WinDivertHandle::close(); + } + } + uint32_t close() override { auto result = WinDivertClose(m_Handle); + m_IsOpened = false; if (!result) { return GetLastError(); @@ -182,6 +191,7 @@ namespace pcpp private: HANDLE m_Handle; + bool m_IsOpened = true; std::vector m_WinDivertAddresses; uint32_t m_WinDivertAddressesSize = 0; }; diff --git a/ci/install_windivert.bat b/ci/install_windivert.bat deleted file mode 100644 index 79c7ff278b..0000000000 --- a/ci/install_windivert.bat +++ /dev/null @@ -1,37 +0,0 @@ -@echo off -setlocal enableextensions enabledelayedexpansion - -REM Install WinDivert SDK and set environment hints for subsequent CI steps -REM - Downloads WinDivert v2.2.0 -REM - Extracts and arranges it under the provided WINDIVERT_ROOT (include/, x64/, optional x86/) -REM - Appends \x64 to GITHUB_PATH for runtime DLL discovery -REM - Sets WINDIVERT_ROOT in GITHUB_ENV so CMake's FindWinDivert can locate it - -REM Accept optional first argument as WINDIVERT_ROOT. Default to C:\WinDivert if not provided -set "TARGET=C:\WinDivert" -if not "%~1"=="" ( - set "TARGET=%~1" -) - -set "URL=https://github.com/basil00/Divert/releases/download/v2.2.0/WinDivert-2.2.0-A.zip" -set "ZIP=%RUNNER_TEMP%\WinDivert.zip" -set "DEST=%RUNNER_TEMP%\WinDivertTmp" - -REM Use PowerShell for download and extraction -powershell -NoProfile -ExecutionPolicy Bypass -Command ^ - "$ErrorActionPreference='Stop'; $url='%URL%'; $zip='%ZIP%'; $dest='%DEST%'; $target='%TARGET%';" ^ - "Invoke-WebRequest -Uri $url -OutFile $zip;" ^ - "if (Test-Path $dest) { Remove-Item -Recurse -Force $dest };" ^ - "Expand-Archive -Path $zip -DestinationPath $dest -Force;" ^ - "$root = Get-ChildItem -Path $dest -Directory | Select-Object -First 1;" ^ - "if (Test-Path $target) { Remove-Item -Recurse -Force $target };" ^ - "New-Item -ItemType Directory -Path $target -Force | Out-Null;" ^ - "foreach($d in 'include','x64','x86'){ $p = Join-Path $root.FullName $d; if (Test-Path $p) { Copy-Item -Recurse -Force $p $target } }" - -IF ERRORLEVEL 1 ( - echo Failed to install WinDivert SDK - exit /b 1 -) - -echo WinDivert installation completed successfully. -exit /b 0 diff --git a/ci/install_windivert.ps1 b/ci/install_windivert.ps1 new file mode 100644 index 0000000000..227d0f09c5 --- /dev/null +++ b/ci/install_windivert.ps1 @@ -0,0 +1,35 @@ +param([string]$Target = "C:\WinDivert") + +$ErrorActionPreference = "Stop" + +try { + $tmp = @($env:RUNNER_TEMP, $env:TEMP) | Where-Object { $_ } | Select-Object -First 1 + $zip = Join-Path $tmp "WinDivert.zip" + $dest = Join-Path $tmp "WinDivertTmp" + + Invoke-WebRequest "https://github.com/basil00/Divert/releases/download/v2.2.0/WinDivert-2.2.0-A.zip" -OutFile $zip + + Remove-Item $dest -Recurse -Force -ErrorAction SilentlyContinue + Expand-Archive $zip $dest -Force + + $root = (Get-ChildItem $dest -Directory | Select-Object -First 1).FullName + if (-not $root) { throw "Extraction failed - WinDivert root not found." } + + Remove-Item $Target -Recurse -Force -ErrorAction SilentlyContinue + New-Item -ItemType Directory -Path $Target -Force | Out-Null + + foreach ($d in "include", "x64", "x86") { + $path = Join-Path $root $d + if (Test-Path $path) { Copy-Item $path $Target -Recurse -Force } + } + + if ($env:GITHUB_ENV) { "WINDIVERT_ROOT=$Target" | Out-File $env:GITHUB_ENV -Append -Encoding utf8 } + if ($env:GITHUB_PATH) { Join-Path $Target "x64" | Out-File $env:GITHUB_PATH -Append -Encoding utf8 } + + Write-Host "WinDivert installation completed." + exit 0 +} +catch { + Write-Error "Failed to install WinDivert: $($_.Exception.Message)" + exit 1 +} From 0ab7d080b1b2baaf1756237cd28a066c8781d24b Mon Sep 17 00:00:00 2001 From: seladb Date: Sun, 23 Nov 2025 12:53:17 -0800 Subject: [PATCH 17/17] Address PR comments --- Pcap++/src/WinDivertDevice.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Pcap++/src/WinDivertDevice.cpp b/Pcap++/src/WinDivertDevice.cpp index d3689a444e..e8a4a10cf6 100644 --- a/Pcap++/src/WinDivertDevice.cpp +++ b/Pcap++/src/WinDivertDevice.cpp @@ -36,6 +36,20 @@ namespace pcpp ~WinDivertOverlappedWrapper() override { + if (CancelIoEx(m_Handle, &m_Overlapped)) + { + WaitForSingleObject(m_Event, INFINITE); + } + else + { + DWORD error = GetLastError(); + if (error != ERROR_NOT_FOUND) + { + PCPP_LOG_ERROR("CancelIoEx failed with unexpected error: " << error); + // May still want to wait with a timeout for safety + WaitForSingleObject(m_Event, 1000); + } + } CloseHandle(m_Event); } @@ -584,7 +598,7 @@ namespace pcpp uint32_t timeout, uint8_t batchSize, std::vector& buffer, internal::IOverlappedWrapper* overlapped) { auto result = m_Handle->recvEx(buffer.data(), buffer.size(), batchSize, overlapped); - if (result != internal::IWinDivertHandle::ErrorIoPending) + if (result != internal::IWinDivertHandle::ErrorIoPending && result != internal::IWinDivertHandle::SuccessResult) { return { ReceiveResult::Status::Failed, "Error receiving packets: " + getErrorString(result), result }; }