Timeouts silent incoming connections

Timeouts silent incoming connections
Adds new stat counters:
- tcp_silent_connection_drop
- tcp_io_timeout_drop
This commit is contained in:
clemahieu 2021-11-18 17:10:35 +00:00 committed by Thiago Silva
commit 62fbc459ec
No known key found for this signature in database
GPG key ID: 034303EB8F453169
5 changed files with 71 additions and 2 deletions

View file

@ -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<nano::socket> (*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<bool> 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 ();

View file

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

View file

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

View file

@ -21,7 +21,9 @@ nano::socket::socket (nano::node & node_a) :
node{ node_a },
next_deadline{ std::numeric_limits<uint64_t>::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<std::vector<uint8_t>> 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<nano::socket> 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<uint64_t>::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 ());

View file

@ -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<uint64_t> next_deadline;
std::atomic<uint64_t> last_completion_time;
std::atomic<uint64_t> last_receive_time;
std::atomic<bool> timed_out{ false };
std::atomic<std::chrono::seconds> io_timeout;
std::chrono::seconds silent_connection_tolerance_time;
std::atomic<std::size_t> 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: