Adding UPnP port mapping.
This commit is contained in:
parent
d99ef7cf9a
commit
82dda80abe
6 changed files with 197 additions and 10 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -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
|
||||
|
|
|
@ -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
1
miniupnp
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 859b9863854244fdb7eb65f9c185df4e8fe71dc0
|
|
@ -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 ();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue