Skip to content

Commit ec74ff4

Browse files
committed
Set response limits on http server connections
1 parent 915c5dc commit ec74ff4

13 files changed

+423
-27
lines changed

contrib/epee/include/net/abstract_tcp_server2.h

+10-1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
#define MONERO_DEFAULT_LOG_CATEGORY "net"
6666

6767
#define ABSTRACT_SERVER_SEND_QUE_MAX_COUNT 1000
68+
#define ABSTRACT_SERVER_SEND_QUE_MAX_BYTES_DEFAULT 100 * 1024 * 1024
6869

6970
namespace epee
7071
{
@@ -170,6 +171,7 @@ namespace net_utils
170171
} read;
171172
struct {
172173
std::deque<epee::byte_slice> queue;
174+
std::size_t total_bytes;
173175
bool wait_consume;
174176
} write;
175177
};
@@ -268,11 +270,17 @@ namespace net_utils
268270
struct shared_state : connection_basic_shared_state, t_protocol_handler::config_type
269271
{
270272
shared_state()
271-
: connection_basic_shared_state(), t_protocol_handler::config_type(), pfilter(nullptr), plimit(nullptr), stop_signal_sent(false)
273+
: connection_basic_shared_state(),
274+
t_protocol_handler::config_type(),
275+
pfilter(nullptr),
276+
plimit(nullptr),
277+
response_soft_limit(ABSTRACT_SERVER_SEND_QUE_MAX_BYTES_DEFAULT),
278+
stop_signal_sent(false)
272279
{}
273280

274281
i_connection_filter* pfilter;
275282
i_connection_limit* plimit;
283+
std::size_t response_soft_limit;
276284
bool stop_signal_sent;
277285
};
278286

@@ -380,6 +388,7 @@ namespace net_utils
380388

381389
void set_connection_filter(i_connection_filter* pfilter);
382390
void set_connection_limit(i_connection_limit* plimit);
391+
void set_response_soft_limit(std::size_t limit);
383392

384393
void set_default_remote(epee::net_utils::network_address remote)
385394
{

contrib/epee/include/net/abstract_tcp_server2.inl

+48-9
Original file line numberDiff line numberDiff line change
@@ -497,10 +497,12 @@ namespace net_utils
497497
if (m_state.socket.cancel_write) {
498498
m_state.socket.cancel_write = false;
499499
m_state.data.write.queue.clear();
500+
m_state.data.write.total_bytes = 0;
500501
state_status_check();
501502
}
502503
else if (ec.value()) {
503504
m_state.data.write.queue.clear();
505+
m_state.data.write.total_bytes = 0;
504506
interrupt();
505507
}
506508
else {
@@ -525,8 +527,11 @@ namespace net_utils
525527

526528
start_timer(get_default_timeout(), true);
527529
}
528-
assert(bytes_transferred == m_state.data.write.queue.back().size());
530+
const std::size_t byte_count = m_state.data.write.queue.back().size();
531+
assert(bytes_transferred == byte_count);
529532
m_state.data.write.queue.pop_back();
533+
m_state.data.write.total_bytes -=
534+
std::min(m_state.data.write.total_bytes, byte_count);
530535
m_state.condition.notify_all();
531536
start_write();
532537
}
@@ -670,8 +675,9 @@ namespace net_utils
670675
return;
671676
if (m_state.timers.throttle.out.wait_expire)
672677
return;
673-
if (m_state.socket.wait_write)
674-
return;
678+
// \NOTE See on_terminating() comments
679+
//if (m_state.socket.wait_write)
680+
// return;
675681
if (m_state.socket.wait_shutdown)
676682
return;
677683
if (m_state.protocol.wait_init)
@@ -729,8 +735,13 @@ namespace net_utils
729735
return;
730736
if (m_state.timers.throttle.out.wait_expire)
731737
return;
732-
if (m_state.socket.wait_write)
733-
return;
738+
// Writes cannot be canceled due to `async_write` being a "composed"
739+
// handler. ASIO has new cancellation routines, not available in 1.66, to
740+
// handle this situation. The problem is that if cancel is called after an
741+
// intermediate handler is queued, the op will not check the cancel flag in
742+
// our code, and will instead queue up another write.
743+
//if (m_state.socket.wait_write)
744+
// return;
734745
if (m_state.socket.wait_shutdown)
735746
return;
736747
if (m_state.protocol.wait_init)
@@ -757,6 +768,8 @@ namespace net_utils
757768
std::lock_guard<std::mutex> guard(m_state.lock);
758769
if (m_state.status != status_t::RUNNING || m_state.socket.wait_handshake)
759770
return false;
771+
if (std::numeric_limits<std::size_t>::max() - m_state.data.write.total_bytes < message.size())
772+
return false;
760773

