From 2033db5ed664f4ef3f372e4b36cd9285f7215345 Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Sun, 8 Mar 2020 18:07:11 +0100 Subject: [PATCH] added a basic ip router --- src/misc/ip_router.cpp | 285 +++++++++++++++++++++++++++++++++++++++++ src/misc/ip_router.h | 67 ++++++++++ 2 files changed, 352 insertions(+) create mode 100644 src/misc/ip_router.cpp create mode 100644 src/misc/ip_router.h diff --git a/src/misc/ip_router.cpp b/src/misc/ip_router.cpp new file mode 100644 index 0000000..b594596 --- /dev/null +++ b/src/misc/ip_router.cpp @@ -0,0 +1,285 @@ +// +// Created by WolverinDEV on 08/03/2020. +// + +#include +#include +#include +#include "ip_router.h" + +using namespace ts::network; + + +constexpr static ip_rounter::route_entry generate_empty_end_node(void*) { + ip_rounter::route_entry result{}; + for(auto& ptr : result.data) + ptr = nullptr; + return result; +} + +template +constexpr std::array generate_default_table() noexcept { + std::array result{}; + + for(ip_rounter::route_entry& entry : result) + entry.use_count = ip_rounter::route_entry::const_flag_mask | 0xFFU; + + for(auto& end_ptr : result[0].data) + end_ptr = nullptr; + + for(size_t index{1}; index < result.size(); index++) + for(auto& ptr : result[index].data) + ptr = &result[index - 1]; + + return result; +} + +std::array ip_rounter::recursive_ends = generate_default_table<16>(); + +struct sockaddr_storage_info { + size_t address_offset{0}; + size_t address_length{0}; + size_t chunk_offset{0}; +}; + +constexpr std::array generate_storage_info() noexcept { + std::array result{}; + for(size_t type{0}; type < result.size(); type++) { + if(type == AF_INET) { + result[type].address_length = 4; + result[type].chunk_offset = 12; + + sockaddr_in address{}; + result[type].address_offset = (uintptr_t) &address.sin_addr.s_addr - (uintptr_t) &address; + } else if(type == AF_INET6) { + result[type].address_length = 16; + result[type].chunk_offset = 0; + + sockaddr_in6 address{}; + result[type].address_offset = (uintptr_t) &address.sin6_addr.__in6_u.__u6_addr8 - (uintptr_t) &address; + } + } + + return result; +} + +std::array storage_infos = generate_storage_info(); + +inline void address_to_chunks(uint8_t* chunks, const sockaddr_storage &address) { + if constexpr (AF_INET < 16 && AF_INET6 < 16) { + /* converter without branches (only one within the memcpy) */ + const auto& info = storage_infos[address.ss_family & 0xFU]; + + memset(chunks, 0, 16); + memcpy(chunks + info.chunk_offset, ((uint8_t*) &address) + info.address_offset, info.address_length); /* we could do this memcpy more performant */ + } else { + /* converter with branches */ + if(address.ss_family == AF_INET) { + auto address4 = (sockaddr_in*) &address; + + memset(chunks, 0, 12); + memcpy(chunks + 12, &address4->sin_addr.s_addr, 4); + } else if(address.ss_family == AF_INET6) { + auto address6 = (sockaddr_in6*) &address; + memcpy(chunks, address6->sin6_addr.__in6_u.__u6_addr8, 16); + } else { + memset(chunks, 0, 16); + } + } +} + +ip_rounter::ip_rounter() { + for(auto& data : this->root_entry.data) + data = &ip_rounter::recursive_ends[14]; + this->root_entry.use_count = ip_rounter::route_entry::const_flag_mask | 0xFFU; +} + +inline void delete_route_entry(ip_rounter::route_entry* entry, size_t level) { + if(level != 0) { + for(auto& data : entry->data) { + auto e = (ip_rounter::route_entry*) data; + if(e->is_const_entry()) continue; + + delete_route_entry(e, level - 1); + } + } + delete entry; +} + +ip_rounter::~ip_rounter() { + for(auto& entry : this->unused_nodes) + delete entry; + + for(auto& data : this->root_entry.data) { + auto entry = (ip_rounter::route_entry*) data; + if(entry->is_const_entry()) continue; + + delete_route_entry(entry, 14); + } +} + +/* + * Because we're only reading memory here, and that even quite fast we do not need to lock the register lock. + * Even if a block gets changed, it will not be deleted immediately. So we should finish reading first before that memory get freed. + */ +void* ip_rounter::resolve(const sockaddr_storage &address) const { + uint8_t address_chunks[16]; + address_to_chunks(address_chunks, address); + const ip_rounter::route_entry* current_chunk = &this->root_entry; + + std::lock_guard lock{this->entry_lock}; + //std::shared_lock lock{this->entry_lock}; + +#pragma GCC unroll 15 + for(size_t index{0}; index < 15; index++) + current_chunk = (ip_rounter::route_entry*) current_chunk->data[address_chunks[index]]; + + return current_chunk->data[address_chunks[15]]; +} + +bool ip_rounter::register_route(const sockaddr_storage &address, void *target, void ** old_target) { + uint8_t address_chunks[16]; + address_to_chunks(address_chunks, address); + + void* _temp_old_target{}; + if(!old_target) + old_target = &_temp_old_target; + + ip_rounter::route_entry* current_chunk = &this->root_entry; + std::lock_guard rlock{this->register_lock}; + + for(size_t index{0}; index < 15; index++) { + auto& next_chunk = (ip_rounter::route_entry*&) current_chunk->data[address_chunks[index]]; + if(next_chunk->is_const_entry()) { + assert(next_chunk == &ip_rounter::recursive_ends[15 - index - 1]); + + auto allocated_entry = this->create_8bit_entry(15 - index - 1); + if(!allocated_entry) return false; + + next_chunk = allocated_entry; + current_chunk->use_count++; + } + current_chunk = next_chunk; + } + + *old_target = std::exchange(current_chunk->data[address_chunks[15]], target); + if(!*old_target) current_chunk->use_count++; + return true; +} + +ip_rounter::route_entry *ip_rounter::create_8bit_entry(size_t level) { + ip_rounter::route_entry *result; + + if(this->unused_nodes.empty()) + result = new ip_rounter::route_entry{}; + else { + result = this->unused_nodes.front(); + this->unused_nodes.pop_front(); + } + + result->use_count = 0; + auto target = level == 0 ? nullptr : &ip_rounter::recursive_ends[level - 1]; + for(auto& data : result->data) + data = target; + + return result; +} + +void *ip_rounter::reset_route(const sockaddr_storage &address) { + uint8_t address_chunks[16]; + address_to_chunks(address_chunks, address); + + ip_rounter::route_entry* current_chunk = &this->root_entry; + std::lock_guard rlock{this->register_lock}; + + for(size_t index{0}; index < 15; index++) { + current_chunk = (ip_rounter::route_entry *) current_chunk->data[address_chunks[index]]; + if(current_chunk->is_const_entry()) return nullptr; /* route does not exists */ + } + + auto old = std::exchange(current_chunk->data[address_chunks[15]], nullptr); + if(!old) return nullptr; /* route does not exists */ + + if(--current_chunk->use_count == 0) { + size_t chunk_index{14}; + do { + current_chunk = &this->root_entry; + for(size_t index{0}; index < chunk_index; index++) { + current_chunk = (ip_rounter::route_entry *) current_chunk->data[address_chunks[index]]; + if(current_chunk->is_const_entry()) return nullptr; /* route does not exists */ + } + + auto& chunk = (ip_rounter::route_entry *&) current_chunk->data[address_chunks[chunk_index]]; + assert(!chunk->is_const_entry()); + assert(chunk->use_count == 0); /* already tested earlier in theory */ + + this->unused_nodes.push_back(chunk); + chunk = &ip_rounter::recursive_ends[15 - chunk_index - 1]; + + if(--current_chunk->use_count > 0) break; + } while(--chunk_index > 0); + } + + return old; +} + +void ip_rounter::cleanup_cache() { + std::lock_guard rlock{this->register_lock}; + for(auto node : this->unused_nodes) + delete node; + this->unused_nodes.clear(); +} + +bool ip_rounter::validate_chunk_entry(const ip_rounter::route_entry* current_entry, size_t level) const { + if(current_entry->is_const_entry() && level != 15) /* level 15 is the default root node which is const as well */ + return current_entry == &ip_rounter::recursive_ends[level]; + + if(level == 0) return true; + + auto default_pointer = &ip_rounter::recursive_ends[level - 1]; + for(const auto& data_ptr : current_entry->data) { + if(data_ptr == default_pointer) continue; + if(!this->validate_chunk_entry((const ip_rounter::route_entry*) data_ptr, level - 1)) + return false; + } + + return true; +} + +bool ip_rounter::validate_tree() const { + std::lock_guard rlock{this->register_lock}; + + /* first lets validate all const chunks */ + for(size_t index{0}; index < 16; index++) { + auto expected_pointer = index == 0 ? nullptr : &ip_rounter::recursive_ends[index - 1]; + + if(!ip_rounter::recursive_ends[index].is_const_entry()) + return false; + + for(const auto& data_ptr : ip_rounter::recursive_ends[index].data) + if(data_ptr != expected_pointer) + return false; + } + + /* not lets check our tree */ + return this->validate_chunk_entry(&this->root_entry, 15); +} + +size_t ip_rounter::chunk_memory(const ip_rounter::route_entry *current_entry, size_t level) const { + size_t result{sizeof(ip_rounter::route_entry)}; + + if(level > 0) { + for(const auto& data_ptr : current_entry->data) { + auto entry = (const ip_rounter::route_entry*) data_ptr; + if(entry->is_const_entry()) continue; + + result += chunk_memory(entry, level - 1); + } + } + + return result; +} + +size_t ip_rounter::used_memory() const { + return this->chunk_memory(&this->root_entry, 15); +} \ No newline at end of file diff --git a/src/misc/ip_router.h b/src/misc/ip_router.h new file mode 100644 index 0000000..d75d869 --- /dev/null +++ b/src/misc/ip_router.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include +#include +#include "./spin_lock.h" +#include + +namespace ts::network { + class ip_rounter { + /* currently its not possible to change this! */ + constexpr static auto bits_per_entry{8U}; /* must be a multiple of 2! */ + public: + struct route_entry { + constexpr static auto const_flag_mask = 0x80000000ULL; + route_entry() noexcept = default; + ~route_entry() = default; + + uint32_t use_count; + void* data[1U << (bits_per_entry + 1)]; + + [[nodiscard]] inline bool is_const_entry() const { return (this->use_count & const_flag_mask) > 0; } + }; + static_assert(std::is_trivially_destructible::value); + static_assert(std::is_trivially_constructible::value); + + + ip_rounter(); + ~ip_rounter(); + + void cleanup_cache(); + [[nodiscard]] bool validate_tree() const; + [[nodiscard]] size_t used_memory() const; + + /** + * @return Whatever the route register succeeded to initialize + */ + bool register_route(const sockaddr_storage& /* address */, void* /* target */, void** /* old route */ = nullptr); + + /** + * @return The old pointer from the route + */ + void* reset_route(const sockaddr_storage& /* address */); + + [[nodiscard]] void* resolve(const sockaddr_storage& /* address */) const; + private: + /** + * Value 0 will be an empty end + * Value 1 will points with all pointers to value 0 + * Value 2 will points with all pointers to value 1 + * ... and so on + */ + static std::array recursive_ends; + + std::mutex register_lock{}; + std::deque unused_nodes{}; + + //std::shared_mutex entry_lock{}; + spin_lock entry_lock{}; + route_entry root_entry{}; + + route_entry* create_8bit_entry(size_t /* level */); + bool validate_chunk_entry(const ip_rounter::route_entry* current_entry, size_t level) const; + size_t chunk_memory(const ip_rounter::route_entry* current_entry, size_t level) const; + }; +} \ No newline at end of file