diff --git a/nano/node/network.cpp b/nano/node/network.cpp index 9fa13d44a0..701e69b64f 100644 --- a/nano/node/network.cpp +++ b/nano/node/network.cpp @@ -145,28 +145,7 @@ void nano::network::send_keepalive (std::shared_ptr ch void nano::network::send_keepalive_self (std::shared_ptr 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 & target_a) const } } +void nano::network::fill_keepalive_self (std::array & 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 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 ()) diff --git a/nano/node/network.hpp b/nano/node/network.hpp index 666cf4d48f..cd4e30b193 100644 --- a/nano/node/network.hpp +++ b/nano/node/network.hpp @@ -123,11 +123,17 @@ class network final 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 const &, float scale); void flood_vote_pr (std::shared_ptr const &); @@ -157,6 +163,7 @@ class network final // Desired fanout for a given scale size_t fanout (float scale = 1.0f) const; void random_fill (std::array &) const; + void fill_keepalive_self (std::array &) const; // Note: The minimum protocol version is used after the random selection, so number of peers can be less than expected. std::unordered_set> random_set (size_t, uint8_t = 0, bool = false) const; // Get the next peer for attempting a tcp bootstrap connection diff --git a/nano/node/portmapping.cpp b/nano/node/portmapping.cpp index 34ab94489e..07e947aa5a 100644 --- a/nano/node/portmapping.cpp +++ b/nano/node/portmapping.cpp @@ -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 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 (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 (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 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 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 int_client_l; + std::array int_port_l; + std::array 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 int_client_l; - std::array int_port_l; - std::array 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 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 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 (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)); } } } diff --git a/nano/node/portmapping.hpp b/nano/node/portmapping.hpp index 3828785bf5..198805868f 100644 --- a/nano/node/portmapping.hpp +++ b/nano/node/portmapping.hpp @@ -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 @@ class port_mapping 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; diff --git a/nano/secure/common.cpp b/nano/secure/common.cpp index 0cea331e3a..19635ff9c6 100644 --- a/nano/secure/common.cpp +++ b/nano/secure/common.cpp @@ -151,8 +151,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) diff --git a/nano/secure/common.hpp b/nano/secure/common.hpp index 3081f06c53..96bfae445b 100644 --- a/nano/secure/common.hpp +++ b/nano/secure/common.hpp @@ -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 */