From 13a98c76a038241fac4b84ad1193f337f018d487 Mon Sep 17 00:00:00 2001 From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> Date: Tue, 6 Feb 2024 19:08:09 +0100 Subject: [PATCH 01/11] - Added GTest - Added some tests - Fixed the receive function that was prone to losing data --- CMakeLists.txt | 21 + README.md | 20 +- .../udpcap_receiver_multicast/src/main.cpp | 29 +- samples/udpcap_receiver_unicast/src/main.cpp | 27 +- tests/udpcap_test/CMakeLists.txt | 44 ++ tests/udpcap_test/src/atomic_signalable.h | 205 ++++++++++ tests/udpcap_test/src/udpcap_test.cpp | 376 ++++++++++++++++++ thirdparty/GTest/GTest_make_available.cmake | 33 ++ thirdparty/GTest/Modules/FindGTest.cmake | 1 + udpcap/CMakeLists.txt | 1 + udpcap/include/udpcap/error.h | 116 ++++++ udpcap/include/udpcap/udpcap_socket.h | 59 +-- udpcap/src/udpcap_socket.cpp | 9 +- udpcap/src/udpcap_socket_private.cpp | 289 ++++++++++++-- udpcap/src/udpcap_socket_private.h | 29 +- 15 files changed, 1160 insertions(+), 99 deletions(-) create mode 100644 tests/udpcap_test/CMakeLists.txt create mode 100644 tests/udpcap_test/src/atomic_signalable.h create mode 100644 tests/udpcap_test/src/udpcap_test.cpp create mode 100644 thirdparty/GTest/GTest_make_available.cmake create mode 100644 thirdparty/GTest/Modules/FindGTest.cmake create mode 100644 udpcap/include/udpcap/error.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a3cc1b..7c15824 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,10 @@ option(UDPCAP_BUILD_SAMPLES "Build project samples" ON) +option(UDPCAP_BUILD_TESTS + "Build the udpcap GTests. Requires GTest::GTest to be available." + OFF) + option(UDPCAP_THIRDPARTY_ENABLED "Enable building against the builtin dependencies" ON) @@ -57,6 +61,12 @@ cmake_dependent_option(UDPCAP_THIRDPARTY_USE_BUILTIN_ASIO "UDPCAP_THIRDPARTY_ENABLED" OFF) +cmake_dependent_option(UDPCAP_THIRDPARTY_USE_BUILTIN_GTEST + "Fetch and build tests against a predefined version of GTest. If disabled, the targets have to be provided externally." + ON + "UDPCAP_THIRDPARTY_ENABLED AND UDPCAP_BUILD_TESTS" + OFF) + # Module path for finding udpcap list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/udpcap/modules) @@ -75,6 +85,12 @@ if (UDPCAP_THIRDPARTY_USE_BUILTIN_ASIO) include(thirdparty/asio/asio_make_available.cmake) endif() +#--- Fetch GTest ------------------------------- +if (UDPCAP_THIRDPARTY_USE_BUILTIN_GTEST) + include(thirdparty/GTest/GTest_make_available.cmake) +endif() + + #---------------------------------------------- # Set Debug postfix @@ -94,6 +110,11 @@ if (UDPCAP_BUILD_SAMPLES) add_subdirectory(samples/asio_sender_unicast) endif() +# Tests +if (UDPCAP_BUILD_TESTS) + enable_testing() + add_subdirectory(tests/udpcap_test) +endif() # Make this package available for packing with CPack include("${CMAKE_CURRENT_LIST_DIR}/cpack_config.cmake") diff --git a/README.md b/README.md index 4e95538..c7a8f54 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Udpcap has a very simple API with strong similarities to other well-known socket int main() { - // Create a Udpcap socket and bind it to a port. For this exampel we want to + // 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. @@ -70,13 +70,23 @@ int main() 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 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. - std::vector received_datagram = socket.receiveDatagram(); + 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 " << received_datagram.size() << " bytes: " - << std::string(received_datagram.data(), received_datagram.size()) + std::cout << "Received " << datagram.size() << " bytes: " + << std::string(datagram.data(), datagram.size()) << std::endl; } @@ -117,10 +127,12 @@ You can set the following CMake Options to control how Udpcap is supposed to bui **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`| | `BUILD_SHARED_LIBS` | `BOOL` | | Not a udpcap option, but use this to control whether you want to have a static or shared library | # How to integrate Udpcap in your project diff --git a/samples/udpcap_receiver_multicast/src/main.cpp b/samples/udpcap_receiver_multicast/src/main.cpp index f4ff5b9..7346947 100644 --- a/samples/udpcap_receiver_multicast/src/main.cpp +++ b/samples/udpcap_receiver_multicast/src/main.cpp @@ -73,11 +73,11 @@ int main() return 1; } - // 5) Receive data from the socket + // 3) Receive data from the socket // - // There are 2 receiveDatagram() functions available. One of them returns - // the data as std::vector, the other expects a pointer to pre-allocated - // memory along with the maximum size. + // The receiveDatagram() function is used to receive data from the socket. + // It requires the application to allocate memory for the received data. + // If an error occurs, the error object is set accordingly. // // The socket.receiveDatagram() function is blocking. In this example we // can use the applications' main thread to wait for incoming data. @@ -91,17 +91,24 @@ int main() Udpcap::HostAddress sender_address; uint16_t sender_port(0); + // Allocate memory for the received datagram (with the maximum possible udp datagram size) + std::vector received_datagram(65536); + + // Initialize error object + Udpcap::Error error = Udpcap::Error::OK; + // Blocking receive a datagram - std::vector received_datagram = socket.receiveDatagram(&sender_address, &sender_port); + size_t received_bytes = socket.receiveDatagram(received_datagram.data(), received_datagram.size(), &sender_address, &sender_port, error); - if (sender_address.isValid()) + if (error) { - std::cout << "Received " << received_datagram.size() << " bytes from " << sender_address.toString() << ":" << sender_port << ": " << std::string(received_datagram.data(), received_datagram.size()) << std::endl; - } - else - { - std::cerr << "ERROR: Failed to receive data from Udpcap Socket" << std::endl; + std::cerr << "ERROR while receiving data:" << error.ToString() << std::endl; + return 1; } + + // Shrink the received_datagram to the actual size + received_datagram.resize(received_bytes); + std::cout << "Received " << received_datagram.size() << " bytes from " << sender_address.toString() << ":" << sender_port << ": " << std::string(received_datagram.data(), received_datagram.size()) << std::endl; } return 0; diff --git a/samples/udpcap_receiver_unicast/src/main.cpp b/samples/udpcap_receiver_unicast/src/main.cpp index 932eb41..c96bd8c 100644 --- a/samples/udpcap_receiver_unicast/src/main.cpp +++ b/samples/udpcap_receiver_unicast/src/main.cpp @@ -58,9 +58,9 @@ int main() // 3) Receive data from the socket // - // There are 2 receiveDatagram() functions available. One of them returns - // the data as std::vector, the other expects a pointer to pre-allocated - // memory along with the maximum size. + // The receiveDatagram() function is used to receive data from the socket. + // It requires the application to allocate memory for the received data. + // If an error occurs, the error object is set accordingly. // // The socket.receiveDatagram() function is blocking. In this example we // can use the applications' main thread to wait for incoming data. @@ -74,17 +74,24 @@ int main() Udpcap::HostAddress sender_address; uint16_t sender_port(0); + // Allocate memory for the received datagram (with the maximum possible udp datagram size) + std::vector received_datagram(65536); + + // Initialize error object + Udpcap::Error error = Udpcap::Error::OK; + // Blocking receive a datagram - std::vector received_datagram = socket.receiveDatagram(&sender_address, &sender_port); + size_t received_bytes = socket.receiveDatagram(received_datagram.data(), received_datagram.size(), &sender_address, &sender_port, error); - if (sender_address.isValid()) + if (error) { - std::cout << "Received " << received_datagram.size() << " bytes from " << sender_address.toString() << ":" << sender_port << ": " << std::string(received_datagram.data(), received_datagram.size()) << std::endl; - } - else - { - std::cerr << "ERROR: Failed to receive data from Udpcap Socket" << std::endl; + std::cerr << "ERROR while receiving data:" << error.ToString() << std::endl; + return 1; } + + // Shrink the received_datagram to the actual size + received_datagram.resize(received_bytes); + std::cout << "Received " << received_datagram.size() << " bytes from " << sender_address.toString() << ":" << sender_port << ": " << std::string(received_datagram.data(), received_datagram.size()) << std::endl; } return 0; diff --git a/tests/udpcap_test/CMakeLists.txt b/tests/udpcap_test/CMakeLists.txt new file mode 100644 index 0000000..e87e86d --- /dev/null +++ b/tests/udpcap_test/CMakeLists.txt @@ -0,0 +1,44 @@ +# =========================== LICENSE ================================= +# +# Copyright (C) 2016 - 2022 Continental Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# =========================== LICENSE ================================= + +cmake_minimum_required(VERSION 3.13) + +project(udpcap_test) + +set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE) + +find_package(udpcap REQUIRED) +find_package(GTest REQUIRED) +find_package(asio REQUIRED) + +set(sources + src/atomic_signalable.h + src/udpcap_test.cpp +) + +add_executable (${PROJECT_NAME} + ${sources} +) + +target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_14) + +target_link_libraries (${PROJECT_NAME} + udpcap::udpcap + GTest::gtest_main + $ +) diff --git a/tests/udpcap_test/src/atomic_signalable.h b/tests/udpcap_test/src/atomic_signalable.h new file mode 100644 index 0000000..237a59d --- /dev/null +++ b/tests/udpcap_test/src/atomic_signalable.h @@ -0,0 +1,205 @@ +/******************************************************************************** + * Copyright (c) 2024 Continental Corporation + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#include +#include +#include +#include + +template +class atomic_signalable +{ +public: + atomic_signalable(T initial_value) : value(initial_value) {} + + atomic_signalable& operator=(const T new_value) + { + std::lock_guard lock(mutex); + value = new_value; + cv.notify_all(); + return *this; + } + + T operator++() + { + std::lock_guard lock(mutex); + T newValue = ++value; + cv.notify_all(); + return newValue; + } + + T operator++(T) + { + std::lock_guard lock(mutex); + T oldValue = value++; + cv.notify_all(); + return oldValue; + } + + T operator--() + { + std::lock_guard lock(mutex); + T newValue = --value; + cv.notify_all(); + return newValue; + } + + T operator--(T) + { + std::lock_guard lock(mutex); + T oldValue = value--; + cv.notify_all(); + return oldValue; + } + + T operator+=(const T& other) + { + std::lock_guard lock(mutex); + value += other; + cv.notify_all(); + return value; + } + + T operator-=(const T& other) + { + std::lock_guard lock(mutex); + value -= other; + cv.notify_all(); + return value; + } + + T operator*=(const T& other) + { + std::lock_guard lock(mutex); + value *= other; + cv.notify_all(); + return value; + } + + T operator/=(const T& other) + { + std::lock_guard lock(mutex); + value /= other; + cv.notify_all(); + return value; + } + + T operator%=(const T& other) + { + std::lock_guard lock(mutex); + value %= other; + cv.notify_all(); + return value; + } + + template + bool wait_for(Predicate predicate, std::chrono::milliseconds timeout) + { + std::unique_lock lock(mutex); + return cv.wait_for(lock, timeout, [&]() { return predicate(value); }); + } + + T get() const + { + std::lock_guard lock(mutex); + return value; + } + + bool operator==(T other) const + { + std::lock_guard lock(mutex); + return value == other; + } + + bool operator==(const atomic_signalable& other) const + { + std::lock_guard lock_this(mutex); + std::lock_guard lock_other(other.mutex); + return value == other.value; + } + + bool operator!=(T other) const + { + std::lock_guard lock(mutex); + return value != other; + } + + bool operator<(T other) const + { + std::lock_guard lock(mutex); + return value < other; + } + + bool operator<=(T other) const + { + std::lock_guard lock(mutex); + return value <= other; + } + + bool operator>(T other) const + { + std::lock_guard lock(mutex); + return value > other; + } + + bool operator>=(T other) const + { + std::lock_guard lock(mutex); + return value >= other; + } + +private: + T value; + std::condition_variable cv; + mutable std::mutex mutex; +}; + + +template +bool operator==(const T& other, const atomic_signalable& atomic) +{ + return atomic == other; +} + +template +bool operator!=(const T& other, const atomic_signalable& atomic) +{ + return atomic != other; +} + +template +bool operator<(const T& other, const atomic_signalable& atomic) +{ + return atomic > other; +} + +template +bool operator<=(const T& other, const atomic_signalable& atomic) +{ + return atomic >= other; +} + +template +bool operator>(const T& other, const atomic_signalable& atomic) +{ + return atomic < other; +} + +template +bool operator>=(const T& other, const atomic_signalable& atomic) +{ + return atomic <= other; +} diff --git a/tests/udpcap_test/src/udpcap_test.cpp b/tests/udpcap_test/src/udpcap_test.cpp new file mode 100644 index 0000000..6b1358c --- /dev/null +++ b/tests/udpcap_test/src/udpcap_test.cpp @@ -0,0 +1,376 @@ +/* =========================== LICENSE ================================= + * + * Copyright (C) 2016 - 2022 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * =========================== LICENSE ================================= + */ + +#include + +#include +#include + +#include + +#include "atomic_signalable.h" + +TEST(udpcap, RAII) +{ + // Create a udpcap socket + Udpcap::UdpcapSocket udpcap_socket; + ASSERT_TRUE(udpcap_socket.isValid()); + + // Delete the socket +} + +TEST(udpcap, RAIIWithClose) +{ + // Create a udpcap socket + Udpcap::UdpcapSocket udpcap_socket; + ASSERT_TRUE(udpcap_socket.isValid()); + + // bind the socket + bool success = udpcap_socket.bind(Udpcap::HostAddress::Any(), 14000); + ASSERT_TRUE(success); + + // Close the socket + udpcap_socket.close(); +} + +TEST(udpcap, RAIIWithSomebodyWaiting) +{ + // Create a udpcap socket + Udpcap::UdpcapSocket udpcap_socket; + ASSERT_TRUE(udpcap_socket.isValid()); + + // bind the socket + bool success = udpcap_socket.bind(Udpcap::HostAddress::Any(), 14000); + ASSERT_TRUE(success); + + // Blocking receive a datagram + std::thread receive_thread([&udpcap_socket]() + { + // Create buffer with max udp datagram size + std::vector received_datagram; + received_datagram.resize(65536); + + Udpcap::Error error = Udpcap::Error::ErrorCode::GENERIC_ERROR; + + // blocking receive + size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), 0, error); + + // Check that we didn't receive any bytes + ASSERT_EQ(received_bytes, 0); + + // TODO: check actual error, which should indicate that the socket is closed + ASSERT_TRUE(bool(error)); + + }); + + // Close the socket + udpcap_socket.close(); + + // Join the thread + receive_thread.join(); + + // Delete the socket +} + +TEST(udpcap, SimpleReceive) +{ + atomic_signalable received_messages(0); + + // Create a udpcap socket + Udpcap::UdpcapSocket udpcap_socket; + ASSERT_TRUE(udpcap_socket.isValid()); + + { + bool success = udpcap_socket.bind(Udpcap::HostAddress::Any(), 14000); + ASSERT_TRUE(success); + } + + // Blocking receive a datagram + std::thread receive_thread([&udpcap_socket, &received_messages]() + { + // Initialize variables for the sender's address and port + Udpcap::HostAddress sender_address; + uint16_t sender_port(0); + Udpcap::Error error = Udpcap::Error::ErrorCode::GENERIC_ERROR; + + // Allocate buffer with max udp datagram size + std::vector received_datagram; + received_datagram.resize(65536); + + // blocking receive + size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), &sender_address, &sender_port, error); + received_datagram.resize(received_bytes); + + // No error must have occurred + ASSERT_FALSE(bool(error)); + + // Check if the received datagram is valid and contains "Hello World" + ASSERT_FALSE(received_datagram.empty()); + ASSERT_EQ(std::string(received_datagram.data(), received_datagram.size()), "Hello World"); + + received_messages++; + }); + + // Create an asio UDP sender socket + asio::io_service io_service; + + const asio::ip::udp::endpoint endpoint(asio::ip::make_address("127.0.0.1"), 14000); + asio::ip::udp::socket asio_socket(io_service, endpoint.protocol()); + + std::string buffer_string = "Hello World"; + asio_socket.send_to(asio::buffer(buffer_string), endpoint); + + // Wait max 100ms for the receive thread to finish + received_messages.wait_for([](int value) { return value >= 1; }, std::chrono::milliseconds(100)); + + // Check if the received message counter is 1 + ASSERT_EQ(received_messages.get(), 1); + + asio_socket.close(); + udpcap_socket.close(); + + receive_thread.join(); +} + +TEST(udpcap, MultipleSmallPackages) +{ + constexpr int num_packages_to_send = 10; + constexpr std::chrono::milliseconds send_delay(1); + + atomic_signalable received_messages(0); + + // Create a udpcap socket + Udpcap::UdpcapSocket udpcap_socket; + ASSERT_TRUE(udpcap_socket.isValid()); + + // Bind the udpcap socket to all interfaces + { + bool success = udpcap_socket.bind(Udpcap::HostAddress::Any(), 14000); + ASSERT_TRUE(success); + } + + // Receive datagrams in a separate thread + std::thread receive_thread([&udpcap_socket, &received_messages, num_packages_to_send]() + { + while (true) + { + Udpcap::Error error = Udpcap::Error::ErrorCode::GENERIC_ERROR; + + // Allocate buffer with max udp datagram size + std::vector received_datagram; + received_datagram.resize(65536); + + // blocking receive + size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), error); + + if (error) + { + // TODO: Check that actual error reason + + // Indicates that somebody closed the socket + ASSERT_EQ(received_messages.get(), num_packages_to_send); + break; + } + + received_datagram.resize(received_bytes); + + // Check if the received datagram is valid and contains "Hello World" + ASSERT_FALSE(received_datagram.empty()); + ASSERT_EQ(std::string(received_datagram.data(), received_datagram.size()), "Hello World"); + + received_messages++; + } + }); + + // Create an asio UDP sender socket + asio::io_service io_service; + + const asio::ip::udp::endpoint endpoint(asio::ip::make_address("127.0.0.1"), 14000); + asio::ip::udp::socket asio_socket(io_service, endpoint.protocol()); + + std::string buffer_string = "Hello World"; + for (int i = 0; i < num_packages_to_send; i++) + { + asio_socket.send_to(asio::buffer(buffer_string), endpoint); + std::this_thread::sleep_for(send_delay); + } + + // Wait max 100ms for the receive thread to finish + received_messages.wait_for([num_packages_to_send](int value) { return value >= num_packages_to_send; }, std::chrono::milliseconds(100)); + + // Check if the received message counter is 1 + ASSERT_EQ(received_messages.get(), num_packages_to_send); + + asio_socket.close(); + udpcap_socket.close(); + + receive_thread.join(); +} + +TEST(udpcap, SimpleReceiveWithBuffer) +{ + atomic_signalable received_messages(0); + + // Create a udpcap socket + Udpcap::UdpcapSocket udpcap_socket; + ASSERT_TRUE(udpcap_socket.isValid()); + + { + bool success = udpcap_socket.bind(Udpcap::HostAddress::LocalHost(), 14000); + ASSERT_TRUE(success); + } + + // Create an asio UDP sender socket + asio::io_service io_service; + + const asio::ip::udp::endpoint endpoint(asio::ip::make_address("127.0.0.1"), 14000); + asio::ip::udp::socket asio_socket(io_service, endpoint.protocol()); + + // Send "Hello World" without currently polling the socket + std::string buffer_string = "Hello World"; + asio_socket.send_to(asio::buffer(buffer_string), endpoint); + + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + // Receive the datagram + std::thread receive_thread([&udpcap_socket, &received_messages]() + { + Udpcap::Error error = Udpcap::Error::ErrorCode::GENERIC_ERROR; + + // Create buffer with max udp datagram size + std::vector received_datagram; + received_datagram.resize(65536); + + received_datagram.resize(udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), error)); + + // No error must have occurred + ASSERT_FALSE(bool(error)); + + // Check if the received datagram is valid and contains "Hello World" + ASSERT_FALSE(received_datagram.empty()); + ASSERT_EQ(std::string(received_datagram.data(), received_datagram.size()), "Hello World"); + + received_messages++; + }); + + + // Wait max 100ms for the receive thread to finish + received_messages.wait_for([](int value) { return value >= 1; }, std::chrono::milliseconds(100)); + + // Check if the received message counter is 1 + ASSERT_EQ(received_messages.get(), 1); + + asio_socket.close(); + udpcap_socket.close(); + + receive_thread.join(); +} + +TEST(udpcap, DelayedPackageReceiveMultiplePackages) +{ + constexpr int num_packages_to_send = 100; // TODO: increase + constexpr int size_per_package = 1024; + constexpr std::chrono::milliseconds receive_delay(10); + + atomic_signalable received_messages(0); + + // Create a 1400 byte buffer for sending + std::vector buffer(size_per_package, 'a'); + + // Create a udpcap socket + Udpcap::UdpcapSocket udpcap_socket; + ASSERT_TRUE(udpcap_socket.isValid()); + + // Bind the udpcap socket to all interfaces + { + bool success = udpcap_socket.bind(Udpcap::HostAddress::Any(), 14000); + ASSERT_TRUE(success); + } + + // Receive datagrams in a separate thread + std::thread receive_thread([&udpcap_socket, &received_messages, num_packages_to_send, size_per_package, receive_delay]() + { + while (true) + { + // Initialize variables for the sender's address and port + Udpcap::HostAddress sender_address; + uint16_t sender_port(0); + + Udpcap::Error error = Udpcap::Error::ErrorCode::GENERIC_ERROR; + + std::vector received_datagram; + received_datagram.resize(65536); + + size_t bytes_received = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), 0, &sender_address, &sender_port, error); + received_datagram.resize(bytes_received); + + if (error) + { + // Indicates that somebody closed the socket + ASSERT_EQ(received_messages.get(), num_packages_to_send); + break; + } + + // Check if the received datagram is valid and contains "Hello World" + ASSERT_EQ(received_datagram.size(), size_per_package); + received_messages++; + + // Wait a bit, so we force the udpcap socket to buffer the datagrams + std::this_thread::sleep_for(receive_delay); + } + }); + + // Create an asio UDP sender socket + asio::io_service io_service; + + const asio::ip::udp::endpoint endpoint(asio::ip::make_address("127.0.0.1"), 14000); + asio::ip::udp::socket asio_socket(io_service, endpoint.protocol()); + + // Capture the start time + auto start_time = std::chrono::steady_clock::now(); + + // Send the buffers + for (int i = 0; i < num_packages_to_send; i++) + { + asio_socket.send_to(asio::buffer(buffer), endpoint); + } + + // Wait some time for the receive thread to finish + received_messages.wait_for([num_packages_to_send](int value) { return value >= num_packages_to_send; }, receive_delay * num_packages_to_send + std::chrono::milliseconds(1000)); + + // Check if the received message counter is equal to the sent messages + ASSERT_EQ(received_messages.get(), num_packages_to_send); + + // Capture the end time + auto end_time = std::chrono::steady_clock::now(); + + // TODO: check the entire delay + + asio_socket.close(); + udpcap_socket.close(); + + receive_thread.join(); +} + + +// TODO: Write a test that tests the Source Address and Source Port + +// TODO: Write a test that tests the timeout + +// TODO: test isclosed function \ No newline at end of file diff --git a/thirdparty/GTest/GTest_make_available.cmake b/thirdparty/GTest/GTest_make_available.cmake new file mode 100644 index 0000000..feb44a8 --- /dev/null +++ b/thirdparty/GTest/GTest_make_available.cmake @@ -0,0 +1,33 @@ +include(FetchContent) +FetchContent_Declare(GTest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG origin/v1.14.x # This is not a Tag, but the release branch + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + ) +FetchContent_GetProperties(GTest) +if(NOT gtest_POPULATED) + message(STATUS "Fetching GTest...") + FetchContent_Populate(GTest) +endif() +set(GTest_ROOT_DIR "${gtest_SOURCE_DIR}") + +# Googletest automatically forces MT instead of MD if we do not set this option. +if(MSVC) + set(gtest_force_shared_crt ON CACHE BOOL "My option" FORCE) + set(BUILD_GMOCK OFF CACHE BOOL "My option" FORCE) + set(INSTALL_GTEST OFF CACHE BOOL "My option" FORCE) +endif() + +add_subdirectory("${GTest_ROOT_DIR}" EXCLUDE_FROM_ALL) + +if(NOT TARGET GTest::gtest) + add_library(GTest::gtest ALIAS gtest) +endif() + +if(NOT TARGET GTest::gtest_main) + add_library(GTest::gtest_main ALIAS gtest_main) +endif() + +# Prepend googletest-module/FindGTest.cmake to Module Path +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/Modules/") \ No newline at end of file diff --git a/thirdparty/GTest/Modules/FindGTest.cmake b/thirdparty/GTest/Modules/FindGTest.cmake new file mode 100644 index 0000000..f62c081 --- /dev/null +++ b/thirdparty/GTest/Modules/FindGTest.cmake @@ -0,0 +1 @@ +set(GTest_FOUND TRUE CACHE BOOL "Found Google Test" FORCE) \ No newline at end of file diff --git a/udpcap/CMakeLists.txt b/udpcap/CMakeLists.txt index 0f03783..2da842e 100644 --- a/udpcap/CMakeLists.txt +++ b/udpcap/CMakeLists.txt @@ -36,6 +36,7 @@ include(GenerateExportHeader) # Public API include directory set (includes + include/udpcap/error.h include/udpcap/host_address.h include/udpcap/npcap_helpers.h include/udpcap/udpcap_socket.h diff --git a/udpcap/include/udpcap/error.h b/udpcap/include/udpcap/error.h new file mode 100644 index 0000000..517c53a --- /dev/null +++ b/udpcap/include/udpcap/error.h @@ -0,0 +1,116 @@ +/******************************************************************************** + * Copyright (c) 2024 Continental Corporation + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#pragma once + +#include + +namespace Udpcap +{ + class Error + { + ////////////////////////////////////////// + // Data model + ////////////////////////////////////////// + public: + enum ErrorCode + { + // Generic + OK, + GENERIC_ERROR, + + // NPCAP errors + NPCAP_NOT_INITIALIZED, + + // Socket errors + NOT_BOUND, + TIMEOUT, + SOCKET_CLOSED, + }; + + ////////////////////////////////////////// + // Constructor & Destructor + ////////////////////////////////////////// + public: + Error(ErrorCode error_code, const std::string& message) : error_code_(error_code), message_(message) {} + Error(ErrorCode error_code) : error_code_(error_code) {} + + // Copy constructor & assignment operator + Error(const Error& other) = default; + Error& operator=(const Error& other) = default; + + // Move constructor & assignment operator + Error(Error&& other) = default; + Error& operator=(Error&& other) = default; + + ~Error() = default; + + ////////////////////////////////////////// + // Public API + ////////////////////////////////////////// + public: + inline std::string GetDescription() const + { + switch (error_code_) + { + // Generic + case OK: return "OK"; break; + case GENERIC_ERROR: return "Error"; break; + + case NPCAP_NOT_INITIALIZED: return "Npcap not initialized"; break; + + case NOT_BOUND: return "Socket not bound"; break; + case TIMEOUT: return "Timeout"; break; + case SOCKET_CLOSED: return "Socket closed"; break; + + default: return "Unknown error"; + } + } + + inline std::string ToString() const + { + return (message_.empty() ? GetDescription() : GetDescription() + " (" + message_ + ")"); + } + + const inline std::string& GetMessage() const + { + return message_; + } + + ////////////////////////////////////////// + // Operators + ////////////////////////////////////////// + inline operator bool() const { return error_code_ != ErrorCode::OK; } + inline bool operator== (const Error& other) const { return error_code_ == other.error_code_; } + inline bool operator== (const ErrorCode other) const { return error_code_ == other; } + inline bool operator!= (const Error& other) const { return error_code_ != other.error_code_; } + inline bool operator!= (const ErrorCode other) const { return error_code_ != other; } + + inline Error& operator=(ErrorCode error_code) + { + error_code_ = error_code; + return *this; + } + + ////////////////////////////////////////// + // Member Variables + ////////////////////////////////////////// + private: + ErrorCode error_code_; + std::string message_; + }; + +} // namespace Udpcap diff --git a/udpcap/include/udpcap/udpcap_socket.h b/udpcap/include/udpcap/udpcap_socket.h index 35eae2d..e535766 100644 --- a/udpcap/include/udpcap/udpcap_socket.h +++ b/udpcap/include/udpcap/udpcap_socket.h @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -133,29 +134,6 @@ namespace Udpcap */ UDPCAP_EXPORT bool hasPendingDatagrams() const; - /** - * @brief Blocks until A packet arives and returns it as char-vector - */ - UDPCAP_EXPORT std::vector receiveDatagram(HostAddress* source_address = nullptr, uint16_t* source_port = nullptr); - - /** - * @brief Blocks for the given time until a packet arives and returns it as char-vector - * - * If the socket is not bound, this method will return immediatelly. - * If a source_adress or source_port is provided, these will be filled with - * the according information from the packet. If the given time elapses - * before a datagram was available, an empty vector is returned. - * - * @param timeout_ms [in]: Maximum time to wait for a datagram in ms - * @param source_address [out]: the sender address of the datagram - * @param source_port [out]: the sender port of the datagram - * - * @return The datagram binary data - */ - UDPCAP_EXPORT std::vector receiveDatagram(unsigned long timeout_ms, HostAddress* source_address = nullptr, uint16_t* source_port = nullptr); - - UDPCAP_EXPORT size_t receiveDatagram(char* data, size_t max_len, HostAddress* source_address = nullptr, uint16_t* source_port = nullptr); - /** * @brief Blocks for the given time until a packet arives and copies it to the given memory * @@ -163,16 +141,42 @@ namespace Udpcap * If a source_adress or source_port is provided, these will be filled with * the according information from the packet. If the given time elapses * before a datagram was available, no data is copied and 0 is returned. + * + * TODO: Document which error occurs in which case * * @param data [out]: The destination memory * @param max_len [in]: The maximum bytes available at the destination * @param timeout_ms [in]: Maximum time to wait for a datagram in ms * @param source_address [out]: the sender address of the datagram * @param source_port [out]: the sender port of the datagram + * @param error [out]: The error that occured * * @return The number of bytes copied to the data pointer */ - UDPCAP_EXPORT size_t receiveDatagram(char* data, size_t max_len, unsigned long timeout_ms, HostAddress* source_address = nullptr, uint16_t* source_port = nullptr); + UDPCAP_EXPORT size_t receiveDatagram(char* data + , size_t max_len + , unsigned long timeout_ms + , HostAddress* source_address + , uint16_t* source_port + , Udpcap::Error& error); + + // TODO: Copy documentation here + UDPCAP_EXPORT size_t receiveDatagram(char* data + , size_t max_len + , unsigned long timeout_ms + , Udpcap::Error& error); + + // TODO: Copy documentation here + UDPCAP_EXPORT size_t receiveDatagram(char* data + , size_t max_len + , Udpcap::Error& error); + + // TODO: Copy documentation here + UDPCAP_EXPORT size_t receiveDatagram(char* data + , size_t max_len + , HostAddress* source_address + , uint16_t* source_port + , Udpcap::Error& error); /** * @brief Joins the given multicast group @@ -222,6 +226,13 @@ namespace Udpcap */ UDPCAP_EXPORT void close(); + /** + * @brief Returns whether the socket is closed + * + * @return true, if the socket is closed + */ + UDPCAP_EXPORT bool isClosed() const; + private: /** This is where the actual implementation lies. But the implementation has * to include many nasty header files (e.g. Windows.h), which is why we only diff --git a/udpcap/src/udpcap_socket.cpp b/udpcap/src/udpcap_socket.cpp index 066c5a6..4b17893 100644 --- a/udpcap/src/udpcap_socket.cpp +++ b/udpcap/src/udpcap_socket.cpp @@ -45,10 +45,10 @@ namespace Udpcap bool UdpcapSocket::hasPendingDatagrams () const { return udpcap_socket_private_->hasPendingDatagrams(); } - std::vector UdpcapSocket::receiveDatagram (HostAddress* source_address, uint16_t* source_port) { return udpcap_socket_private_->receiveDatagram(source_address, source_port); } - std::vector UdpcapSocket::receiveDatagram (unsigned long timeout_ms, HostAddress* source_address, uint16_t* source_port) { return udpcap_socket_private_->receiveDatagram(timeout_ms, source_address, source_port); } - size_t UdpcapSocket::receiveDatagram (char* data, size_t max_len, HostAddress* source_address, uint16_t* source_port) { return udpcap_socket_private_->receiveDatagram(data, max_len, source_address, source_port); } - size_t UdpcapSocket::receiveDatagram (char* data, size_t max_len, unsigned long timeout_ms, HostAddress* source_address, uint16_t* source_port) { return udpcap_socket_private_->receiveDatagram(data, max_len, timeout_ms, source_address, source_port); } + size_t UdpcapSocket::receiveDatagram(char* data, size_t max_len, unsigned long timeout_ms, HostAddress* source_address, uint16_t* source_port, Udpcap::Error& error) { return udpcap_socket_private_->receiveDatagram(data, max_len, timeout_ms, source_address, source_port, error); } + size_t UdpcapSocket::receiveDatagram(char* data, size_t max_len, unsigned long timeout_ms, Udpcap::Error& error) { return udpcap_socket_private_->receiveDatagram(data, max_len, timeout_ms, nullptr, nullptr, error); } + size_t UdpcapSocket::receiveDatagram(char* data, size_t max_len, Udpcap::Error& error) { return udpcap_socket_private_->receiveDatagram(data, max_len, 0, nullptr, nullptr, error); } + size_t UdpcapSocket::receiveDatagram(char* data, size_t max_len, HostAddress* source_address, uint16_t* source_port, Udpcap::Error& error) { return udpcap_socket_private_->receiveDatagram(data, max_len, 0, source_address, source_port, error); } bool UdpcapSocket::joinMulticastGroup (const HostAddress& group_address) { return udpcap_socket_private_->joinMulticastGroup(group_address); } bool UdpcapSocket::leaveMulticastGroup (const HostAddress& group_address) { return udpcap_socket_private_->leaveMulticastGroup(group_address); } @@ -57,5 +57,6 @@ namespace Udpcap bool UdpcapSocket::isMulticastLoopbackEnabled () const { return udpcap_socket_private_->isMulticastLoopbackEnabled(); } void UdpcapSocket::close () { udpcap_socket_private_->close(); } + bool UdpcapSocket::isClosed () const { return udpcap_socket_private_->isClosed(); } } \ No newline at end of file diff --git a/udpcap/src/udpcap_socket_private.cpp b/udpcap/src/udpcap_socket_private.cpp index 18ce58f..345a31d 100644 --- a/udpcap/src/udpcap_socket_private.cpp +++ b/udpcap/src/udpcap_socket_private.cpp @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include @@ -45,6 +47,7 @@ namespace Udpcap , bound_port_ (0) , multicast_loopback_enabled_(true) , receive_buffer_size_ (-1) + , pcap_devices_closed_ (false) { } @@ -86,12 +89,14 @@ namespace Udpcap // Valid address => Try to bind to address! + std::unique_lock pcap_devices_lists_lock(pcap_devices_lists_mutex_); + if (local_address.isLoopback()) { // Bind to localhost (We cannot find it by IP 127.0.0.1, as that IP is technically not even assignable to the loopback adapter). LOG_DEBUG(std::string("Opening Loopback device ") + GetLoopbackDeviceName()); - if (!openPcapDevice(GetLoopbackDeviceName())) + if (!openPcapDevice_nolock(GetLoopbackDeviceName())) { LOG_DEBUG(std::string("Bind error: Unable to bind to ") + GetLoopbackDeviceName()); close(); @@ -114,7 +119,7 @@ namespace Udpcap { LOG_DEBUG(std::string("Opening ") + dev.first + " (" + dev.second + ")"); - if (!openPcapDevice(dev.first)) + if (!openPcapDevice_nolock(dev.first)) { LOG_DEBUG(std::string("Bind error: Unable to bind to ") + dev.first); } @@ -134,7 +139,7 @@ namespace Udpcap LOG_DEBUG(std::string("Opening ") + dev.first + " (" + dev.second + ")"); - if (!openPcapDevice(dev.first)) + if (!openPcapDevice_nolock(dev.first)) { LOG_DEBUG(std::string("Bind error: Unable to bind to ") + dev.first); close(); @@ -144,7 +149,7 @@ namespace Udpcap // Also open loopback adapter. We always have to expect the local machine sending data to its own IP address. LOG_DEBUG(std::string("Opening Loopback device ") + GetLoopbackDeviceName()); - if (!openPcapDevice(GetLoopbackDeviceName())) + if (!openPcapDevice_nolock(GetLoopbackDeviceName())) { LOG_DEBUG(std::string("Bind error: Unable to open ") + GetLoopbackDeviceName()); close(); @@ -152,9 +157,10 @@ namespace Udpcap } } - bound_address_ = local_address; - bound_port_ = local_port; - bound_state_ = true; + bound_address_ = local_address; + bound_port_ = local_port; + bound_state_ = true; + pcap_devices_closed_ = false; for (auto& pcap_dev : pcap_devices_) { @@ -223,6 +229,19 @@ namespace Udpcap return false; } + // Lock the lists of open pcap devices in read-mode. We may use the handles, but not modify the lists themselfes. + const std::shared_lock pcap_devices_lists_lock(pcap_devices_lists_mutex_); + + { + const std::lock_guard pcap_callback_lock(pcap_devices_callback_mutex_); + if (pcap_devices_closed_) + { + // No open devices => fail! + LOG_DEBUG("Has Pending Datagrams error: Socket has been closed."); + return false; + } + } + if (pcap_win32_handles_.empty()) { // No open devices => fail! @@ -245,12 +264,12 @@ namespace Udpcap } - std::vector UdpcapSocketPrivate::receiveDatagram(HostAddress* source_address, uint16_t* source_port) + std::vector UdpcapSocketPrivate::receiveDatagram_OLD(HostAddress* source_address, uint16_t* source_port) { - return receiveDatagram(INFINITE, source_address, source_port); + return receiveDatagram_OLD(INFINITE, source_address, source_port); } - std::vector UdpcapSocketPrivate::receiveDatagram(unsigned long timeout_ms, HostAddress* source_address, uint16_t* source_port) + std::vector UdpcapSocketPrivate::receiveDatagram_OLD(unsigned long timeout_ms, HostAddress* source_address, uint16_t* source_port) { if (!is_valid_) { @@ -266,6 +285,9 @@ namespace Udpcap return{}; } + // Lock the lists of open pcap devices in read-mode. We may use the handles, but not modify the lists themselfes. + const std::shared_lock pcap_devices_lists_lock(pcap_devices_lists_mutex_); + if (pcap_win32_handles_.empty()) { // No open devices => fail! @@ -302,16 +324,29 @@ namespace Udpcap } } + std::cerr << "WaitForMultipleObjects START...\n"; const DWORD wait_result = WaitForMultipleObjects(num_handles, pcap_win32_handles_.data(), static_cast(false), remaining_time_to_wait_ms); + std::cerr << "WaitForMultipleObjects END...\n"; if ((wait_result >= WAIT_OBJECT_0) && wait_result <= (WAIT_OBJECT_0 + num_handles - 1)) { const int dev_index = (wait_result - WAIT_OBJECT_0); - callback_args.link_type_ = static_cast(pcap_datalink(pcap_devices_[dev_index].pcap_handle_)); - callback_args.ip_reassembly_ = ip_reassembly_[dev_index].get(); + { + // Lock the callback lock. While the callback is running, we cannot close the pcap handle, as that may invalidate the data pointer. + const std::lock_guard pcap_devices_callback_lock(pcap_devices_callback_mutex_); - pcap_dispatch(pcap_devices_[dev_index].pcap_handle_, 1, UdpcapSocketPrivate::PacketHandlerVector, reinterpret_cast(&callback_args)); + if (pcap_devices_closed_) + { + // TODO: Return an error + return {}; + } + + callback_args.link_type_ = static_cast(pcap_datalink(pcap_devices_[dev_index].pcap_handle_)); + callback_args.ip_reassembly_ = pcap_devices_ip_reassembly_[dev_index].get(); + + pcap_dispatch(pcap_devices_[dev_index].pcap_handle_, 100, UdpcapSocketPrivate::PacketHandlerVector, reinterpret_cast(&callback_args)); + } if (callback_args.success_) { @@ -329,19 +364,21 @@ namespace Udpcap } else if (wait_result == WAIT_FAILED) { - LOG_DEBUG("Receive error: WAIT_FAILED: " + std::to_string(GetLastError())); + LOG_DEBUG("Receive error: WAIT_FAILED: " + std::system_category().message(GetLastError())); + // TODO: Check if I can always just return here. This definitively happens when I close the socket, so I MUST return in certain cases. But I don't know if there may be cases when this happens without closing the socket. + return {}; } } while (wait_forever || (std::chrono::steady_clock::now() < wait_until)); return{}; } - size_t UdpcapSocketPrivate::receiveDatagram(char* data, size_t max_len, HostAddress* source_address, uint16_t* source_port) + size_t UdpcapSocketPrivate::receiveDatagram_OLD(char* data, size_t max_len, HostAddress* source_address, uint16_t* source_port) { - return receiveDatagram(data, max_len, INFINITE, source_address, source_port); + return receiveDatagram_OLD(data, max_len, INFINITE, source_address, source_port); } - size_t UdpcapSocketPrivate::receiveDatagram(char* data, size_t max_len, unsigned long timeout_ms, HostAddress* source_address, uint16_t* source_port) + size_t UdpcapSocketPrivate::receiveDatagram_OLD(char* data, size_t max_len, unsigned long timeout_ms, HostAddress* source_address, uint16_t* source_port) { if (!is_valid_) { @@ -357,6 +394,9 @@ namespace Udpcap return{}; } + // Lock the lists of open pcap devices in read-mode. We may use the handles, but not modify the lists themselfes. + const std::shared_lock pcap_devices_list_lock(pcap_devices_lists_mutex_); + if (pcap_win32_handles_.empty()) { // No open devices => fail! @@ -398,10 +438,21 @@ namespace Udpcap { const int dev_index = (wait_result - WAIT_OBJECT_0); - callback_args.link_type_ = static_cast(pcap_datalink(pcap_devices_[dev_index].pcap_handle_)); - callback_args.ip_reassembly_ = ip_reassembly_[dev_index].get(); + { + // Lock the callback lock. While the callback is running, we cannot close the pcap handle, as that may invalidate the data pointer. + const std::lock_guard pcap_devices_callback__lock(pcap_devices_callback_mutex_); + + if (pcap_devices_closed_) + { + // TODO: Return an error + return {}; + } - pcap_dispatch(pcap_devices_[dev_index].pcap_handle_, 1, UdpcapSocketPrivate::PacketHandlerRawPtr, reinterpret_cast(&callback_args)); + callback_args.link_type_ = static_cast(pcap_datalink(pcap_devices_[dev_index].pcap_handle_)); + callback_args.ip_reassembly_ = pcap_devices_ip_reassembly_[dev_index].get(); + + pcap_dispatch(pcap_devices_[dev_index].pcap_handle_, 1, UdpcapSocketPrivate::PacketHandlerRawPtr, reinterpret_cast(&callback_args)); + } if (callback_args.success_) { @@ -419,13 +470,143 @@ namespace Udpcap } else if (wait_result == WAIT_FAILED) { - LOG_DEBUG("Receive error: WAIT_FAILED: " + std::to_string(GetLastError())); + LOG_DEBUG("Receive error: WAIT_FAILED: " + std::system_category().message(GetLastError())); + // TODO: Check if I can always just return here. This definitively happens when I close the socket, so I MUST return in certain cases. But I don't know if there may be cases when this happens without closing the socket. + return {}; } } while (wait_forever || (std::chrono::steady_clock::now() < wait_until)); return 0; } + size_t UdpcapSocketPrivate::receiveDatagram(char* data + , size_t max_len + , unsigned long timeout_ms + , HostAddress* source_address + , uint16_t* source_port + , Udpcap::Error& error) + { + if (!is_valid_) + { + // Invalid socket, cannot bind => fail! + LOG_DEBUG("Receive error: Socket is invalid"); + error = Udpcap::Error::NPCAP_NOT_INITIALIZED; + return 0; + } + + if (!bound_state_) + { + // Not bound => fail! + LOG_DEBUG("Receive error: Socket is not bound"); + error = Udpcap::Error::NOT_BOUND; + return 0; + } + + // Check all devices for data + { + // Variable to store the result + pcap_pkthdr* packet_header (nullptr); + const u_char* packet_data (nullptr); + + // Lock the lists of open pcap devices in read-mode. We may use the handles, but not modify the lists themselfes. + const std::shared_lock pcap_devices_list_lock(pcap_devices_lists_mutex_); + + // Check for data on pcap devices until we are either out of time or have + // received a datagaram. A datagram may consist of multiple packaets in + // case of IP Fragmentation. + while (true) // TODO: respect the timeout parameter + { + bool received_any_data = false; + + { + // Lock the callback lock. While the callback is running, we cannot close the pcap handle, as that may invalidate the data pointer. + const std::lock_guard pcap_devices_callback_lock(pcap_devices_callback_mutex_); + + // Check if the socket is closed and return an error + if (pcap_devices_closed_) + { + error = Udpcap::Error::SOCKET_CLOSED; + return 0; + } + + // Iterate through all devices and check if they have data + for (const auto& pcap_dev : pcap_devices_) + { + CallbackArgsRawPtr callback_args(data, max_len, source_address, source_port, bound_port_, pcpp::LinkLayerType::LINKTYPE_NULL); + + int pcap_next_packet_errorcode = pcap_next_ex(pcap_dev.pcap_handle_, &packet_header, &packet_data); + + if (pcap_next_packet_errorcode == 1) + { + received_any_data = true; + + // Success! + PacketHandlerRawPtr(reinterpret_cast(&callback_args), packet_header, packet_data); + + if (callback_args.success_) + { + // Only return datagram if we successfully received a packet. Otherwise, we will continue receiving data, if there is time left. + error = Udpcap::Error::OK; + return callback_args.bytes_copied_; + } + } + else + { + // TODO: Handle errors coming from pcap. + } + } + } + + // Use WaitForMultipleObjects in order to wait for data on the pcap + // devices. Only wait for data, if we haven't received any data in the + // last loop. The Win32 event will be resetted after we got notified, + // regardless of the amount of packets that are in the buffer. Thus, we + // cannot use the event to always check / wait for new data, as there + // may still be data left in the buffer without the event being set. + if (!received_any_data) + { + // TODO: make WaitForMultipleObjects use the timeout + unsigned long remaining_time_to_wait_ms = INFINITE; + //unsigned long remaining_time_to_wait_ms = 0; + //if (wait_forever) + //{ + // remaining_time_to_wait_ms = INFINITE; + //} + //else + //{ + // auto now = std::chrono::steady_clock::now(); + // if (now < wait_until) + // { + // remaining_time_to_wait_ms = static_cast(std::chrono::duration_cast(wait_until - now).count()); + // } + //} + + + DWORD num_handles = static_cast(pcap_win32_handles_.size()); + if (num_handles > MAXIMUM_WAIT_OBJECTS) + { + LOG_DEBUG("WARNING: Too many open Adapters. " + std::to_string(num_handles) + " adapters are open, only " + std::to_string(MAXIMUM_WAIT_OBJECTS) + " are supported."); + num_handles = MAXIMUM_WAIT_OBJECTS; + } + + const DWORD wait_result = WaitForMultipleObjects(num_handles, pcap_win32_handles_.data(), static_cast(false), remaining_time_to_wait_ms); + + if ((wait_result >= WAIT_OBJECT_0) && wait_result <= (WAIT_OBJECT_0 + num_handles - 1)) + { + // SUCCESS! Some event is notified! We could actually check which + // event it is, in order to read data from that specific event. But + // it is way easier to just let the code above run again and check + // all pcap devices for data. + continue; + } + else + { + // TODO: Handle errors, especially closed and timeout errors + } + } + } + } + } bool UdpcapSocketPrivate::joinMulticastGroup(const HostAddress& group_address) { @@ -463,7 +644,7 @@ namespace Udpcap multicast_groups_.emplace(group_address); // Update the capture filters, so the devices will capture the multicast traffic - updateAllCaptureFilters(); + updateAllCaptureFilters(); // TODO: I probably need to protect the pcap_devices_ list with a mutex here if (multicast_loopback_enabled_) { @@ -499,12 +680,11 @@ namespace Udpcap multicast_groups_.erase(group_it); // Update all capture filtes - updateAllCaptureFilters(); + updateAllCaptureFilters(); // TODO: I probably need to protect the pcap_devices_ list with a mutex here return true; } - void UdpcapSocketPrivate::setMulticastLoopbackEnabled(bool enabled) { if (multicast_loopback_enabled_ == enabled) @@ -521,7 +701,7 @@ namespace Udpcap kickstartLoopbackMulticast(); } - updateAllCaptureFilters(); + updateAllCaptureFilters(); // TODO: I probably need to protect the pcap_devices_ list with a mutex here } bool UdpcapSocketPrivate::isMulticastLoopbackEnabled() const @@ -532,20 +712,47 @@ namespace Udpcap void UdpcapSocketPrivate::close() { // TODO: make close thread safe, so one thread can wait for data while another thread closes the socket - for (auto& pcap_dev : pcap_devices_) + // TODO: 2024-01-30: Check if this now is actually thread safe + { - LOG_DEBUG(std::string("Closing ") + pcap_dev.device_name_); - pcap_close(pcap_dev.pcap_handle_); + // Lock the lists of open pcap devices in read-mode. We may use the handles, + // but not modify the lists themselfes. This is in order to assure that the + // ReceiveDatagram function still has all pcap devices available after + // returning from WaitForMultipleObjects. + const std::shared_lock pcap_devices_lists_lock(pcap_devices_lists_mutex_); + + { + // Lock the callback lock. While the callback is running, we cannot close + // the pcap handle, as that may invalidate the data pointer. + const std::lock_guard pcap_callback_lock(pcap_devices_callback_mutex_); + pcap_devices_closed_ = true; //todo: must i protect this variable with the lists lock or the callback lock + for (auto& pcap_dev : pcap_devices_) + { + LOG_DEBUG(std::string("Closing ") + pcap_dev.device_name_); + pcap_close(pcap_dev.pcap_handle_); + } + } + } + + { + // Lock the lists of open pcap devices in write-mode. We may now modify the lists themselfes. + const std::unique_lock pcap_devices_lists_lock(pcap_devices_lists_mutex_); + pcap_devices_ .clear(); + pcap_win32_handles_ .clear(); + pcap_devices_ip_reassembly_.clear(); } - pcap_devices_ .clear(); - pcap_win32_handles_.clear(); - ip_reassembly_ .clear(); bound_state_ = false; bound_port_ = 0; bound_address_ = HostAddress::Invalid(); } + bool UdpcapSocketPrivate::isClosed() const + { + std::lock_guard pcap_callback_lock(pcap_devices_callback_mutex_); + return pcap_devices_closed_; + } + ////////////////////////////////////////// //// Internal ////////////////////////////////////////// @@ -647,7 +854,7 @@ namespace Udpcap } } - bool UdpcapSocketPrivate::openPcapDevice(const std::string& device_name) + bool UdpcapSocketPrivate::openPcapDevice_nolock(const std::string& device_name) { std::array errbuf{}; @@ -663,12 +870,15 @@ namespace Udpcap pcap_set_promisc(pcap_handle, 1 /*true*/); // We only want Packets destined for this adapter. We are not interested in others. pcap_set_immediate_mode(pcap_handle, 1 /*true*/); + std::array pcap_setnonblock_errbuf{}; + pcap_setnonblock(pcap_handle, 1 /*true*/,pcap_setnonblock_errbuf.data()); + if (receive_buffer_size_ > 0) { - pcap_set_buffer_size(pcap_handle, receive_buffer_size_); + pcap_set_buffer_size(pcap_handle, receive_buffer_size_); // TODO: the buffer size should probably not be zero by default. Currently (2024-01-31) it is. } - const int errorcode = pcap_activate(pcap_handle); + const int errorcode = pcap_activate(pcap_handle); // TODO : If pcap_activate() fails, the pcap_t * is not closed and freed; it should be closed using pcap_close(3PCAP). switch (errorcode) { case 0: @@ -705,9 +915,9 @@ namespace Udpcap const PcapDev pcap_dev(pcap_handle, IsLoopbackDevice(device_name), device_name); - pcap_devices_ .push_back(pcap_dev); - pcap_win32_handles_.push_back(pcap_getevent(pcap_handle)); - ip_reassembly_ .emplace_back(std::make_unique(std::chrono::seconds(5))); + pcap_devices_ .push_back(pcap_dev); + pcap_win32_handles_ .push_back(pcap_getevent(pcap_handle)); + pcap_devices_ip_reassembly_.emplace_back(std::make_unique(std::chrono::seconds(5))); return true; } @@ -788,7 +998,7 @@ namespace Udpcap if (pcap_setfilter(pcap_dev.pcap_handle_, &filter_program) == PCAP_ERROR) { pcap_perror(pcap_dev.pcap_handle_, ("UdpcapSocket ERROR: Unable to set filter \"" + filter_string + "\"").c_str()); - pcap_freecode(&filter_program); + pcap_freecode(&filter_program); // TODO: Check if I need to free the filter program at other places as well (e.g. destructor) } } } @@ -803,7 +1013,7 @@ namespace Udpcap void UdpcapSocketPrivate::kickstartLoopbackMulticast() const { - const uint16_t kickstart_port = 62000; + constexpr uint16_t kickstart_port = 62000; asio::io_context iocontext; asio::ip::udp::socket kickstart_socket(iocontext); @@ -846,6 +1056,7 @@ namespace Udpcap void UdpcapSocketPrivate::PacketHandlerVector(unsigned char* param, const struct pcap_pkthdr* header, const unsigned char* pkt_data) { + std::cerr << "PacketHandlerVector\n"; CallbackArgsVector* callback_args = reinterpret_cast(param); pcpp::RawPacket rawPacket(pkt_data, header->caplen, header->ts, false, callback_args->link_type_); diff --git a/udpcap/src/udpcap_socket_private.h b/udpcap/src/udpcap_socket_private.h index f92cfdc..834ed3e 100644 --- a/udpcap/src/udpcap_socket_private.h +++ b/udpcap/src/udpcap_socket_private.h @@ -20,11 +20,14 @@ #pragma once #include +#include + #include #include #include #include #include +#include #define WIN32_LEAN_AND_MEAN #define NOMINMAX @@ -129,20 +132,28 @@ namespace Udpcap bool isValid() const; bool bind(const HostAddress& local_address, uint16_t local_port); - bool isBound() const; + HostAddress localAddress() const; uint16_t localPort() const; bool setReceiveBufferSize(int buffer_size); + // TODO: Re-implement or remove. This is currently (2024-02-06) implemented faulty. bool hasPendingDatagrams() const; - std::vector receiveDatagram(HostAddress* source_address = nullptr, uint16_t* source_port = nullptr); - std::vector receiveDatagram(unsigned long timeout_ms, HostAddress* source_address = nullptr, uint16_t* source_port = nullptr); + std::vector receiveDatagram_OLD(HostAddress* source_address = nullptr, uint16_t* source_port = nullptr); + std::vector receiveDatagram_OLD(unsigned long timeout_ms, HostAddress* source_address = nullptr, uint16_t* source_port = nullptr); + + size_t receiveDatagram_OLD(char* data, size_t max_len, HostAddress* source_address = nullptr, uint16_t* source_port = nullptr); + size_t receiveDatagram_OLD(char* data, size_t max_len, unsigned long timeout_ms, HostAddress* source_address = nullptr, uint16_t* source_port = nullptr); - size_t receiveDatagram(char* data, size_t max_len, HostAddress* source_address = nullptr, uint16_t* source_port = nullptr); - size_t receiveDatagram(char* data, size_t max_len, unsigned long timeout_ms, HostAddress* source_address = nullptr, uint16_t* source_port = nullptr); + size_t receiveDatagram(char* data + , size_t max_len + , unsigned long timeout_ms + , HostAddress* source_address + , uint16_t* source_port + , Udpcap::Error& error); bool joinMulticastGroup(const HostAddress& group_address); bool leaveMulticastGroup(const HostAddress& group_address); @@ -151,6 +162,7 @@ namespace Udpcap bool isMulticastLoopbackEnabled() const; void close(); + bool isClosed() const; ////////////////////////////////////////// //// Internal @@ -164,7 +176,7 @@ namespace Udpcap static std::string getMac(pcap_t* const pcap_handle); - bool openPcapDevice(const std::string& device_name); + bool openPcapDevice_nolock(const std::string& device_name); std::string createFilterString(PcapDev& pcap_dev) const; void updateCaptureFilter(PcapDev& pcap_dev); @@ -189,9 +201,12 @@ namespace Udpcap std::set multicast_groups_; bool multicast_loopback_enabled_; /**< Winsocks style IP_MULTICAST_LOOP: if enabled, the socket can receive loopback multicast packages */ + mutable std::shared_mutex pcap_devices_lists_mutex_; /**< Mutex to protect the pcap_devices_, pcap_win32_handles_, pcap_devices_ip_reassembly_ lists. Only the lists, not the content. */ + mutable std::mutex pcap_devices_callback_mutex_; /**< Mutex to protect the pcap_devices during a callback AND the pcap_devices_closed variable. While a callback is running, the pcap_devices MUST NOT be closed. */ + bool pcap_devices_closed_; /**< Tells whether we have already closed the socket. */ std::vector pcap_devices_; /**< List of open PcapDevices */ std::vector pcap_win32_handles_; /**< Native Win32 handles to wait for data on the PCAP Devices. The List is in sync with pcap_devices. */ - std::vector> ip_reassembly_; /**< IP Reassembly for fragmented IP traffic. The list is in sync with the pcap_devices. */ + std::vector> pcap_devices_ip_reassembly_; /**< IP Reassembly for fragmented IP traffic. The list is in sync with the pcap_devices. */ int receive_buffer_size_; }; From 43030760a8576b092b2629cb886f2e5c24407f27 Mon Sep 17 00:00:00 2001 From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> Date: Mon, 12 Feb 2024 19:36:41 +0100 Subject: [PATCH 02/11] Implemented timeout and fixed memory leak --- tests/udpcap_test/src/udpcap_test.cpp | 146 ++++++++++++++++++++++++-- udpcap/include/udpcap/udpcap_socket.h | 4 +- udpcap/src/udpcap_socket.cpp | 4 +- udpcap/src/udpcap_socket_private.cpp | 84 +++++++++++---- udpcap/src/udpcap_socket_private.h | 3 +- 5 files changed, 207 insertions(+), 34 deletions(-) diff --git a/tests/udpcap_test/src/udpcap_test.cpp b/tests/udpcap_test/src/udpcap_test.cpp index 6b1358c..fac0e0d 100644 --- a/tests/udpcap_test/src/udpcap_test.cpp +++ b/tests/udpcap_test/src/udpcap_test.cpp @@ -26,15 +26,18 @@ #include "atomic_signalable.h" +// Create and destroy as UdpcapSocket TEST(udpcap, RAII) { - // Create a udpcap socket - Udpcap::UdpcapSocket udpcap_socket; - ASSERT_TRUE(udpcap_socket.isValid()); - - // Delete the socket + { + // Create a udpcap socket + Udpcap::UdpcapSocket udpcap_socket; + ASSERT_TRUE(udpcap_socket.isValid()); + // Delete the socket + } } +// Create and destroy a abound UdpcapSocket TEST(udpcap, RAIIWithClose) { // Create a udpcap socket @@ -49,6 +52,7 @@ TEST(udpcap, RAIIWithClose) udpcap_socket.close(); } +// Create and destroy a bound UdpcapSocket with a thread waiting for a datagram TEST(udpcap, RAIIWithSomebodyWaiting) { // Create a udpcap socket @@ -88,6 +92,7 @@ TEST(udpcap, RAIIWithSomebodyWaiting) // Delete the socket } +// Receive a simple Hello World Message TEST(udpcap, SimpleReceive) { atomic_signalable received_messages(0); @@ -148,6 +153,7 @@ TEST(udpcap, SimpleReceive) receive_thread.join(); } +// Receive multiple small packages with a small delay between sending TEST(udpcap, MultipleSmallPackages) { constexpr int num_packages_to_send = 10; @@ -223,6 +229,7 @@ TEST(udpcap, MultipleSmallPackages) receive_thread.join(); } +// Receive a datagram after it has been sent, so it had to be buffered TEST(udpcap, SimpleReceiveWithBuffer) { atomic_signalable received_messages(0); @@ -282,9 +289,10 @@ TEST(udpcap, SimpleReceiveWithBuffer) receive_thread.join(); } +// Receive multiple datagrams slower than they are sent, so they have to be buffered TEST(udpcap, DelayedPackageReceiveMultiplePackages) { - constexpr int num_packages_to_send = 100; // TODO: increase + constexpr int num_packages_to_send = 100; constexpr int size_per_package = 1024; constexpr std::chrono::milliseconds receive_delay(10); @@ -317,7 +325,7 @@ TEST(udpcap, DelayedPackageReceiveMultiplePackages) std::vector received_datagram; received_datagram.resize(65536); - size_t bytes_received = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), 0, &sender_address, &sender_port, error); + size_t bytes_received = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), &sender_address, &sender_port, error); received_datagram.resize(bytes_received); if (error) @@ -368,9 +376,129 @@ TEST(udpcap, DelayedPackageReceiveMultiplePackages) receive_thread.join(); } +// Test the timeout of the receiveDatagram function +TEST(udpcap, Timeout) +{ + atomic_signalable received_messages(0); + + // Create a udpcap socket + Udpcap::UdpcapSocket udpcap_socket; + ASSERT_TRUE(udpcap_socket.isValid()); + + { + bool success = udpcap_socket.bind(Udpcap::HostAddress::Any(), 14000); + ASSERT_TRUE(success); + } + + // Initialize variables for the sender's address and port + Udpcap::HostAddress sender_address; + uint16_t sender_port(0); + Udpcap::Error error = Udpcap::Error::ErrorCode::GENERIC_ERROR; + + // Intialize an asio socket + asio::io_service io_service; + const asio::ip::udp::endpoint endpoint(asio::ip::make_address("127.0.0.1"), 14000); + asio::ip::udp::socket asio_socket(io_service, endpoint.protocol()); + std::string buffer_string = "Hello World"; + + + // Allocate buffer with max udp datagram size + std::vector received_datagram; + received_datagram.resize(65536); + + // Nothing is received while waiting + { + // Take Start time + auto start_time = std::chrono::steady_clock::now(); + + // blocking receive with a 100ms timeout + size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), 100, &sender_address, &sender_port, error); + + // Take End time + auto end_time = std::chrono::steady_clock::now(); + + ASSERT_EQ(error, Udpcap::Error::TIMEOUT); + ASSERT_EQ(received_bytes, 0); + ASSERT_GE(std::chrono::duration_cast(end_time - start_time).count(), 100); + } + + // Something already is in the socket, so the call must return earlier + { + asio_socket.send_to(asio::buffer(buffer_string), endpoint); + + // sleep 10ms + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + // Take Start time + auto start_time = std::chrono::steady_clock::now(); + + // blocking receive with a 500ms timeout + size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), 500, &sender_address, &sender_port, error); + + // Take End time + auto end_time = std::chrono::steady_clock::now(); + + ASSERT_EQ(error, Udpcap::Error::OK); + ASSERT_EQ(received_bytes, buffer_string.size()); + ASSERT_LE(std::chrono::duration_cast(end_time - start_time).count(), 500); + + // Resize the buffer and check the content + received_datagram.resize(received_bytes); + ASSERT_EQ(std::string(received_datagram.data(), received_datagram.size()), buffer_string); + } + + // 0ms timeout returns immediately, when nothing is in the socket + { + // Take Start time + auto start_time = std::chrono::steady_clock::now(); + + // blocking receive with a 0ms timeout + size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), 0, &sender_address, &sender_port, error); + + // Take End time + auto end_time = std::chrono::steady_clock::now(); + + ASSERT_EQ(error, Udpcap::Error::TIMEOUT); + ASSERT_EQ(received_bytes, 0); + ASSERT_LE(std::chrono::duration_cast(end_time - start_time).count(), 100); + } + + // 0ms timeout returns immediately when something is in the socket + { + asio_socket.send_to(asio::buffer(buffer_string), endpoint); + + // Take Start time + auto start_time = std::chrono::steady_clock::now(); + + // blocking receive with a 0ms timeout + size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), 0, &sender_address, &sender_port, error); + + // Take End time + auto end_time = std::chrono::steady_clock::now(); + + ASSERT_EQ(error, Udpcap::Error::OK); + ASSERT_EQ(received_bytes, buffer_string.size()); + ASSERT_LE(std::chrono::duration_cast(end_time - start_time).count(), 100); + + // Resize the buffer and check the content + received_datagram.resize(received_bytes); + ASSERT_EQ(std::string(received_datagram.data(), received_datagram.size()), buffer_string); + } + + // Close the socket + udpcap_socket.close(); +} // TODO: Write a test that tests the Source Address and Source Port -// TODO: Write a test that tests the timeout +// TODO: Test the returned errors of the receiveDatagram function + +// TODO: test isclosed function + +// TODO: rapidly create and destroy sockets to see if the memory is freed correctly https://stackoverflow.com/questions/29174938/googletest-and-memory-leaks + +// TODO: Test Multicast Receive + +// TODO: Test with multiple multicast sockets, that each only receive their own multicast group -// TODO: test isclosed function \ No newline at end of file +// TODO: Create many sockets in threads, wait for them and destroy them to see if there are any race conditions that lead to crashes diff --git a/udpcap/include/udpcap/udpcap_socket.h b/udpcap/include/udpcap/udpcap_socket.h index e535766..7ab9308 100644 --- a/udpcap/include/udpcap/udpcap_socket.h +++ b/udpcap/include/udpcap/udpcap_socket.h @@ -137,7 +137,7 @@ namespace Udpcap /** * @brief Blocks for the given time until a packet arives and copies it to the given memory * - * If the socket is not bound, this method will return immediatelly. + * If the socket is not bound, this method will return immediately. * If a source_adress or source_port is provided, these will be filled with * the according information from the packet. If the given time elapses * before a datagram was available, no data is copied and 0 is returned. @@ -146,7 +146,7 @@ namespace Udpcap * * @param data [out]: The destination memory * @param max_len [in]: The maximum bytes available at the destination - * @param timeout_ms [in]: Maximum time to wait for a datagram in ms + * @param timeout_ms [in]: Maximum time to wait for a datagram in ms. If -1, the method will block until a datagram is available * @param source_address [out]: the sender address of the datagram * @param source_port [out]: the sender port of the datagram * @param error [out]: The error that occured diff --git a/udpcap/src/udpcap_socket.cpp b/udpcap/src/udpcap_socket.cpp index 4b17893..b852de7 100644 --- a/udpcap/src/udpcap_socket.cpp +++ b/udpcap/src/udpcap_socket.cpp @@ -47,8 +47,8 @@ namespace Udpcap size_t UdpcapSocket::receiveDatagram(char* data, size_t max_len, unsigned long timeout_ms, HostAddress* source_address, uint16_t* source_port, Udpcap::Error& error) { return udpcap_socket_private_->receiveDatagram(data, max_len, timeout_ms, source_address, source_port, error); } size_t UdpcapSocket::receiveDatagram(char* data, size_t max_len, unsigned long timeout_ms, Udpcap::Error& error) { return udpcap_socket_private_->receiveDatagram(data, max_len, timeout_ms, nullptr, nullptr, error); } - size_t UdpcapSocket::receiveDatagram(char* data, size_t max_len, Udpcap::Error& error) { return udpcap_socket_private_->receiveDatagram(data, max_len, 0, nullptr, nullptr, error); } - size_t UdpcapSocket::receiveDatagram(char* data, size_t max_len, HostAddress* source_address, uint16_t* source_port, Udpcap::Error& error) { return udpcap_socket_private_->receiveDatagram(data, max_len, 0, source_address, source_port, error); } + size_t UdpcapSocket::receiveDatagram(char* data, size_t max_len, Udpcap::Error& error) { return udpcap_socket_private_->receiveDatagram(data, max_len, -1, nullptr, nullptr, error); } + size_t UdpcapSocket::receiveDatagram(char* data, size_t max_len, HostAddress* source_address, uint16_t* source_port, Udpcap::Error& error) { return udpcap_socket_private_->receiveDatagram(data, max_len, -1, source_address, source_port, error); } bool UdpcapSocket::joinMulticastGroup (const HostAddress& group_address) { return udpcap_socket_private_->joinMulticastGroup(group_address); } bool UdpcapSocket::leaveMulticastGroup (const HostAddress& group_address) { return udpcap_socket_private_->leaveMulticastGroup(group_address); } diff --git a/udpcap/src/udpcap_socket_private.cpp b/udpcap/src/udpcap_socket_private.cpp index 345a31d..3f33f24 100644 --- a/udpcap/src/udpcap_socket_private.cpp +++ b/udpcap/src/udpcap_socket_private.cpp @@ -51,6 +51,10 @@ namespace Udpcap { } + UdpcapSocketPrivate::~UdpcapSocketPrivate() + { + close(); + } //UdpcapSocketPrivate::~UdpcapSocketPrivate() //{ // // @todo: reinvestigate why it crashes on close. (Maybe check if i have implemented copy / move constructors properly) @@ -486,6 +490,17 @@ namespace Udpcap , uint16_t* source_port , Udpcap::Error& error) { + // calculate until when to wait. If timeout_ms is 0 or smaller, we will wait forever. + std::chrono::steady_clock::time_point wait_until; + if (timeout_ms < 0) + { + wait_until = std::chrono::steady_clock::time_point::max(); + } + else + { + wait_until = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout_ms); + } + if (!is_valid_) { // Invalid socket, cannot bind => fail! @@ -529,7 +544,16 @@ namespace Udpcap return 0; } - // Iterate through all devices and check if they have data + // Iterate through all devices and check if they have data. There is + // no other API (that I know of) to check whether data is available on + // a PCAP device other than trying to claim it. There is a very valid + // possibility that no device will have any data available. In that + // case, we use the native Win32 event handles to wait for new data + // becoming available. We however cannot do that here before trying to + // receive the data, as waiting on the event would clear the event + // state and we don't have information about the amount of data being + // availabe (e.g. there are 2 packets available, but the event is + // cleared after we waited for the first one). for (const auto& pcap_dev : pcap_devices_) { CallbackArgsRawPtr callback_args(data, max_len, source_address, source_port, bound_port_, pcpp::LinkLayerType::LINKTYPE_NULL); @@ -565,22 +589,25 @@ namespace Udpcap // may still be data left in the buffer without the event being set. if (!received_any_data) { - // TODO: make WaitForMultipleObjects use the timeout - unsigned long remaining_time_to_wait_ms = INFINITE; - //unsigned long remaining_time_to_wait_ms = 0; - //if (wait_forever) - //{ - // remaining_time_to_wait_ms = INFINITE; - //} - //else - //{ - // auto now = std::chrono::steady_clock::now(); - // if (now < wait_until) - // { - // remaining_time_to_wait_ms = static_cast(std::chrono::duration_cast(wait_until - now).count()); - // } - //} + // Check if we are out of time and return an error if so. + auto now = std::chrono::steady_clock::now(); + if (now >= wait_until) + { + error = Udpcap::Error::TIMEOUT; + return 0; + } + // If we are not out of time, we calculate how many milliseconds we are allowed to wait for new data. + unsigned long remaining_time_to_wait_ms = 0; + const bool wait_forever = (timeout_ms < 0); // Original parameter "timeout_ms" is negative if we want to wait forever + if (wait_forever) + { + remaining_time_to_wait_ms = INFINITE; + } + else + { + remaining_time_to_wait_ms = static_cast(std::chrono::duration_cast(wait_until - now).count()); + } DWORD num_handles = static_cast(pcap_win32_handles_.size()); if (num_handles > MAXIMUM_WAIT_OBJECTS) @@ -599,9 +626,24 @@ namespace Udpcap // all pcap devices for data. continue; } - else + else if ((wait_result >= WAIT_ABANDONED_0) && wait_result <= (WAIT_ABANDONED_0 + num_handles - 1)) + { + error = Udpcap::Error(Udpcap::Error::GENERIC_ERROR, "Internal error \"WAIT_ABANDONED\" while waiting for data: " + std::system_category().message(GetLastError())); + LOG_DEBUG(error.ToString()); // This should never happen in a proper application + } + else if (wait_result == WAIT_TIMEOUT) + { + // LOG_DEBUG("Receive error: WAIT_TIMEOUT"); + error = Udpcap::Error::TIMEOUT; + return 0; + } + else if (wait_result == WAIT_FAILED) { - // TODO: Handle errors, especially closed and timeout errors + // This probably indicates a closed socket. But we don't need to + // check it here, we can simply continue the loop, as the first + // thing the loop does is checking for a closed socket. + LOG_DEBUG("Receive error: WAIT_FAILED: " + std::system_category().message(GetLastError())); + continue; } } } @@ -995,11 +1037,13 @@ namespace Udpcap else { // Set the filter - if (pcap_setfilter(pcap_dev.pcap_handle_, &filter_program) == PCAP_ERROR) + auto set_filter_error = pcap_setfilter(pcap_dev.pcap_handle_, &filter_program); + if (set_filter_error == PCAP_ERROR) { pcap_perror(pcap_dev.pcap_handle_, ("UdpcapSocket ERROR: Unable to set filter \"" + filter_string + "\"").c_str()); - pcap_freecode(&filter_program); // TODO: Check if I need to free the filter program at other places as well (e.g. destructor) } + + pcap_freecode(&filter_program); } } diff --git a/udpcap/src/udpcap_socket_private.h b/udpcap/src/udpcap_socket_private.h index 834ed3e..c6d4810 100644 --- a/udpcap/src/udpcap_socket_private.h +++ b/udpcap/src/udpcap_socket_private.h @@ -119,7 +119,7 @@ namespace Udpcap static const int MAX_PACKET_SIZE = 65536; // Npcap Doc: A snapshot length of 65535 should be sufficient, on most if not all networks, to capture all the data available from the packet. UdpcapSocketPrivate(); - ~UdpcapSocketPrivate() = default; + ~UdpcapSocketPrivate(); // Copy UdpcapSocketPrivate(UdpcapSocketPrivate const&) = delete; @@ -145,6 +145,7 @@ namespace Udpcap std::vector receiveDatagram_OLD(HostAddress* source_address = nullptr, uint16_t* source_port = nullptr); std::vector receiveDatagram_OLD(unsigned long timeout_ms, HostAddress* source_address = nullptr, uint16_t* source_port = nullptr); + // TODO: cleanup size_t receiveDatagram_OLD(char* data, size_t max_len, HostAddress* source_address = nullptr, uint16_t* source_port = nullptr); size_t receiveDatagram_OLD(char* data, size_t max_len, unsigned long timeout_ms, HostAddress* source_address = nullptr, uint16_t* source_port = nullptr); From 39ff173c70f46a8b11f0817a45500fe60a4e3cc8 Mon Sep 17 00:00:00 2001 From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> Date: Thu, 22 Feb 2024 13:20:56 +0100 Subject: [PATCH 03/11] More tests --- samples/asio_sender_multicast/src/main.cpp | 8 +- samples/asio_sender_unicast/src/main.cpp | 4 +- tests/udpcap_test/src/udpcap_test.cpp | 212 +++++++++++++- udpcap/src/npcap_helpers.cpp | 11 +- udpcap/src/udpcap_socket_private.cpp | 318 ++------------------- udpcap/src/udpcap_socket_private.h | 34 --- 6 files changed, 239 insertions(+), 348 deletions(-) diff --git a/samples/asio_sender_multicast/src/main.cpp b/samples/asio_sender_multicast/src/main.cpp index 131ed72..674b76c 100644 --- a/samples/asio_sender_multicast/src/main.cpp +++ b/samples/asio_sender_multicast/src/main.cpp @@ -33,13 +33,13 @@ int main() asio::io_service io_service; const asio::ip::udp::endpoint endpoint(asio::ip::make_address("239.0.0.1"), 14000); - asio::ip::udp::socket upd_socket(io_service, endpoint.protocol()); + asio::ip::udp::socket udp_socket(io_service, endpoint.protocol()); // set multicast packet TTL { const asio::ip::multicast::hops ttl(2); asio::error_code ec; - upd_socket.set_option(ttl, ec); + udp_socket.set_option(ttl, ec); if (ec) { std::cerr << "ERROR: Setting TTL failed: " << ec.message() << std::endl; @@ -51,7 +51,7 @@ int main() { const asio::ip::multicast::enable_loopback loopback(true); asio::error_code ec; - upd_socket.set_option(loopback, ec); + udp_socket.set_option(loopback, ec); if (ec) { std::cerr << "ERROR: Error setting loopback option: " << ec.message() << std::endl; @@ -65,7 +65,7 @@ int main() std::string buffer_string = "Hello World " + std::to_string(counter); std::cout << "Sending data \"" << buffer_string << "\"" << std::endl; - upd_socket.send_to(asio::buffer(buffer_string), endpoint); + udp_socket.send_to(asio::buffer(buffer_string), endpoint); counter++; std::this_thread::sleep_for(std::chrono::milliseconds(500)); diff --git a/samples/asio_sender_unicast/src/main.cpp b/samples/asio_sender_unicast/src/main.cpp index b89fe26..0a2ecbd 100644 --- a/samples/asio_sender_unicast/src/main.cpp +++ b/samples/asio_sender_unicast/src/main.cpp @@ -33,7 +33,7 @@ int main() asio::io_service io_service; const asio::ip::udp::endpoint endpoint(asio::ip::make_address("127.0.0.1"), 14000); - asio::ip::udp::socket upd_socket(io_service, endpoint.protocol()); + asio::ip::udp::socket udp_socket(io_service, endpoint.protocol()); int counter = 0; for(;;) @@ -41,7 +41,7 @@ int main() std::string buffer_string = "Hello World " + std::to_string(counter); std::cout << "Sending data \"" << buffer_string << "\"" << std::endl; - upd_socket.send_to(asio::buffer(buffer_string), endpoint); + udp_socket.send_to(asio::buffer(buffer_string), endpoint); counter++; std::this_thread::sleep_for(std::chrono::milliseconds(500)); diff --git a/tests/udpcap_test/src/udpcap_test.cpp b/tests/udpcap_test/src/udpcap_test.cpp index fac0e0d..6f83295 100644 --- a/tests/udpcap_test/src/udpcap_test.cpp +++ b/tests/udpcap_test/src/udpcap_test.cpp @@ -78,9 +78,14 @@ TEST(udpcap, RAIIWithSomebodyWaiting) // Check that we didn't receive any bytes ASSERT_EQ(received_bytes, 0); - // TODO: check actual error, which should indicate that the socket is closed ASSERT_TRUE(bool(error)); + // Check the error reason + ASSERT_EQ(error, Udpcap::Error(Udpcap::Error::ErrorCode::SOCKET_CLOSED)); + + // Check that the socket is closed + ASSERT_TRUE(udpcap_socket.isClosed()); + }); // Close the socket @@ -187,10 +192,15 @@ TEST(udpcap, MultipleSmallPackages) if (error) { - // TODO: Check that actual error reason - // Indicates that somebody closed the socket ASSERT_EQ(received_messages.get(), num_packages_to_send); + + // Check the error reason + ASSERT_EQ(error, Udpcap::Error(Udpcap::Error::ErrorCode::SOCKET_CLOSED)); + + // Check that the socket is closed + ASSERT_TRUE(udpcap_socket.isClosed()); + break; } @@ -332,6 +342,13 @@ TEST(udpcap, DelayedPackageReceiveMultiplePackages) { // Indicates that somebody closed the socket ASSERT_EQ(received_messages.get(), num_packages_to_send); + + // Check the error reason + ASSERT_EQ(error, Udpcap::Error(Udpcap::Error::ErrorCode::SOCKET_CLOSED)); + + // Check that the socket is closed + ASSERT_TRUE(udpcap_socket.isClosed()); + break; } @@ -419,7 +436,7 @@ TEST(udpcap, Timeout) ASSERT_EQ(error, Udpcap::Error::TIMEOUT); ASSERT_EQ(received_bytes, 0); - ASSERT_GE(std::chrono::duration_cast(end_time - start_time).count(), 100); + ASSERT_GE(std::chrono::duration_cast(end_time - start_time).count(), 100); // TODO: This sometimes fails. Check why! } // Something already is in the socket, so the call must return earlier @@ -489,16 +506,191 @@ TEST(udpcap, Timeout) udpcap_socket.close(); } -// TODO: Write a test that tests the Source Address and Source Port +// Test receiving without binding the socket (-> error) +TEST(udpcap, ReceiveNotBound) +{ + // Create a udpcap socket + Udpcap::UdpcapSocket udpcap_socket; + ASSERT_TRUE(udpcap_socket.isValid()); + + // Initialize variables for the sender's address and port + Udpcap::HostAddress sender_address; + uint16_t sender_port(0); + + // Allocate buffer with max udp datagram size + std::vector received_datagram; + received_datagram.resize(65536); + + // Initialize error object + Udpcap::Error error = Udpcap::Error::ErrorCode::GENERIC_ERROR; + + // blocking receive + size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), &sender_address, &sender_port, error); + + // Check if the received datagram is valid and contains "Hello World" + ASSERT_EQ(received_bytes, 0); + ASSERT_TRUE(bool(error)); + ASSERT_EQ(error, Udpcap::Error(Udpcap::Error::ErrorCode::NOT_BOUND)); +} -// TODO: Test the returned errors of the receiveDatagram function +// Test the multicast functionality +TEST(udpcap, MulticastReceive) +{ + atomic_signalable received_messages1(0); + atomic_signalable received_messages2(0); -// TODO: test isclosed function + // Create two udpcap sockets + Udpcap::UdpcapSocket udpcap_socket1; + ASSERT_TRUE(udpcap_socket1.isValid()); -// TODO: rapidly create and destroy sockets to see if the memory is freed correctly https://stackoverflow.com/questions/29174938/googletest-and-memory-leaks + Udpcap::UdpcapSocket udpcap_socket2; + ASSERT_TRUE(udpcap_socket2.isValid()); -// TODO: Test Multicast Receive + udpcap_socket1.setMulticastLoopbackEnabled(true); + udpcap_socket2.setMulticastLoopbackEnabled(true); -// TODO: Test with multiple multicast sockets, that each only receive their own multicast group + // Bind the udpcap sockets to all interfaces + { + bool success = udpcap_socket1.bind(Udpcap::HostAddress::Any(), 14000); + ASSERT_TRUE(success); + } + { + bool success = udpcap_socket2.bind(Udpcap::HostAddress::Any(), 14000); + ASSERT_TRUE(success); + } + + // Join the multicast group 224.0.0.1 on both sockets + { + bool success = udpcap_socket1.joinMulticastGroup(Udpcap::HostAddress("224.0.0.1")); + ASSERT_TRUE(success); + } + { + bool success = udpcap_socket2.joinMulticastGroup(Udpcap::HostAddress("224.0.0.1")); + ASSERT_TRUE(success); + } + + // Join the multicast group 224.0.0.2 on the second socket + { + bool success = udpcap_socket2.joinMulticastGroup(Udpcap::HostAddress("224.0.0.2")); + ASSERT_TRUE(success); + } + + // Create an asio UDP sender socket + asio::io_service io_service; + asio::ip::udp::socket asio_socket(io_service, asio::ip::udp::v4()); + + // open the socket for multicast sending + asio_socket.set_option(asio::ip::multicast::hops(1)); + asio_socket.set_option(asio::ip::multicast::enable_loopback(true)); + + + // Receive datagrams in a separate thread for Socket1 (checks for 224.0.0.1) + std::thread receive_thread1([&udpcap_socket1, &received_messages1]() + { + while (true) + { + // Initialize variables for the sender's address and port + Udpcap::HostAddress sender_address; + uint16_t sender_port(0); + + Udpcap::Error error = Udpcap::Error::ErrorCode::GENERIC_ERROR; + + std::vector received_datagram; + received_datagram.resize(65536); + + size_t bytes_received = udpcap_socket1.receiveDatagram(received_datagram.data(), received_datagram.size(), &sender_address, &sender_port, error); + received_datagram.resize(bytes_received); + + if (error) + { + // Indicates that somebody closed the socket + ASSERT_EQ(received_messages1.get(), 1); + + // Check the error reason + ASSERT_EQ(error, Udpcap::Error(Udpcap::Error::ErrorCode::SOCKET_CLOSED)); + + // Check that the socket is closed + ASSERT_TRUE(udpcap_socket1.isClosed()); + + break; + } + + // Check if the received datagram is valid and contains "224.0.0.1" + ASSERT_EQ(std::string(received_datagram.data(), received_datagram.size()), "224.0.0.1"); + received_messages1++; + } + }); + + // Receive datagrams in a separate thread for Socket2 (checks for 224.0.0.1 or 224.0.0.2) + std::thread receive_thread2([&udpcap_socket2, &received_messages2]() + { + while (true) + { + // Initialize variables for the sender's address and port + Udpcap::HostAddress sender_address; + uint16_t sender_port(0); + + Udpcap::Error error = Udpcap::Error::ErrorCode::GENERIC_ERROR; + + std::vector received_datagram; + received_datagram.resize(65536); + + size_t bytes_received = udpcap_socket2.receiveDatagram(received_datagram.data(), received_datagram.size(), &sender_address, &sender_port, error); + received_datagram.resize(bytes_received); + + if (error) + { + // Indicates that somebody closed the socket + ASSERT_EQ(received_messages2.get(), 2); + + // Check the error reason + ASSERT_EQ(error, Udpcap::Error(Udpcap::Error::ErrorCode::SOCKET_CLOSED)); + + // Check that the socket is closed + ASSERT_TRUE(udpcap_socket2.isClosed()); + + break; + } + + // Check if the received datagram is valid and contains "224.0.0.1" or "224.0.0.2" + ASSERT_TRUE(std::string(received_datagram.data(), received_datagram.size()) == "224.0.0.1" + || std::string(received_datagram.data(), received_datagram.size()) == "224.0.0.2"); + received_messages2++; + } + }); + + // Send the multicast message to 224.0.0.1 + { + const asio::ip::udp::endpoint endpoint(asio::ip::make_address("224.0.0.1"), 14000); + std::string buffer_string = "224.0.0.1"; + asio_socket.send_to(asio::buffer(buffer_string), endpoint); + } + + // Send the multicast message to 224.0.0.2 + { + const asio::ip::udp::endpoint endpoint(asio::ip::make_address("224.0.0.2"), 14000); + std::string buffer_string = "224.0.0.2"; + asio_socket.send_to(asio::buffer(buffer_string), endpoint); + } + + // Wait for received_messages1 to be 1 and received_messages2 to be 2 + received_messages1.wait_for([](int value) { return value >= 1; }, std::chrono::milliseconds(500)); + received_messages2.wait_for([](int value) { return value >= 2; }, std::chrono::milliseconds(500)); + + // Check if the received message counters + ASSERT_EQ(received_messages1.get(), 1); + ASSERT_EQ(received_messages2.get(), 2); + + // Close the sockets + asio_socket.close(); + udpcap_socket1.close(); + udpcap_socket2.close(); + + // Join the threads + receive_thread1.join(); + receive_thread2.join(); +} + +// TODO: Write a test that tests the Source Address and Source Port // TODO: Create many sockets in threads, wait for them and destroy them to see if there are any race conditions that lead to crashes diff --git a/udpcap/src/npcap_helpers.cpp b/udpcap/src/npcap_helpers.cpp index 83d0ad3..83dd8c9 100644 --- a/udpcap/src/npcap_helpers.cpp +++ b/udpcap/src/npcap_helpers.cpp @@ -207,23 +207,22 @@ namespace Udpcap bool TestLoopbackDevice() { - typedef std::unique_ptr pcap_if_t_uniqueptr; - std::array errbuf{}; pcap_if_t* alldevs_rawptr = nullptr; - const pcap_if_t_uniqueptr alldevs(&alldevs_rawptr, [](pcap_if_t** p) { pcap_freealldevs(*p); }); bool loopback_device_found = false; - if (pcap_findalldevs(alldevs.get(), errbuf.data()) == -1) + if (pcap_findalldevs(&alldevs_rawptr, errbuf.data()) == -1) { human_readible_error_ = "Error in pcap_findalldevs: " + std::string(errbuf.data()); fprintf(stderr, "Error in pcap_findalldevs: %s\n", errbuf.data()); + if (alldevs_rawptr != nullptr) + pcap_freealldevs(alldevs_rawptr); return false; } // Check if the loopback device is accessible - for (pcap_if_t* pcap_dev = *alldevs.get(); pcap_dev != nullptr; pcap_dev = pcap_dev->next) + for (pcap_if_t* pcap_dev = alldevs_rawptr; pcap_dev != nullptr; pcap_dev = pcap_dev->next) { if (IsLoopbackDevice_NoLock(pcap_dev->name)) { @@ -231,6 +230,8 @@ namespace Udpcap } } + pcap_freealldevs(alldevs_rawptr); + // if we didn't find the loopback device, the test has failed if (!loopback_device_found) { diff --git a/udpcap/src/udpcap_socket_private.cpp b/udpcap/src/udpcap_socket_private.cpp index 3f33f24..0995f7f 100644 --- a/udpcap/src/udpcap_socket_private.cpp +++ b/udpcap/src/udpcap_socket_private.cpp @@ -267,222 +267,6 @@ namespace Udpcap return((wait_result >= WAIT_OBJECT_0) && wait_result <= (WAIT_OBJECT_0 + num_handles - 1)); } - - std::vector UdpcapSocketPrivate::receiveDatagram_OLD(HostAddress* source_address, uint16_t* source_port) - { - return receiveDatagram_OLD(INFINITE, source_address, source_port); - } - - std::vector UdpcapSocketPrivate::receiveDatagram_OLD(unsigned long timeout_ms, HostAddress* source_address, uint16_t* source_port) - { - if (!is_valid_) - { - // Invalid socket, cannot bind => fail! - LOG_DEBUG("Receive error: Socket is invalid"); - return {}; - } - - if (!bound_state_) - { - // Not bound => fail! - LOG_DEBUG("Receive error: Socket is not bound"); - return{}; - } - - // Lock the lists of open pcap devices in read-mode. We may use the handles, but not modify the lists themselfes. - const std::shared_lock pcap_devices_lists_lock(pcap_devices_lists_mutex_); - - if (pcap_win32_handles_.empty()) - { - // No open devices => fail! - LOG_DEBUG("Receive error: No open devices"); - return{}; - } - - DWORD num_handles = static_cast(pcap_win32_handles_.size()); - if (num_handles > MAXIMUM_WAIT_OBJECTS) - { - LOG_DEBUG("WARNING: Too many open Adapters. " + std::to_string(num_handles) + " adapters are open, only " + std::to_string(MAXIMUM_WAIT_OBJECTS) + " are supported."); - num_handles = MAXIMUM_WAIT_OBJECTS; - } - - const bool wait_forever = (timeout_ms == INFINITE); - auto wait_until = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout_ms); - - std::vector datagram; - CallbackArgsVector callback_args(&datagram, source_address, source_port, bound_port_, pcpp::LinkLayerType::LINKTYPE_NULL); - - do - { - unsigned long remaining_time_to_wait_ms = 0; - if (wait_forever) - { - remaining_time_to_wait_ms = INFINITE; - } - else - { - auto now = std::chrono::steady_clock::now(); - if (now < wait_until) - { - remaining_time_to_wait_ms = static_cast(std::chrono::duration_cast(wait_until - now).count()); - } - } - - std::cerr << "WaitForMultipleObjects START...\n"; - const DWORD wait_result = WaitForMultipleObjects(num_handles, pcap_win32_handles_.data(), static_cast(false), remaining_time_to_wait_ms); - std::cerr << "WaitForMultipleObjects END...\n"; - - if ((wait_result >= WAIT_OBJECT_0) && wait_result <= (WAIT_OBJECT_0 + num_handles - 1)) - { - const int dev_index = (wait_result - WAIT_OBJECT_0); - - { - // Lock the callback lock. While the callback is running, we cannot close the pcap handle, as that may invalidate the data pointer. - const std::lock_guard pcap_devices_callback_lock(pcap_devices_callback_mutex_); - - if (pcap_devices_closed_) - { - // TODO: Return an error - return {}; - } - - callback_args.link_type_ = static_cast(pcap_datalink(pcap_devices_[dev_index].pcap_handle_)); - callback_args.ip_reassembly_ = pcap_devices_ip_reassembly_[dev_index].get(); - - pcap_dispatch(pcap_devices_[dev_index].pcap_handle_, 100, UdpcapSocketPrivate::PacketHandlerVector, reinterpret_cast(&callback_args)); - } - - if (callback_args.success_) - { - // Only return datagram if we successfully received a packet. Otherwise, we will continue receiving data, if there is time left. - return datagram; - } - } - else if ((wait_result >= WAIT_ABANDONED_0) && wait_result <= (WAIT_ABANDONED_0 + num_handles - 1)) - { - LOG_DEBUG("Receive error: WAIT_ABANDONED"); - } - else if (wait_result == WAIT_TIMEOUT) - { - // LOG_DEBUG("Receive error: WAIT_TIMEOUT"); - } - else if (wait_result == WAIT_FAILED) - { - LOG_DEBUG("Receive error: WAIT_FAILED: " + std::system_category().message(GetLastError())); - // TODO: Check if I can always just return here. This definitively happens when I close the socket, so I MUST return in certain cases. But I don't know if there may be cases when this happens without closing the socket. - return {}; - } - } while (wait_forever || (std::chrono::steady_clock::now() < wait_until)); - - return{}; - } - - size_t UdpcapSocketPrivate::receiveDatagram_OLD(char* data, size_t max_len, HostAddress* source_address, uint16_t* source_port) - { - return receiveDatagram_OLD(data, max_len, INFINITE, source_address, source_port); - } - - size_t UdpcapSocketPrivate::receiveDatagram_OLD(char* data, size_t max_len, unsigned long timeout_ms, HostAddress* source_address, uint16_t* source_port) - { - if (!is_valid_) - { - // Invalid socket, cannot bind => fail! - LOG_DEBUG("Receive error: Socket is invalid"); - return{}; - } - - if (!bound_state_) - { - // Not bound => fail! - LOG_DEBUG("Receive error: Socket is not bound"); - return{}; - } - - // Lock the lists of open pcap devices in read-mode. We may use the handles, but not modify the lists themselfes. - const std::shared_lock pcap_devices_list_lock(pcap_devices_lists_mutex_); - - if (pcap_win32_handles_.empty()) - { - // No open devices => fail! - LOG_DEBUG("Receive error: No open devices"); - return{}; - } - - DWORD num_handles = static_cast(pcap_win32_handles_.size()); - if (num_handles > MAXIMUM_WAIT_OBJECTS) - { - LOG_DEBUG("WARNING: Too many open Adapters. " + std::to_string(num_handles) + " adapters are open, only " + std::to_string(MAXIMUM_WAIT_OBJECTS) + " are supported."); - num_handles = MAXIMUM_WAIT_OBJECTS; - } - - const bool wait_forever = (timeout_ms == INFINITE); - auto wait_until = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout_ms); - - CallbackArgsRawPtr callback_args(data, max_len, source_address, source_port, bound_port_, pcpp::LinkLayerType::LINKTYPE_NULL); - - do - { - unsigned long remaining_time_to_wait_ms = 0; - if (wait_forever) - { - remaining_time_to_wait_ms = INFINITE; - } - else - { - auto now = std::chrono::steady_clock::now(); - if (now < wait_until) - { - remaining_time_to_wait_ms = static_cast(std::chrono::duration_cast(wait_until - now).count()); - } - } - - const DWORD wait_result = WaitForMultipleObjects(num_handles, pcap_win32_handles_.data(), static_cast(false), remaining_time_to_wait_ms); - - if ((wait_result >= WAIT_OBJECT_0) && wait_result <= (WAIT_OBJECT_0 + num_handles - 1)) - { - const int dev_index = (wait_result - WAIT_OBJECT_0); - - { - // Lock the callback lock. While the callback is running, we cannot close the pcap handle, as that may invalidate the data pointer. - const std::lock_guard pcap_devices_callback__lock(pcap_devices_callback_mutex_); - - if (pcap_devices_closed_) - { - // TODO: Return an error - return {}; - } - - callback_args.link_type_ = static_cast(pcap_datalink(pcap_devices_[dev_index].pcap_handle_)); - callback_args.ip_reassembly_ = pcap_devices_ip_reassembly_[dev_index].get(); - - pcap_dispatch(pcap_devices_[dev_index].pcap_handle_, 1, UdpcapSocketPrivate::PacketHandlerRawPtr, reinterpret_cast(&callback_args)); - } - - if (callback_args.success_) - { - // Only return datagram if we successfully received a packet. Otherwise, we will continue receiving data, if there is time left. - return callback_args.bytes_copied_; - } - } - else if ((wait_result >= WAIT_ABANDONED_0) && wait_result <= (WAIT_ABANDONED_0 + num_handles - 1)) - { - LOG_DEBUG("Receive error: WAIT_ABANDONED"); - } - else if (wait_result == WAIT_TIMEOUT) - { - // LOG_DEBUG("Receive error: WAIT_TIMEOUT"); - } - else if (wait_result == WAIT_FAILED) - { - LOG_DEBUG("Receive error: WAIT_FAILED: " + std::system_category().message(GetLastError())); - // TODO: Check if I can always just return here. This definitively happens when I close the socket, so I MUST return in certain cases. But I don't know if there may be cases when this happens without closing the socket. - return {}; - } - } while (wait_forever || (std::chrono::steady_clock::now() < wait_until)); - - return 0; - } - size_t UdpcapSocketPrivate::receiveDatagram(char* data , size_t max_len , unsigned long timeout_ms @@ -509,14 +293,6 @@ namespace Udpcap return 0; } - if (!bound_state_) - { - // Not bound => fail! - LOG_DEBUG("Receive error: Socket is not bound"); - error = Udpcap::Error::NOT_BOUND; - return 0; - } - // Check all devices for data { // Variable to store the result @@ -543,6 +319,15 @@ namespace Udpcap error = Udpcap::Error::SOCKET_CLOSED; return 0; } + + // Check if the socket is bound and return an error + if (!bound_state_) + { + // Not bound => fail! + LOG_DEBUG("Receive error: Socket is not bound"); + error = Udpcap::Error::NOT_BOUND; + return 0; + } // Iterate through all devices and check if they have data. There is // no other API (that I know of) to check whether data is available on @@ -806,16 +591,17 @@ namespace Udpcap // Retrieve device list std::array errbuf{}; - pcap_if_t* alldevs_rawptr = nullptr; - const pcap_if_t_uniqueptr alldevs(&alldevs_rawptr, [](pcap_if_t** p) { pcap_freealldevs(*p); }); + pcap_if_t* alldevs_ptr = nullptr; - if (pcap_findalldevs(alldevs.get(), errbuf.data()) == -1) + if (pcap_findalldevs(&alldevs_ptr, errbuf.data()) == -1) { fprintf(stderr, "Error in pcap_findalldevs: %s\n", errbuf.data()); + if (alldevs_ptr != nullptr) + pcap_freealldevs(alldevs_ptr); return{}; } - for (pcap_if_t* pcap_dev = *alldevs.get(); pcap_dev != nullptr; pcap_dev = pcap_dev->next) + for (pcap_if_t* pcap_dev = alldevs_ptr; pcap_dev != nullptr; pcap_dev = pcap_dev->next) { // A user may have done something bad like assigning an IPv4 address to // the loopback adapter. We don't want to open it in that case. In a real- @@ -835,12 +621,15 @@ namespace Udpcap if (device_ipv4_addr->sin_addr.s_addr == ip.toInt()) { // The IPv4 address matches! + pcap_freealldevs(alldevs_ptr); return std::make_pair(std::string(pcap_dev->name), std::string(pcap_dev->description)); } } } } + pcap_freealldevs(alldevs_ptr); + // Nothing found => nullptr return{}; } @@ -849,20 +638,24 @@ namespace Udpcap { // Retrieve device list std::array errbuf{}; - pcap_if_t* alldevs_rawptr = nullptr; - const pcap_if_t_uniqueptr alldevs(&alldevs_rawptr, [](pcap_if_t** p) { pcap_freealldevs(*p); }); + pcap_if_t* alldevs_ptr = nullptr; - if (pcap_findalldevs(alldevs.get(), errbuf.data()) == -1) + if (pcap_findalldevs(&alldevs_ptr, errbuf.data()) == -1) { fprintf(stderr, "Error in pcap_findalldevs: %s\n", errbuf.data()); + if (alldevs_ptr != nullptr) + pcap_freealldevs(alldevs_ptr); return{}; } std::vector> alldev_vector; - for (pcap_if_t* pcap_dev = *alldevs.get(); pcap_dev != nullptr; pcap_dev = pcap_dev->next) + for (pcap_if_t* pcap_dev = alldevs_ptr; pcap_dev != nullptr; pcap_dev = pcap_dev->next) { alldev_vector.emplace_back(std::string(pcap_dev->name), std::string(pcap_dev->description)); } + + pcap_freealldevs(alldevs_ptr); + return alldev_vector; } @@ -1098,67 +891,6 @@ namespace Udpcap kickstart_socket.close(); } - void UdpcapSocketPrivate::PacketHandlerVector(unsigned char* param, const struct pcap_pkthdr* header, const unsigned char* pkt_data) - { - std::cerr << "PacketHandlerVector\n"; - CallbackArgsVector* callback_args = reinterpret_cast(param); - - pcpp::RawPacket rawPacket(pkt_data, header->caplen, header->ts, false, callback_args->link_type_); - const pcpp::Packet packet(&rawPacket, pcpp::UDP); - - const pcpp::IPv4Layer* ip_layer = packet.getLayerOfType(); - const pcpp::UdpLayer* udp_layer = packet.getLayerOfType(); - - if (ip_layer != nullptr) - { - if (ip_layer->isFragment()) - { - // Handle fragmented IP traffic - pcpp::IPReassembly::ReassemblyStatus status(pcpp::IPReassembly::ReassemblyStatus::NON_IP_PACKET); - - // Try to reassemble packet - const pcpp::Packet* reassembled_packet = callback_args->ip_reassembly_->processPacket(&rawPacket, status); - - // If we are done reassembling the packet, we return it to the user - if (reassembled_packet != nullptr) - { - const pcpp::Packet re_parsed_packet(reassembled_packet->getRawPacket(), pcpp::UDP); - - const pcpp::IPv4Layer* reassembled_ip_layer = re_parsed_packet.getLayerOfType(); - const pcpp::UdpLayer* reassembled_udp_layer = re_parsed_packet.getLayerOfType(); - - if ((reassembled_ip_layer != nullptr) && (reassembled_udp_layer != nullptr)) - FillCallbackArgsVector(callback_args, reassembled_ip_layer, reassembled_udp_layer); - - delete reassembled_packet; // We need to manually delete the packet pointer - } - } - else if (udp_layer != nullptr) - { - // Handle normal IP traffic (un-fragmented) - FillCallbackArgsVector(callback_args, ip_layer, udp_layer); - } - } - } - - void UdpcapSocketPrivate::FillCallbackArgsVector(CallbackArgsVector* callback_args, const pcpp::IPv4Layer* ip_layer, const pcpp::UdpLayer* udp_layer) - { - auto dst_port = ntohs(udp_layer->getUdpHeader()->portDst); - - if (dst_port == callback_args->bound_port_) - { - if (callback_args->source_address_ != nullptr) - *callback_args->source_address_ = HostAddress(ip_layer->getSrcIPv4Address().toInt()); - - if (callback_args->source_port_ != nullptr) - *callback_args->source_port_ = ntohs(udp_layer->getUdpHeader()->portSrc); - - callback_args->destination_vector_->reserve(udp_layer->getLayerPayloadSize()); - callback_args->destination_vector_->assign(udp_layer->getLayerPayload(), udp_layer->getLayerPayload() + udp_layer->getLayerPayloadSize()); - callback_args->success_ = true; - } - } - void UdpcapSocketPrivate::PacketHandlerRawPtr(unsigned char* param, const struct pcap_pkthdr* header, const unsigned char* pkt_data) { CallbackArgsRawPtr* callback_args = reinterpret_cast(param); diff --git a/udpcap/src/udpcap_socket_private.h b/udpcap/src/udpcap_socket_private.h index c6d4810..bab61ca 100644 --- a/udpcap/src/udpcap_socket_private.h +++ b/udpcap/src/udpcap_socket_private.h @@ -66,27 +66,6 @@ namespace Udpcap std::string device_name_; }; - struct CallbackArgsVector - { - CallbackArgsVector(std::vector* destination_vector, HostAddress* source_address, uint16_t* source_port, uint16_t bound_port, pcpp::LinkLayerType link_type) - : destination_vector_(destination_vector) - , source_address_ (source_address) - , source_port_ (source_port) - , success_ (false) - , link_type_ (link_type) - , bound_port_ (bound_port) - , ip_reassembly_ (nullptr) - {} - std::vector* const destination_vector_; - HostAddress* const source_address_; - uint16_t* const source_port_; - bool success_; - - pcpp::LinkLayerType link_type_; - const uint16_t bound_port_; - Udpcap::IpReassembly* ip_reassembly_; - }; - struct CallbackArgsRawPtr { CallbackArgsRawPtr(char* destination_buffer, size_t destination_buffer_size, HostAddress* source_address, uint16_t* source_port, uint16_t bound_port, pcpp::LinkLayerType link_type) @@ -142,13 +121,6 @@ namespace Udpcap // TODO: Re-implement or remove. This is currently (2024-02-06) implemented faulty. bool hasPendingDatagrams() const; - std::vector receiveDatagram_OLD(HostAddress* source_address = nullptr, uint16_t* source_port = nullptr); - std::vector receiveDatagram_OLD(unsigned long timeout_ms, HostAddress* source_address = nullptr, uint16_t* source_port = nullptr); - - // TODO: cleanup - size_t receiveDatagram_OLD(char* data, size_t max_len, HostAddress* source_address = nullptr, uint16_t* source_port = nullptr); - size_t receiveDatagram_OLD(char* data, size_t max_len, unsigned long timeout_ms, HostAddress* source_address = nullptr, uint16_t* source_port = nullptr); - size_t receiveDatagram(char* data , size_t max_len , unsigned long timeout_ms @@ -169,9 +141,6 @@ namespace Udpcap //// Internal ////////////////////////////////////////// private: - // RAII for pcap_if_t* - typedef std::unique_ptr pcap_if_t_uniqueptr; - static std::pair getDeviceByIp(const HostAddress& ip); static std::vector> getAllDevices(); @@ -186,9 +155,6 @@ namespace Udpcap void kickstartLoopbackMulticast() const; // Callbacks - static void PacketHandlerVector(unsigned char* param, const struct pcap_pkthdr* header, const unsigned char* pkt_data); - static void FillCallbackArgsVector(CallbackArgsVector* callback_args, const pcpp::IPv4Layer* ip_layer, const pcpp::UdpLayer* udp_layer); - static void PacketHandlerRawPtr(unsigned char* param, const struct pcap_pkthdr* header, const unsigned char* pkt_data); static void FillCallbackArgsRawPtr(CallbackArgsRawPtr* callback_args, const pcpp::IPv4Layer* ip_layer, const pcpp::UdpLayer* udp_layer); From c603577f1731715ea0bf72170b2c3670ba95fa25 Mon Sep 17 00:00:00 2001 From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> Date: Thu, 22 Feb 2024 13:21:31 +0100 Subject: [PATCH 04/11] Bumped version number --- udpcap/version.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/udpcap/version.cmake b/udpcap/version.cmake index 76c4b7b..a57fa5b 100644 --- a/udpcap/version.cmake +++ b/udpcap/version.cmake @@ -1,3 +1,3 @@ -set(UDPCAP_VERSION_MAJOR 1) +set(UDPCAP_VERSION_MAJOR 2) set(UDPCAP_VERSION_MINOR 0) -set(UDPCAP_VERSION_PATCH 2) +set(UDPCAP_VERSION_PATCH 0) From 200d36709cf8de12af3a33e94bb554cf1f120176 Mon Sep 17 00:00:00 2001 From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> Date: Thu, 22 Feb 2024 14:15:46 +0100 Subject: [PATCH 05/11] Fixed compiler and clang-tidy warnings --- .clang-tidy | 4 ++ tests/udpcap_test/src/atomic_signalable.h | 34 +++++++-------- tests/udpcap_test/src/udpcap_test.cpp | 50 +++++++++++------------ udpcap/include/udpcap/host_address.h | 5 ++- udpcap/include/udpcap/npcap_helpers.h | 4 +- udpcap/include/udpcap/udpcap_socket.h | 8 ++-- udpcap/src/host_address.cpp | 4 +- udpcap/src/ip_reassembly.cpp | 4 ++ udpcap/src/ip_reassembly.h | 2 +- udpcap/src/npcap_helpers.cpp | 17 ++++---- udpcap/src/udpcap_socket.cpp | 12 ++++-- udpcap/src/udpcap_socket_private.cpp | 32 +++++++++++---- udpcap/src/udpcap_socket_private.h | 10 ++--- 13 files changed, 110 insertions(+), 76 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 43f8989..4696ee4 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -34,21 +34,25 @@ Checks: "-*, -cppcoreguidelines-pro-bounds-pointer-arithmetic, -cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-pro-type-reinterpret-cast, + -cppcoreguidelines-use-default-member-init, misc-*, -misc-non-private-member-variables-in-classes, -misc-no-recursion, + -misc-include-cleaner, modernize-*, -modernize-pass-by-value, -modernize-use-trailing-return-type, -modernize-use-auto, + -modernize-use-default-member-init, -modernize-concat-nested-namespaces, -modernize-return-braced-init-list, -modernize-use-nodiscard, -modernize-avoid-bind, performance-*, + -performance-avoid-endl, readability-*, -readability-braces-around-statements, diff --git a/tests/udpcap_test/src/atomic_signalable.h b/tests/udpcap_test/src/atomic_signalable.h index 237a59d..b10b024 100644 --- a/tests/udpcap_test/src/atomic_signalable.h +++ b/tests/udpcap_test/src/atomic_signalable.h @@ -27,7 +27,7 @@ class atomic_signalable atomic_signalable& operator=(const T new_value) { - std::lock_guard lock(mutex); + const std::lock_guard lock(mutex); value = new_value; cv.notify_all(); return *this; @@ -35,7 +35,7 @@ class atomic_signalable T operator++() { - std::lock_guard lock(mutex); + const std::lock_guard lock(mutex); T newValue = ++value; cv.notify_all(); return newValue; @@ -43,7 +43,7 @@ class atomic_signalable T operator++(T) { - std::lock_guard lock(mutex); + const std::lock_guard lock(mutex); T oldValue = value++; cv.notify_all(); return oldValue; @@ -51,7 +51,7 @@ class atomic_signalable T operator--() { - std::lock_guard lock(mutex); + const std::lock_guard lock(mutex); T newValue = --value; cv.notify_all(); return newValue; @@ -59,7 +59,7 @@ class atomic_signalable T operator--(T) { - std::lock_guard lock(mutex); + const std::lock_guard lock(mutex); T oldValue = value--; cv.notify_all(); return oldValue; @@ -67,7 +67,7 @@ class atomic_signalable T operator+=(const T& other) { - std::lock_guard lock(mutex); + const std::lock_guard lock(mutex); value += other; cv.notify_all(); return value; @@ -75,7 +75,7 @@ class atomic_signalable T operator-=(const T& other) { - std::lock_guard lock(mutex); + const std::lock_guard lock(mutex); value -= other; cv.notify_all(); return value; @@ -83,7 +83,7 @@ class atomic_signalable T operator*=(const T& other) { - std::lock_guard lock(mutex); + const std::lock_guard lock(mutex); value *= other; cv.notify_all(); return value; @@ -91,7 +91,7 @@ class atomic_signalable T operator/=(const T& other) { - std::lock_guard lock(mutex); + const std::lock_guard lock(mutex); value /= other; cv.notify_all(); return value; @@ -99,7 +99,7 @@ class atomic_signalable T operator%=(const T& other) { - std::lock_guard lock(mutex); + const std::lock_guard lock(mutex); value %= other; cv.notify_all(); return value; @@ -114,13 +114,13 @@ class atomic_signalable T get() const { - std::lock_guard lock(mutex); + const std::lock_guard lock(mutex); return value; } bool operator==(T other) const { - std::lock_guard lock(mutex); + const std::lock_guard lock(mutex); return value == other; } @@ -133,31 +133,31 @@ class atomic_signalable bool operator!=(T other) const { - std::lock_guard lock(mutex); + const std::lock_guard lock(mutex); return value != other; } bool operator<(T other) const { - std::lock_guard lock(mutex); + const std::lock_guard lock(mutex); return value < other; } bool operator<=(T other) const { - std::lock_guard lock(mutex); + const std::lock_guard lock(mutex); return value <= other; } bool operator>(T other) const { - std::lock_guard lock(mutex); + const std::lock_guard lock(mutex); return value > other; } bool operator>=(T other) const { - std::lock_guard lock(mutex); + const std::lock_guard lock(mutex); return value >= other; } diff --git a/tests/udpcap_test/src/udpcap_test.cpp b/tests/udpcap_test/src/udpcap_test.cpp index 6f83295..2060d5c 100644 --- a/tests/udpcap_test/src/udpcap_test.cpp +++ b/tests/udpcap_test/src/udpcap_test.cpp @@ -31,7 +31,7 @@ TEST(udpcap, RAII) { { // Create a udpcap socket - Udpcap::UdpcapSocket udpcap_socket; + const Udpcap::UdpcapSocket udpcap_socket; ASSERT_TRUE(udpcap_socket.isValid()); // Delete the socket } @@ -45,7 +45,7 @@ TEST(udpcap, RAIIWithClose) ASSERT_TRUE(udpcap_socket.isValid()); // bind the socket - bool success = udpcap_socket.bind(Udpcap::HostAddress::Any(), 14000); + const bool success = udpcap_socket.bind(Udpcap::HostAddress::Any(), 14000); ASSERT_TRUE(success); // Close the socket @@ -60,7 +60,7 @@ TEST(udpcap, RAIIWithSomebodyWaiting) ASSERT_TRUE(udpcap_socket.isValid()); // bind the socket - bool success = udpcap_socket.bind(Udpcap::HostAddress::Any(), 14000); + const bool success = udpcap_socket.bind(Udpcap::HostAddress::Any(), 14000); ASSERT_TRUE(success); // Blocking receive a datagram @@ -73,7 +73,7 @@ TEST(udpcap, RAIIWithSomebodyWaiting) Udpcap::Error error = Udpcap::Error::ErrorCode::GENERIC_ERROR; // blocking receive - size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), 0, error); + const size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), 0, error); // Check that we didn't receive any bytes ASSERT_EQ(received_bytes, 0); @@ -107,7 +107,7 @@ TEST(udpcap, SimpleReceive) ASSERT_TRUE(udpcap_socket.isValid()); { - bool success = udpcap_socket.bind(Udpcap::HostAddress::Any(), 14000); + const bool success = udpcap_socket.bind(Udpcap::HostAddress::Any(), 14000); ASSERT_TRUE(success); } @@ -124,7 +124,7 @@ TEST(udpcap, SimpleReceive) received_datagram.resize(65536); // blocking receive - size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), &sender_address, &sender_port, error); + const size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), &sender_address, &sender_port, error); received_datagram.resize(received_bytes); // No error must have occurred @@ -172,7 +172,7 @@ TEST(udpcap, MultipleSmallPackages) // Bind the udpcap socket to all interfaces { - bool success = udpcap_socket.bind(Udpcap::HostAddress::Any(), 14000); + const bool success = udpcap_socket.bind(Udpcap::HostAddress::Any(), 14000); ASSERT_TRUE(success); } @@ -188,7 +188,7 @@ TEST(udpcap, MultipleSmallPackages) received_datagram.resize(65536); // blocking receive - size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), error); + const size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), error); if (error) { @@ -249,7 +249,7 @@ TEST(udpcap, SimpleReceiveWithBuffer) ASSERT_TRUE(udpcap_socket.isValid()); { - bool success = udpcap_socket.bind(Udpcap::HostAddress::LocalHost(), 14000); + const bool success = udpcap_socket.bind(Udpcap::HostAddress::LocalHost(), 14000); ASSERT_TRUE(success); } @@ -317,7 +317,7 @@ TEST(udpcap, DelayedPackageReceiveMultiplePackages) // Bind the udpcap socket to all interfaces { - bool success = udpcap_socket.bind(Udpcap::HostAddress::Any(), 14000); + const bool success = udpcap_socket.bind(Udpcap::HostAddress::Any(), 14000); ASSERT_TRUE(success); } @@ -335,7 +335,7 @@ TEST(udpcap, DelayedPackageReceiveMultiplePackages) std::vector received_datagram; received_datagram.resize(65536); - size_t bytes_received = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), &sender_address, &sender_port, error); + const size_t bytes_received = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), &sender_address, &sender_port, error); received_datagram.resize(bytes_received); if (error) @@ -396,14 +396,12 @@ TEST(udpcap, DelayedPackageReceiveMultiplePackages) // Test the timeout of the receiveDatagram function TEST(udpcap, Timeout) { - atomic_signalable received_messages(0); - // Create a udpcap socket Udpcap::UdpcapSocket udpcap_socket; ASSERT_TRUE(udpcap_socket.isValid()); { - bool success = udpcap_socket.bind(Udpcap::HostAddress::Any(), 14000); + const bool success = udpcap_socket.bind(Udpcap::HostAddress::Any(), 14000); ASSERT_TRUE(success); } @@ -429,7 +427,7 @@ TEST(udpcap, Timeout) auto start_time = std::chrono::steady_clock::now(); // blocking receive with a 100ms timeout - size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), 100, &sender_address, &sender_port, error); + const size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), 100, &sender_address, &sender_port, error); // Take End time auto end_time = std::chrono::steady_clock::now(); @@ -450,7 +448,7 @@ TEST(udpcap, Timeout) auto start_time = std::chrono::steady_clock::now(); // blocking receive with a 500ms timeout - size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), 500, &sender_address, &sender_port, error); + const size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), 500, &sender_address, &sender_port, error); // Take End time auto end_time = std::chrono::steady_clock::now(); @@ -470,7 +468,7 @@ TEST(udpcap, Timeout) auto start_time = std::chrono::steady_clock::now(); // blocking receive with a 0ms timeout - size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), 0, &sender_address, &sender_port, error); + const size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), 0, &sender_address, &sender_port, error); // Take End time auto end_time = std::chrono::steady_clock::now(); @@ -488,7 +486,7 @@ TEST(udpcap, Timeout) auto start_time = std::chrono::steady_clock::now(); // blocking receive with a 0ms timeout - size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), 0, &sender_address, &sender_port, error); + const size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), 0, &sender_address, &sender_port, error); // Take End time auto end_time = std::chrono::steady_clock::now(); @@ -525,7 +523,7 @@ TEST(udpcap, ReceiveNotBound) Udpcap::Error error = Udpcap::Error::ErrorCode::GENERIC_ERROR; // blocking receive - size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), &sender_address, &sender_port, error); + const size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), &sender_address, &sender_port, error); // Check if the received datagram is valid and contains "Hello World" ASSERT_EQ(received_bytes, 0); @@ -551,27 +549,27 @@ TEST(udpcap, MulticastReceive) // Bind the udpcap sockets to all interfaces { - bool success = udpcap_socket1.bind(Udpcap::HostAddress::Any(), 14000); + const bool success = udpcap_socket1.bind(Udpcap::HostAddress::Any(), 14000); ASSERT_TRUE(success); } { - bool success = udpcap_socket2.bind(Udpcap::HostAddress::Any(), 14000); + const bool success = udpcap_socket2.bind(Udpcap::HostAddress::Any(), 14000); ASSERT_TRUE(success); } // Join the multicast group 224.0.0.1 on both sockets { - bool success = udpcap_socket1.joinMulticastGroup(Udpcap::HostAddress("224.0.0.1")); + const bool success = udpcap_socket1.joinMulticastGroup(Udpcap::HostAddress("224.0.0.1")); ASSERT_TRUE(success); } { - bool success = udpcap_socket2.joinMulticastGroup(Udpcap::HostAddress("224.0.0.1")); + const bool success = udpcap_socket2.joinMulticastGroup(Udpcap::HostAddress("224.0.0.1")); ASSERT_TRUE(success); } // Join the multicast group 224.0.0.2 on the second socket { - bool success = udpcap_socket2.joinMulticastGroup(Udpcap::HostAddress("224.0.0.2")); + const bool success = udpcap_socket2.joinMulticastGroup(Udpcap::HostAddress("224.0.0.2")); ASSERT_TRUE(success); } @@ -598,7 +596,7 @@ TEST(udpcap, MulticastReceive) std::vector received_datagram; received_datagram.resize(65536); - size_t bytes_received = udpcap_socket1.receiveDatagram(received_datagram.data(), received_datagram.size(), &sender_address, &sender_port, error); + const size_t bytes_received = udpcap_socket1.receiveDatagram(received_datagram.data(), received_datagram.size(), &sender_address, &sender_port, error); received_datagram.resize(bytes_received); if (error) @@ -635,7 +633,7 @@ TEST(udpcap, MulticastReceive) std::vector received_datagram; received_datagram.resize(65536); - size_t bytes_received = udpcap_socket2.receiveDatagram(received_datagram.data(), received_datagram.size(), &sender_address, &sender_port, error); + const size_t bytes_received = udpcap_socket2.receiveDatagram(received_datagram.data(), received_datagram.size(), &sender_address, &sender_port, error); received_datagram.resize(bytes_received); if (error) diff --git a/udpcap/include/udpcap/host_address.h b/udpcap/include/udpcap/host_address.h index e6ba5ce..1eb9ad6 100644 --- a/udpcap/include/udpcap/host_address.h +++ b/udpcap/include/udpcap/host_address.h @@ -20,7 +20,10 @@ #pragma once #include + +// IWYU pragma: begin_exports #include +// IWYU pragma: end_exports namespace Udpcap { @@ -57,7 +60,7 @@ namespace Udpcap UDPCAP_EXPORT HostAddress(const std::string& address); /** @brief Constructs a HostAddress from a 32bit integer in host byte order. */ - UDPCAP_EXPORT HostAddress(const uint32_t address); + UDPCAP_EXPORT HostAddress(uint32_t address); /** @brief Checks if the Host Address is valid. * Invalid HostAddresses are created when providing a wrong IPv4 string, diff --git a/udpcap/include/udpcap/npcap_helpers.h b/udpcap/include/udpcap/npcap_helpers.h index 874febb..a764a10 100644 --- a/udpcap/include/udpcap/npcap_helpers.h +++ b/udpcap/include/udpcap/npcap_helpers.h @@ -19,10 +19,12 @@ #pragma once -#include #include +#include +// IWYU pragma: begin_exports #include +// IWYU pragma: end_exports namespace Udpcap { diff --git a/udpcap/include/udpcap/udpcap_socket.h b/udpcap/include/udpcap/udpcap_socket.h index 7ab9308..fc070fa 100644 --- a/udpcap/include/udpcap/udpcap_socket.h +++ b/udpcap/include/udpcap/udpcap_socket.h @@ -19,9 +19,11 @@ #pragma once +// IWYU pragma: begin_exports +#include #include #include -#include +// IWYU pragma: end_exports #include #include @@ -155,7 +157,7 @@ namespace Udpcap */ UDPCAP_EXPORT size_t receiveDatagram(char* data , size_t max_len - , unsigned long timeout_ms + , long long timeout_ms , HostAddress* source_address , uint16_t* source_port , Udpcap::Error& error); @@ -163,7 +165,7 @@ namespace Udpcap // TODO: Copy documentation here UDPCAP_EXPORT size_t receiveDatagram(char* data , size_t max_len - , unsigned long timeout_ms + , long long timeout_ms , Udpcap::Error& error); // TODO: Copy documentation here diff --git a/udpcap/src/host_address.cpp b/udpcap/src/host_address.cpp index 7501164..91e3ce8 100644 --- a/udpcap/src/host_address.cpp +++ b/udpcap/src/host_address.cpp @@ -24,6 +24,8 @@ #include #include +#include +#include namespace Udpcap { @@ -43,7 +45,7 @@ namespace Udpcap valid_ = (inet_pton(AF_INET, address.c_str(), (void*)(&ipv4_)) == 1); } - HostAddress::HostAddress(const uint32_t address) + HostAddress::HostAddress(uint32_t address) : valid_(true) , ipv4_(address) {} diff --git a/udpcap/src/ip_reassembly.cpp b/udpcap/src/ip_reassembly.cpp index 725a5c4..0e49c08 100644 --- a/udpcap/src/ip_reassembly.cpp +++ b/udpcap/src/ip_reassembly.cpp @@ -29,7 +29,11 @@ #pragma warning( pop ) #endif // _MSC_VER +#include +#include #include +#include +#include namespace Udpcap { diff --git a/udpcap/src/ip_reassembly.h b/udpcap/src/ip_reassembly.h index 33968d6..63bbdc6 100644 --- a/udpcap/src/ip_reassembly.h +++ b/udpcap/src/ip_reassembly.h @@ -29,8 +29,8 @@ #endif // _MSC_VER #include -#include #include +#include namespace Udpcap { diff --git a/udpcap/src/npcap_helpers.cpp b/udpcap/src/npcap_helpers.cpp index 83dd8c9..5e0629c 100644 --- a/udpcap/src/npcap_helpers.cpp +++ b/udpcap/src/npcap_helpers.cpp @@ -19,20 +19,21 @@ #include "udpcap/npcap_helpers.h" -#include - -#include -#include #include -#include #include - -#include +#include #include +#include +#include +#include +#include +#include +#include +#include #define NOMINMAX #define WIN32_LEAN_AND_MEAN -#include +#include // IWYU pragma: keep #include diff --git a/udpcap/src/udpcap_socket.cpp b/udpcap/src/udpcap_socket.cpp index b852de7..c7a48d1 100644 --- a/udpcap/src/udpcap_socket.cpp +++ b/udpcap/src/udpcap_socket.cpp @@ -21,6 +21,10 @@ #include "udpcap_socket_private.h" +#include +#include +#include + namespace Udpcap { UdpcapSocket::UdpcapSocket() @@ -45,10 +49,10 @@ namespace Udpcap bool UdpcapSocket::hasPendingDatagrams () const { return udpcap_socket_private_->hasPendingDatagrams(); } - size_t UdpcapSocket::receiveDatagram(char* data, size_t max_len, unsigned long timeout_ms, HostAddress* source_address, uint16_t* source_port, Udpcap::Error& error) { return udpcap_socket_private_->receiveDatagram(data, max_len, timeout_ms, source_address, source_port, error); } - size_t UdpcapSocket::receiveDatagram(char* data, size_t max_len, unsigned long timeout_ms, Udpcap::Error& error) { return udpcap_socket_private_->receiveDatagram(data, max_len, timeout_ms, nullptr, nullptr, error); } - size_t UdpcapSocket::receiveDatagram(char* data, size_t max_len, Udpcap::Error& error) { return udpcap_socket_private_->receiveDatagram(data, max_len, -1, nullptr, nullptr, error); } - size_t UdpcapSocket::receiveDatagram(char* data, size_t max_len, HostAddress* source_address, uint16_t* source_port, Udpcap::Error& error) { return udpcap_socket_private_->receiveDatagram(data, max_len, -1, source_address, source_port, error); } + size_t UdpcapSocket::receiveDatagram(char* data, size_t max_len, long long timeout_ms, HostAddress* source_address, uint16_t* source_port, Udpcap::Error& error) { return udpcap_socket_private_->receiveDatagram(data, max_len, timeout_ms, source_address, source_port, error); } + size_t UdpcapSocket::receiveDatagram(char* data, size_t max_len, long long timeout_ms, Udpcap::Error& error) { return udpcap_socket_private_->receiveDatagram(data, max_len, timeout_ms, nullptr, nullptr, error); } + size_t UdpcapSocket::receiveDatagram(char* data, size_t max_len, Udpcap::Error& error) { return udpcap_socket_private_->receiveDatagram(data, max_len, -1, nullptr, nullptr, error); } + size_t UdpcapSocket::receiveDatagram(char* data, size_t max_len, HostAddress* source_address, uint16_t* source_port, Udpcap::Error& error) { return udpcap_socket_private_->receiveDatagram(data, max_len, -1, source_address, source_port, error); } bool UdpcapSocket::joinMulticastGroup (const HostAddress& group_address) { return udpcap_socket_private_->joinMulticastGroup(group_address); } bool UdpcapSocket::leaveMulticastGroup (const HostAddress& group_address) { return udpcap_socket_private_->leaveMulticastGroup(group_address); } diff --git a/udpcap/src/udpcap_socket_private.cpp b/udpcap/src/udpcap_socket_private.cpp index 0995f7f..c939bd4 100644 --- a/udpcap/src/udpcap_socket_private.cpp +++ b/udpcap/src/udpcap_socket_private.cpp @@ -19,7 +19,11 @@ #include "udpcap_socket_private.h" -#include "udpcap/npcap_helpers.h" +#include +#include +#include + +#include "ip_reassembly.h" #include "log_debug.h" #define WIN32_LEAN_AND_MEAN @@ -27,13 +31,23 @@ #include // User-space defines for NDIS driver communication -#include -#include #include -#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include -#include +#include // IWYU pragma: keep namespace Udpcap { @@ -93,7 +107,7 @@ namespace Udpcap // Valid address => Try to bind to address! - std::unique_lock pcap_devices_lists_lock(pcap_devices_lists_mutex_); + const std::unique_lock pcap_devices_lists_lock(pcap_devices_lists_mutex_); if (local_address.isLoopback()) { @@ -269,7 +283,7 @@ namespace Udpcap size_t UdpcapSocketPrivate::receiveDatagram(char* data , size_t max_len - , unsigned long timeout_ms + , long long timeout_ms , HostAddress* source_address , uint16_t* source_port , Udpcap::Error& error) @@ -343,7 +357,7 @@ namespace Udpcap { CallbackArgsRawPtr callback_args(data, max_len, source_address, source_port, bound_port_, pcpp::LinkLayerType::LINKTYPE_NULL); - int pcap_next_packet_errorcode = pcap_next_ex(pcap_dev.pcap_handle_, &packet_header, &packet_data); + const int pcap_next_packet_errorcode = pcap_next_ex(pcap_dev.pcap_handle_, &packet_header, &packet_data); if (pcap_next_packet_errorcode == 1) { @@ -576,7 +590,7 @@ namespace Udpcap bool UdpcapSocketPrivate::isClosed() const { - std::lock_guard pcap_callback_lock(pcap_devices_callback_mutex_); + const std::lock_guard pcap_callback_lock(pcap_devices_callback_mutex_); return pcap_devices_closed_; } diff --git a/udpcap/src/udpcap_socket_private.h b/udpcap/src/udpcap_socket_private.h index bab61ca..31d8373 100644 --- a/udpcap/src/udpcap_socket_private.h +++ b/udpcap/src/udpcap_socket_private.h @@ -22,12 +22,12 @@ #include #include -#include -#include -#include #include #include +#include +#include #include +#include #define WIN32_LEAN_AND_MEAN #define NOMINMAX @@ -123,7 +123,7 @@ namespace Udpcap size_t receiveDatagram(char* data , size_t max_len - , unsigned long timeout_ms + , long long timeout_ms , HostAddress* source_address , uint16_t* source_port , Udpcap::Error& error); @@ -144,7 +144,7 @@ namespace Udpcap static std::pair getDeviceByIp(const HostAddress& ip); static std::vector> getAllDevices(); - static std::string getMac(pcap_t* const pcap_handle); + static std::string getMac(pcap_t* pcap_handle); bool openPcapDevice_nolock(const std::string& device_name); From eff8a8fae84c32a102fea0607cdd903a43f65501 Mon Sep 17 00:00:00 2001 From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> Date: Thu, 22 Feb 2024 14:21:21 +0100 Subject: [PATCH 06/11] Solved many TODOs --- udpcap/include/udpcap/udpcap_socket.h | 5 --- udpcap/src/udpcap_socket.cpp | 2 - udpcap/src/udpcap_socket_private.cpp | 59 +-------------------------- udpcap/src/udpcap_socket_private.h | 3 -- 4 files changed, 2 insertions(+), 67 deletions(-) diff --git a/udpcap/include/udpcap/udpcap_socket.h b/udpcap/include/udpcap/udpcap_socket.h index fc070fa..4e21adc 100644 --- a/udpcap/include/udpcap/udpcap_socket.h +++ b/udpcap/include/udpcap/udpcap_socket.h @@ -131,11 +131,6 @@ namespace Udpcap */ UDPCAP_EXPORT bool setReceiveBufferSize(int receive_buffer_size); - /** - * @brief Returns whether there are datagrams ready to be read - */ - UDPCAP_EXPORT bool hasPendingDatagrams() const; - /** * @brief Blocks for the given time until a packet arives and copies it to the given memory * diff --git a/udpcap/src/udpcap_socket.cpp b/udpcap/src/udpcap_socket.cpp index c7a48d1..dcd2dde 100644 --- a/udpcap/src/udpcap_socket.cpp +++ b/udpcap/src/udpcap_socket.cpp @@ -47,8 +47,6 @@ namespace Udpcap bool UdpcapSocket::setReceiveBufferSize (int receive_buffer_size) { return udpcap_socket_private_->setReceiveBufferSize(receive_buffer_size); } - bool UdpcapSocket::hasPendingDatagrams () const { return udpcap_socket_private_->hasPendingDatagrams(); } - size_t UdpcapSocket::receiveDatagram(char* data, size_t max_len, long long timeout_ms, HostAddress* source_address, uint16_t* source_port, Udpcap::Error& error) { return udpcap_socket_private_->receiveDatagram(data, max_len, timeout_ms, source_address, source_port, error); } size_t UdpcapSocket::receiveDatagram(char* data, size_t max_len, long long timeout_ms, Udpcap::Error& error) { return udpcap_socket_private_->receiveDatagram(data, max_len, timeout_ms, nullptr, nullptr, error); } size_t UdpcapSocket::receiveDatagram(char* data, size_t max_len, Udpcap::Error& error) { return udpcap_socket_private_->receiveDatagram(data, max_len, -1, nullptr, nullptr, error); } diff --git a/udpcap/src/udpcap_socket_private.cpp b/udpcap/src/udpcap_socket_private.cpp index c939bd4..31310df 100644 --- a/udpcap/src/udpcap_socket_private.cpp +++ b/udpcap/src/udpcap_socket_private.cpp @@ -69,11 +69,6 @@ namespace Udpcap { close(); } - //UdpcapSocketPrivate::~UdpcapSocketPrivate() - //{ - // // @todo: reinvestigate why it crashes on close. (Maybe check if i have implemented copy / move constructors properly) - // //close(); - //} bool UdpcapSocketPrivate::isValid() const { @@ -231,56 +226,6 @@ namespace Udpcap return true; } - bool UdpcapSocketPrivate::hasPendingDatagrams() const - { - if (!is_valid_) - { - // Invalid socket, cannot bind => fail! - LOG_DEBUG("Has Pending Datagrams error: Socket is invalid"); - return false; - } - - if (!bound_state_) - { - // Not bound => fail! - LOG_DEBUG("Has Pending Datagrams error: Socket is not bound"); - return false; - } - - // Lock the lists of open pcap devices in read-mode. We may use the handles, but not modify the lists themselfes. - const std::shared_lock pcap_devices_lists_lock(pcap_devices_lists_mutex_); - - { - const std::lock_guard pcap_callback_lock(pcap_devices_callback_mutex_); - if (pcap_devices_closed_) - { - // No open devices => fail! - LOG_DEBUG("Has Pending Datagrams error: Socket has been closed."); - return false; - } - } - - if (pcap_win32_handles_.empty()) - { - // No open devices => fail! - LOG_DEBUG("Has Pending Datagrams error: No open devices"); - return false; - } - - // Wait 0 ms for data - DWORD num_handles = static_cast(pcap_win32_handles_.size()); - if (num_handles > MAXIMUM_WAIT_OBJECTS) - { - LOG_DEBUG("WARNING: Too many open Adapters. " + std::to_string(num_handles) + " adapters are open, only " + std::to_string(MAXIMUM_WAIT_OBJECTS) + " are supported."); - num_handles = MAXIMUM_WAIT_OBJECTS; - } - - const DWORD wait_result = WaitForMultipleObjects(num_handles, pcap_win32_handles_.data(), static_cast(false), 0); - - // Check if any HADNLE was in signaled state - return((wait_result >= WAIT_OBJECT_0) && wait_result <= (WAIT_OBJECT_0 + num_handles - 1)); - } - size_t UdpcapSocketPrivate::receiveDatagram(char* data , size_t max_len , long long timeout_ms @@ -319,7 +264,7 @@ namespace Udpcap // Check for data on pcap devices until we are either out of time or have // received a datagaram. A datagram may consist of multiple packaets in // case of IP Fragmentation. - while (true) // TODO: respect the timeout parameter + while (true) { bool received_any_data = false; @@ -724,7 +669,7 @@ namespace Udpcap if (receive_buffer_size_ > 0) { - pcap_set_buffer_size(pcap_handle, receive_buffer_size_); // TODO: the buffer size should probably not be zero by default. Currently (2024-01-31) it is. + pcap_set_buffer_size(pcap_handle, receive_buffer_size_); } const int errorcode = pcap_activate(pcap_handle); // TODO : If pcap_activate() fails, the pcap_t * is not closed and freed; it should be closed using pcap_close(3PCAP). diff --git a/udpcap/src/udpcap_socket_private.h b/udpcap/src/udpcap_socket_private.h index 31d8373..58a917d 100644 --- a/udpcap/src/udpcap_socket_private.h +++ b/udpcap/src/udpcap_socket_private.h @@ -118,9 +118,6 @@ namespace Udpcap bool setReceiveBufferSize(int buffer_size); - // TODO: Re-implement or remove. This is currently (2024-02-06) implemented faulty. - bool hasPendingDatagrams() const; - size_t receiveDatagram(char* data , size_t max_len , long long timeout_ms From 2cd4505745c9607974a023256559f21587818793 Mon Sep 17 00:00:00 2001 From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> Date: Thu, 22 Feb 2024 14:22:33 +0100 Subject: [PATCH 07/11] Upgraded GH Action to non-deprecated versions --- .github/workflows/build-windows.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 900b0e6..8e148e3 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -41,7 +41,7 @@ jobs: } - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'true' fetch-depth: 0 @@ -81,7 +81,7 @@ jobs: echo "CMAKE_PROJECT_VERSION=$cmake_project_version" >> "$Env:GITHUB_ENV" - name: Upload binaries - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.PROJECT_NAME }}-${{ env.CMAKE_PROJECT_VERSION }}-windows-${{ matrix.build_arch }}-${{ env.VS_NAME }}-${{ matrix.library_type }} path: ${{github.workspace}}/${{env.INSTALL_PREFIX}} From 5e72a8a0d99485f50c262c3d47abea972cbd36ef Mon Sep 17 00:00:00 2001 From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> Date: Fri, 23 Feb 2024 07:49:53 +0100 Subject: [PATCH 08/11] Solved some TODOs --- tests/udpcap_test/src/udpcap_test.cpp | 10 +++++-- udpcap/src/udpcap_socket_private.cpp | 43 +++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/tests/udpcap_test/src/udpcap_test.cpp b/tests/udpcap_test/src/udpcap_test.cpp index 2060d5c..41ec025 100644 --- a/tests/udpcap_test/src/udpcap_test.cpp +++ b/tests/udpcap_test/src/udpcap_test.cpp @@ -433,7 +433,11 @@ TEST(udpcap, Timeout) auto end_time = std::chrono::steady_clock::now(); ASSERT_EQ(error, Udpcap::Error::TIMEOUT); - ASSERT_EQ(received_bytes, 0); + ASSERT_EQ(received_bytes, 0); + + // Print the used time in milliseconds to console + std::cout << "Time: " << std::chrono::duration_cast(end_time - start_time).count() << std::endl; + ASSERT_GE(std::chrono::duration_cast(end_time - start_time).count(), 100); // TODO: This sometimes fails. Check why! } @@ -676,8 +680,8 @@ TEST(udpcap, MulticastReceive) received_messages2.wait_for([](int value) { return value >= 2; }, std::chrono::milliseconds(500)); // Check if the received message counters - ASSERT_EQ(received_messages1.get(), 1); - ASSERT_EQ(received_messages2.get(), 2); + ASSERT_EQ(received_messages1.get(), 1) << "Make sure, your FIREWALL is DISABLED!!!"; + ASSERT_EQ(received_messages2.get(), 2) << "Make sure, your FIREWALL is DISABLED!!!"; // Close the sockets asio_socket.close(); diff --git a/udpcap/src/udpcap_socket_private.cpp b/udpcap/src/udpcap_socket_private.cpp index 31310df..e64fe9d 100644 --- a/udpcap/src/udpcap_socket_private.cpp +++ b/udpcap/src/udpcap_socket_private.cpp @@ -304,11 +304,19 @@ namespace Udpcap const int pcap_next_packet_errorcode = pcap_next_ex(pcap_dev.pcap_handle_, &packet_header, &packet_data); + // Possible return values: + // 1: Success! We received a packet. + // 0: Timeout. As we set the handle to non-blocking mode, this always happens if no packet is available. + // PCAP_ERROR_NOT_ACTIVATED: The pcap handle is not activated. This should never happen, as we only use activated handles. + // PCAP_ERROR: An error occured. Details can be retrieved using pcap_geterr() or printed to the console using pcap_perror(). + if (pcap_next_packet_errorcode == 1) { received_any_data = true; - // Success! + // Success! We received a packet. Call the packet handler, which + // also handles IP reassembly and sets the success variable, if we + // successfully received an entire UDP datagram. PacketHandlerRawPtr(reinterpret_cast(&callback_args), packet_header, packet_data); if (callback_args.success_) @@ -318,9 +326,31 @@ namespace Udpcap return callback_args.bytes_copied_; } } + else if (pcap_next_packet_errorcode == 0) + { + // Timeout. No packet available. We will continue receiving data, if there is time left. + continue; + } + else if (pcap_next_packet_errorcode == PCAP_ERROR_NOT_ACTIVATED) + { + // This should never happen, as we only use activated handles. + error = Udpcap::Error(Udpcap::Error::NOT_BOUND, "Internal error: PCAP handle not activated"); + LOG_DEBUG(error.ToString()); // This should never happen in a proper application + return 0; + } + else if (pcap_next_packet_errorcode == PCAP_ERROR) + { + // An error occured. Details can be retrieved using pcap_geterr() or printed to the console using pcap_perror(). + error = Udpcap::Error(Udpcap::Error::GENERIC_ERROR, pcap_geterr(pcap_dev.pcap_handle_)); + LOG_DEBUG(error.ToString()); + return 0; + } else { - // TODO: Handle errors coming from pcap. + // This should never happen according to the documentation. + error = Udpcap::Error(Udpcap::Error::GENERIC_ERROR, "Internal error: Unknown error code " + std::to_string(pcap_next_packet_errorcode)); + LOG_DEBUG(error.ToString()); // This should never happen in a proper application + return 0; } } } @@ -672,7 +702,7 @@ namespace Udpcap pcap_set_buffer_size(pcap_handle, receive_buffer_size_); } - const int errorcode = pcap_activate(pcap_handle); // TODO : If pcap_activate() fails, the pcap_t * is not closed and freed; it should be closed using pcap_close(3PCAP). + const int errorcode = pcap_activate(pcap_handle); switch (errorcode) { case 0: @@ -685,24 +715,31 @@ namespace Udpcap break; case PCAP_ERROR_ACTIVATED: fprintf(stderr, "%s", ("UdpcapSocket ERROR: Device " + device_name + " already activated").c_str()); + pcap_close(pcap_handle); return false; case PCAP_ERROR_NO_SUCH_DEVICE: pcap_perror(pcap_handle, ("UdpcapSocket ERROR: Device " + device_name + " does not exist").c_str()); + pcap_close(pcap_handle); return false; case PCAP_ERROR_PERM_DENIED: pcap_perror(pcap_handle, ("UdpcapSocket ERROR: Device " + device_name + ": Permissoin denied").c_str()); + pcap_close(pcap_handle); return false; case PCAP_ERROR_RFMON_NOTSUP: fprintf(stderr, "%s", ("UdpcapSocket ERROR: Device " + device_name + ": Does not support monitoring").c_str()); + pcap_close(pcap_handle); return false; case PCAP_ERROR_IFACE_NOT_UP: fprintf(stderr, "%s", ("UdpcapSocket ERROR: Device " + device_name + ": Interface is down").c_str()); + pcap_close(pcap_handle); return false; case PCAP_ERROR: pcap_perror(pcap_handle, ("UdpcapSocket ERROR: Device " + device_name).c_str()); + pcap_close(pcap_handle); return false; default: fprintf(stderr, "%s", ("UdpcapSocket ERROR: Device " + device_name + ": Unknown error").c_str()); + pcap_close(pcap_handle); return false; } From bc8391b7b633b12cc337b58a32ec14e248584b2b Mon Sep 17 00:00:00 2001 From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> Date: Fri, 23 Feb 2024 09:06:43 +0100 Subject: [PATCH 09/11] Solved some TODOs --- udpcap/CMakeLists.txt | 8 +++++--- udpcap/sourcetree.cmake | 12 ------------ udpcap/src/ip_reassembly.h | 1 - 3 files changed, 5 insertions(+), 16 deletions(-) delete mode 100644 udpcap/sourcetree.cmake diff --git a/udpcap/CMakeLists.txt b/udpcap/CMakeLists.txt index 2da842e..eeade40 100644 --- a/udpcap/CMakeLists.txt +++ b/udpcap/CMakeLists.txt @@ -71,7 +71,7 @@ generate_export_header(${PROJECT_NAME} add_library (udpcap::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) target_link_libraries(${PROJECT_NAME} - PRIVATE #TODO: This used to be private + PRIVATE npcap::npcap pcapplusplus::pcapplusplus $<$:ws2_32> @@ -129,8 +129,10 @@ endif() ################################## -include(sourcetree.cmake) -create_source_tree(${includes} ${sources}) +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES + ${includes} + ${sources} +) ################################################################################ ### Installation rules diff --git a/udpcap/sourcetree.cmake b/udpcap/sourcetree.cmake deleted file mode 100644 index 04969f5..0000000 --- a/udpcap/sourcetree.cmake +++ /dev/null @@ -1,12 +0,0 @@ -function(create_source_tree) - foreach(_source IN ITEMS ${ARGN}) - if (IS_ABSOLUTE "${_source}") - file(RELATIVE_PATH _source_rel "${CMAKE_CURRENT_SOURCE_DIR}" "${_source}") - else() - set(_source_rel "${_source}") - endif() - get_filename_component(_source_path "${_source_rel}" PATH) - string(REPLACE "/" "\\" _source_path_msvc "${_source_path}") - source_group("${_source_path_msvc}" FILES "${_source}") - endforeach() -endfunction(create_source_tree) diff --git a/udpcap/src/ip_reassembly.h b/udpcap/src/ip_reassembly.h index 63bbdc6..53c8c93 100644 --- a/udpcap/src/ip_reassembly.h +++ b/udpcap/src/ip_reassembly.h @@ -121,7 +121,6 @@ namespace Udpcap * - If the reassembled packet isn't ready then NULL is returned */ pcpp::Packet* processPacket(pcpp::RawPacket* fragment, pcpp::IPReassembly::ReassemblyStatus& status, pcpp::ProtocolType parse_until = pcpp::UnknownProtocol, pcpp::OsiModelLayer parse_until_layer = pcpp::OsiModelLayerUnknown); - // TODO: Implement rawPacket function /** * Get the maximum capacity as determined in the c'tor From d3d3098122846ab0d8a4ab851536d8e63b5b1339 Mon Sep 17 00:00:00 2001 From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> Date: Fri, 23 Feb 2024 09:14:20 +0100 Subject: [PATCH 10/11] Unified license headers --- samples/asio_sender_multicast/CMakeLists.txt | 30 ++++++++--------- samples/asio_sender_multicast/src/main.cpp | 31 ++++++++---------- samples/asio_sender_unicast/CMakeLists.txt | 30 ++++++++--------- samples/asio_sender_unicast/src/main.cpp | 31 ++++++++---------- .../udpcap_receiver_multicast/CMakeLists.txt | 30 ++++++++--------- .../udpcap_receiver_multicast/src/main.cpp | 31 ++++++++---------- .../udpcap_receiver_unicast/CMakeLists.txt | 30 ++++++++--------- samples/udpcap_receiver_unicast/src/main.cpp | 31 ++++++++---------- tests/udpcap_test/CMakeLists.txt | 30 ++++++++--------- tests/udpcap_test/src/udpcap_test.cpp | 31 ++++++++---------- udpcap/CMakeLists.txt | 30 ++++++++--------- udpcap/include/udpcap/host_address.h | 31 ++++++++---------- udpcap/include/udpcap/npcap_helpers.h | 31 ++++++++---------- udpcap/include/udpcap/udpcap_socket.h | 31 ++++++++---------- udpcap/src/host_address.cpp | 31 ++++++++---------- udpcap/src/ip_reassembly.cpp | 31 ++++++++---------- udpcap/src/ip_reassembly.h | 31 ++++++++---------- udpcap/src/log_debug.h | 31 ++++++++---------- udpcap/src/npcap_helpers.cpp | 32 ++++++++----------- udpcap/src/udpcap_socket.cpp | 31 ++++++++---------- udpcap/src/udpcap_socket_private.cpp | 31 ++++++++---------- udpcap/src/udpcap_socket_private.h | 31 ++++++++---------- 22 files changed, 308 insertions(+), 369 deletions(-) diff --git a/samples/asio_sender_multicast/CMakeLists.txt b/samples/asio_sender_multicast/CMakeLists.txt index 65256ec..3c9e4ba 100644 --- a/samples/asio_sender_multicast/CMakeLists.txt +++ b/samples/asio_sender_multicast/CMakeLists.txt @@ -1,20 +1,18 @@ -# =========================== LICENSE ================================= -# -# Copyright (C) 2016 - 2022 Continental Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# +################################################################################ +# Copyright (c) 2016 Continental Corporation +# +# This program and the accompanying materials are made available under the +# terms of the Apache License, Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0. +# # Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# =========================== LICENSE ================================= +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# SPDX-License-Identifier: Apache-2.0 +################################################################################ cmake_minimum_required(VERSION 3.13) diff --git a/samples/asio_sender_multicast/src/main.cpp b/samples/asio_sender_multicast/src/main.cpp index 674b76c..dcac4bb 100644 --- a/samples/asio_sender_multicast/src/main.cpp +++ b/samples/asio_sender_multicast/src/main.cpp @@ -1,21 +1,18 @@ -/* =========================== LICENSE ================================= - * - * Copyright (C) 2016 - 2022 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * +/******************************************************************************** + * Copyright (c) 2016 Continental Corporation + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * =========================== LICENSE ================================= - */ + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ #include diff --git a/samples/asio_sender_unicast/CMakeLists.txt b/samples/asio_sender_unicast/CMakeLists.txt index 12b46a5..8fcc704 100644 --- a/samples/asio_sender_unicast/CMakeLists.txt +++ b/samples/asio_sender_unicast/CMakeLists.txt @@ -1,20 +1,18 @@ -# =========================== LICENSE ================================= -# -# Copyright (C) 2016 - 2022 Continental Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# +################################################################################ +# Copyright (c) 2016 Continental Corporation +# +# This program and the accompanying materials are made available under the +# terms of the Apache License, Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0. +# # Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# =========================== LICENSE ================================= +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# SPDX-License-Identifier: Apache-2.0 +################################################################################ cmake_minimum_required(VERSION 3.13) diff --git a/samples/asio_sender_unicast/src/main.cpp b/samples/asio_sender_unicast/src/main.cpp index 0a2ecbd..1d5c2b9 100644 --- a/samples/asio_sender_unicast/src/main.cpp +++ b/samples/asio_sender_unicast/src/main.cpp @@ -1,21 +1,18 @@ -/* =========================== LICENSE ================================= - * - * Copyright (C) 2016 - 2022 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * +/******************************************************************************** + * Copyright (c) 2016 Continental Corporation + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * =========================== LICENSE ================================= - */ + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ #include diff --git a/samples/udpcap_receiver_multicast/CMakeLists.txt b/samples/udpcap_receiver_multicast/CMakeLists.txt index 39b858b..71d1dcd 100644 --- a/samples/udpcap_receiver_multicast/CMakeLists.txt +++ b/samples/udpcap_receiver_multicast/CMakeLists.txt @@ -1,20 +1,18 @@ -# =========================== LICENSE ================================= -# -# Copyright (C) 2016 - 2022 Continental Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# +################################################################################ +# Copyright (c) 2016 Continental Corporation +# +# This program and the accompanying materials are made available under the +# terms of the Apache License, Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0. +# # Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# =========================== LICENSE ================================= +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# SPDX-License-Identifier: Apache-2.0 +################################################################################ cmake_minimum_required(VERSION 3.13) diff --git a/samples/udpcap_receiver_multicast/src/main.cpp b/samples/udpcap_receiver_multicast/src/main.cpp index 7346947..b9f36b0 100644 --- a/samples/udpcap_receiver_multicast/src/main.cpp +++ b/samples/udpcap_receiver_multicast/src/main.cpp @@ -1,21 +1,18 @@ -/* =========================== LICENSE ================================= - * - * Copyright (C) 2016 - 2022 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * +/******************************************************************************** + * Copyright (c) 2016 Continental Corporation + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * =========================== LICENSE ================================= - */ + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ #include diff --git a/samples/udpcap_receiver_unicast/CMakeLists.txt b/samples/udpcap_receiver_unicast/CMakeLists.txt index 2657456..69e88ff 100644 --- a/samples/udpcap_receiver_unicast/CMakeLists.txt +++ b/samples/udpcap_receiver_unicast/CMakeLists.txt @@ -1,20 +1,18 @@ -# =========================== LICENSE ================================= -# -# Copyright (C) 2016 - 2022 Continental Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# +################################################################################ +# Copyright (c) 2016 Continental Corporation +# +# This program and the accompanying materials are made available under the +# terms of the Apache License, Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0. +# # Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# =========================== LICENSE ================================= +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# SPDX-License-Identifier: Apache-2.0 +################################################################################ cmake_minimum_required(VERSION 3.13) diff --git a/samples/udpcap_receiver_unicast/src/main.cpp b/samples/udpcap_receiver_unicast/src/main.cpp index c96bd8c..80a0546 100644 --- a/samples/udpcap_receiver_unicast/src/main.cpp +++ b/samples/udpcap_receiver_unicast/src/main.cpp @@ -1,21 +1,18 @@ -/* =========================== LICENSE ================================= - * - * Copyright (C) 2016 - 2022 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * +/******************************************************************************** + * Copyright (c) 2016 Continental Corporation + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * =========================== LICENSE ================================= - */ + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ #include diff --git a/tests/udpcap_test/CMakeLists.txt b/tests/udpcap_test/CMakeLists.txt index e87e86d..e27936d 100644 --- a/tests/udpcap_test/CMakeLists.txt +++ b/tests/udpcap_test/CMakeLists.txt @@ -1,20 +1,18 @@ -# =========================== LICENSE ================================= -# -# Copyright (C) 2016 - 2022 Continental Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# +################################################################################ +# Copyright (c) 2024 Continental Corporation +# +# This program and the accompanying materials are made available under the +# terms of the Apache License, Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0. +# # Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# =========================== LICENSE ================================= +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# SPDX-License-Identifier: Apache-2.0 +################################################################################ cmake_minimum_required(VERSION 3.13) diff --git a/tests/udpcap_test/src/udpcap_test.cpp b/tests/udpcap_test/src/udpcap_test.cpp index 41ec025..767bf90 100644 --- a/tests/udpcap_test/src/udpcap_test.cpp +++ b/tests/udpcap_test/src/udpcap_test.cpp @@ -1,21 +1,18 @@ -/* =========================== LICENSE ================================= - * - * Copyright (C) 2016 - 2022 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * +/******************************************************************************** + * Copyright (c) 2024 Continental Corporation + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * =========================== LICENSE ================================= - */ + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ #include diff --git a/udpcap/CMakeLists.txt b/udpcap/CMakeLists.txt index eeade40..0e7c07b 100644 --- a/udpcap/CMakeLists.txt +++ b/udpcap/CMakeLists.txt @@ -1,20 +1,18 @@ -# =========================== LICENSE ================================= -# -# Copyright (C) 2016 - 2022 Continental Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# +################################################################################ +# Copyright (c) 2016 Continental Corporation +# +# This program and the accompanying materials are made available under the +# terms of the Apache License, Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0. +# # Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# =========================== LICENSE ================================= +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# SPDX-License-Identifier: Apache-2.0 +################################################################################ cmake_minimum_required(VERSION 3.13) diff --git a/udpcap/include/udpcap/host_address.h b/udpcap/include/udpcap/host_address.h index 1eb9ad6..e1f2e05 100644 --- a/udpcap/include/udpcap/host_address.h +++ b/udpcap/include/udpcap/host_address.h @@ -1,21 +1,18 @@ -/* =========================== LICENSE ================================= - * - * Copyright (C) 2016 - 2022 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * +/******************************************************************************** + * Copyright (c) 2016 Continental Corporation + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * =========================== LICENSE ================================= - */ + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ #pragma once diff --git a/udpcap/include/udpcap/npcap_helpers.h b/udpcap/include/udpcap/npcap_helpers.h index a764a10..b780a6e 100644 --- a/udpcap/include/udpcap/npcap_helpers.h +++ b/udpcap/include/udpcap/npcap_helpers.h @@ -1,21 +1,18 @@ -/* =========================== LICENSE ================================= - * - * Copyright (C) 2016 - 2022 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * +/******************************************************************************** + * Copyright (c) 2016 Continental Corporation + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * =========================== LICENSE ================================= - */ + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ #pragma once diff --git a/udpcap/include/udpcap/udpcap_socket.h b/udpcap/include/udpcap/udpcap_socket.h index 4e21adc..902632f 100644 --- a/udpcap/include/udpcap/udpcap_socket.h +++ b/udpcap/include/udpcap/udpcap_socket.h @@ -1,21 +1,18 @@ -/* =========================== LICENSE ================================= - * - * Copyright (C) 2016 - 2022 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * +/******************************************************************************** + * Copyright (c) 2016 Continental Corporation + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * =========================== LICENSE ================================= - */ + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ #pragma once diff --git a/udpcap/src/host_address.cpp b/udpcap/src/host_address.cpp index 91e3ce8..f4a0a95 100644 --- a/udpcap/src/host_address.cpp +++ b/udpcap/src/host_address.cpp @@ -1,21 +1,18 @@ -/* =========================== LICENSE ================================= - * - * Copyright (C) 2016 - 2022 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * +/******************************************************************************** + * Copyright (c) 2016 Continental Corporation + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * =========================== LICENSE ================================= - */ + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ #include "udpcap/host_address.h" diff --git a/udpcap/src/ip_reassembly.cpp b/udpcap/src/ip_reassembly.cpp index 0e49c08..76eb0d7 100644 --- a/udpcap/src/ip_reassembly.cpp +++ b/udpcap/src/ip_reassembly.cpp @@ -1,21 +1,18 @@ -/* =========================== LICENSE ================================= - * - * Copyright (C) 2016 - 2022 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * +/******************************************************************************** + * Copyright (c) 2016 Continental Corporation + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * =========================== LICENSE ================================= - */ + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ #include "ip_reassembly.h" diff --git a/udpcap/src/ip_reassembly.h b/udpcap/src/ip_reassembly.h index 53c8c93..c31c56e 100644 --- a/udpcap/src/ip_reassembly.h +++ b/udpcap/src/ip_reassembly.h @@ -1,21 +1,18 @@ -/* =========================== LICENSE ================================= - * - * Copyright (C) 2016 - 2022 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * +/******************************************************************************** + * Copyright (c) 2016 Continental Corporation + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * =========================== LICENSE ================================= - */ + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ #pragma once diff --git a/udpcap/src/log_debug.h b/udpcap/src/log_debug.h index 0946b89..eb89439 100644 --- a/udpcap/src/log_debug.h +++ b/udpcap/src/log_debug.h @@ -1,21 +1,18 @@ -/* =========================== LICENSE ================================= - * - * Copyright (C) 2016 - 2022 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * +/******************************************************************************** + * Copyright (c) 2016 Continental Corporation + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * =========================== LICENSE ================================= - */ + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ #pragma once diff --git a/udpcap/src/npcap_helpers.cpp b/udpcap/src/npcap_helpers.cpp index 5e0629c..82fc897 100644 --- a/udpcap/src/npcap_helpers.cpp +++ b/udpcap/src/npcap_helpers.cpp @@ -1,22 +1,18 @@ -/* =========================== LICENSE ================================= - * - * Copyright (C) 2016 - 2022 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * +/******************************************************************************** + * Copyright (c) 2016 Continental Corporation + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * =========================== LICENSE ================================= - */ - + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ #include "udpcap/npcap_helpers.h" #include diff --git a/udpcap/src/udpcap_socket.cpp b/udpcap/src/udpcap_socket.cpp index dcd2dde..a7b1117 100644 --- a/udpcap/src/udpcap_socket.cpp +++ b/udpcap/src/udpcap_socket.cpp @@ -1,21 +1,18 @@ -/* =========================== LICENSE ================================= - * - * Copyright (C) 2016 - 2022 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * +/******************************************************************************** + * Copyright (c) 2016 Continental Corporation + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * =========================== LICENSE ================================= - */ + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ #include "udpcap/udpcap_socket.h" diff --git a/udpcap/src/udpcap_socket_private.cpp b/udpcap/src/udpcap_socket_private.cpp index e64fe9d..419ebb8 100644 --- a/udpcap/src/udpcap_socket_private.cpp +++ b/udpcap/src/udpcap_socket_private.cpp @@ -1,21 +1,18 @@ -/* =========================== LICENSE ================================= - * - * Copyright (C) 2016 - 2022 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * +/******************************************************************************** + * Copyright (c) 2016 Continental Corporation + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * =========================== LICENSE ================================= - */ + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ #include "udpcap_socket_private.h" diff --git a/udpcap/src/udpcap_socket_private.h b/udpcap/src/udpcap_socket_private.h index 58a917d..422b3de 100644 --- a/udpcap/src/udpcap_socket_private.h +++ b/udpcap/src/udpcap_socket_private.h @@ -1,21 +1,18 @@ -/* =========================== LICENSE ================================= - * - * Copyright (C) 2016 - 2022 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * +/******************************************************************************** + * Copyright (c) 2016 Continental Corporation + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * =========================== LICENSE ================================= - */ + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ #pragma once From d914707376700fc4e384a9bc6e79a388499b4592 Mon Sep 17 00:00:00 2001 From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:17:01 +0100 Subject: [PATCH 11/11] Solved TODOs --- tests/udpcap_test/CMakeLists.txt | 2 + tests/udpcap_test/src/udpcap_test.cpp | 197 ++++++++++++++++++++------ udpcap/include/udpcap/udpcap_socket.h | 26 +++- udpcap/src/ip_reassembly.h | 2 +- udpcap/src/udpcap_socket_private.cpp | 15 +- 5 files changed, 185 insertions(+), 57 deletions(-) diff --git a/tests/udpcap_test/CMakeLists.txt b/tests/udpcap_test/CMakeLists.txt index e27936d..bd97916 100644 --- a/tests/udpcap_test/CMakeLists.txt +++ b/tests/udpcap_test/CMakeLists.txt @@ -35,6 +35,8 @@ add_executable (${PROJECT_NAME} target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_14) +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${sources}) + target_link_libraries (${PROJECT_NAME} udpcap::udpcap GTest::gtest_main diff --git a/tests/udpcap_test/src/udpcap_test.cpp b/tests/udpcap_test/src/udpcap_test.cpp index 767bf90..a7fe88b 100644 --- a/tests/udpcap_test/src/udpcap_test.cpp +++ b/tests/udpcap_test/src/udpcap_test.cpp @@ -108,8 +108,15 @@ TEST(udpcap, SimpleReceive) ASSERT_TRUE(success); } + // Create an asio UDP sender socket + asio::io_service io_service; + const asio::ip::udp::endpoint endpoint(asio::ip::make_address("127.0.0.1"), 14000); + asio::ip::udp::socket asio_socket(io_service, endpoint.protocol()); + asio_socket.connect(endpoint); + const auto asio_local_endpoint = asio_socket.local_endpoint(); + // Blocking receive a datagram - std::thread receive_thread([&udpcap_socket, &received_messages]() + std::thread receive_thread([&udpcap_socket, &received_messages, &asio_local_endpoint]() { // Initialize variables for the sender's address and port Udpcap::HostAddress sender_address; @@ -131,15 +138,13 @@ TEST(udpcap, SimpleReceive) ASSERT_FALSE(received_datagram.empty()); ASSERT_EQ(std::string(received_datagram.data(), received_datagram.size()), "Hello World"); + // Check if the sender's address and port are correct + ASSERT_EQ(sender_address.toString(), asio_local_endpoint.address().to_string()); + ASSERT_EQ(sender_port, asio_local_endpoint.port()); + received_messages++; }); - // Create an asio UDP sender socket - asio::io_service io_service; - - const asio::ip::udp::endpoint endpoint(asio::ip::make_address("127.0.0.1"), 14000); - asio::ip::udp::socket asio_socket(io_service, endpoint.protocol()); - std::string buffer_string = "Hello World"; asio_socket.send_to(asio::buffer(buffer_string), endpoint); @@ -173,11 +178,21 @@ TEST(udpcap, MultipleSmallPackages) ASSERT_TRUE(success); } + // Create an asio UDP sender socket + asio::io_service io_service; + const asio::ip::udp::endpoint endpoint(asio::ip::make_address("127.0.0.1"), 14000); + asio::ip::udp::socket asio_socket(io_service, endpoint.protocol()); + asio_socket.connect(endpoint); + const auto asio_local_endpoint = asio_socket.local_endpoint(); + // Receive datagrams in a separate thread - std::thread receive_thread([&udpcap_socket, &received_messages, num_packages_to_send]() + std::thread receive_thread([&udpcap_socket, &received_messages, num_packages_to_send, &asio_local_endpoint]() { while (true) { + // Initialize variables for the sender's address and port + Udpcap::HostAddress sender_address; + uint16_t sender_port(0); Udpcap::Error error = Udpcap::Error::ErrorCode::GENERIC_ERROR; // Allocate buffer with max udp datagram size @@ -185,7 +200,7 @@ TEST(udpcap, MultipleSmallPackages) received_datagram.resize(65536); // blocking receive - const size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), error); + const size_t received_bytes = udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), &sender_address, &sender_port, error); if (error) { @@ -207,16 +222,14 @@ TEST(udpcap, MultipleSmallPackages) ASSERT_FALSE(received_datagram.empty()); ASSERT_EQ(std::string(received_datagram.data(), received_datagram.size()), "Hello World"); + // Check if the sender's address and port are correct + ASSERT_EQ(sender_address.toString(), asio_local_endpoint.address().to_string()); + ASSERT_EQ(sender_port, asio_local_endpoint.port()); + received_messages++; } }); - // Create an asio UDP sender socket - asio::io_service io_service; - - const asio::ip::udp::endpoint endpoint(asio::ip::make_address("127.0.0.1"), 14000); - asio::ip::udp::socket asio_socket(io_service, endpoint.protocol()); - std::string buffer_string = "Hello World"; for (int i = 0; i < num_packages_to_send; i++) { @@ -252,9 +265,10 @@ TEST(udpcap, SimpleReceiveWithBuffer) // Create an asio UDP sender socket asio::io_service io_service; - const asio::ip::udp::endpoint endpoint(asio::ip::make_address("127.0.0.1"), 14000); asio::ip::udp::socket asio_socket(io_service, endpoint.protocol()); + asio_socket.connect(endpoint); + const auto asio_local_endpoint = asio_socket.local_endpoint(); // Send "Hello World" without currently polling the socket std::string buffer_string = "Hello World"; @@ -263,15 +277,18 @@ TEST(udpcap, SimpleReceiveWithBuffer) std::this_thread::sleep_for(std::chrono::milliseconds(10)); // Receive the datagram - std::thread receive_thread([&udpcap_socket, &received_messages]() + std::thread receive_thread([&udpcap_socket, &received_messages, &asio_local_endpoint]() { + // Initialize variables for the sender's address and port Udpcap::Error error = Udpcap::Error::ErrorCode::GENERIC_ERROR; + Udpcap::HostAddress sender_address; + uint16_t sender_port(0); // Create buffer with max udp datagram size std::vector received_datagram; received_datagram.resize(65536); - received_datagram.resize(udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), error)); + received_datagram.resize(udpcap_socket.receiveDatagram(received_datagram.data(), received_datagram.size(), &sender_address, &sender_port, error)); // No error must have occurred ASSERT_FALSE(bool(error)); @@ -280,6 +297,10 @@ TEST(udpcap, SimpleReceiveWithBuffer) ASSERT_FALSE(received_datagram.empty()); ASSERT_EQ(std::string(received_datagram.data(), received_datagram.size()), "Hello World"); + // Check if the sender's address and port are correct + ASSERT_EQ(sender_address.toString(), asio_local_endpoint.address().to_string()); + ASSERT_EQ(sender_port, asio_local_endpoint.port()); + received_messages++; }); @@ -318,8 +339,16 @@ TEST(udpcap, DelayedPackageReceiveMultiplePackages) ASSERT_TRUE(success); } + // Create an asio UDP sender socket + asio::io_service io_service; + const asio::ip::udp::endpoint endpoint(asio::ip::make_address("127.0.0.1"), 14000); + asio::ip::udp::socket asio_socket(io_service, endpoint.protocol()); + asio_socket.connect(endpoint); + const auto asio_local_endpoint = asio_socket.local_endpoint(); + + // Receive datagrams in a separate thread - std::thread receive_thread([&udpcap_socket, &received_messages, num_packages_to_send, size_per_package, receive_delay]() + std::thread receive_thread([&udpcap_socket, &received_messages, num_packages_to_send, size_per_package, receive_delay, &asio_local_endpoint]() { while (true) { @@ -353,20 +382,15 @@ TEST(udpcap, DelayedPackageReceiveMultiplePackages) ASSERT_EQ(received_datagram.size(), size_per_package); received_messages++; + // Check the sender endpoint + ASSERT_EQ(sender_address.toString(), asio_local_endpoint.address().to_string()); + ASSERT_EQ(sender_port, asio_local_endpoint.port()); + // Wait a bit, so we force the udpcap socket to buffer the datagrams std::this_thread::sleep_for(receive_delay); } }); - // Create an asio UDP sender socket - asio::io_service io_service; - - const asio::ip::udp::endpoint endpoint(asio::ip::make_address("127.0.0.1"), 14000); - asio::ip::udp::socket asio_socket(io_service, endpoint.protocol()); - - // Capture the start time - auto start_time = std::chrono::steady_clock::now(); - // Send the buffers for (int i = 0; i < num_packages_to_send; i++) { @@ -379,11 +403,6 @@ TEST(udpcap, DelayedPackageReceiveMultiplePackages) // Check if the received message counter is equal to the sent messages ASSERT_EQ(received_messages.get(), num_packages_to_send); - // Capture the end time - auto end_time = std::chrono::steady_clock::now(); - - // TODO: check the entire delay - asio_socket.close(); udpcap_socket.close(); @@ -432,9 +451,6 @@ TEST(udpcap, Timeout) ASSERT_EQ(error, Udpcap::Error::TIMEOUT); ASSERT_EQ(received_bytes, 0); - // Print the used time in milliseconds to console - std::cout << "Time: " << std::chrono::duration_cast(end_time - start_time).count() << std::endl; - ASSERT_GE(std::chrono::duration_cast(end_time - start_time).count(), 100); // TODO: This sometimes fails. Check why! } @@ -582,7 +598,6 @@ TEST(udpcap, MulticastReceive) asio_socket.set_option(asio::ip::multicast::hops(1)); asio_socket.set_option(asio::ip::multicast::enable_loopback(true)); - // Receive datagrams in a separate thread for Socket1 (checks for 224.0.0.1) std::thread receive_thread1([&udpcap_socket1, &received_messages1]() { @@ -650,7 +665,7 @@ TEST(udpcap, MulticastReceive) break; } - + // Check if the received datagram is valid and contains "224.0.0.1" or "224.0.0.2" ASSERT_TRUE(std::string(received_datagram.data(), received_datagram.size()) == "224.0.0.1" || std::string(received_datagram.data(), received_datagram.size()) == "224.0.0.2"); @@ -677,8 +692,8 @@ TEST(udpcap, MulticastReceive) received_messages2.wait_for([](int value) { return value >= 2; }, std::chrono::milliseconds(500)); // Check if the received message counters - ASSERT_EQ(received_messages1.get(), 1) << "Make sure, your FIREWALL is DISABLED!!!"; - ASSERT_EQ(received_messages2.get(), 2) << "Make sure, your FIREWALL is DISABLED!!!"; + ASSERT_EQ(received_messages1.get(), 1) << "Make sure that your FIREWALL is DISABLED!!!"; + ASSERT_EQ(received_messages2.get(), 2) << "Make sure that your FIREWALL is DISABLED!!!"; // Close the sockets asio_socket.close(); @@ -690,6 +705,104 @@ TEST(udpcap, MulticastReceive) receive_thread2.join(); } -// TODO: Write a test that tests the Source Address and Source Port +TEST(udpcap, ManySockets) +{ + constexpr int num_udpcap_socket = 100; + constexpr char* ip_address = "127.0.0.1"; + constexpr uint16_t port = 14000; + + // Create an asio socket that sends datagrams to the ip address and port + asio::io_service io_service; + asio::ip::udp::socket asio_socket(io_service, asio::ip::udp::v4()); + asio::ip::udp::endpoint endpoint(asio::ip::make_address(ip_address), port); + asio_socket.connect(endpoint); + + // Thread that constantly pushes datagrams via the asio socket + std::thread send_thread([&asio_socket]() + { + std::string buffer_string = "Hello World"; + while(true) + { + asio::error_code ec; + asio_socket.send(asio::buffer(buffer_string), 0, ec); + if (ec) + { + break; + } + } + }); + + // Create num_udpcap_socket udpcap sockets + std::vector udpcap_sockets; + std::vector receive_threads; + + // Reserve space for the sockets + udpcap_sockets.reserve(num_udpcap_socket); -// TODO: Create many sockets in threads, wait for them and destroy them to see if there are any race conditions that lead to crashes + for (int i = 0; i < num_udpcap_socket; i++) + { + udpcap_sockets.emplace_back(); + ASSERT_TRUE(udpcap_sockets.back().isValid()); + const bool success = udpcap_sockets.back().bind(Udpcap::HostAddress(ip_address), port); + ASSERT_TRUE(success); + + // Create a receive thread that constantly receives datagrams + receive_threads.emplace_back([&udpcap_sockets, i]() + { + while (true) + { + // Initialize variables for the sender's address and port + Udpcap::HostAddress sender_address; + uint16_t sender_port(0); + Udpcap::Error error = Udpcap::Error::ErrorCode::GENERIC_ERROR; + + // Allocate buffer with max udp datagram size + std::vector received_datagram; + received_datagram.resize(65536); + + // blocking receive + const size_t received_bytes = udpcap_sockets[i].receiveDatagram(received_datagram.data(), received_datagram.size(), &sender_address, &sender_port, error); + received_datagram.resize(received_bytes); + + if (error) + { + // Indicates that somebody closed the socket + ASSERT_EQ(error, Udpcap::Error(Udpcap::Error::ErrorCode::SOCKET_CLOSED)); + + // Check that the socket is closed + ASSERT_TRUE(udpcap_sockets[i].isClosed()); + + break; + } + else + { + // Check if the received datagram is valid and contains "Hello World" + ASSERT_FALSE(received_datagram.empty()); + ASSERT_EQ(std::string(received_datagram.data(), received_datagram.size()), "Hello World"); + } + } + }); + } + + // wait 10ms + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + + // Close the sockets + for (auto& udpcap_socket : udpcap_sockets) + { + udpcap_socket.close(); + } + + // Join the threads + for (auto& receive_thread : receive_threads) + { + receive_thread.join(); + } + + // Close the asio socket + asio_socket.close(); + + // Join the send thread + send_thread.join(); +} \ No newline at end of file diff --git a/udpcap/include/udpcap/udpcap_socket.h b/udpcap/include/udpcap/udpcap_socket.h index 902632f..e292e64 100644 --- a/udpcap/include/udpcap/udpcap_socket.h +++ b/udpcap/include/udpcap/udpcap_socket.h @@ -62,6 +62,11 @@ namespace Udpcap * MulticastLoopbackEnabled=true and joining a multicast group, this * implementation will receive loopback multicast traffic. Winsocks would * not do that (It's not clear to me why). + * + * Thread safety: + * - There must only be 1 thread calling receiveDatagram() at the same time + * - It is safe to call close() while another thread is calling receiveDatagram() + * - Other modifications to the socket must not be made while another thread is calling receiveDatagram() */ class UdpcapSocket { @@ -136,8 +141,19 @@ namespace Udpcap * the according information from the packet. If the given time elapses * before a datagram was available, no data is copied and 0 is returned. * - * TODO: Document which error occurs in which case - * + * Possible errors: + * OK if no error occured + * NPCAP_NOT_INITIALIZED if npcap has not been initialized + * NOT_BOUND if the socket hasn't been bound, yet + * SOCKET_CLOSED if the socket has been closed by the user + * TIMEOUT if the given timeout has elapsed and no datagram was available + * GNERIC_ERROR in cases of internal libpcap errors + * + * Thread safety: + * - This method must not be called from multiple threads at the same time + * - While one thread is calling this method, another thread may call close() + * - While one thread is calling this method, no modifications must be made to the socket (except close()) + * * @param data [out]: The destination memory * @param max_len [in]: The maximum bytes available at the destination * @param timeout_ms [in]: Maximum time to wait for a datagram in ms. If -1, the method will block until a datagram is available @@ -154,18 +170,15 @@ namespace Udpcap , uint16_t* source_port , Udpcap::Error& error); - // TODO: Copy documentation here UDPCAP_EXPORT size_t receiveDatagram(char* data , size_t max_len , long long timeout_ms , Udpcap::Error& error); - // TODO: Copy documentation here UDPCAP_EXPORT size_t receiveDatagram(char* data , size_t max_len , Udpcap::Error& error); - // TODO: Copy documentation here UDPCAP_EXPORT size_t receiveDatagram(char* data , size_t max_len , HostAddress* source_address @@ -217,6 +230,9 @@ namespace Udpcap /** * @brief Closes the socket + * + * Thread safety: + * - It is safe to call this method while another thread is calling receiveDatagram() */ UDPCAP_EXPORT void close(); diff --git a/udpcap/src/ip_reassembly.h b/udpcap/src/ip_reassembly.h index c31c56e..097eadc 100644 --- a/udpcap/src/ip_reassembly.h +++ b/udpcap/src/ip_reassembly.h @@ -37,7 +37,7 @@ namespace Udpcap ///////////////////////////////////////// /// Constructor & Destructor ///////////////////////////////////////// - public: // TODO: Document + public: /** * A c'tor for this class. * diff --git a/udpcap/src/udpcap_socket_private.cpp b/udpcap/src/udpcap_socket_private.cpp index 419ebb8..d32c907 100644 --- a/udpcap/src/udpcap_socket_private.cpp +++ b/udpcap/src/udpcap_socket_private.cpp @@ -404,7 +404,7 @@ namespace Udpcap } else if (wait_result == WAIT_TIMEOUT) { - // LOG_DEBUG("Receive error: WAIT_TIMEOUT"); + //LOG_DEBUG("Receive error: WAIT_TIMEOUT"); error = Udpcap::Error::TIMEOUT; return 0; } @@ -457,7 +457,7 @@ namespace Udpcap multicast_groups_.emplace(group_address); // Update the capture filters, so the devices will capture the multicast traffic - updateAllCaptureFilters(); // TODO: I probably need to protect the pcap_devices_ list with a mutex here + updateAllCaptureFilters(); if (multicast_loopback_enabled_) { @@ -493,7 +493,7 @@ namespace Udpcap multicast_groups_.erase(group_it); // Update all capture filtes - updateAllCaptureFilters(); // TODO: I probably need to protect the pcap_devices_ list with a mutex here + updateAllCaptureFilters(); return true; } @@ -514,7 +514,7 @@ namespace Udpcap kickstartLoopbackMulticast(); } - updateAllCaptureFilters(); // TODO: I probably need to protect the pcap_devices_ list with a mutex here + updateAllCaptureFilters(); } bool UdpcapSocketPrivate::isMulticastLoopbackEnabled() const @@ -524,9 +524,6 @@ namespace Udpcap void UdpcapSocketPrivate::close() { - // TODO: make close thread safe, so one thread can wait for data while another thread closes the socket - // TODO: 2024-01-30: Check if this now is actually thread safe - { // Lock the lists of open pcap devices in read-mode. We may use the handles, // but not modify the lists themselfes. This is in order to assure that the @@ -537,8 +534,8 @@ namespace Udpcap { // Lock the callback lock. While the callback is running, we cannot close // the pcap handle, as that may invalidate the data pointer. - const std::lock_guard pcap_callback_lock(pcap_devices_callback_mutex_); - pcap_devices_closed_ = true; //todo: must i protect this variable with the lists lock or the callback lock + const std::lock_guard pcap_devices_callback_lock(pcap_devices_callback_mutex_); + pcap_devices_closed_ = true; for (auto& pcap_dev : pcap_devices_) { LOG_DEBUG(std::string("Closing ") + pcap_dev.device_name_);