761774
// Wait for the write queue to fall below the max. If it doesn't after a
762775
// randomized delay, drop the connection.
@@ -774,7 +787,14 @@ namespace net_utils
774787
std::uniform_int_distribution<>(5000, 6000)(rng)
775788
);
776789
};
777-
if (m_state.data.write.queue.size() <= ABSTRACT_SERVER_SEND_QUE_MAX_COUNT)
790+
791+
// The bytes check intentionally does not include incoming message size.
792+
// This allows for a soft overflow; a single http response will never fail
793+
// this check, but multiple responses could. Clients can avoid this case
794+
// by reading the entire response before making another request. P2P
795+
// should never hit the MAX_BYTES check (when using default values).
796+
if (m_state.data.write.queue.size() <= ABSTRACT_SERVER_SEND_QUE_MAX_COUNT &&
797+
m_state.data.write.total_bytes <= static_cast<shared_state&>(connection_basic::get_state()).response_soft_limit)
778798
return true;
779799
m_state.data.write.wait_consume = true;
780800
bool success = m_state.condition.wait_for(
@@ -783,14 +803,23 @@ namespace net_utils
783803
[this]{
784804
return (
785805
m_state.status != status_t::RUNNING ||
786-
m_state.data.write.queue.size() <=
787-
ABSTRACT_SERVER_SEND_QUE_MAX_COUNT
806+
(
807+
m_state.data.write.queue.size() <=
808+
ABSTRACT_SERVER_SEND_QUE_MAX_COUNT &&
809+
m_state.data.write.total_bytes <=
810+
static_cast<shared_state&>(connection_basic::get_state()).response_soft_limit
811+
)
788812
);
789813
}
790814
);
791815
m_state.data.write.wait_consume = false;
792816
if (!success) {
793-
terminate();
817+
// synchronize with intermediate writes on `m_strand`
818+
auto self = connection<T>::shared_from_this();
819+
boost::asio::post(m_strand, [this, self] {
820+
std::lock_guard<std::mutex> guard(m_state.lock);
821+
terminate();
822+
});
794823
return false;
795824
}
796825
else
@@ -816,7 +845,9 @@ namespace net_utils
816845
) {
817846
if (!wait_consume())
818847
return false;
848+
const std::size_t byte_count = message.size();
819849
m_state.data.write.queue.emplace_front(std::move(message));
850+
m_state.data.write.total_bytes += byte_count;
820851
start_write();
821852
}
822853
else {
@@ -826,6 +857,7 @@ namespace net_utils
826857
m_state.data.write.queue.emplace_front(
827858
message.take_slice(CHUNK_SIZE)
828859
);
860+
m_state.data.write.total_bytes += m_state.data.write.queue.front().size();
829861
start_write();
830862
}
831863
}
@@ -1369,6 +1401,13 @@ namespace net_utils
13691401
}
13701402
//---------------------------------------------------------------------------------
13711403
template<class t_protocol_handler>
1404+
void boosted_tcp_server<t_protocol_handler>::set_response_soft_limit(const std::size_t limit)
1405+
{
1406+
assert(m_state != nullptr); // always set in constructor
1407+
m_state->response_soft_limit = limit;
1408+
}
1409+
//---------------------------------------------------------------------------------
1410+
template<class t_protocol_handler>
13721411
bool boosted_tcp_server<t_protocol_handler>::run_server(size_t threads_count, bool wait, const boost::thread::attributes& attrs)
13731412
{
13741413
TRY_ENTRY();

contrib/epee/include/net/http_protocol_handler.h

+9-9
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
#include <boost/optional/optional.hpp>
3434
#include <string>
35+
#include <unordered_map>
3536
#include "net_utils_base.h"
3637
#include "http_auth.h"
3738
#include "http_base.h"
@@ -54,8 +55,13 @@ namespace net_utils
5455
{
5556
std::string m_folder;
5657
std::vector<std::string> m_access_control_origins;
58+
std::unordered_map<std::string, std::size_t> m_connections;
5759
boost::optional<login> m_user;
5860
size_t m_max_content_length{std::numeric_limits<size_t>::max()};
61+
std::size_t m_connection_count{0};
62+
std::size_t m_max_public_ip_connections{3};
63+
std::size_t m_max_private_ip_connections{25};
64+
std::size_t m_max_connections{100};
5965
critical_section m_lock;
6066
};
6167

@@ -70,7 +76,7 @@ namespace net_utils
7076
typedef http_server_config config_type;
7177

7278
simple_http_connection_handler(i_service_endpoint* psnd_hndlr, config_type& config, t_connection_context& conn_context);
73-
virtual ~simple_http_connection_handler(){}
79+
virtual ~simple_http_connection_handler();
7480

7581
bool release_protocol()
7682
{
@@ -86,10 +92,7 @@ namespace net_utils
8692
{
8793
return true;
8894
}
89-
bool after_init_connection()
90-
{
91-
return true;
92-
}
95+
bool after_init_connection();
9396
virtual bool handle_recv(const void* ptr, size_t cb);
9497
virtual bool handle_request(const http::http_request_info& query_info, http_response_info& response);
9598

@@ -146,6 +149,7 @@ namespace net_utils
146149
protected:
147150
i_service_endpoint* m_psnd_hndlr;
148151
t_connection_context& m_conn_context;
152+
bool m_initialized;
149153
};
150154

151155
template<class t_connection_context>
@@ -212,10 +216,6 @@ namespace net_utils
212216
}
213217
void handle_qued_callback()
214218
{}
215-
bool after_init_connection()
216-
{
217-
return true;
218-
}
219219

