UPnP existing lease and other misc enhancements (#2837)
* UPnP existing lease and other misc enhancements - Add timed leases (30 minutes) with automatic renewal. The periodic map check will also renew it if something went wrong - Response from a mapping check was being misinterpreted as remaining lease time, but it returns the total lease time, not remaining (pretty useless) - Bug: `external_port` was not being set when a lease already exists (e.g., node crashed and restarted). This means self keepalives don't include external address and port information. - Make it more evident (with debug_assert) that `check_mapping` and `refresh_mapping` can't be used in tests - Always log on successful mapping rather than being behind the optional `logging.upnp_details` config; otherwise on new leases, it shows the first mapping check as an error and nothing afterwards, causes confusion * Periodically flood keepalive_self This ensures peers are updated with new mapping details
This commit is contained in:
parent
5c8d13f952
commit
4d57d70082
6 changed files with 107 additions and 91 deletions
|
@ -145,28 +145,7 @@ void nano::network::send_keepalive (std::shared_ptr<nano::transport::channel> ch
|
|||
void nano::network::send_keepalive_self (std::shared_ptr<nano::transport::channel> channel_a)
|
||||
{
|
||||
nano::keepalive message;
|
||||
random_fill (message.peers);
|
||||
// Replace part of message with node external address or listening port
|
||||
message.peers[1] = nano::endpoint (boost::asio::ip::address_v6{}, 0); // For node v19 (response channels)
|
||||
if (node.config.external_address != boost::asio::ip::address_v6{}.to_string () && node.config.external_port != 0)
|
||||
{
|
||||
message.peers[0] = nano::endpoint (boost::asio::ip::make_address_v6 (node.config.external_address), node.config.external_port);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto external_address (node.port_mapping.external_address ());
|
||||
if (external_address.address () != boost::asio::ip::address_v4::any ())
|
||||
{
|
||||
message.peers[0] = nano::endpoint (boost::asio::ip::address_v6{}, endpoint ().port ());
|
||||
boost::system::error_code ec;
|
||||
auto external_v6 = boost::asio::ip::make_address_v6 (external_address.address ().to_string (), ec);
|
||||
message.peers[1] = nano::endpoint (external_v6, external_address.port ());
|
||||
}
|
||||
else
|
||||
{
|
||||
message.peers[0] = nano::endpoint (boost::asio::ip::address_v6{}, endpoint ().port ());
|
||||
}
|
||||
}
|
||||
fill_keepalive_self (message.peers);
|
||||
channel_a->send (message);
|
||||
}
|
||||
|
||||
|
@ -667,6 +646,32 @@ void nano::network::random_fill (std::array<nano::endpoint, 8> & target_a) const
|
|||
}
|
||||
}
|
||||
|
||||
void nano::network::fill_keepalive_self (std::array<nano::endpoint, 8> & target_a) const
|
||||
{
|
||||
random_fill (target_a);
|
||||
// Replace part of message with node external address or listening port
|
||||
target_a[1] = nano::endpoint (boost::asio::ip::address_v6{}, 0); // For node v19 (response channels)
|
||||
if (node.config.external_address != boost::asio::ip::address_v6{}.to_string () && node.config.external_port != 0)
|
||||
{
|
||||
target_a[0] = nano::endpoint (boost::asio::ip::make_address_v6 (node.config.external_address), node.config.external_port);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto external_address (node.port_mapping.external_address ());
|
||||
if (external_address.address () != boost::asio::ip::address_v4::any ())
|
||||
{
|
||||
target_a[0] = nano::endpoint (boost::asio::ip::address_v6{}, port);
|
||||
boost::system::error_code ec;
|
||||
auto external_v6 = boost::asio::ip::make_address_v6 (external_address.address ().to_string (), ec);
|
||||
target_a[1] = nano::endpoint (external_v6, external_address.port ());
|
||||
}
|
||||
else
|
||||
{
|
||||
target_a[0] = nano::endpoint (boost::asio::ip::address_v6{}, port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nano::tcp_endpoint nano::network::bootstrap_peer (bool lazy_bootstrap)
|
||||
{
|
||||
nano::tcp_endpoint result (boost::asio::ip::address_v6::any (), 0);
|
||||
|
@ -743,7 +748,8 @@ void nano::network::ongoing_syn_cookie_cleanup ()
|
|||
|
||||
void nano::network::ongoing_keepalive ()
|
||||
{
|
||||
flood_keepalive ();
|
||||
flood_keepalive (0.75f);
|
||||
flood_keepalive_self (0.25f);
|
||||
std::weak_ptr<nano::node> node_w (node.shared ());
|
||||
node.alarm.add (std::chrono::steady_clock::now () + node.network_params.node.half_period, [node_w]() {
|
||||
if (auto node_l = node_w.lock ())
|
||||
|
|
|
@ -123,11 +123,17 @@ public:
|
|||
void start ();
|
||||
void stop ();
|
||||
void flood_message (nano::message const &, nano::buffer_drop_policy const = nano::buffer_drop_policy::limiter, float const = 1.0f);
|
||||
void flood_keepalive ()
|
||||
void flood_keepalive (float const scale_a = 1.0f)
|
||||
{
|
||||
nano::keepalive message;
|
||||
random_fill (message.peers);
|
||||
flood_message (message);
|
||||
flood_message (message, nano::buffer_drop_policy::limiter, scale_a);
|
||||
}
|
||||
void flood_keepalive_self (float const scale_a = 0.5f)
|
||||
{
|
||||
nano::keepalive message;
|
||||
fill_keepalive_self (message.peers);
|
||||
flood_message (message, nano::buffer_drop_policy::limiter, scale_a);
|
||||
}
|
||||
void flood_vote (std::shared_ptr<nano::vote> const &, float scale);
|
||||
void flood_vote_pr (std::shared_ptr<nano::vote> const &);
|
||||
|
@ -157,6 +163,7 @@ public:
|
|||
// Desired fanout for a given scale
|
||||
size_t fanout (float scale = 1.0f) const;
|
||||
void random_fill (std::array<nano::endpoint, 8> &) const;
|
||||
void fill_keepalive_self (std::array<nano::endpoint, 8> &) const;
|
||||
// Note: The minimum protocol version is used after the random selection, so number of peers can be less than expected.
|
||||
std::unordered_set<std::shared_ptr<nano::transport::channel>> random_set (size_t, uint8_t = 0, bool = false) const;
|
||||
// Get the next peer for attempting a tcp bootstrap connection
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
nano::port_mapping::port_mapping (nano::node & node_a) :
|
||||
node (node_a),
|
||||
protocols ({ { { "TCP", 0, boost::asio::ip::address_v4::any (), 0, true }, { "UDP", 0, boost::asio::ip::address_v4::any (), 0, !node_a.flags.disable_udp } } })
|
||||
protocols ({ { { "TCP", boost::asio::ip::address_v4::any (), 0, true }, { "UDP", boost::asio::ip::address_v4::any (), 0, !node_a.flags.disable_udp } } })
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -74,7 +74,8 @@ nano::endpoint nano::port_mapping::external_address ()
|
|||
|
||||
void nano::port_mapping::refresh_mapping ()
|
||||
{
|
||||
if (!network_params.network.is_test_network ())
|
||||
debug_assert (!network_params.network.is_test_network ());
|
||||
if (on)
|
||||
{
|
||||
nano::lock_guard<std::mutex> guard_l (mutex);
|
||||
auto node_port_l (std::to_string (node.network.endpoint ().port ()));
|
||||
|
@ -83,8 +84,9 @@ void nano::port_mapping::refresh_mapping ()
|
|||
// We don't map the RPC port because, unless RPC authentication was added, this would almost always be a security risk
|
||||
for (auto & protocol : protocols | boost::adaptors::filtered ([](auto const & p) { return p.enabled; }))
|
||||
{
|
||||
auto const lease_duration = std::chrono::duration_cast<std::chrono::seconds> (network_params.portmapping.lease_duration);
|
||||
auto upnp_description = std::string ("Nano Node (") + network_params.network.get_current_network_as_string () + ")";
|
||||
auto add_port_mapping_error_l (UPNP_AddPortMapping (upnp.urls.controlURL, upnp.data.first.servicetype, config_port_l.c_str (), node_port_l.c_str (), address.to_string ().c_str (), upnp_description.c_str (), protocol.name, nullptr, nullptr));
|
||||
auto add_port_mapping_error_l (UPNP_AddPortMapping (upnp.urls.controlURL, upnp.data.first.servicetype, config_port_l.c_str (), node_port_l.c_str (), address.to_string ().c_str (), upnp_description.c_str (), protocol.name, nullptr, std::to_string (lease_duration.count ()).c_str ()));
|
||||
if (node.config.logging.upnp_details_logging ())
|
||||
{
|
||||
node.logger.always_log (boost::str (boost::format ("UPnP %1% port mapping response: %2%") % protocol.name % add_port_mapping_error_l));
|
||||
|
@ -92,10 +94,12 @@ void nano::port_mapping::refresh_mapping ()
|
|||
if (add_port_mapping_error_l == UPNPCOMMAND_SUCCESS)
|
||||
{
|
||||
protocol.external_port = static_cast<uint16_t> (std::atoi (config_port_l.data ()));
|
||||
if (node.config.logging.upnp_details_logging ())
|
||||
{
|
||||
node.logger.always_log (boost::str (boost::format ("%1% mapped to %2%") % config_port_l % node_port_l));
|
||||
}
|
||||
node.logger.always_log (boost::str (boost::format ("UPnP %1%:%2% mapped to %3%") % protocol.external_address % config_port_l % node_port_l));
|
||||
|
||||
// Refresh mapping before the leasing ends
|
||||
node.alarm.add (std::chrono::steady_clock::now () + lease_duration - std::chrono::seconds (10), [node_l = node.shared ()]() {
|
||||
node_l->port_mapping.refresh_mapping ();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -106,49 +110,46 @@ void nano::port_mapping::refresh_mapping ()
|
|||
}
|
||||
}
|
||||
|
||||
int nano::port_mapping::check_mapping ()
|
||||
bool nano::port_mapping::check_mapping ()
|
||||
{
|
||||
int result_l (3600);
|
||||
if (!network_params.network.is_test_network ())
|
||||
// Long discovery time and fast setup/teardown make this impractical for testing
|
||||
debug_assert (!network_params.network.is_test_network ());
|
||||
bool result_l (true);
|
||||
nano::lock_guard<std::mutex> guard_l (mutex);
|
||||
auto node_port_l (std::to_string (node.network.endpoint ().port ()));
|
||||
auto config_port_l (get_config_port (node_port_l));
|
||||
for (auto & protocol : protocols | boost::adaptors::filtered ([](auto const & p) { return p.enabled; }))
|
||||
{
|
||||
// Long discovery time and fast setup/teardown make this impractical for testing
|
||||
nano::lock_guard<std::mutex> guard_l (mutex);
|
||||
auto node_port_l (std::to_string (node.network.endpoint ().port ()));
|
||||
auto config_port_l (get_config_port (node_port_l));
|
||||
for (auto & protocol : protocols | boost::adaptors::filtered ([](auto const & p) { return p.enabled; }))
|
||||
std::array<char, 64> int_client_l;
|
||||
std::array<char, 6> int_port_l;
|
||||
std::array<char, 16> remaining_mapping_duration_l;
|
||||
remaining_mapping_duration_l.fill (0);
|
||||
auto verify_port_mapping_error_l (UPNP_GetSpecificPortMappingEntry (upnp.urls.controlURL, upnp.data.first.servicetype, config_port_l.c_str (), protocol.name, nullptr, int_client_l.data (), int_port_l.data (), nullptr, nullptr, remaining_mapping_duration_l.data ()));
|
||||
if (verify_port_mapping_error_l == UPNPCOMMAND_SUCCESS)
|
||||
{
|
||||
std::array<char, 64> int_client_l;
|
||||
std::array<char, 6> int_port_l;
|
||||
std::array<char, 16> remaining_mapping_duration_l;
|
||||
remaining_mapping_duration_l.fill (0);
|
||||
auto verify_port_mapping_error_l (UPNP_GetSpecificPortMappingEntry (upnp.urls.controlURL, upnp.data.first.servicetype, config_port_l.c_str (), protocol.name, nullptr, int_client_l.data (), int_port_l.data (), nullptr, nullptr, remaining_mapping_duration_l.data ()));
|
||||
if (verify_port_mapping_error_l == UPNPCOMMAND_SUCCESS)
|
||||
{
|
||||
protocol.remaining = std::atoi (remaining_mapping_duration_l.data ());
|
||||
}
|
||||
else
|
||||
{
|
||||
protocol.remaining = 0;
|
||||
node.logger.always_log (boost::str (boost::format ("UPNP_GetSpecificPortMappingEntry failed %1%: %2%") % verify_port_mapping_error_l % strupnperror (verify_port_mapping_error_l)));
|
||||
}
|
||||
result_l = std::min (result_l, protocol.remaining);
|
||||
std::array<char, 64> external_address_l;
|
||||
external_address_l.fill (0);
|
||||
auto external_ip_error_l (UPNP_GetExternalIPAddress (upnp.urls.controlURL, upnp.data.first.servicetype, external_address_l.data ()));
|
||||
if (external_ip_error_l == UPNPCOMMAND_SUCCESS)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
protocol.external_address = boost::asio::ip::address_v4::from_string (external_address_l.data (), ec);
|
||||
}
|
||||
else
|
||||
{
|
||||
protocol.external_address = boost::asio::ip::address_v4::any ();
|
||||
node.logger.always_log (boost::str (boost::format ("UPNP_GetExternalIPAddress failed %1%: %2%") % verify_port_mapping_error_l % strupnperror (verify_port_mapping_error_l)));
|
||||
}
|
||||
if (node.config.logging.upnp_details_logging ())
|
||||
{
|
||||
node.logger.always_log (boost::str (boost::format ("UPnP %1% mapping verification response: %2%, external ip response: %3%, external ip: %4%, internal ip: %5%, remaining lease: %6%") % protocol.name % verify_port_mapping_error_l % external_ip_error_l % external_address_l.data () % address.to_string () % remaining_mapping_duration_l.data ()));
|
||||
}
|
||||
result_l = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
node.logger.always_log (boost::str (boost::format ("UPNP_GetSpecificPortMappingEntry failed %1%: %2%") % verify_port_mapping_error_l % strupnperror (verify_port_mapping_error_l)));
|
||||
}
|
||||
std::array<char, 64> external_address_l;
|
||||
external_address_l.fill (0);
|
||||
auto external_ip_error_l (UPNP_GetExternalIPAddress (upnp.urls.controlURL, upnp.data.first.servicetype, external_address_l.data ()));
|
||||
if (external_ip_error_l == UPNPCOMMAND_SUCCESS)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
protocol.external_address = boost::asio::ip::address_v4::from_string (external_address_l.data (), ec);
|
||||
protocol.external_port = static_cast<uint16_t> (std::atoi (config_port_l.data ()));
|
||||
}
|
||||
else
|
||||
{
|
||||
protocol.external_address = boost::asio::ip::address_v4::any ();
|
||||
node.logger.always_log (boost::str (boost::format ("UPNP_GetExternalIPAddress failed %1%: %2%") % verify_port_mapping_error_l % strupnperror (verify_port_mapping_error_l)));
|
||||
}
|
||||
if (node.config.logging.upnp_details_logging ())
|
||||
{
|
||||
node.logger.always_log (boost::str (boost::format ("UPnP %1% mapping verification response: %2%, external ip response: %3%, external ip: %4%, internal ip: %5%, lease: %6%") % protocol.name % verify_port_mapping_error_l % external_ip_error_l % external_address_l.data () % address.to_string () % remaining_mapping_duration_l.data ()));
|
||||
}
|
||||
}
|
||||
return result_l;
|
||||
|
@ -156,30 +157,28 @@ int nano::port_mapping::check_mapping ()
|
|||
|
||||
void nano::port_mapping::check_mapping_loop ()
|
||||
{
|
||||
int wait_duration_l = network_params.portmapping.check_timeout;
|
||||
refresh_devices ();
|
||||
if (upnp.devices != nullptr)
|
||||
{
|
||||
auto remaining (check_mapping ());
|
||||
// If the mapping is lost, refresh it
|
||||
if (remaining == 0)
|
||||
if (check_mapping ())
|
||||
{
|
||||
// Schedules a mapping refresh just before the leasing ends
|
||||
refresh_mapping ();
|
||||
}
|
||||
// Check for mapping health frequently
|
||||
node.alarm.add (std::chrono::steady_clock::now () + network_params.portmapping.health_check_period, [node_l = node.shared ()]() {
|
||||
node_l->port_mapping.check_mapping_loop ();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
wait_duration_l = 300;
|
||||
if (check_count < 10)
|
||||
if (check_count++ < 10)
|
||||
{
|
||||
node.logger.always_log (boost::str (boost::format ("UPnP No IGD devices found")));
|
||||
}
|
||||
}
|
||||
++check_count;
|
||||
if (on)
|
||||
{
|
||||
auto node_l (node.shared ());
|
||||
node.alarm.add (std::chrono::steady_clock::now () + std::chrono::seconds (wait_duration_l), [node_l]() {
|
||||
// Check for new devices later
|
||||
node.alarm.add (std::chrono::steady_clock::now () + std::chrono::minutes (5), [node_l = node.shared ()]() {
|
||||
node_l->port_mapping.check_mapping_loop ();
|
||||
});
|
||||
}
|
||||
|
@ -197,7 +196,11 @@ void nano::port_mapping::stop ()
|
|||
auto delete_error_l (UPNP_DeletePortMapping (upnp.urls.controlURL, upnp.data.first.servicetype, std::to_string (protocol.external_port).c_str (), protocol.name, address.to_string ().c_str ()));
|
||||
if (delete_error_l)
|
||||
{
|
||||
node.logger.always_log (boost::str (boost::format ("Shutdown port mapping response: %1%") % delete_error_l));
|
||||
node.logger.always_log (boost::str (boost::format ("UPnP shutdown %1% port mapping response: %2%") % protocol.name % delete_error_l));
|
||||
}
|
||||
else
|
||||
{
|
||||
node.logger.always_log (boost::str (boost::format ("UPnP shutdown %1% port mapping successful: %2%:%3%") % protocol.name % protocol.external_address % protocol.external_port));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ class mapping_protocol
|
|||
public:
|
||||
/** Protocol name; TPC or UDP */
|
||||
char const * name;
|
||||
int remaining;
|
||||
boost::asio::ip::address_v4 external_address;
|
||||
uint16_t external_port;
|
||||
bool enabled;
|
||||
|
@ -49,9 +48,10 @@ public:
|
|||
private:
|
||||
/** Add port mappings for the node port (not RPC). Refresh when the lease ends. */
|
||||
void refresh_mapping ();
|
||||
/** Refresh occasionally in case router loses mapping */
|
||||
/** Check occasionally to refresh in case router loses mapping */
|
||||
void check_mapping_loop ();
|
||||
int check_mapping ();
|
||||
/** Returns false if mapping still exists */
|
||||
bool check_mapping ();
|
||||
std::string get_config_port (std::string const &);
|
||||
upnp_state upnp;
|
||||
nano::node & node;
|
||||
|
|
|
@ -149,8 +149,8 @@ nano::voting_constants::voting_constants (nano::network_constants & network_cons
|
|||
|
||||
nano::portmapping_constants::portmapping_constants (nano::network_constants & network_constants)
|
||||
{
|
||||
mapping_timeout = network_constants.is_test_network () ? 53 : 3593;
|
||||
check_timeout = network_constants.is_test_network () ? 17 : 53;
|
||||
lease_duration = std::chrono::seconds (1787); // ~30 minutes
|
||||
health_check_period = std::chrono::seconds (53);
|
||||
}
|
||||
|
||||
nano::bootstrap_constants::bootstrap_constants (nano::network_constants & network_constants)
|
||||
|
|
|
@ -440,8 +440,8 @@ class portmapping_constants
|
|||
public:
|
||||
portmapping_constants (nano::network_constants & network_constants);
|
||||
// Timeouts are primes so they infrequently happen at the same time
|
||||
int mapping_timeout;
|
||||
int check_timeout;
|
||||
std::chrono::seconds lease_duration;
|
||||
std::chrono::seconds health_check_period;
|
||||
};
|
||||
|
||||
/** Bootstrap related constants whose value depends on the active network */
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue