Adding UPnP port mapping.

This commit is contained in:
clemahieu 2017-02-15 23:43:35 -06:00
commit 82dda80abe
6 changed files with 197 additions and 10 deletions

3
.gitmodules vendored
View file

@ -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

View file

@ -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)

1
miniupnp Submodule

@ -0,0 +1 @@
Subproject commit 859b9863854244fdb7eb65f9c185df4e8fe71dc0

View file

@ -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 ();
}
}

View file

@ -19,11 +19,15 @@
#include <ed25519-donna/ed25519.h>
#include <upnpcommands.h>
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 <std::string> const & peers_a)
@ -2912,3 +2919,126 @@ work (nullptr)
boost::filesystem::create_directories (path);
node = std::make_shared <rai::node> (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 <std::mutex> 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 <std::mutex> 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 <std::mutex> lock (mutex);
auto node_port (std::to_string (node.network.endpoint ().port ()));
std::array <char, 64> int_client;
std::array <char, 6> 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 <std::mutex> 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;
}

View file

@ -20,6 +20,8 @@
#include <boost/multi_index/random_access_index.hpp>
#include <boost/circular_buffer.hpp>
#include <miniupnpc.h>
std::ostream & operator << (std::ostream &, std::chrono::system_clock::time_point const &);
namespace boost
@ -205,6 +207,37 @@ public:
size_t rebroadcast;
std::function <void (boost::system::error_code const &, size_t)> 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 <char, 6> 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 <char, 64> local_address;
std::array <char const *, 2> const protocols;
std::array <char, 16> 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);