220220
private:
221221
//simple_http_connection_handler::config_type m_stub_config;

contrib/epee/include/net/http_protocol_handler.inl

+36-1
Original file line numberDiff line numberDiff line change
@@ -208,11 +208,46 @@ namespace net_utils
208208
m_newlines(0),
209209
m_bytes_read(0),
210210
m_psnd_hndlr(psnd_hndlr),
211-
m_conn_context(conn_context)
211+
m_conn_context(conn_context),
212+
m_initialized(false)
212213
{
213214

214215
}
215216
//--------------------------------------------------------------------------------------------
217+
template<class t_connection_context>
218+
simple_http_connection_handler<t_connection_context>::~simple_http_connection_handler()
219+
{
220+
try
221+
{
222+
if (m_initialized)
223+
{
224+
CRITICAL_REGION_LOCAL(m_config.m_lock);
225+
if (m_config.m_connection_count)
226+
--m_config.m_connection_count;
227+
auto elem = m_config.m_connections.find(m_conn_context.m_remote_address.host_str());
228+
if (elem != m_config.m_connections.end())
229+
{
230+
if (elem->second == 1 || elem->second == 0)
231+
m_config.m_connections.erase(elem);
232+
else
233+
--(elem->second);
234+
}
235+
}
236+
}
237+
catch (...)
238+
{}
239+
}
240+
//--------------------------------------------------------------------------------------------
241+
template<class t_connection_context>
242+
bool simple_http_connection_handler<t_connection_context>::after_init_connection()
243+
{
244+
CRITICAL_REGION_LOCAL(m_config.m_lock);
245+
++m_config.m_connections[m_conn_context.m_remote_address.host_str()];
246+
++m_config.m_connection_count;
247+
m_initialized = true;
248+
return true;
249+
}
250+
//--------------------------------------------------------------------------------------------
216251
template<class t_connection_context>
217252
bool simple_http_connection_handler<t_connection_context>::set_ready_state()
218253
{

contrib/epee/include/net/http_server_impl_base.h

+37-2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include <boost/thread.hpp>
3434
#include <boost/bind/bind.hpp>
3535

36+
#include "cryptonote_config.h"
3637
#include "net/abstract_tcp_server2.h"
3738
#include "http_protocol_handler.h"
3839
#include "net/http_server_handlers_map2.h"
@@ -44,7 +45,8 @@ namespace epee
4445
{
4546

4647
template<class t_child_class, class t_connection_context = epee::net_utils::connection_context_base>
47-
class http_server_impl_base: public net_utils::http::i_http_server_handler<t_connection_context>
48+
class http_server_impl_base: public net_utils::http::i_http_server_handler<t_connection_context>,
49+
net_utils::i_connection_limit
4850
{
4951

5052
public:
@@ -60,8 +62,16 @@ namespace epee
6062
const std::string& bind_ipv6_address = "::", bool use_ipv6 = false, bool require_ipv4 = true,
6163
std::vector<std::string> access_control_origins = std::vector<std::string>(),
6264
boost::optional<net_utils::http::login> user = boost::none,
63-
net_utils::ssl_options_t ssl_options = net_utils::ssl_support_t::e_ssl_support_autodetect)
65+
net_utils::ssl_options_t ssl_options = net_utils::ssl_support_t::e_ssl_support_autodetect,
66+
const std::size_t max_public_ip_connections = DEFAULT_RPC_MAX_CONNECTIONS_PER_PUBLIC_IP,
67+
const std::size_t max_private_ip_connections = DEFAULT_RPC_MAX_CONNECTIONS_PER_PRIVATE_IP,
68+
const std::size_t max_connections = DEFAULT_RPC_MAX_CONNECTIONS,
69+
const std::size_t response_soft_limit = DEFAULT_RPC_SOFT_LIMIT_SIZE)
6470
{
71+
if (max_connections < max_public_ip_connections)
72+
throw std::invalid_argument{"Max public IP connections cannot be more than max connections"};
73+
if (max_connections < max_private_ip_connections)
74+
throw std::invalid_argument{"Max private IP connections cannot be more than max connections"};
6575

6676
//set self as callback handler
6777
m_net_server.get_config_object().m_phandler = static_cast<t_child_class*>(this);
@@ -75,6 +85,11 @@ namespace epee
7585
m_net_server.get_config_object().m_access_control_origins = std::move(access_control_origins);
7686

7787
m_net_server.get_config_object().m_user = std::move(user);
88+
m_net_server.get_config_object().m_max_public_ip_connections = max_public_ip_connections;
89+
m_net_server.get_config_object().m_max_private_ip_connections = max_private_ip_connections;
90+
m_net_server.get_config_object().m_max_connections = max_connections;
91+
m_net_server.set_response_soft_limit(response_soft_limit);
92+
m_net_server.set_connection_limit(this);
7893

7994
MGINFO("Binding on " << bind_ip << " (IPv4):" << bind_port);
8095
if (use_ipv6)
@@ -131,6 +146,26 @@ namespace epee
131146
}
132147

133148
protected:
149+
150+
virtual bool is_host_limit(const net_utils::network_address& na) override final
151+
{
152+
auto& config = m_net_server.get_config_object();
153+
CRITICAL_REGION_LOCAL(config.m_lock);
154+
if (config.m_max_connections <= config.m_connection_count)
155+
return true;
156+
157+
const bool is_private = na.is_loopback() || na.is_local();
158+
const auto elem = config.m_connections.find(na.host_str());
159+
if (elem != config.m_connections.end())
160+
{
161+
if (is_private)
162+
return config.m_max_private_ip_connections <= elem->second;
163+
else
164+
return config.m_max_public_ip_connections <= elem->second;
165+
}
166+
return false;
167+
}
168+
134169
net_utils::boosted_tcp_server<net_utils::http::http_custom_handler<t_connection_context> > m_net_server;
135170
};
136171
}

