From 62fbc459ec460f7f3699e55abb8291dd9e6679f4 Mon Sep 17 00:00:00 2001 From: clemahieu Date: Thu, 18 Nov 2021 17:10:35 +0000 Subject: [PATCH] Timeouts silent incoming connections Timeouts silent incoming connections Adds new stat counters: - tcp_silent_connection_drop - tcp_io_timeout_drop --- nano/core_test/socket.cpp | 28 +++++++++++++++++++++++ nano/lib/config.hpp | 2 ++ nano/node/bootstrap/bootstrap_server.cpp | 2 +- nano/node/socket.cpp | 29 +++++++++++++++++++++++- nano/node/socket.hpp | 12 ++++++++++ 5 files changed, 71 insertions(+), 2 deletions(-) diff --git a/nano/core_test/socket.cpp b/nano/core_test/socket.cpp index 03ebc096..32d921dd 100644 --- a/nano/core_test/socket.cpp +++ b/nano/core_test/socket.cpp @@ -351,6 +351,34 @@ TEST (socket, disabled_max_peers_per_ip) node->stop (); } +TEST (socket, disconnection_of_silent_connections) +{ + nano::system system; + auto node = system.add_node (); + auto socket = std::make_shared (*node); + // Classify the socket type as real-time as the disconnections are done only for this connection type. + socket->type_set (nano::socket::type_t::realtime); + // Silent connections are connections open by external peers that don't contribute with any data. + socket->set_silent_connection_tolerance_time (std::chrono::seconds{ 5 }); + auto bootstrap_endpoint = node->bootstrap.endpoint (); + std::atomic connected{ false }; + // Opening a connection that will be closed because it remains silent during the tolerance time. + socket->async_connect (bootstrap_endpoint, [socket, &connected] (boost::system::error_code const & ec) { + ASSERT_FALSE (ec); + connected = true; + }); + ASSERT_TIMELY (4s, connected); + // Checking the connection was closed. + ASSERT_TIMELY (10s, socket->is_closed ()); + + auto get_tcp_silent_connection_drops = [&node] () { + return node->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_silent_connection_drop, nano::stat::dir::in); + }; + ASSERT_EQ (1, get_tcp_silent_connection_drops ()); + + node->stop (); +} + TEST (socket, drop_policy) { auto node_flags = nano::inactive_node_flag_defaults (); diff --git a/nano/lib/config.hpp b/nano/lib/config.hpp index 52c8ab21..e6eb1c50 100644 --- a/nano/lib/config.hpp +++ b/nano/lib/config.hpp @@ -156,6 +156,7 @@ public: request_interval_ms = is_dev_network () ? 20 : 500; cleanup_period = is_dev_network () ? std::chrono::seconds (1) : std::chrono::seconds (60); idle_timeout = is_dev_network () ? cleanup_period * 15 : cleanup_period * 2; + silent_connection_tolerance_time = std::chrono::seconds (120); syn_cookie_cutoff = std::chrono::seconds (5); bootstrap_interval = std::chrono::seconds (15 * 60); max_peers_per_ip = is_dev_network () ? 10 : 5; @@ -189,6 +190,7 @@ public: } /** Default maximum idle time for a socket before it's automatically closed */ std::chrono::seconds idle_timeout; + std::chrono::seconds silent_connection_tolerance_time; std::chrono::seconds syn_cookie_cutoff; std::chrono::seconds bootstrap_interval; /** Maximum number of peers per IP. It is also the max number of connections per IP */ diff --git a/nano/node/bootstrap/bootstrap_server.cpp b/nano/node/bootstrap/bootstrap_server.cpp index b7632ef7..a4993cd6 100644 --- a/nano/node/bootstrap/bootstrap_server.cpp +++ b/nano/node/bootstrap/bootstrap_server.cpp @@ -749,5 +749,5 @@ bool nano::bootstrap_server::is_bootstrap_connection () bool nano::bootstrap_server::is_realtime_connection () { - return socket->type () == nano::socket::type_t::realtime || socket->type () == nano::socket::type_t::realtime_response_server; + return socket->is_realtime_connection (); } diff --git a/nano/node/socket.cpp b/nano/node/socket.cpp index ae1710be..1e4d78d7 100644 --- a/nano/node/socket.cpp +++ b/nano/node/socket.cpp @@ -21,7 +21,9 @@ nano::socket::socket (nano::node & node_a) : node{ node_a }, next_deadline{ std::numeric_limits::max () }, last_completion_time{ 0 }, - io_timeout{ node_a.config.tcp_io_timeout } + last_receive_time{ 0 }, + io_timeout{ node_a.config.tcp_io_timeout }, + silent_connection_tolerance_time{ node_a.network_params.network.silent_connection_tolerance_time } { } @@ -58,6 +60,7 @@ void nano::socket::async_read (std::shared_ptr> const & buf [this_l, buffer_a, callback_a] (boost::system::error_code const & ec, std::size_t size_a) { this_l->node.stats.add (nano::stat::type::traffic_tcp, nano::stat::dir::in, size_a); this_l->stop_timer (); + this_l->update_last_receive_time (); callback_a (ec, size_a); })); })); @@ -124,6 +127,11 @@ void nano::socket::stop_timer () last_completion_time = nano::seconds_since_epoch (); } +void nano::socket::update_last_receive_time () +{ + last_receive_time = nano::seconds_since_epoch (); +} + void nano::socket::checkup () { std::weak_ptr this_w (shared_from_this ()); @@ -131,7 +139,18 @@ void nano::socket::checkup () if (auto this_l = this_w.lock ()) { uint64_t now (nano::seconds_since_epoch ()); + auto condition_to_disconnect{ false }; + if (this_l->is_realtime_connection () && now - this_l->last_receive_time > this_l->silent_connection_tolerance_time.count ()) + { + this_l->node.stats.inc (nano::stat::type::tcp, nano::stat::detail::tcp_silent_connection_drop, nano::stat::dir::in); + condition_to_disconnect = true; + } if (this_l->next_deadline != std::numeric_limits::max () && now - this_l->last_completion_time > this_l->next_deadline) + { + this_l->node.stats.inc (nano::stat::type::tcp, nano::stat::detail::tcp_io_timeout_drop, nano::stat::dir::in); + condition_to_disconnect = true; + } + if (condition_to_disconnect) { if (this_l->node.config.logging.network_timeout_logging ()) { @@ -164,6 +183,14 @@ void nano::socket::timeout_set (std::chrono::seconds io_timeout_a) io_timeout = io_timeout_a; } +void nano::socket::set_silent_connection_tolerance_time (std::chrono::seconds tolerance_time_a) +{ + auto this_l (shared_from_this ()); + boost::asio::dispatch (strand, boost::asio::bind_executor (strand, [this_l, tolerance_time_a] () { + this_l->silent_connection_tolerance_time = tolerance_time_a; + })); +} + void nano::socket::close () { auto this_l (shared_from_this ()); diff --git a/nano/node/socket.hpp b/nano/node/socket.hpp index ddbe5ed2..cda8c2bc 100644 --- a/nano/node/socket.hpp +++ b/nano/node/socket.hpp @@ -72,6 +72,7 @@ public: /** This can be called to change the maximum idle time, e.g. based on the type of traffic detected. */ void timeout_set (std::chrono::seconds io_timeout_a); void start_timer (std::chrono::seconds deadline_a); + void set_silent_connection_tolerance_time (std::chrono::seconds tolerance_time_a); bool max () const { return queue_size >= queue_size_max; @@ -88,6 +89,14 @@ public: { type_m = type_a; } + bool is_realtime_connection () + { + return type () == nano::socket::type_t::realtime || type () == nano::socket::type_t::realtime_response_server; + } + bool is_closed () + { + return closed; + } protected: /** Holds the buffer and callback for queued writes */ @@ -107,8 +116,10 @@ protected: std::atomic next_deadline; std::atomic last_completion_time; + std::atomic last_receive_time; std::atomic timed_out{ false }; std::atomic io_timeout; + std::chrono::seconds silent_connection_tolerance_time; std::atomic queue_size{ 0 }; /** Set by close() - completion handlers must check this. This is more reliable than checking @@ -117,6 +128,7 @@ protected: void close_internal (); void start_timer (); void stop_timer (); + void update_last_receive_time (); void checkup (); private: