Udpcap is a receive-only UDP-Socket emulation based on Npcap. It utilizes the Npcap packet capture driver to capture ethernet traffic, parse all necessary headers and return the UDP Payload. With Udpcap you can open a UDP Socket and receive data without actually opening a Socket!
The Project is Windows only, as Npcap only targets Windows as well.
Udpcap can:
- Bind to an IPv4 address and a port
- Set the receive buffer size
- Join and leave multicast groups
- Enable and disable multicast loopback
- Receive unicast and multicast packages (Only one memcpy from kernel to user space memory)
- Handle fragmented IPv4 traffic
Udpcap cannot:
- Send data (use an actual socket for that π)
- Set bind flags ("sockets" are always opened shared)
- Use IPv6
All dependencies are conveniently fetched by CMake. For actually using Udpcap however, the Npcap driver needs to be installed. Keep in mind that the Npcap license is proprietary.
Using a packet capture driver to emulate a UDP socket sounds like a terrible idea, when you could just use a proper UDP Socket? Well, it probably is. But hang on, there is a very specific Windows issue that this project can work around.
Compared to Windows 7, Windows 10 has a more aggressive Windows Defender (The MpsSvc Service). When receiving many UDP multicast packets with a regular socket on Windows 10, the Windows Defender causes a massive CPU load making the entire system laggy. The issue appears to be worse, when the Windows machine is connected to a Windows Domain, as it is usally done in large corporation networks.
When investigating the System with Process Hacker, one can see that the System
process (the Windows Kernel) occupies as much as an entire CPU core. It is always one thread inside the kernel that that uses up all CPU cycles and it has tcpip.sys
in its call stack:
The Defender Firewall cannot be deactivated in Windows 10 any more. The MpsSvc Service will even keep running when temporarily deactivated, as it offers further security features besides the Defender Firewall.
On Windows, every (user space initiated) network access uses the Winsocks/2 API. That API uses the Kernel-Mode filesystem driver Afd.sys
, which then uses the Transport Protocol Driver tcpip.sys
. The tcpip.sys driver is where all the Protocol magic happens in Windows. This is also the point where the Windows Defender Firewall analyzes the traffic. Microsoft has created an entire API only for that purpose: The Windows Filtering Platform (WFP). This API is available in Windows Vista and later. Below tcpip.sys
only comes the NDIS.sys
(Network Driver Interface Specification) and the actual Ethernet NIC drivers. So to work around the Defender Firewall CPU load issue, we need to bypass the entire tcpip.sys
driver and everything above it. Refer to the image below (black lines) to get an overview. When using Npcap, the Windows Defender does not see any open sockets and does not analyze the traffic. This however means that the UDP protocol stack has to be re-implemented in user space (that's where Udpcap comes in handy!).
Udpcap has a very simple API with strong similarities to other well-known socket APIs:
#include <iostream>
#include <udpcap/udpcap_socket.h>
int main()
{
// Create a Udpcap socket and bind it to a port. For this example we want to
// receive data from any local or remote source and therefore not bind to an
// IP address.
Udpcap::UdpcapSocket socket;
socket.bind(Udpcap::HostAddress::Any(), 14000);
for (;;)
{
// Allocate a buffer for the received datagram. The size of the buffer
// should be large enough to hold the largest possible datagram.
std::vector<char> datagram(65535);
// Create an error code object to hold the error code if an error occurs.
Udpcap::Error error = Udpcap::Error::OK;
// Receive a datagram from the Socket. This is a blocking
// operation. The operation will return once a datagram has been received,
// the socket was closed by another thread or an error occured.
size_t num_bytes = socket.receiveDatagram(datagram.data(), datagram.size(), error);
// Resize the buffer to the actual size of the received datagram.
datagram.resize(num_bytes);
std::cout << "Received " << datagram.size() << " bytes: "
<< std::string(datagram.data(), datagram.size())
<< std::endl;
}
return 0;
}
You will need git-for-windows, Visual Studio 2015 or newer and CMake 3.13 or newer to compile Udpcap.
-
Clone this repo
(Alternatively you can download this repo as archive. There are no submodules.)
git clone https://github.com/eclipse-ecal/udpcap.git cd udpcap
-
Call
CMakeWindows.bat
This will also download the following dependencies:
- asio (header only)
- Npcap SDK (as binary
.lib
files) - Pcap++ (as binary
.lib
files)
-
Open
_build/udpcap.sln
with Visual Studio and compileudpcap
and the samplesFor seeing some output from the samples, you will obviously have to always execute both the sender snd udpcap_reciever sample.
You can set the following CMake Options to control how Udpcap is supposed to build:
Option | Type | Default | Explanation |
---|---|---|---|
UDPCAP_BUILD_SAMPLES |
BOOL |
ON |
Build the Udpcap (and asio) samples for sending and receiving dummy data |
UDPCAP_BUILD_TESTS |
BOOL |
OFF |
Build the udpcap GTests. Requires GTest::GTest to be available. |
UDPCAP_THIRDPARTY_ENABLED |
BOOL |
ON |
Activate / Deactivate the usage of integrated dependencies. |
UDPCAP_THIRDPARTY_USE_BUILTIN_NPCAP |
BOOL |
ON |
Fetch and build against an integrated Version of the npcap SDK. Only available if UDPCAP_THIRDPARTY_ENABLED=ON |
UDPCAP_THIRDPARTY_USE_BUILTIN_PCAPPLUSPLUS |
BOOL |
ON |
Fetch and build against an integrated Version of Pcap++. Only available if UDPCAP_THIRDPARTY_ENABLED=ON |
UDPCAP_THIRDPARTY_USE_BUILTIN_ASIO |
BOOL |
ON |
Fetch and build against an integrated Version of asio. Only available if UDPCAP_THIRDPARTY_ENABLED=ON |
UDPCAP_THIRDPARTY_USE_BUILTIN_GTEST |
BOOL |
ON |
Fetch and build tests against a predefined version of GTest. If disabled, the targets have to be provided externally. Only available if UDPCAP_THIRDPARTY_ENABLED=ON and UDPCAP_BUILD_TESTS=ON |
UDPCAP_LIBRARY_TYPE |
STRING |
Controls the library type of Udpcap by injecting the string into the add_library call. Can be set to STATIC / SHARED / OBJECT. If set, this will override the regular BUILD_SHARED_LIBS CMake option. If not set, CMake will use the default setting, which is controlled by BUILD_SHARED_LIBS . |
Integrate as binaries:
-
Download a udpcap release or build it as described earlier. If you build yourself, make sure to build and install both a debug and release version.
-
If you chose the shared udpcap library (->
.dll
), it will be self-contained and you only need to copy theudpcap.dll
/udpcapd.dll
to your application directory.If you chose the static udpcap library (->
.lib
), you need to make the following targets available for CMake as well:pcapplusplus::pcapplusplus
npcap::npcap
Check out the Udpcap integration sample for a suggestion on how to do that. You can find the scripts and modules for fetching and finding Npcap and Pcap++ here:
-
Add the udpcap directory to your
CMAKE_PREFIX_PATH
:cmake your_command_line -DCMAKE_PREFIX_PATH=path/to/udpcap/install/dir
Integrate as source
-
Make the udpcap source available either way. You can e.g. download it manually, use a git submodule or use CMake FetchContent.
-
Add the following to your
CMakeLists.txt
:# You will probably not need the samples, so turn them off. set(UDPCAP_BUILD_SAMPLES OFF) # Add the top-level directory as cmake subdirectory add_subdirectory("path_to_udpcap") # Add the dummy Findudpcap.cmake do the module path to make # find_package(udpcap) succeed. list(APPEND CMAKE_MODULE_PATH "path_to_udpcap/thirdparty/udpcap/Modules")
-
Now you can link against
udpcap::udpcap