-
Notifications
You must be signed in to change notification settings - Fork 724
Add support for WinDivert packet capture engine #2019
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #2019 +/- ##
==========================================
- Coverage 83.46% 83.45% -0.01%
==========================================
Files 311 312 +1
Lines 54556 54590 +34
Branches 11491 11500 +9
==========================================
+ Hits 45534 45560 +26
- Misses 7795 7832 +37
+ Partials 1227 1198 -29
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
- Add missing doxygen documentation
| 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test also failed in CI so I made it more robust
| parser.add_argument( | ||
| "--include-tests", | ||
| "-t", | ||
| type=str, | ||
| nargs="+", | ||
| default=[], | ||
| help="Pcap++ tests to include", | ||
| ) | ||
| parser.add_argument( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change was needed to support running only WinDivert tests in the windivert job in CI
…and IWinDivertImplementation creates it
| /// @brief Replace the underlying implementation (intended for testing/mocking). | ||
| /// @param[in] implementation An implementation of the WinDivert backend APIs. | ||
| void setImplementation(std::unique_ptr<internal::IWinDivertImplementation> implementation); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, this should use constructor dependency injection and disallow switching of implementation after a device has been constructed. It simplifies the lifecycle.
Curently, this setter directly swaps the implementation. If that happens while the device is already open, it might cause a desync between implementation and handle? It's not a big deal currently, but it's still an inconsistency.
| /// - 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: "Implementation" seems a bit generic descriptor? Maybe IWinDivertDriver or IWinDivertAPI? I prefer driver, since its not supposed to be the pubic API we provide.
| /// @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<void(const WinDivertRawPacketVector& packetVec)>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious, do we want to add a context second parameter as future extension point? It can be an empty struct or it can hold a pointer to the device? Adding it initially would allow adding more context data in the future if needed without breaking the callback signature.
The other devices have a "Device" callback, but I find that a bit rigid if you want to add data only relevant to the callback context.
struct WinDivertReceiveCallbackCtx
{
WinDivertDevice* device = nullptr; // Maybe?
};
std::function<void(const WinDivertRawPacketVector& packetVec, WinDivertReceiveCallbackCtx& ctx)>`
``| WinDivertOverlappedWrapper(const WinDivertOverlappedWrapper&) = delete; | ||
| WinDivertOverlappedWrapper& operator=(const WinDivertOverlappedWrapper&) = delete; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: If we are holding the overlapped struct as value inside the object, perhaps mark the move ctors explicitly deleted too? We don't want the object to be moved either.
They won't get implicitly declared anyway, so its fine either way code wise.
| remainingBytes -= packetLength; | ||
| } | ||
|
|
||
| callback(receivedPackets); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should probably check if the callback is empty. Calling an empty std::function is UB.
The check can be either here (if for some reason receiving packets and doing nothing with them is valid) or at the start. I think the start would be better?
| return { SendResult::Status::Failed, 0, "Batch size has to be a positive number" }; | ||
| } | ||
|
|
||
| uint8_t buffer[WINDIVERT_BUFFER_LEN]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: std::array?
| endif() | ||
|
|
||
| # Normalize user-provided root path | ||
| if(DEFINED WINDIVERT_ROOT) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make this WinDivert_ROOT to match the rest of the variables in the module?
As far as I know, it is expected for the variables to match precisely with the package name.
| if(DEFINED WINDIVERT_ROOT) | |
| if(DEFINED WinDivert_ROOT) |
| # Normalize user-provided root path | ||
| if(DEFINED WINDIVERT_ROOT) | ||
| file(TO_CMAKE_PATH "${WINDIVERT_ROOT}" _WinDivert_ROOT_HINT) | ||
| elseif(DEFINED ENV{WINDIVERT_ROOT}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto: var name.
Unsure about this one tho. Env vars are case sensitive on unix, and case-insensitive on windows. But probably better to match exact anyway.
| elseif(DEFINED ENV{WINDIVERT_ROOT}) | |
| elseif(DEFINED ENV{WinDivert_ROOT}) |
This PR integrates WinDivert as another packet capture engine in PcapPlusPlus, enabling packet capture/injection on Windows via the WinDivert driver.
What is WinDivert
WinDivert is an open-source Windows library (kernel + user-mode) that allows applications to intercept, modify, drop or inject network packets traversing the Windows network stack. It is designed for use cases such as packet sniffing, firewalling, NAT-/VPN-tunneling, loopback traffic inspection, etc.
Key features include:
Project Links
Testing
This PR includes basic tests for the
WinDivertDevice. However, it also adds a lightweight abstraction over the WinDivert API using internal interfaces. It enables testingWinDivertDevicelogic without the real driver by providing mock implementations. These mock tests aren't implemented in this PR, but can be added later.