From e8d8e10980f83718d7faab24771032437d414da2 Mon Sep 17 00:00:00 2001 From: Green Sky Date: Sun, 10 Mar 2024 16:03:15 +0100 Subject: [PATCH] should work on hardcoded port (not tested) --- .gitignore | 22 +++++++ CMakeLists.txt | 72 +++++++++++++++++++++++ external/CMakeLists.txt | 26 ++++++++ plugins/CMakeLists.txt | 13 ++++ plugins/plugin_tox_upnp.cpp | 58 ++++++++++++++++++ src/CMakeLists.txt | 17 ++++++ src/solanaceae/tox_upnp.cpp | 114 ++++++++++++++++++++++++++++++++++++ src/solanaceae/tox_upnp.hpp | 19 ++++++ 8 files changed, 341 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 external/CMakeLists.txt create mode 100644 plugins/CMakeLists.txt create mode 100644 plugins/plugin_tox_upnp.cpp create mode 100644 src/CMakeLists.txt create mode 100644 src/solanaceae/tox_upnp.cpp create mode 100644 src/solanaceae/tox_upnp.hpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..248798c --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +.vs/ +*.o +*.swp +~* +*~ +.idea/ +cmake-build-debug/ +cmake-build-debugandtest/ +cmake-build-release/ +*.stackdump +*.coredump +compile_commands.json +/build* +.clangd +.cache + +.DS_Store +.AppleDouble +.LSOverride + +CMakeLists.txt.user* +CMakeCache.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ee563f3 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,72 @@ +cmake_minimum_required(VERSION 3.24 FATAL_ERROR) + +# cmake setup begin +project(solanaceae_tox_upnp) + +if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + set(SOLANACEAE_TOX_UPNP_STANDALONE ON) +else() + set(SOLANACEAE_TOX_UPNP_STANDALONE OFF) +endif() +message("II SOLANACEAE_TOX_UPNP_STANDALONE " ${SOLANACEAE_TOX_UPNP_STANDALONE}) + +option(SOLANACEAE_TOX_UPNP_BUILD_PLUGINS "Build the solanaceae_tox_upnp plugins" ${SOLANACEAE_TOX_UPNP_STANDALONE}) + +if (SOLANACEAE_TOX_UPNP_STANDALONE) + set(CMAKE_POSITION_INDEPENDENT_CODE ON) + + # defaulting to debug mode, if not specified + if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Debug") + endif() + + # setup my vim ycm :D + set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + + # more paths + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") +endif() + +# external libs +add_subdirectory(./external EXCLUDE_FROM_ALL) # before increasing warn levels, sad :( + +if (SOLANACEAE_TOX_UPNP_STANDALONE) + set(CMAKE_CXX_EXTENSIONS OFF) + + # bump up warning levels appropriately for clang, gcc & msvc + if (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU" OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") + add_compile_options( + -Wall -Wextra # Reasonable and standard + -Wpedantic # Warn if non-standard C++ is used + -Wunused # Warn on anything being unused + #-Wconversion # Warn on type conversions that may lose data + #-Wsign-conversion # Warn on sign conversions + -Wshadow # Warn if a variable declaration shadows one from a parent context + ) + + if (NOT WIN32) + #link_libraries(-fsanitize=address) + #link_libraries(-fsanitize=address,undefined) + #link_libraries(-fsanitize-address-use-after-scope) + #link_libraries(-fsanitize=undefined) + endif() + elseif (${CMAKE_CXX_COMPILER_ID} STREQUAL "MSVC") + if (CMAKE_CXX_FLAGS MATCHES "/W[0-4]") + string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") + endif() + endif() + +endif() + +# cmake setup end + +add_subdirectory(./src) + +if (SOLANACEAE_TOX_UPNP_BUILD_PLUGINS) + add_subdirectory(./plugins) +endif() + diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt new file mode 100644 index 0000000..be5afba --- /dev/null +++ b/external/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.24 FATAL_ERROR) + +include(FetchContent) + +if (NOT TARGET solanaceae_plugin) + FetchContent_Declare(solanaceae_plugin + GIT_REPOSITORY https://github.com/Green-Sky/solanaceae_plugin.git + GIT_TAG master + ) + FetchContent_MakeAvailable(solanaceae_plugin) +endif() + +if (NOT TARGET miniupnpc::miniupnpc) + set(UPNPC_BUILD_STATIC ON CACHE BOOL "" FORCE) + set(UPNPC_BUILD_SHARED OFF CACHE BOOL "" FORCE) + set(UPNPC_BUILD_TESTS OFF CACHE BOOL "" FORCE) + set(UPNPC_BUILD_SAMPLE ON CACHE BOOL "" FORCE) + set(UPNPC_NO_INSTALL ON CACHE BOOL "" FORCE) + FetchContent_Declare(miniupnpc + GIT_REPOSITORY https://github.com/miniupnp/miniupnp.git + GIT_TAG master # meh + SOURCE_SUBDIR miniupnpc + ) + FetchContent_MakeAvailable(miniupnpc) +endif() + diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt new file mode 100644 index 0000000..fe19252 --- /dev/null +++ b/plugins/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.9...3.24 FATAL_ERROR) + +######################################## + +add_library(plugin_tox_upnp SHARED + ./plugin_tox_upnp.cpp +) +target_compile_features(plugin_tox_upnp PUBLIC cxx_std_17) +target_link_libraries(plugin_tox_upnp PUBLIC + solanaceae_tox_upnp + solanaceae_plugin +) + diff --git a/plugins/plugin_tox_upnp.cpp b/plugins/plugin_tox_upnp.cpp new file mode 100644 index 0000000..6659bbb --- /dev/null +++ b/plugins/plugin_tox_upnp.cpp @@ -0,0 +1,58 @@ +#include + +#include + +#include +#include +#include + +static std::unique_ptr g_tox_upnp = nullptr; + +constexpr const char* plugin_name = "ToxUPnP"; + +extern "C" { + +SOLANA_PLUGIN_EXPORT const char* solana_plugin_get_name(void) { + return plugin_name; +} + +SOLANA_PLUGIN_EXPORT uint32_t solana_plugin_get_version(void) { + return SOLANA_PLUGIN_VERSION; +} + +SOLANA_PLUGIN_EXPORT uint32_t solana_plugin_start(struct SolanaAPI* solana_api) { + std::cout << "PLUGIN " << plugin_name << " START()\n"; + + if (solana_api == nullptr) { + return 1; + } + + try { + // TODO: toxI + + // static store, could be anywhere tho + // construct with fetched dependencies + g_tox_upnp = std::make_unique(); + + // register types + PLUG_PROVIDE_INSTANCE(ToxUPnP, plugin_name, g_tox_upnp.get()); + } catch (const ResolveException& e) { + std::cerr << "PLUGIN " << plugin_name << " " << e.what << "\n"; + return 2; + } + + return 0; +} + +SOLANA_PLUGIN_EXPORT void solana_plugin_stop(void) { + std::cout << "PLUGIN " << plugin_name << " STOP()\n"; + + g_tox_upnp.reset(); +} + +SOLANA_PLUGIN_EXPORT float solana_plugin_tick(float) { + return std::numeric_limits::max(); +} + +} // extern C + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..f381e27 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.9...3.24 FATAL_ERROR) + +project(solanaceae) + +######################################### + +add_library(solanaceae_tox_upnp + ./solanaceae/tox_upnp.hpp + ./solanaceae/tox_upnp.cpp +) + +target_include_directories(solanaceae_tox_upnp PUBLIC .) +target_compile_features(solanaceae_tox_upnp PUBLIC cxx_std_17) +target_link_libraries(solanaceae_tox_upnp PUBLIC + miniupnpc::miniupnpc +) + diff --git a/src/solanaceae/tox_upnp.cpp b/src/solanaceae/tox_upnp.cpp new file mode 100644 index 0000000..bd21fad --- /dev/null +++ b/src/solanaceae/tox_upnp.cpp @@ -0,0 +1,114 @@ +#include "./tox_upnp.hpp" + +#include +#include +#include + +#include + +#include + +ToxUPnP::ToxUPnP(void) { + // get tox port + + // start upnp thread + _thread = std::thread([this](void) { + int seconds_since_last {60*10}; + bool last_mapping_succ {false}; + while (!_quit) { + if (seconds_since_last < 60*10) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + seconds_since_last++; + continue; + } + seconds_since_last = 0; + last_mapping_succ = false; + + // first get available devices + int error {0}; + std::cerr << "TUPNP: starting search\n"; + auto* devices = upnpDiscover(2000, nullptr, nullptr, UPNP_LOCAL_PORT_ANY, 0, 2, &error); + if (error != 0 || devices == nullptr) { + std::cerr << "TUPNP error: no device found\n"; + continue; + } + + std::cerr << "TUPNP: discovered devices:\n"; + for (auto* d = devices; d != nullptr; d = d->pNext) { + std::cerr << " " << d->descURL << " " << d->st << " " << d->usn << "\n"; + } + + UPNPUrls urls; + IGDdatas data; + char lanaddr[64] = "unset"; + auto res = UPNP_GetValidIGD(devices, &urls, &data, lanaddr, sizeof(lanaddr)); + + if (res != 1) { + std::cerr << "TUPNP error: no valid connected IGD has been found\n"; + continue; + } + + std::cerr << "TUPNP: valid IGD found, local ip: " << lanaddr << "\n"; + + const auto port_string = std::to_string(_local_port); + auto map_ret = UPNP_AddPortMapping( + urls.controlURL, + data.first.servicetype, + port_string.c_str(), + port_string.c_str(), + lanaddr, + "Tomato UPnP Tox UDP port forwarding", + "UDP", + nullptr, + "900" // lease duration in seconds + ); + + if (map_ret != UPNPCOMMAND_SUCCESS) { + std::cerr << "TUPNP error: adding port mapping failed " << strupnperror(map_ret) << "\n"; + continue; + } + + char intClient[40] {}; + char intPort[6] {}; + char duration[16] {}; + auto getmap_ret = UPNP_GetSpecificPortMappingEntry( + urls.controlURL, + data.first.servicetype, + port_string.c_str(), + "UDP", + nullptr, + intClient, + intPort, + nullptr, + nullptr, + duration + ); + + if (getmap_ret != UPNPCOMMAND_SUCCESS) { + std::cerr << "TUPNP error: getting port mapping failed " << strupnperror(getmap_ret) << "\n"; + + // potentially succ ??? + last_mapping_succ = true; + continue; + } + + std::cerr << "TUPNP: mapping active external :" << port_string << " is redirected to internal " << intClient << ":" << intPort << " (for " << duration << "s)\n"; + // potentially succ + last_mapping_succ = true; + + FreeUPNPUrls(&urls); + freeUPNPDevlist(devices); + } + + // remove mapping here? + }); +} + +ToxUPnP::~ToxUPnP(void) { + _quit = true; + + if (_thread.joinable()) { + _thread.join(); + } +} + diff --git a/src/solanaceae/tox_upnp.hpp b/src/solanaceae/tox_upnp.hpp new file mode 100644 index 0000000..71ef98c --- /dev/null +++ b/src/solanaceae/tox_upnp.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include + +class ToxUPnP { + // TODO: ToxI& _t; + + uint16_t _local_port {33445}; + + std::atomic_bool _quit {false}; + std::thread _thread; + + public: + ToxUPnP(void); + ~ToxUPnP(void); +}; +