From 82dda80abeaeb04ce0bb3fb6a597720e036e54b7 Mon Sep 17 00:00:00 2001 From: clemahieu Date: Wed, 15 Feb 2017 23:43:35 -0600 Subject: [PATCH] Adding UPnP port mapping. --- .gitmodules | 3 + CMakeLists.txt | 24 ++++--- miniupnp | 1 + rai/core_test/network.cpp | 13 ++++ rai/node/node.cpp | 132 +++++++++++++++++++++++++++++++++++++- rai/node/node.hpp | 34 ++++++++++ 6 files changed, 197 insertions(+), 10 deletions(-) create mode 160000 miniupnp diff --git a/.gitmodules b/.gitmodules index da68c970..97d586de 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "beast"] path = beast url = https://github.com/vinniefalco/Beast.git +[submodule "miniupnp"] + path = miniupnp + url = https://github.com/miniupnp/miniupnp.git diff --git a/CMakeLists.txt b/CMakeLists.txt index e98e9799..85d2ca08 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required (VERSION 2.8.11) project (rai) if (WIN32) - set (PLATFORM_COMPILE_FLAGS "-DBOOST_SPIRIT_THREADSAFE -D_WIN32_WINNT=0x0600 -DWINVER=0x0600 -DWIN32_LEAN_AND_MEAN") + set (PLATFORM_COMPILE_FLAGS "-DBOOST_SPIRIT_THREADSAFE -D_WIN32_WINNT=0x0600 -DWINVER=0x0600 -DWIN32_LEAN_AND_MEAN -DMINIUPNP_STATICLIB") set (BLAKE2_IMPLEMENTATION "blake2/blake2b.c") else (WIN32) set (PLATFORM_COMPILE_FLAGS "-DBOOST_SPIRIT_THREADSAFE -Werror=switch -fPIC") @@ -79,6 +79,12 @@ include_directories ("gtest/include") include_directories (beast/include) +set (UPNPC_BUILD_SHARED OFF CACHE BOOL "") +set (WINSOCK2_LIBRARY Ws2_32 CACHE STRING "") +set (IPHLPAPI_LIBRARY iphlpapi CACHE STRING "") +add_subdirectory (miniupnp/miniupnpc) +include_directories (miniupnp/miniupnpc) + add_library (cryptopp cryptopp/algparam.cpp cryptopp/asn.cpp @@ -253,7 +259,7 @@ set_target_properties (secure node core_test slow_test rai_node qt qt_system rai set_target_properties (rai_wallet qt_test PROPERTIES LINK_FLAGS "${PLATFORM_LINK_FLAGS}") if (WIN32) - set (PLATFORM_LIBS ws2_32 mswsock) + set (PLATFORM_LIBS Ws2_32 mswsock iphlpapi) else (WIN32) set (PLATFORM_LIBS pthread dl) endif (WIN32) @@ -264,19 +270,19 @@ else (WIN32) set (PLATFORM_WALLET_LIBS) endif (WIN32) -target_link_libraries (core_test node secure lmdb xxhash ed25519 argon2 blake2 cryptopp gtest_main gtest ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_THREAD_LIBRARY} ${PLATFORM_LIBS}) +target_link_libraries (core_test node secure lmdb xxhash ed25519 argon2 blake2 cryptopp gtest_main gtest libminiupnpc-static ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_THREAD_LIBRARY} ${PLATFORM_LIBS}) -target_link_libraries (slow_test node secure lmdb xxhash ed25519 argon2 blake2 cryptopp gtest_main gtest ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_THREAD_LIBRARY} ${PLATFORM_LIBS}) +target_link_libraries (slow_test node secure lmdb xxhash ed25519 argon2 blake2 cryptopp gtest_main gtest libminiupnpc-static ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_THREAD_LIBRARY} ${PLATFORM_LIBS}) -target_link_libraries (rai_node node secure lmdb xxhash ed25519 argon2 blake2 cryptopp ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_THREAD_LIBRARY} ${PLATFORM_LIBS}) +target_link_libraries (rai_node node secure lmdb xxhash ed25519 argon2 blake2 cryptopp libminiupnpc-static ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_THREAD_LIBRARY} ${PLATFORM_LIBS}) -target_link_libraries (rai_landing node secure lmdb xxhash ed25519 argon2 blake2 cryptopp ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_THREAD_LIBRARY} ${PLATFORM_LIBS}) +target_link_libraries (rai_landing node secure lmdb xxhash ed25519 argon2 blake2 cryptopp libminiupnpc-static ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_THREAD_LIBRARY} ${PLATFORM_LIBS}) -target_link_libraries (qt_test node secure lmdb xxhash ed25519 qt argon2 blake2 cryptopp gtest ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_THREAD_LIBRARY} Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Test ${QT_QTGUI_LIBRARY} ${PLATFORM_LIBS}) +target_link_libraries (qt_test node secure lmdb xxhash ed25519 qt argon2 blake2 cryptopp gtest libminiupnpc-static ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_THREAD_LIBRARY} Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Test ${QT_QTGUI_LIBRARY} ${PLATFORM_LIBS}) -target_link_libraries (rai_wallet node secure lmdb xxhash ed25519 qt argon2 blake2 cryptopp ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_THREAD_LIBRARY} Qt5::Core Qt5::Gui Qt5::Widgets ${QT_QTGUI_LIBRARY} ${PLATFORM_LIBS} ${PLATFORM_WALLET_LIBS}) +target_link_libraries (rai_wallet node secure lmdb xxhash ed25519 qt argon2 blake2 cryptopp libminiupnpc-static ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_THREAD_LIBRARY} Qt5::Core Qt5::Gui Qt5::Widgets ${QT_QTGUI_LIBRARY} ${PLATFORM_LIBS} ${PLATFORM_WALLET_LIBS}) -target_link_libraries (qt_system node secure lmdb xxhash ed25519 qt argon2 blake2 cryptopp gtest ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_THREAD_LIBRARY} Qt5::Core Qt5::Gui Qt5::Widgets ${QT_QTGUI_LIBRARY} ${PLATFORM_LIBS}) +target_link_libraries (qt_system node secure lmdb xxhash ed25519 qt argon2 blake2 cryptopp gtest libminiupnpc-static ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_THREAD_LIBRARY} Qt5::Core Qt5::Gui Qt5::Widgets ${QT_QTGUI_LIBRARY} ${PLATFORM_LIBS}) set (CPACK_RESOURCE_FILE_LICENSE ${CMAKE_SOURCE_DIR}/LICENSE) if (APPLE) diff --git a/miniupnp b/miniupnp new file mode 160000 index 00000000..859b9863 --- /dev/null +++ b/miniupnp @@ -0,0 +1 @@ +Subproject commit 859b9863854244fdb7eb65f9c185df4e8fe71dc0 diff --git a/rai/core_test/network.cpp b/rai/core_test/network.cpp index 7c09a33a..7dadb7ab 100644 --- a/rai/core_test/network.cpp +++ b/rai/core_test/network.cpp @@ -911,3 +911,16 @@ TEST (network, reserved_address) { ASSERT_FALSE (rai::reserved_address (rai::endpoint (boost::asio::ip::address_v6::from_string ("2001::"), 0))); } + +TEST (node, port_mapping) +{ + rai::system system (24000, 1); + auto node0 (system.nodes [0]); + node0->port_mapping.refresh_devices (); + node0->port_mapping.start (); + auto end (std::chrono::system_clock::now () + std::chrono::seconds (500)); + //while (std::chrono::system_clock::now () < end) + { + system.poll (); + } +} diff --git a/rai/node/node.cpp b/rai/node/node.cpp index 134ffd9c..2943f07f 100755 --- a/rai/node/node.cpp +++ b/rai/node/node.cpp @@ -19,11 +19,15 @@ #include +#include + double constexpr rai::node::price_max; double constexpr rai::node::free_cutoff; std::chrono::seconds constexpr rai::node::period; std::chrono::seconds constexpr rai::node::cutoff; std::chrono::minutes constexpr rai::node::backup_interval; +int constexpr rai::port_mapping::mapping_timeout; +int constexpr rai::port_mapping::check_timeout; rai::network::network (boost::asio::io_service & service_a, uint16_t port, rai::node & node_a) : socket (service_a, boost::asio::ip::udp::endpoint (boost::asio::ip::address_v6::any (), port)), @@ -831,7 +835,8 @@ network (service_a, config.peering_port, *this), bootstrap_initiator (*this), bootstrap (service_a, config.peering_port, *this), peers (network.endpoint ()), -application_path (application_path_a) +application_path (application_path_a), +port_mapping (*this) { wallets.observer = [this] (rai::account const & account_a, bool active) { @@ -1327,6 +1332,7 @@ void rai::node::start () bootstrap.start (); backup_wallet (); active.announce_votes (); + port_mapping.start (); } void rai::node::stop () @@ -1336,6 +1342,7 @@ void rai::node::stop () network.stop (); bootstrap_initiator.stop (); bootstrap.stop (); + port_mapping.stop (); } void rai::node::keepalive_preconfigured (std::vector const & peers_a) @@ -2912,3 +2919,126 @@ work (nullptr) boost::filesystem::create_directories (path); node = std::make_shared (init, *service, 24000, path, alarm, logging, work); } + +rai::port_mapping::port_mapping (rai::node & node_a) : +node (node_a), +devices (nullptr), +protocols ({{"TCP", "UDP"}}) +{ + urls = {0}; + local_address.fill (0); +} + +void rai::port_mapping::start () +{ + if (has_address ()) + { + refresh_mapping_loop (); + check_mapping_loop (); + } +} + +void rai::port_mapping::refresh_devices () +{ + std::lock_guard lock (mutex); + int discover_error = 0; + freeUPNPDevlist (devices); + devices = upnpDiscover (2000, nullptr, nullptr, UPNP_LOCAL_PORT_ANY, false, 2, &discover_error); + auto igd_error (UPNP_GetValidIGD (devices, &urls, &data, local_address.data (), sizeof (local_address))); + BOOST_LOG (node.log) << boost::str (boost::format ("UPnP local address: %3%, discovery: %1%, IGD search: %2%") % discover_error % igd_error % local_address.data ()); + for (auto i (devices); i != nullptr; i = i->pNext) + { + BOOST_LOG (node.log) << boost::str (boost::format ("UPnP device url: %1% st: %2% usn: %3%") % i->descURL % i->st % i->usn); + } +} + +bool rai::port_mapping::has_address () +{ + return local_address [0] != '\0'; +} + +bool rai::port_mapping::has_mapped_port () +{ + return actual_external_port [0] != '\0'; +} + +void rai::port_mapping::refresh_mapping () +{ + std::lock_guard lock (mutex); + auto node_port (std::to_string (node.network.endpoint ().port ())); + + // Intentionally omitted: we don't map the RPC port because, unless RPC authentication was added, this would almost always be a security risk + for (auto const protocol: protocols) + { + auto add_port_mapping_error (UPNP_AddAnyPortMapping (urls.controlURL, data.first.servicetype, node_port.c_str (), node_port.c_str (), local_address.data (), nullptr, protocol, nullptr, std::to_string (mapping_timeout).c_str (), actual_external_port.data ())); + BOOST_LOG (node.log) << boost::str (boost::format ("UPnP %1% port mapping response: %2%, actual external port %5%") % protocol % add_port_mapping_error % 0 % 0 % actual_external_port.data ()); + } +} + +void rai::port_mapping::refresh_mapping_loop () +{ + if (rai::rai_network != rai::rai_networks::rai_test_network) + { + // Long discovery time and fast setup/teardown make this impractical for testing + refresh_devices (); + } + refresh_mapping (); + auto node_l (node.shared ()); + node.alarm.add (std::chrono::system_clock::now () + std::chrono::seconds (check_mapping ()), [node_l] () + { + node_l->port_mapping.refresh_mapping_loop (); + }); +} + +int rai::port_mapping::check_mapping () +{ + std::lock_guard lock (mutex); + auto node_port (std::to_string (node.network.endpoint ().port ())); + std::array int_client; + std::array int_port; + int result = 0; + for (auto const protocol: protocols) + { + auto verify_port_mapping_error (UPNP_GetSpecificPortMappingEntry (urls.controlURL, data.first.servicetype, node_port.c_str (), protocol, nullptr, int_client.data (), int_port.data (), nullptr, nullptr, remaining_mapping_duration.data ())); + BOOST_LOG (node.log) << boost::str (boost::format ("UPnP %3% mapping verification response: %1%, remaining lease: %2%") % verify_port_mapping_error % remaining_mapping_duration.data () % protocol); + if (verify_port_mapping_error == UPNPCOMMAND_SUCCESS) + { + result = std::atoi (remaining_mapping_duration.data ()); + } + } + return next_wakeup (result); +} + +int rai::port_mapping::next_wakeup (int raw_a) +{ + // Filter out small durations so we never hammer the router with requests + int const minimum_duration = 5; + int result (std::max (raw_a, minimum_duration)); + return result; +} + +void rai::port_mapping::check_mapping_loop () +{ + check_mapping (); + auto node_l (node.shared ()); + node.alarm.add (std::chrono::system_clock::now () + std::chrono::seconds (check_timeout), [node_l] () + { + node_l->port_mapping.check_mapping_loop (); + }); +} + +void rai::port_mapping::stop () +{ + std::lock_guard lock (mutex); + if (has_mapped_port ()) + { + for (auto const protocol: protocols) + { + // Be a good citizen for the router and shut down our mapping + auto delete_error (UPNP_DeletePortMapping (urls.controlURL, data.first.servicetype, actual_external_port.data (), protocol, local_address.data ())); + BOOST_LOG (node.log) << boost::str (boost::format ("Shutdown port mapping response: %1%") % delete_error); + } + } + freeUPNPDevlist (devices); + devices = nullptr; +} diff --git a/rai/node/node.hpp b/rai/node/node.hpp index bd07834c..4af55af0 100644 --- a/rai/node/node.hpp +++ b/rai/node/node.hpp @@ -20,6 +20,8 @@ #include #include +#include + std::ostream & operator << (std::ostream &, std::chrono::system_clock::time_point const &); namespace boost @@ -205,6 +207,37 @@ public: size_t rebroadcast; std::function callback; }; +// These APIs aren't easy to understand so comments are verbose +class port_mapping +{ +public: + port_mapping (rai::node &); + void start (); + void stop (); + void refresh_devices (); + // Refresh when the lease ends + void refresh_mapping_loop (); + void refresh_mapping (); + // Refresh ocassionally in case router loses mapping + void check_mapping_loop (); + int check_mapping (); + int next_wakeup (int); + bool has_address (); + bool has_mapped_port (); + std::mutex mutex; + rai::node & node; + UPNPDev * devices; // List of all UPnP devices + UPNPUrls urls; // Something for UPnP + IGDdatas data; // Some other UPnP thing + std::array actual_external_port; + // Primes so they infrequently happen at the same time + static int constexpr mapping_timeout = rai::rai_network == rai::rai_networks::rai_test_network ? 53 : 3593; + static int constexpr check_timeout = rai::rai_network == rai::rai_networks::rai_test_network ? 17 : 53; + // Our local address according to the IGD + std::array local_address; + std::array const protocols; + std::array remaining_mapping_duration; +}; class network { public: @@ -379,6 +412,7 @@ public: rai::peer_container peers; boost::filesystem::path application_path; rai::node_observers observers; + rai::port_mapping port_mapping; static double constexpr price_max = 16.0; static double constexpr free_cutoff = 1024.0; static std::chrono::seconds constexpr period = std::chrono::seconds (60);