diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 5930776830..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" @@ -705,6 +699,43 @@ 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 + + - 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/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 feb4bae479..d517f62be7 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() @@ -97,6 +102,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..faa7232255 --- /dev/null +++ b/Pcap++/header/WinDivertDevice.h @@ -0,0 +1,466 @@ +#pragma once + +#include +#include +#include +#include +#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 + { + /// @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, ///< The wait completed successfully + 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) + }; + + /// @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, ///< 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) + }; + + virtual WaitResult wait(uint32_t timeout) = 0; + virtual void reset() = 0; + virtual OverlappedResult getOverlappedResult() = 0; + 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; ///< 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 + }; + + /// @class IWinDivertHandle + /// @brief An abstract handle for interacting with the WinDivert device. + /// + /// 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. + 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 + }; + + /// @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 ~IWinDivertHandle() = default; + + /// @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; + + /// @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; + + /// @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 + + /// @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: + 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) + {} + + ~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 + { + 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; + } + + private: + uint32_t m_InterfaceIndex; + 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 "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. + /// - 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 Status + /// @brief Status codes for receive operations. + enum class Status + { + 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; ///< 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 Status + /// @brief Status codes for send operations. + enum class Status + { + Completed, ///< Send operation completed successfully + Failed ///< Send operation failed (see error and errorCode) + }; + + 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; ///< 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; + std::wstring name; + std::wstring description; + bool isLoopback; + 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, ///< 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("true") 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; + + 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. + /// 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: + 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; + bool m_DeviceOpened = 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..f7911dcf04 --- /dev/null +++ b/Pcap++/src/WinDivertDevice.cpp @@ -0,0 +1,684 @@ +#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 +#include + +namespace pcpp +{ + namespace internal + { + class WinDivertOverlappedWrapper : public IOverlappedWrapper + { + public: + explicit WinDivertOverlappedWrapper(const HANDLE handle) : m_Handle(handle) + { + 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() override + { + DWORD packetLen = 0; + if (GetOverlappedResult(m_Handle, &m_Overlapped, &packetLen, FALSE)) + { + return { OverlappedResult::Status::Success, static_cast(packetLen), 0 }; + } + + return { OverlappedResult::Status::Failed, 0, static_cast(GetLastError()) }; + } + + private: + HANDLE m_Event; + HANDLE m_Handle; + OVERLAPPED m_Overlapped = {}; + }; + + class WinDivertHandle : public IWinDivertHandle + { + public: + explicit WinDivertHandle(const HANDLE handle) : m_Handle(handle) + {} + + uint32_t close() override + { + auto result = WinDivertClose(m_Handle); + if (!result) + { + return GetLastError(); + } + return SuccessResult; + } + + uint32_t recvEx(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"); + } + + m_WinDivertAddresses.resize(addressesSize); + m_WinDivertAddressesSize = sizeof(WINDIVERT_ADDRESS) * addressesSize; + + uint32_t recvLen; + auto result = WinDivertRecvEx(m_Handle, 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(uint8_t* buffer, uint32_t bufferLen, size_t addressesSize) override + { + std::vector winDivertAddresses; + for (size_t i = 0; i < addressesSize; i++) + { + WINDIVERT_ADDRESS addr = {}; + addr.Outbound = 1; + winDivertAddresses.push_back(addr); + } + + auto result = WinDivertSendEx(m_Handle, 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(m_Handle); + } + + bool getParam(WinDivertParam param, uint64_t& value) override + { + return WinDivertGetParam(m_Handle, static_cast(param), &value); + } + + bool setParam(WinDivertParam param, uint64_t value) override + { + 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 + { + 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; + } + }; + + } // namespace internal + +#define WINDIVERT_BUFFER_LEN 65536 + + WinDivertDevice::WinDivertDevice() : m_Impl(std::make_unique()) + {} + + bool WinDivertDevice::open() + { + return open("true"); + } + + 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_Handle->close(); + if (result != internal::IWinDivertHandle::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_Handle->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_Handle->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_Handle->sendEx(buffer, WINDIVERT_BUFFER_LEN, packetsInCurrentBatch); + if (result != internal::IWinDivertHandle::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_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) + { + 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_Handle->setParam(internal::IWinDivertHandle::WinDivertParam::QueueLength, param.second); + break; + } + case QueueParam::QueueTime: + { + m_Handle->setParam(internal::IWinDivertHandle::WinDivertParam::QueueTime, param.second); + break; + } + case QueueParam::QueueSize: + { + m_Handle->setParam(internal::IWinDivertHandle::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_Handle->getParam(internal::IWinDivertHandle::WinDivertParam::VersionMajor, versionMajor); + getParamResult |= m_Handle->getParam(internal::IWinDivertHandle::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_Handle->recvEx(buffer.data(), buffer.size(), batchSize, overlapped); + if (result != internal::IWinDivertHandle::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(); + 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_Handle->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/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. 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 8e9da6b79e..8c5f263fd2 100644 --- a/Tests/Pcap++Test/TestDefinition.h +++ b/Tests/Pcap++Test/TestDefinition.h @@ -128,3 +128,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/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 new file mode 100644 index 0000000000..97b73845b9 --- /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 25ee1eba7b..c9bc288b7d 100644 --- a/Tests/Pcap++Test/main.cpp +++ b/Tests/Pcap++Test/main.cpp @@ -313,6 +313,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/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}") 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 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, diff --git a/cmake/modules/FindWinDivert.cmake b/cmake/modules/FindWinDivert.cmake new file mode 100644 index 0000000000..6f5ba59f76 --- /dev/null +++ b/cmake/modules/FindWinDivert.cmake @@ -0,0 +1,120 @@ +# 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 + +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() + +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}" "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} +) + +# 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} +) + +# 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} +) + +# Handle REQUIRED + FOUND logic +include(FindPackageHandleStandardArgs) +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}") +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 IMPORTED_LOCATION "${WinDivert_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${WinDivert_INCLUDE_DIR}" + ) +endif()