From bf02a13aa6dfc57baacace4a7aab3ebef2de94ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Hollender?= Date: Sun, 30 Nov 2025 11:41:13 +0100 Subject: [PATCH 1/2] dev-netqueue: added epoll-based network queue --- .github/workflows/test.yaml | 37 +++ CMakeLists.txt | 68 +++++- include/vnet/const.hpp | 6 + include/vnet/netqueue/element.hpp | 23 ++ include/vnet/netqueue/fsm.hpp | 30 +++ include/vnet/netqueue/handler.hpp | 54 ++++ include/vnet/netqueue/netqueue.hpp | 207 ++++++++++++++++ include/vnet/protocol/header.hpp | 25 ++ include/vnet/protocol/types.hpp | 15 ++ src/netqueue/element.cpp | 12 + src/netqueue/handler.cpp | 8 + src/netqueue/netqueue.cpp | 298 +++++++++++++++++++++++ src/protocol/header.cpp | 21 ++ src/protocol/types.cpp | 13 + tests/CMakeLists.txt | 28 +++ tests/lookup_wrong_fds.py | 45 ++++ tests/netqueue/element_tests.cpp | 17 ++ tests/netqueue/netqueue_epoll_tests.cpp | 135 ++++++++++ tests/netqueue/netqueue_macro.hpp | 32 +++ tests/netqueue/netqueue_socket_tests.cpp | 163 +++++++++++++ tests/netqueue/netqueue_test_handler.hpp | 28 +++ tests/netqueue/netqueue_tests.cpp | 113 +++++++++ tests/netqueue/netqueue_tun_tests.cpp | 102 ++++++++ tests/protocol/header_tests.cpp | 33 +++ tests/protocol/types_tests.cpp | 30 +++ tests/utils/malloc_wrapper.cpp | 51 ++++ tests/utils/malloc_wrapper.hpp | 12 + tests/utils/tun_wrapper.cpp | 100 ++++++++ tests/utils/tun_wrapper.hpp | 3 + tests/utils/tun_wrapper_tests.cpp | 52 ++++ 30 files changed, 1759 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/test.yaml create mode 100644 include/vnet/const.hpp create mode 100644 include/vnet/netqueue/element.hpp create mode 100644 include/vnet/netqueue/fsm.hpp create mode 100644 include/vnet/netqueue/handler.hpp create mode 100644 include/vnet/netqueue/netqueue.hpp create mode 100644 include/vnet/protocol/header.hpp create mode 100644 include/vnet/protocol/types.hpp create mode 100644 src/netqueue/element.cpp create mode 100644 src/netqueue/handler.cpp create mode 100644 src/netqueue/netqueue.cpp create mode 100644 src/protocol/header.cpp create mode 100644 src/protocol/types.cpp create mode 100644 tests/CMakeLists.txt create mode 100644 tests/lookup_wrong_fds.py create mode 100644 tests/netqueue/element_tests.cpp create mode 100644 tests/netqueue/netqueue_epoll_tests.cpp create mode 100644 tests/netqueue/netqueue_macro.hpp create mode 100644 tests/netqueue/netqueue_socket_tests.cpp create mode 100644 tests/netqueue/netqueue_test_handler.hpp create mode 100644 tests/netqueue/netqueue_tests.cpp create mode 100644 tests/netqueue/netqueue_tun_tests.cpp create mode 100644 tests/protocol/header_tests.cpp create mode 100644 tests/protocol/types_tests.cpp create mode 100644 tests/utils/malloc_wrapper.cpp create mode 100644 tests/utils/malloc_wrapper.hpp create mode 100644 tests/utils/tun_wrapper.cpp create mode 100644 tests/utils/tun_wrapper.hpp create mode 100644 tests/utils/tun_wrapper_tests.cpp diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..d3bc4b8 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,37 @@ +on: + push: + branches: + - "**" +jobs: + ubuntu-test: + name: test on all operating systems + runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash + strategy: + matrix: + os: [ubuntu-latest] + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-python@v6 + with: + python-version: '3.13' + - name: install tools + run: | + sudo apt-get update + sudo apt-get install -y g++ cmake valgrind libprotobuf-dev protobuf-compiler + - name: compile + run: | + mkdir build + cd build + cmake .. -DCMAKE_BUILD_TYPE=Debug -DENABLE_COVERAGE=ON + cmake --build . + - name: run tests + run: | + cd build + cmake --build . --target coverage + - name: run memory tests + run: | + cd build + cmake --build . --target memcheck \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index c2c99c8..eea1e69 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,70 @@ cmake_minimum_required(VERSION 3.28.0) PROJECT(vNet) -SET(CMAKE_CXX_FLAGS "-g -Wall -Werror -std=c++11") +SET(CMAKE_CXX_FLAGS "-g -Wall -Werror -std=c++17") + +option(ENABLE_COVERAGE "Enable code coverage instrumentation (requires gcov/lcov)" OFF) + +find_program(VALGRIND_EXECUTABLE NAMES valgrind) + +if (ENABLE_COVERAGE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage -O0") + + set(MEMORYCHECK_COMMAND "${VALGRIND_EXECUTABLE}") + set(MEMORYCHECK_COMMAND_OPTIONS "--tool=memcheck --leak-check=full --track-fds=yes --error-exitcode=1") + + add_custom_target( + memcheck + + COMMAND ctest -T memcheck + COMMAND python3 ${CMAKE_SOURCE_DIR}/tests/lookup_wrong_fds.py ${CMAKE_CURRENT_BINARY_DIR}/Testing/Temporary + ) + add_custom_target( + coverage + + COMMAND lcov --directory . --zerocounters --rc branch_coverage=1 + COMMAND ctest + + COMMAND lcov --capture --directory . --output-file coverage.info --ignore-errors mismatch --rc branch_coverage=1 + COMMAND lcov --remove coverage.info '/usr/*' '*test*' --output-file coverage_filtered.info --rc branch_coverage=1 + + COMMAND genhtml coverage_filtered.info --output-directory html_report --rc branch_coverage=1 + ) +endif() + +############################################################# +###################### PROTOCOL BUFFER ###################### +############################################################# ADD_SUBDIRECTORY(proto) -include_directories(${ProtobufIncludePath}) \ No newline at end of file +include_directories(${ProtobufIncludePath}) + +############################################################# +######################## VIRTUAL NET ######################## +############################################################# + +SET(VNET_SOURCE_FILES + src/protocol/header.cpp + src/protocol/types.cpp + + src/netqueue/element.cpp + src/netqueue/netqueue.cpp + src/netqueue/handler.cpp) + +add_library(vnet STATIC ${VNET_SOURCE_FILES}) +target_link_libraries(vnet PRIVATE gcov) +target_include_directories(vnet PUBLIC include) + +############################################################# +######################## GOOGLE TEST ######################## +############################################################# + +include(FetchContent) + +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/refs/tags/v1.17.0.zip +) +FetchContent_MakeAvailable(googletest) + +enable_testing() +add_subdirectory(tests) diff --git a/include/vnet/const.hpp b/include/vnet/const.hpp new file mode 100644 index 0000000..8251e78 --- /dev/null +++ b/include/vnet/const.hpp @@ -0,0 +1,6 @@ + +#pragma once + +namespace vnet { + const int NET_BUFFER_SIZE = 66000; +}; diff --git a/include/vnet/netqueue/element.hpp b/include/vnet/netqueue/element.hpp new file mode 100644 index 0000000..9966e3b --- /dev/null +++ b/include/vnet/netqueue/element.hpp @@ -0,0 +1,23 @@ + +#pragma once + +#include +#include + +#include "vnet/const.hpp" +#include "vnet/netqueue/fsm.hpp" +#include + +namespace vnet::netqueue { + struct NetworkElement { + FiniteStateMachine state; + + int fd; + void *ptr; + + size_t net_buffer_used; + uint8_t net_buffer[NET_BUFFER_SIZE]; + + NetworkElement (int fd, void *ptr, FiniteStateMachine state); + }; +} diff --git a/include/vnet/netqueue/fsm.hpp b/include/vnet/netqueue/fsm.hpp new file mode 100644 index 0000000..24785ae --- /dev/null +++ b/include/vnet/netqueue/fsm.hpp @@ -0,0 +1,30 @@ + +#pragma once + +namespace vnet::netqueue { + /** + * This finite state machine represents the state in which the file descriptor + * is currently. In particular, there are two distinct finite state machines + * depending on the type of socket. + * + * Anything in TUN_* represents a device that operates as a queue of packets, + * under which reading yields the entire IP packet directly and on which you + * should only write a entire IP packet. + * + * Anything in SCK_* represents a device that follows the specific protobuf + * format in the specifications. In particular, it contains first a 6 byte + * header containing the payload size and payload type. + */ + enum FiniteStateMachine { + TUN_RECEIVE, + TUN_READY, + TUN_ERROR, + TUN_EOF, + + SCK_HEADER, + SCK_PAYLOAD, + SCK_READY, + SCK_ERROR, + SCK_EOF + }; +}; diff --git a/include/vnet/netqueue/handler.hpp b/include/vnet/netqueue/handler.hpp new file mode 100644 index 0000000..0fe5d2d --- /dev/null +++ b/include/vnet/netqueue/handler.hpp @@ -0,0 +1,54 @@ + +#pragma once + +#include "vnet/netqueue/element.hpp" +#include "vnet/protocol/header.hpp" + +namespace vnet::netqueue { + enum close_reason { + CLOSE_ERROR, + CLOSE_EPOLL, + CLOSE_HANGUP + }; + struct close_data { + NetworkElement* net_element; + + void* ptr_data; + int fd; + + close_reason reason; + }; + struct socket_data { + NetworkElement* net_element; + + void* ptr_data; + int fd; + + protocol::PacketType packet_type; + unsigned char* packet_buffer; + size_t payload_size; + + unsigned char* full_buffer; + size_t full_buffer_size; + }; + struct tun_data { + NetworkElement* net_element; + + void* ptr_data; + int fd; + + unsigned char* ip_buffer; + size_t ip_buffer_size; + }; + + void doNothingOnClose (close_data close_content); + void doNothingOnSocketReady (socket_data data); + void doNothingOnTunReady (tun_data data); + + struct NetworkQueueHandler { + void (*onClose) (close_data close_content) = &doNothingOnClose; + void (*onSocketReady) (socket_data data) = &doNothingOnSocketReady; + void (*onTunReady) (tun_data data) = &doNothingOnTunReady; + }; + +} diff --git a/include/vnet/netqueue/netqueue.hpp b/include/vnet/netqueue/netqueue.hpp new file mode 100644 index 0000000..2581da9 --- /dev/null +++ b/include/vnet/netqueue/netqueue.hpp @@ -0,0 +1,207 @@ + +#pragma once + +#include "vnet/netqueue/fsm.hpp" +#include "vnet/netqueue/element.hpp" +#include "vnet/netqueue/handler.hpp" + +#include +#include + +namespace vnet::netqueue { + /** + * Maximum number for events returned + * by a single call to epoll_wait + */ + const int EPOLL_MAX_EVENTS = 32; + + /** + * Instances of this class represents a general + * purpose pool of file descriptors intended to + * represent a queue for incoming network packets. + */ + struct NetQueue { + private: + /* + * File descriptor of epoll + * + * This file descriptor is owned by the instance. + */ + int epoll_fd = -1; + /* Timeout on epoll_wait */ + int epoll_timeout = -1; + + /* + * Unordered map with file descriptor linked + * to the respective network elements that contain it. + */ + std::unordered_map fd_to_network_element; + /* Mutex for all operations related to the fd_to_network_element unordered_map */ + std::mutex fd_to_network_element_mutex; + + /* + * Handler of events (e.g. receiving a packet, + * error of a specific fd etc...) + * + * The memory of the handler is owned by this object. + */ + NetworkQueueHandler handler; + + public: + /** + * Put a file descriptor into the queue + * with a pointer to some data the user wants + * to store. + * + * The user GIVES OWNERSHIP of the file descriptor + * to the network queue. One should never close it + * manually. If the user wishes to close the file + * descriptor, it should use the close method of + * the network queue. + * + * The user still HAS OWNERSHIP over the pointer of + * the data it wanted to store and will be responsible + * for freeing it or handling it upon receival of an + * error or a close through the NetworkQueueHandler. + * + * The user DOES NOT HAVE OWNERSHIP over the resulting + * network element and should in no case free it, close + * the internal file descriptor or manipulate the state, + * buffer and number of bytes used in the buffer. + * + * The user MAY modify the network's element internal + * pointer to some new data it wants to use to handle + * for example a client reconnecting. + * + * @return the generated network element. If anything fails + * whilst trying to put the file descriptor, the method + * will return NULL + */ + NetworkElement* put (int fd, void* ptr, FiniteStateMachine start); + + /** + * Get the network element for the given file + * descriptor. + * + * @param fd the file descriptor for the lookup + * @return the network element for that given file + * descriptor, or NULL if the file descriptor isn't + * inside the queue. + */ + NetworkElement* get_network_element_from_fd (int fd); + + /** + * Close the current file descriptor and remove it from + * the queue. + */ + void close (int fd); + /** + * Close the current network element and remove it from + * the queue. It also closes the file descriptor and frees + * the network element. + */ + void close (NetworkElement* element); + + /** + * Take a network element (that is an object containing + * the file descriptor, data of the element), and read + * from it until it outputs EAGAIN. + * + * The element must be owned by the network queue + * + * @param element the network element to be processed + */ + void process (NetworkElement* element); + /** + * Calls epoll_wait to get all the available events + * and their respective network elements, before + * calling process on them. + */ + void wait_and_process (); + + /** + * Put a TUN file descriptor into the queue + * with a pointer to some data the user wants + * to store. + * + * The user GIVES OWNERSHIP of the file descriptor + * to the network queue. One should never close it + * manually. If the user wishes to close the file + * descriptor, it should use the close method of + * the network queue. + * + * The user still HAS OWNERSHIP over the pointer of + * the data it wanted to store and will be responsible + * for freeing it or handling it upon receival of an + * error or a close through the NetworkQueueHandler. + * + * The user DOES NOT HAVE OWNERSHIP over the resulting + * network element and should in no case free it, close + * the internal file descriptor or manipulate the state, + * buffer and number of bytes used in the buffer. + * + * The user MAY modify the network's element internal + * pointer to some new data it wants to use to handle + * for example a client reconnecting. + * + * @return the generated network element. If anything fails + * whilst trying to put the file descriptor, the method + * will return NULL + */ + NetworkElement* put_tun (int fd, void* ptr); + /** + * Put a file descriptor for a socket into the + * queue with a pointer to some data the user + * wants to store. + * + * The user GIVES OWNERSHIP of the file descriptor + * to the network queue. One should never close it + * manually. If the user wishes to close the file + * descriptor, it should use the close method of + * the network queue. + * + * The user still HAS OWNERSHIP over the pointer of + * the data it wanted to store and will be responsible + * for freeing it or handling it upon receival of an + * error or a close through the NetworkQueueHandler. + * + * The user DOES NOT HAVE OWNERSHIP over the resulting + * network element and should in no case free it, close + * the internal file descriptor or manipulate the state, + * buffer and number of bytes used in the buffer. + * + * The user MAY modify the network's element internal + * pointer to some new data it wants to use to handle + * for example a client reconnecting. + * + * @return the generated network element. If anything fails + * whilst trying to put the file descriptor, the method + * will return NULL + */ + NetworkElement* put_sck (int fd, void* ptr); + + /** + * Create a network queue with the given + * handler for the events. + * + * @return the network queue created. If anything fails, + * it will return NULL. + */ + NetQueue (NetworkQueueHandler handler); + /** + * Create a network queue with the given + * handler for the events and given wait timeout. + * + * @return the network queue created. If anything fails, + * it will return NULL. + */ + NetQueue (NetworkQueueHandler handler, int epoll_timeout); + + /** + * Destroy the network queue. This will free all of the + * network elements and cleanup the EPOLL. This won't + * handle destruction of the user data. + */ + ~NetQueue (); + }; +}; diff --git a/include/vnet/protocol/header.hpp b/include/vnet/protocol/header.hpp new file mode 100644 index 0000000..be648ea --- /dev/null +++ b/include/vnet/protocol/header.hpp @@ -0,0 +1,25 @@ + +#pragma once + +#include +#include + +#include "vnet/protocol/types.hpp" + +namespace vnet::protocol { + #pragma pack(push, 1) + struct PacketHeader { + private: + uint32_t payload_size; + uint_packet_t packet_type; + public: + uint32_t get_payload_size(); + PacketType get_packet_type (); + + PacketHeader (); + PacketHeader (uint32_t payload_size, PacketType packet_type); + }; + #pragma pack(pop) + + const size_t PACKET_HEADER_SIZE = sizeof(PacketHeader); +}; diff --git a/include/vnet/protocol/types.hpp b/include/vnet/protocol/types.hpp new file mode 100644 index 0000000..6494401 --- /dev/null +++ b/include/vnet/protocol/types.hpp @@ -0,0 +1,15 @@ + +#pragma once + +#include + +namespace vnet::protocol { + using uint_packet_t = uint16_t; + + enum PacketType : uint16_t { + + }; + + PacketType ntoh_packet_type (uint_packet_t type); + uint_packet_t hton_packet_type (PacketType type); +}; diff --git a/src/netqueue/element.cpp b/src/netqueue/element.cpp new file mode 100644 index 0000000..d9e4698 --- /dev/null +++ b/src/netqueue/element.cpp @@ -0,0 +1,12 @@ + +#include "vnet/netqueue/element.hpp" + +using namespace vnet::netqueue; + +NetworkElement::NetworkElement (int fd, void *ptr, FiniteStateMachine state) { + this->net_buffer_used = 0; + + this->fd = fd; + this->ptr = ptr; + this->state = state; +} diff --git a/src/netqueue/handler.cpp b/src/netqueue/handler.cpp new file mode 100644 index 0000000..b3c2604 --- /dev/null +++ b/src/netqueue/handler.cpp @@ -0,0 +1,8 @@ + +#include "vnet/netqueue/handler.hpp" + +using namespace vnet::netqueue; + +void vnet::netqueue::doNothingOnClose (close_data error_content) {} +void vnet::netqueue::doNothingOnSocketReady (socket_data data) {} +void vnet::netqueue::doNothingOnTunReady (tun_data data) {} diff --git a/src/netqueue/netqueue.cpp b/src/netqueue/netqueue.cpp new file mode 100644 index 0000000..9a5d3bd --- /dev/null +++ b/src/netqueue/netqueue.cpp @@ -0,0 +1,298 @@ + +#include "vnet/netqueue/netqueue.hpp" +#include "vnet/protocol/header.hpp" + +#include +#include +#include +#include +#include + +using namespace vnet::netqueue; + +void _real_close (int fd) { + close(fd); +} + +NetworkElement* NetQueue::put_tun (int tun_fd, void* data) { + return put(tun_fd, data, FiniteStateMachine::TUN_RECEIVE); +} +NetworkElement* NetQueue::put_sck (int sck_fd, void* data) { + return put(sck_fd, data, FiniteStateMachine::SCK_HEADER); +} + +NetworkElement* NetQueue::put (int fd, void* data, FiniteStateMachine state) { + std::lock_guard lock_fd_to_network_element (fd_to_network_element_mutex); + + NetworkElement* element; + + try { + element = new NetworkElement(fd, data, state); + } catch (...) { + return nullptr; + } + + struct epoll_event event; + event.data.ptr = element; + event.events = EPOLLIN | EPOLLET | EPOLLONESHOT; + + if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event) < 0) { + delete element; + return nullptr; + } + + try { + fd_to_network_element[fd] = element; + } catch (...) { + delete element; + epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &event); + return nullptr; + } + + return element; +} + +NetQueue::NetQueue (NetworkQueueHandler handler) : NetQueue(handler, -1) {} +NetQueue::NetQueue (NetworkQueueHandler handler, int epoll_timeout) { + if (handler.onClose == nullptr || handler.onSocketReady == nullptr || handler.onTunReady == nullptr) { + throw std::invalid_argument( "handlers should exist" ); + } + + int epoll_fd = epoll_create1(0); + if (epoll_fd < 0) + throw std::runtime_error( "could not create EPOLL file descriptor" ); + + this->epoll_fd = epoll_fd; + this->epoll_timeout = epoll_timeout; + this->handler = handler; +} + +void NetQueue::wait_and_process () { + struct epoll_event events[EPOLL_MAX_EVENTS]; + int nb_events = epoll_wait(epoll_fd, events, EPOLL_MAX_EVENTS, epoll_timeout); + if (nb_events < 0) { + return ; + } + + std::unordered_set elements_closed; + + for (int i_event = 0; i_event < nb_events; i_event ++) { + NetworkElement* current_element = (NetworkElement*) events[i_event].data.ptr; + if (elements_closed.find(current_element) != elements_closed.end()) + continue ; + + bool should_close = false; + close_reason reason = CLOSE_ERROR; + if (events[i_event].events & EPOLLIN) { + process(current_element); + + if (current_element->state == SCK_ERROR || current_element->state == TUN_ERROR) { + should_close = true; + reason = CLOSE_ERROR; + } else if (current_element->state == SCK_EOF || current_element->state == TUN_EOF) { + should_close = true; + reason = CLOSE_HANGUP; + } + } + if (events[i_event].events & EPOLLERR) { + should_close = true; + reason = CLOSE_ERROR; + } + if (events[i_event].events & EPOLLHUP) { + should_close = true; + reason = CLOSE_HANGUP; + } + + if (!should_close) { + struct epoll_event event; + event.data.ptr = current_element; + event.events = EPOLLIN | EPOLLET | EPOLLONESHOT; + + if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, current_element->fd, &event) < 0) { + should_close = true; + reason = CLOSE_EPOLL; + } + } + if (should_close) { + close_data data; + data.fd = current_element->fd; + data.net_element = current_element; + data.ptr_data = current_element->ptr; + data.reason = reason; + + handler.onClose(data); + + elements_closed.insert(current_element); + + close(current_element); + } + } +} + +void NetQueue::process (NetworkElement* element) { + while (1) { + switch (element->state) { + case SCK_HEADER: { + ssize_t nb_rem = protocol::PACKET_HEADER_SIZE - element->net_buffer_used; + ssize_t nb_read = read(element->fd, element->net_buffer + element->net_buffer_used, nb_rem); + if (nb_read < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return ; + + element->state = SCK_ERROR; + break ; + } + if (nb_read == 0) { + element->state = SCK_EOF; + break ; + } + + element->net_buffer_used += nb_read; + if (element->net_buffer_used == protocol::PACKET_HEADER_SIZE) { + element->state = SCK_PAYLOAD; + } + + break ; + } + case SCK_PAYLOAD: { + protocol::PacketHeader* header = (protocol::PacketHeader*) element->net_buffer; + + size_t buffer_size = header->get_payload_size() + protocol::PACKET_HEADER_SIZE; + ssize_t number_read = read(element->fd, element->net_buffer + element->net_buffer_used, buffer_size - element->net_buffer_used); + if (number_read < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return ; + + element->state = SCK_ERROR; + break ; + } + + element->net_buffer_used += number_read; + if (element->net_buffer_used == buffer_size) { + element->state = SCK_READY; + } + + break ; + } + case SCK_READY: { + socket_data sck_data; + sck_data.fd = element->fd; + sck_data.ptr_data = element->ptr; + + sck_data.net_element = element; + + sck_data.full_buffer = element->net_buffer; + sck_data.full_buffer_size = element->net_buffer_used; + + protocol::PacketHeader* header = (protocol::PacketHeader*) element->net_buffer; + + sck_data.packet_type = header->get_packet_type(); + sck_data.payload_size = header->get_payload_size(); + sck_data.packet_buffer = element->net_buffer + sizeof(protocol::PacketHeader); + + handler.onSocketReady(sck_data); + + element->net_buffer_used = 0; + element->state = SCK_HEADER; + break ; + } + case SCK_ERROR: + return ; + case SCK_EOF: + return ; + + case TUN_RECEIVE: { + ssize_t nb_read = read(element->fd, element->net_buffer, NET_BUFFER_SIZE); + if (nb_read < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return ; + + element->state = TUN_ERROR; + break ; + } + if (nb_read == 0) { + element->state = TUN_EOF; + break ; + } + + element->net_buffer_used = nb_read; + element->state = TUN_READY; + + break ; + } + case TUN_READY: { + tun_data data; + data.fd = element->fd; + data.ptr_data = element->ptr; + data.net_element = element; + data.ip_buffer = element->net_buffer; + data.ip_buffer_size = element->net_buffer_used; + + handler.onTunReady(data); + + element->state = TUN_RECEIVE; + + break ; + } + case TUN_EOF: + return ; + case TUN_ERROR: + return ; + + default: + return ; + } + } +} + +NetQueue::~NetQueue () { + if (epoll_fd < 0) return ; + + for (std::pair fd_with_network_element : fd_to_network_element) { + _real_close(fd_with_network_element.first); + delete fd_with_network_element.second; + } + + fd_to_network_element.clear(); + + _real_close(epoll_fd); +} + +NetworkElement* NetQueue::get_network_element_from_fd (int fd) { + std::lock_guard lock (fd_to_network_element_mutex); + + auto it = fd_to_network_element.find(fd); + if (it == fd_to_network_element.end()) { + return nullptr; + } + + return (*it).second; +} + +void NetQueue::close (int fd) { + NetworkElement* element = get_network_element_from_fd(fd); + if (element == nullptr) { + return ; + } + + close(element); +} +void NetQueue::close (NetworkElement* element) { + if (element == nullptr) { + return ; + } + + std::lock_guard lock (fd_to_network_element_mutex); + + int fd = element->fd; + + auto it = fd_to_network_element.find(fd); + if (it == fd_to_network_element.end() || (*it).second != element) { + return ; + } + + epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL); + _real_close(fd); + delete element; + fd_to_network_element.erase(it); +} \ No newline at end of file diff --git a/src/protocol/header.cpp b/src/protocol/header.cpp new file mode 100644 index 0000000..a04f11e --- /dev/null +++ b/src/protocol/header.cpp @@ -0,0 +1,21 @@ + +#include +#include "vnet/protocol/header.hpp" + +using namespace vnet::protocol; + +uint32_t PacketHeader::get_payload_size () { + return ntohl(payload_size); +} +PacketType PacketHeader::get_packet_type () { + return ntoh_packet_type(packet_type); +} + +PacketHeader::PacketHeader () { + this->payload_size = 0; + this->packet_type = 0; +} +PacketHeader::PacketHeader (uint32_t payload_size, PacketType packet_type) { + this->payload_size = htonl(payload_size); + this->packet_type = hton_packet_type(packet_type); +} diff --git a/src/protocol/types.cpp b/src/protocol/types.cpp new file mode 100644 index 0000000..1a4f1a8 --- /dev/null +++ b/src/protocol/types.cpp @@ -0,0 +1,13 @@ + +#include "vnet/protocol/types.hpp" + +#include + +using namespace vnet::protocol; + +PacketType vnet::protocol::ntoh_packet_type (uint_packet_t type) { + return (PacketType) ntohs(type); +} +uint_packet_t vnet::protocol::hton_packet_type (PacketType type) { + return htons(type); +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..5b97de4 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,28 @@ + +include(CTest) +set(CTEST_TIMEOUT 30) + +include(GoogleTest) + +function(create_test test_name test_file) + add_executable(${test_name} ${test_file} utils/malloc_wrapper.cpp utils/tun_wrapper.cpp) + target_include_directories(${test_name} PRIVATE .) + target_link_libraries(${test_name} PRIVATE vnet GTest::gtest_main GTest::gmock) + target_link_options(${test_name} PRIVATE -Wl,--wrap,malloc -Wl,--wrap,read -Wl,--wrap,write) + gtest_discover_tests(${test_name}) +endfunction() + +# vnet/protocol +create_test(header_tests protocol/header_tests.cpp) +create_test(types_tests protocol/types_tests.cpp) + +# vnet/netqueue +create_test(element_tests netqueue/element_tests.cpp) + +create_test(netqueue_socket_tests netqueue/netqueue_socket_tests.cpp) +create_test(netqueue_epoll_tests netqueue/netqueue_epoll_tests.cpp) +create_test(netqueue_tun_tests netqueue/netqueue_tun_tests.cpp) +create_test(netqueue_tests netqueue/netqueue_tests.cpp) + +# testing utils +create_test(tun_wrapper_tests utils/tun_wrapper_tests.cpp) diff --git a/tests/lookup_wrong_fds.py b/tests/lookup_wrong_fds.py new file mode 100644 index 0000000..84e20de --- /dev/null +++ b/tests/lookup_wrong_fds.py @@ -0,0 +1,45 @@ + +import sys +import os + +print() +print() +print("Search for wrong file descriptors") +folder = sys.argv[1] + +print(" - search folder :", folder) + +files_to_search = [] + +for file in os.listdir(folder): + words = file.split(".") + if len(words) == 3 and words[0] == "MemoryChecker" and words[2] == "log": + files_to_search.append((int(words[1]), os.path.join(folder, file))) + +print() + +files_to_search.sort() + +should_exit = False +for uuid, file in files_to_search: + with open(file, "r") as tfile: + lines = tfile.read().split("\n") + + lines.append("") + invalid = 0 + for idx in range( len(lines) - 1 ): + if "Open" in lines[idx] and "fd, 0); + EXPECT_EQ(element->ptr, nullptr); + EXPECT_EQ(element->state, SCK_HEADER); + EXPECT_EQ(element->net_buffer_used, 0); + + delete element; +} diff --git a/tests/netqueue/netqueue_epoll_tests.cpp b/tests/netqueue/netqueue_epoll_tests.cpp new file mode 100644 index 0000000..adbfc6a --- /dev/null +++ b/tests/netqueue/netqueue_epoll_tests.cpp @@ -0,0 +1,135 @@ + +#include "netqueue/netqueue_macro.hpp" +#include "netqueue/netqueue_test_handler.hpp" + +using namespace vnet::netqueue; +using namespace vnet::protocol; + +TEST(NetQueueEpoll, SetupEPOLL) { + NetQueue* queue = new NetQueue(NetworkQueueHandler(), 500); + queue->wait_and_process(); + + delete queue; +} +TEST(NetQueueEpoll, ProcessFullAsEpoll) { + uint8_t buffer[24]; + PacketHeader header(0x12, (PacketType) 0x32); + memcpy( buffer, &header, sizeof(PacketHeader)); + memcpy( buffer + sizeof(PacketHeader), "Hello, SCK World !", 0x12 ); + + std::vector>> cuts_lists = { + { { SCK_HEADER, 24 } }, + { { SCK_PAYLOAD, 16 }, { SCK_HEADER, 8 } }, + { { SCK_HEADER, 4 }, { SCK_PAYLOAD, 4 }, { SCK_PAYLOAD, 8 }, { SCK_HEADER, 8 } }, + { { SCK_HEADER, 4 }, { SCK_PAYLOAD, 13 }, { SCK_HEADER, 7 } } + }; + + NetQueue* queue = new NetQueue(NetworkQueueHandler(), 500); + + for (const auto &cuts : cuts_lists) { + PREPARE_SOCKET_PAIR + NetworkElement* element = queue->put_sck(fd_client, NULL); + std::cout << "========" << std::endl; + + //queue.wait_and_process(); + + size_t sent = 0; + for (auto [final_state, to_send] : cuts) { + std::cout << final_state << " " << to_send << std::endl; + + write( fd_server, buffer + sent, to_send ); + sent += to_send; + + queue->wait_and_process(); + + EXPECT_EQ(element->state, final_state); + if (sent != 24) EXPECT_EQ(element->net_buffer_used, sent); + else EXPECT_EQ(element->net_buffer_used, 0); + + for (size_t offset = 0; offset < sent; offset ++) + EXPECT_EQ(element->net_buffer[offset], buffer[offset]); + } + + close(fd_server); + + std::cout << std::endl; + } + + delete queue; +} +TEST(NetQueueEpoll, ProcessManyAsEpoll) { + uint8_t buffer[24]; + PacketHeader header(0x12, (PacketType) 0x32); + memcpy( buffer, &header, sizeof(PacketHeader)); + memcpy( buffer + sizeof(PacketHeader), "Hello, SCK World !", 0x12 ); + + const int NUM_BUFFERS = 4; + + NetQueue* queue = new NetQueue(createTestHandler(), 500); + + std::vector elements; + std::vector fds; + for (int i = 0; i < NUM_BUFFERS; i ++) { + PREPARE_SOCKET_PAIR + NetworkElement* element = queue->put_sck(fd_client, NULL); + EXPECT_EQ( write(fd_server, buffer, 24), 24 ); + elements.push_back(element); + fds.push_back(fd_server); + } + + queue->wait_and_process(); + + EXPECT_EQ(socket_datas.size(), NUM_BUFFERS); + for (auto data : socket_datas) { + EXPECT_EQ(data.full_buffer_size, 24); + for (size_t offset = 0; offset < 24; offset ++) + EXPECT_EQ(data.full_buffer[offset], buffer[offset]); + for (size_t offset = 0; offset < 18; offset ++) + EXPECT_EQ(data.packet_buffer[offset], buffer[offset + 6]); + EXPECT_EQ(data.packet_type, (PacketType) 0x32); + EXPECT_EQ(data.ptr_data, nullptr); + } + + for (int fd : fds) close(fd); + + delete queue; +} +TEST(NetQueueEpoll, NetQueuePutError) { + NetQueue* queue = new NetQueue(NetworkQueueHandler(), 500); + NetworkElement* element = queue->put(-1, nullptr, SCK_HEADER); + EXPECT_EQ(element, nullptr); + EXPECT_EQ(errno, EBADF); + + delete queue; +} + +TEST(NetQueueEpoll, NetQueueWaitError) { + NetQueue* queue = new NetQueue(NetworkQueueHandler(), 500); + uint32_t* buffer = (uint32_t*) queue; + close(buffer[0]); + + queue->wait_and_process(); + delete queue; +} + +TEST(NetQueueSocket, ProcessEOF) { + PREPARE_SOCKET_PAIR + + NetQueue* queue = new NetQueue(createTestHandler(), 100); + NetworkElement* element = queue->put_sck( fd_client, nullptr ); + + queue->wait_and_process(); + EXPECT_EQ(close_datas.size(), 0); + + close(fd_server); + + queue->wait_and_process(); + EXPECT_EQ(close_datas.size(), 1); + + EXPECT_EQ(close_datas.front().net_element, element); + EXPECT_EQ(close_datas.front().fd, fd_client); + EXPECT_EQ(close_datas.front().ptr_data, nullptr); + EXPECT_EQ(close_datas.front().reason, CLOSE_HANGUP); + + delete queue; +} diff --git a/tests/netqueue/netqueue_macro.hpp b/tests/netqueue/netqueue_macro.hpp new file mode 100644 index 0000000..2d0ab6a --- /dev/null +++ b/tests/netqueue/netqueue_macro.hpp @@ -0,0 +1,32 @@ + +#include +#include +#include +#include + +#include "vnet/netqueue/netqueue.hpp" +#include "vnet/protocol/header.hpp" +#include "utils/malloc_wrapper.hpp" +#include "utils/tun_wrapper.hpp" + +#define PREPARE_SOCKET_PAIR \ + int sv[2]; \ + EXPECT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, sv), 0); \ + int fd_client = sv[0]; \ + int fd_server = sv[1]; + +#define PREPARE_NETWORK_TEST(state) \ + PREPARE_SOCKET_PAIR \ + NetworkElement* element = new NetworkElement(fd_client, nullptr, state); \ + EXPECT_NE(element, nullptr); + +#define PREPARE_TUN_PAIR \ + int sv[2]; \ + tun_wrapper_open(sv); \ + int fd_client = sv[0]; \ + int fd_server = sv[1]; + +#define PREPARE_NETWORK_TUN_TEST(state) \ + PREPARE_TUN_PAIR \ + NetworkElement* element = new NetworkElement(fd_client, nullptr, state); \ + EXPECT_NE(element, nullptr); diff --git a/tests/netqueue/netqueue_socket_tests.cpp b/tests/netqueue/netqueue_socket_tests.cpp new file mode 100644 index 0000000..55d4a5e --- /dev/null +++ b/tests/netqueue/netqueue_socket_tests.cpp @@ -0,0 +1,163 @@ + +#include "netqueue/netqueue_macro.hpp" + +using namespace vnet::netqueue; +using namespace vnet::protocol; + +TEST(NetQueueSocket, ProcessHeader_Failure) { + PREPARE_NETWORK_TEST(SCK_HEADER) + + close(fd_client); + close(fd_server); + + NetQueue* queue = new NetQueue(NetworkQueueHandler()); + queue->process(element); + + EXPECT_EQ( element->net_buffer_used, 0 ); + EXPECT_EQ( element->state, SCK_ERROR ); + + delete queue; + delete element; +} +TEST(NetQueueSocket, ProcessHeader_PartialSend) { + PREPARE_NETWORK_TEST(SCK_HEADER) + + PacketHeader header(0x12, (PacketType) 0x32); + EXPECT_EQ( write(fd_server, &header, 3), 3 ); + + NetQueue* queue = new NetQueue(NetworkQueueHandler()); + queue->process(element); + + EXPECT_EQ( element->net_buffer_used, 3 ); + EXPECT_EQ( element->state, SCK_HEADER ); + + delete queue; + delete element; + close(fd_client); + close(fd_server); +} +TEST(NetQueueSocket, ProcessHeader_Success) { + PREPARE_NETWORK_TEST(SCK_HEADER) + + PacketHeader header(0x12, (PacketType) 0x32); + EXPECT_EQ( write(fd_server, &header, sizeof(header)), sizeof(header) ); + + NetQueue* queue = new NetQueue(NetworkQueueHandler()); + queue->process(element); + + EXPECT_EQ( element->net_buffer_used, sizeof(header) ); + EXPECT_EQ( element->state, SCK_PAYLOAD ); + + delete queue; + delete element; + close(fd_client); + close(fd_server); +} + +TEST(NetQueueSocket, ProcessPayload_Failure) { + PREPARE_NETWORK_TEST(SCK_PAYLOAD) + + PacketHeader header(0x12, (PacketType) 0x32); + element->net_buffer_used = sizeof(PacketHeader); + *((PacketHeader*) element->net_buffer) = header; + + close(fd_client); + close(fd_server); + + NetQueue* queue = new NetQueue(NetworkQueueHandler()); + queue->process(element); + + EXPECT_EQ( element->net_buffer_used, 6 ); + EXPECT_EQ( element->state, SCK_ERROR ); + + delete queue; + delete element; +} +TEST(NetQueueSocket, ProcessPayload_PartialSend) { + PREPARE_NETWORK_TEST(SCK_PAYLOAD) + + PacketHeader header(0x12, (PacketType) 0x32); + element->net_buffer_used = sizeof(PacketHeader); + *((PacketHeader*) element->net_buffer) = header; + + const char* to_send = "Hello, SCK World !"; + EXPECT_EQ( write(fd_server, to_send, 12), 12 ); + + NetQueue* queue = new NetQueue(NetworkQueueHandler()); + queue->process(element); + + EXPECT_EQ( element->net_buffer_used, 18 ); + EXPECT_EQ( element->state, SCK_PAYLOAD ); + + delete queue; + delete element; + close(fd_client); + close(fd_server); +} +TEST(NetQueueSocket, ProcessPayload_Success) { + PREPARE_NETWORK_TEST(SCK_PAYLOAD) + + PacketHeader header(0x12, (PacketType) 0x32); + element->net_buffer_used = sizeof(PacketHeader); + *((PacketHeader*) element->net_buffer) = header; + + const char* to_send = "Hello, SCK World !"; + EXPECT_EQ( write(fd_server, to_send, 18), 18 ); + + NetQueue* queue = new NetQueue(NetworkQueueHandler()); + queue->process(element); + + EXPECT_EQ( element->net_buffer_used, 0 ); + EXPECT_EQ( element->state, SCK_HEADER ); + + delete queue; + delete element; + close(fd_client); + close(fd_server); +} + +TEST(NetQueueSocket, ProcessFull_InBlocks) { + uint8_t buffer[24]; + PacketHeader header(0x12, (PacketType) 0x32); + memcpy( buffer, &header, sizeof(PacketHeader)); + memcpy( buffer + sizeof(PacketHeader), "Hello, SCK World !", 0x12 ); + + std::vector>> cuts_lists = { + { { SCK_HEADER, 24 } }, + { { SCK_PAYLOAD, 16 }, { SCK_HEADER, 8 } }, + { { SCK_HEADER, 4 }, { SCK_PAYLOAD, 4 }, { SCK_PAYLOAD, 8 }, { SCK_HEADER, 8 } }, + { { SCK_HEADER, 4 }, { SCK_PAYLOAD, 13 }, { SCK_HEADER, 7 } } + }; + + NetQueue* queue = new NetQueue(NetworkQueueHandler()); + + for (const auto &cuts : cuts_lists) { + PREPARE_NETWORK_TEST(SCK_HEADER); + std::cout << "========" << std::endl; + + size_t sent = 0; + for (auto [final_state, to_send] : cuts) { + std::cout << final_state << " " << to_send << std::endl; + + write( fd_server, buffer + sent, to_send ); + sent += to_send; + + queue->process(element); + + EXPECT_EQ(element->state, final_state); + if (sent != 24) EXPECT_EQ(element->net_buffer_used, sent); + else EXPECT_EQ(element->net_buffer_used, 0); + + for (size_t offset = 0; offset < sent; offset ++) + EXPECT_EQ(element->net_buffer[offset], buffer[offset]); + } + + delete element; + close(fd_client); + close(fd_server); + + std::cout << std::endl; + } + + delete queue; +} diff --git a/tests/netqueue/netqueue_test_handler.hpp b/tests/netqueue/netqueue_test_handler.hpp new file mode 100644 index 0000000..856b5fe --- /dev/null +++ b/tests/netqueue/netqueue_test_handler.hpp @@ -0,0 +1,28 @@ + +#include "vnet/netqueue/handler.hpp" +#include + +using namespace vnet::netqueue; + +std::vector socket_datas; +std::vector close_datas; +std::vector tun_datas; + +void onSocketReady (socket_data data) { + socket_datas.push_back(data); +} +void onTunReady (tun_data data) { + tun_datas.push_back(data); +} +void onClose (close_data data) { + close_datas.push_back(data); +} + +NetworkQueueHandler createTestHandler () { + NetworkQueueHandler handler; + handler.onSocketReady = &onSocketReady; + handler.onTunReady = &onTunReady; + handler.onClose = &onClose; + + return handler; +} diff --git a/tests/netqueue/netqueue_tests.cpp b/tests/netqueue/netqueue_tests.cpp new file mode 100644 index 0000000..99d5c9d --- /dev/null +++ b/tests/netqueue/netqueue_tests.cpp @@ -0,0 +1,113 @@ + +#include "netqueue/netqueue_macro.hpp" + +using namespace vnet::netqueue; +using namespace vnet::protocol; + +TEST(NetQueue, InvalidFSM) { + PREPARE_NETWORK_TEST((FiniteStateMachine) 123); + + NetQueue* queue = new NetQueue(NetworkQueueHandler()); + queue->process(element); + + EXPECT_EQ(element->state, (FiniteStateMachine) 123); + + delete queue; + delete element; + close(fd_server); + close(fd_client); +} +TEST(NetQueue, InvalidErrorHandler) { + NetworkQueueHandler handler; + handler.onClose = nullptr; + + EXPECT_THROW(new NetQueue(handler), std::invalid_argument); + + handler = NetworkQueueHandler(); + handler.onSocketReady = nullptr; + EXPECT_THROW(new NetQueue(handler), std::invalid_argument); + + handler = NetworkQueueHandler(); + handler.onTunReady = nullptr; + EXPECT_THROW(new NetQueue(handler), std::invalid_argument); +} + +TEST(NetQueue, GetNetworkElementInvalidFD) { + NetQueue* queue = new NetQueue(NetworkQueueHandler()); + EXPECT_EQ( queue->get_network_element_from_fd(25), nullptr ); + delete queue; +} +TEST(NetQueue, GetNetworkElement) { + PREPARE_SOCKET_PAIR + + NetQueue* queue = new NetQueue(NetworkQueueHandler()); + NetworkElement* element = queue->put_sck( fd_client, nullptr ); + + EXPECT_NE(element, nullptr); + EXPECT_EQ(element, queue->get_network_element_from_fd(fd_client)); + + close(fd_server); + delete queue; +} +TEST(NetQueue, CloseNetworkElement) { + PREPARE_SOCKET_PAIR + + unsigned char bf[1]; + + NetQueue* queue = new NetQueue(NetworkQueueHandler()); + NetworkElement* element = queue->put_sck( fd_client, nullptr ); + + EXPECT_EQ( read(fd_client, bf, 1), -1 ); + EXPECT_EQ( errno, EAGAIN ); + EXPECT_EQ( element, queue->get_network_element_from_fd(fd_client) ); + + queue->close( nullptr ); + + EXPECT_EQ( read(fd_client, bf, 1), -1 ); + EXPECT_EQ( errno, EAGAIN ); + EXPECT_EQ( element, queue->get_network_element_from_fd(fd_client) ); + + NetworkElement* wrong_element = new NetworkElement(STDIN_FILENO, nullptr, SCK_HEADER); + queue->close( wrong_element ); + delete wrong_element; + + EXPECT_EQ( read(fd_client, bf, 1), -1 ); + EXPECT_EQ( errno, EAGAIN ); + EXPECT_EQ( element, queue->get_network_element_from_fd(fd_client) ); + + queue->close(element); + + EXPECT_EQ( read(fd_client, bf, 1), -1 ); + EXPECT_EQ( errno, EBADF ); + EXPECT_EQ( queue->get_network_element_from_fd(fd_client), nullptr ); + + close(fd_server); + delete queue; +} +TEST(NetQueue, CloseFD) { + PREPARE_SOCKET_PAIR + + unsigned char bf[1]; + + NetQueue* queue = new NetQueue(NetworkQueueHandler()); + NetworkElement* element = queue->put_sck( fd_client, nullptr ); + + EXPECT_EQ( read(fd_client, bf, 1), -1 ); + EXPECT_EQ( errno, EAGAIN ); + EXPECT_EQ( element, queue->get_network_element_from_fd(fd_client) ); + + queue->close( fd_client + 1 ); + + EXPECT_EQ( read(fd_client, bf, 1), -1 ); + EXPECT_EQ( errno, EAGAIN ); + EXPECT_EQ( element, queue->get_network_element_from_fd(fd_client) ); + + queue->close( fd_client ); + + EXPECT_EQ( read(fd_client, bf, 1), -1 ); + EXPECT_EQ( errno, EBADF ); + EXPECT_EQ( queue->get_network_element_from_fd(fd_client), nullptr ); + + close(fd_server); + delete queue; +} diff --git a/tests/netqueue/netqueue_tun_tests.cpp b/tests/netqueue/netqueue_tun_tests.cpp new file mode 100644 index 0000000..81bd483 --- /dev/null +++ b/tests/netqueue/netqueue_tun_tests.cpp @@ -0,0 +1,102 @@ + +#include "netqueue/netqueue_macro.hpp" +#include "netqueue/netqueue_test_handler.hpp" + +using namespace vnet::netqueue; +using namespace vnet::protocol; + +TEST(NetQueueTUN, ProcessReceive) { + PREPARE_NETWORK_TUN_TEST(TUN_RECEIVE); + + NetQueue* queue = new NetQueue(NetworkQueueHandler()); + queue->process(element); + + EXPECT_EQ(TUN_RECEIVE, element->state); + + delete queue; + delete element; + close(fd_server); + close(fd_client); +} +TEST(NetQueueTUN, ProcessReady) { + PREPARE_NETWORK_TUN_TEST(TUN_READY); + + NetQueue* queue = new NetQueue(NetworkQueueHandler()); + queue->process(element); + + EXPECT_EQ(TUN_RECEIVE, element->state); + + delete queue; + delete element; + close(fd_server); + close(fd_client); +} +TEST(NetQueueTUN, ProcessError) { + PREPARE_NETWORK_TUN_TEST(TUN_ERROR); + + NetQueue* queue = new NetQueue(NetworkQueueHandler()); + queue->process(element); + + EXPECT_EQ(TUN_ERROR, element->state); + + delete queue; + delete element; + close(fd_server); + close(fd_client); +} +TEST(NetQueueTUN, ProcessEOF) { + PREPARE_NETWORK_TUN_TEST(TUN_EOF); + + NetQueue* queue = new NetQueue(NetworkQueueHandler()); + queue->process(element); + + EXPECT_EQ(TUN_EOF, element->state); + + delete queue; + delete element; + close(fd_server); + close(fd_client); +} + +TEST(NetQueueTUN, FullProcess) { + PREPARE_TUN_PAIR + + NetQueue* queue = new NetQueue(createTestHandler(), 100); + NetworkElement* element = queue->put_tun(fd_client, nullptr); + + write(fd_server, "sample1", 7); + queue->wait_and_process(); + + EXPECT_EQ( (int) tun_datas.size(), 1 ); + EXPECT_EQ( tun_datas[0].fd, fd_client ); + EXPECT_EQ( tun_datas[0].ptr_data, nullptr ); + EXPECT_EQ( tun_datas[0].net_element, element ); + EXPECT_EQ( tun_datas[0].ip_buffer_size, 7 ); + EXPECT_EQ( tun_datas[0].ip_buffer[0], 's' ); + EXPECT_EQ( tun_datas[0].ip_buffer[1], 'a' ); + EXPECT_EQ( tun_datas[0].ip_buffer[2], 'm' ); + EXPECT_EQ( tun_datas[0].ip_buffer[3], 'p' ); + EXPECT_EQ( tun_datas[0].ip_buffer[4], 'l' ); + EXPECT_EQ( tun_datas[0].ip_buffer[5], 'e' ); + EXPECT_EQ( tun_datas[0].ip_buffer[6], '1' ); + + write(fd_server, "sample23", 8); + queue->wait_and_process(); + + EXPECT_EQ( (int) tun_datas.size(), 2 ); + EXPECT_EQ( tun_datas[1].fd, fd_client ); + EXPECT_EQ( tun_datas[1].ptr_data, nullptr ); + EXPECT_EQ( tun_datas[1].net_element, element ); + EXPECT_EQ( tun_datas[1].ip_buffer_size, 8 ); + EXPECT_EQ( tun_datas[1].ip_buffer[0], 's' ); + EXPECT_EQ( tun_datas[1].ip_buffer[1], 'a' ); + EXPECT_EQ( tun_datas[1].ip_buffer[2], 'm' ); + EXPECT_EQ( tun_datas[1].ip_buffer[3], 'p' ); + EXPECT_EQ( tun_datas[1].ip_buffer[4], 'l' ); + EXPECT_EQ( tun_datas[1].ip_buffer[5], 'e' ); + EXPECT_EQ( tun_datas[1].ip_buffer[6], '2' ); + EXPECT_EQ( tun_datas[1].ip_buffer[7], '3' ); + + delete queue; + close(fd_server); +} \ No newline at end of file diff --git a/tests/protocol/header_tests.cpp b/tests/protocol/header_tests.cpp new file mode 100644 index 0000000..e3936d5 --- /dev/null +++ b/tests/protocol/header_tests.cpp @@ -0,0 +1,33 @@ + +#include "vnet/protocol/header.hpp" + +#include + +using namespace vnet::protocol; + +TEST(ProtocolHeader, TestConstructorAndGetters) { + PacketHeader header; + EXPECT_EQ(header.get_packet_type (), (PacketType) 0); + EXPECT_EQ(header.get_payload_size(), 0); + + header = PacketHeader(0x12345678, (PacketType) 0x4321); + EXPECT_EQ(header.get_packet_type (), (PacketType) 0x4321); + EXPECT_EQ(header.get_payload_size(), 0x12345678); +} + +TEST(ProtocolHeader, TestOrdering) { + unsigned char buffer[PACKET_HEADER_SIZE]; + + *((PacketHeader*) buffer) = PacketHeader(0x12345678, (PacketType) 0x4321); + + uint32_t payload_size = *((uint32_t*) buffer); + uint16_t packet_type = *((uint16_t*) (buffer + sizeof(uint32_t))); + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + EXPECT_EQ(payload_size, 0x78563412); + EXPECT_EQ(packet_type, 0x2143); +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + EXPECT_EQ(payload_size, 0x12345678); + EXPECT_EQ(packet_type, 0x4321); +#endif +} diff --git a/tests/protocol/types_tests.cpp b/tests/protocol/types_tests.cpp new file mode 100644 index 0000000..6ce2402 --- /dev/null +++ b/tests/protocol/types_tests.cpp @@ -0,0 +1,30 @@ + +#include "vnet/protocol/types.hpp" + +#include + +using namespace vnet::protocol; + +TEST(ProtocolTypes, HostToNetwork) { + PacketType pkt = (PacketType) 0x0FEF; + + uint_packet_t pkt_tp = hton_packet_type(pkt); + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + EXPECT_EQ(pkt_tp, 0xEF0F); +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + EXPECT_EQ(pkt_tp, 0x0FEF); +#endif +} + +TEST(ProtocolTypes, NetworkToHost) { + uint_packet_t pkt_tp = 0x0FEF; + + uint_packet_t pkt = ntoh_packet_type(pkt_tp); + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + EXPECT_EQ(pkt, 0xEF0F); +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + EXPECT_EQ(pkt, 0x0FEF); +#endif +} diff --git a/tests/utils/malloc_wrapper.cpp b/tests/utils/malloc_wrapper.cpp new file mode 100644 index 0000000..6f66500 --- /dev/null +++ b/tests/utils/malloc_wrapper.cpp @@ -0,0 +1,51 @@ + +#include +#include + +#include "utils/malloc_wrapper.hpp" + +static bool g_force_malloc_fail = false; +static bool g_store_malloc_results = false; + +static std::vector g_malloc_storage; + +extern "C" void* __real_malloc(size_t size); +extern "C" void* __wrap_malloc(size_t size) { + if (g_store_malloc_results) { + g_malloc_storage.push_back(size); + } + if (g_force_malloc_fail) { + return NULL; + } + + return __real_malloc(size); +} + +void enable_malloc () { + g_force_malloc_fail = false; +} +void disable_malloc () { + g_force_malloc_fail = true; +} + +void use_malloc (bool enabled) { + g_force_malloc_fail = !enabled; +} + +void enable_malloc_storage () { + g_store_malloc_results = true; +} +void disable_malloc_storage () { + g_store_malloc_results = false; +} + + +void clear_malloc_storage () { + g_malloc_storage.clear(); +} +size_t get_malloc_storage_size () { + return g_malloc_storage.size(); +} +size_t get_malloc_ith_call (int call_id) { + return g_malloc_storage[call_id]; +} diff --git a/tests/utils/malloc_wrapper.hpp b/tests/utils/malloc_wrapper.hpp new file mode 100644 index 0000000..4cbf46c --- /dev/null +++ b/tests/utils/malloc_wrapper.hpp @@ -0,0 +1,12 @@ + +void enable_malloc (); +void disable_malloc (); + +void use_malloc (bool enabled); + +void enable_malloc_storage (); +void disable_malloc_storage (); + +void clear_malloc_storage (); +size_t get_malloc_storage_size (); +size_t get_malloc_ith_call (int call_id); diff --git a/tests/utils/tun_wrapper.cpp b/tests/utils/tun_wrapper.cpp new file mode 100644 index 0000000..5f5998e --- /dev/null +++ b/tests/utils/tun_wrapper.cpp @@ -0,0 +1,100 @@ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +struct tun_wrapped_data { + int opp; + + std::queue read_buffers; +}; + +std::map tun_data_per_fd; + +void* byte_array_dup(const void* src, size_t size) { + if (src == NULL || size == 0) { + return NULL; + } + + void* dest = malloc(size); + + if (dest == NULL) { + return NULL; + } + + memcpy(dest, src, size); + + return dest; +} + +char total_buffer[70'000]; + +extern "C" ssize_t __real_read(int fd, void* buf, size_t count); +extern "C" ssize_t __wrap_read(int fd, void* buf, size_t count) { + auto it_tun_data = tun_data_per_fd.find(fd); + if (it_tun_data == tun_data_per_fd.end()) { + return __real_read(fd, buf, count); + } + + tun_wrapped_data& data = (*it_tun_data).second; + if (data.read_buffers.size() == 0) { + errno = EAGAIN; + return -1; + } + + size_t length = data.read_buffers.front(); + data.read_buffers.pop(); + + __real_read(fd, total_buffer, length); + size_t res = 0; + for (; res < count && res < length; res ++) { + ((char*) buf)[res] = total_buffer[res]; + } + + return res; +} + +extern "C" ssize_t __real_write(int fd, const void* buf, size_t count); +extern "C" ssize_t __wrap_write(int fd, const void* buf, size_t count) { + auto it_tun_data = tun_data_per_fd.find(fd); + if (it_tun_data == tun_data_per_fd.end()) { + return __real_write(fd, buf, count); + } + + tun_wrapped_data& data = (*it_tun_data).second; + + int opp = data.opp; + tun_wrapped_data& opp_data = tun_data_per_fd[opp]; + + opp_data.read_buffers.push( count ); + return __real_write(fd, buf, count); +} + +int tun_wrapper_open (int sv[2]) { + int res = socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, sv); + if (res == -1) return -1; + + tun_data_per_fd[sv[0]] = { sv[1], {} }; + tun_data_per_fd[sv[1]] = { sv[0], {} }; + return res; +} +void tun_wrapper_close (int tunfd) { + auto it = tun_data_per_fd.find(tunfd); + if (it == tun_data_per_fd.end()) return ; + + int oppfd = (*it).second.opp; + tun_data_per_fd.erase(it); + close(tunfd); + + tun_wrapper_close(oppfd); +} diff --git a/tests/utils/tun_wrapper.hpp b/tests/utils/tun_wrapper.hpp new file mode 100644 index 0000000..6b9e035 --- /dev/null +++ b/tests/utils/tun_wrapper.hpp @@ -0,0 +1,3 @@ + +void tun_wrapper_open (int sv[2]); +void tun_wrapper_close (int tunfd); diff --git a/tests/utils/tun_wrapper_tests.cpp b/tests/utils/tun_wrapper_tests.cpp new file mode 100644 index 0000000..46ed919 --- /dev/null +++ b/tests/utils/tun_wrapper_tests.cpp @@ -0,0 +1,52 @@ + +#include +#include +#include +#include + +#include "utils/tun_wrapper.hpp" +#include "vnet/const.hpp" + +TEST(TunWrapper, ReadWriteWorks) { + int sv[2]; + tun_wrapper_open(sv); + int fd_client = sv[0]; + int fd_server = sv[1]; + + unsigned char buffer[vnet::NET_BUFFER_SIZE]; + write(fd_server, "sample1", 7); + write(fd_server, "sample23", 8); + write(fd_server, "sample45", 6); + + ssize_t nb_read; + nb_read = read(fd_client, buffer, vnet::NET_BUFFER_SIZE); + EXPECT_EQ(nb_read, 7); + EXPECT_EQ(buffer[0], 's'); + EXPECT_EQ(buffer[1], 'a'); + EXPECT_EQ(buffer[2], 'm'); + EXPECT_EQ(buffer[3], 'p'); + EXPECT_EQ(buffer[4], 'l'); + EXPECT_EQ(buffer[5], 'e'); + EXPECT_EQ(buffer[6], '1'); + nb_read = read(fd_client, buffer, vnet::NET_BUFFER_SIZE); + EXPECT_EQ(nb_read, 8); + EXPECT_EQ(buffer[0], 's'); + EXPECT_EQ(buffer[1], 'a'); + EXPECT_EQ(buffer[2], 'm'); + EXPECT_EQ(buffer[3], 'p'); + EXPECT_EQ(buffer[4], 'l'); + EXPECT_EQ(buffer[5], 'e'); + EXPECT_EQ(buffer[6], '2'); + EXPECT_EQ(buffer[7], '3'); + nb_read = read(fd_client, buffer, 6); + EXPECT_EQ(nb_read, 6); + EXPECT_EQ(buffer[0], 's'); + EXPECT_EQ(buffer[1], 'a'); + EXPECT_EQ(buffer[2], 'm'); + EXPECT_EQ(buffer[3], 'p'); + EXPECT_EQ(buffer[4], 'l'); + EXPECT_EQ(buffer[5], 'e'); + + tun_wrapper_close(fd_client); + tun_wrapper_close(fd_server); +} From 45ca174b7b96eb8c7700a42f656ab16e827151ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Hollender?= Date: Sun, 30 Nov 2025 11:44:40 +0100 Subject: [PATCH 2/2] dev-netqueue: fixed tests --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d3bc4b8..7a5db1b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -20,7 +20,7 @@ jobs: - name: install tools run: | sudo apt-get update - sudo apt-get install -y g++ cmake valgrind libprotobuf-dev protobuf-compiler + sudo apt-get install -y g++ cmake valgrind libprotobuf-dev protobuf-compiler lcov - name: compile run: | mkdir build