src/cryptonote_config.h

+4
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@
127127

128128
#define COMMAND_RPC_GET_BLOCKS_FAST_MAX_BLOCK_COUNT 1000
129129
#define COMMAND_RPC_GET_BLOCKS_FAST_MAX_TX_COUNT 20000
130+
#define DEFAULT_RPC_MAX_CONNECTIONS_PER_PUBLIC_IP 3
131+
#define DEFAULT_RPC_MAX_CONNECTIONS_PER_PRIVATE_IP 25
132+
#define DEFAULT_RPC_MAX_CONNECTIONS 100
133+
#define DEFAULT_RPC_SOFT_LIMIT_SIZE 25 * 1024 * 1024 // 25 MiB
130134
#define MAX_RPC_CONTENT_LENGTH 1048576 // 1 MB
131135

132136
#define P2P_LOCAL_WHITE_PEERLIST_LIMIT 1000

src/p2p/net_node.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ namespace nodetool
169169
const command_line::arg_descriptor<bool> arg_pad_transactions = {
170170
"pad-transactions", "Pad relayed transactions to help defend against traffic volume analysis", false
171171
};
172-
const command_line::arg_descriptor<uint32_t> arg_max_connections_per_ip = {"max-connections-per-ip", "Maximum number of connections allowed from the same IP address", 1};
172+
const command_line::arg_descriptor<uint32_t> arg_max_connections_per_ip = {"max-connections-per-ip", "Maximum number of p2p connections allowed from the same IP address", 1};
173173

174174
boost::optional<std::vector<proxy>> get_proxies(boost::program_options::variables_map const& vm)
175175
{

0 commit comments

Comments
 (0)