[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#1098489: marked as done (bookworm-pu: package monero/0.18.0.0+~0+20200826-1+deb12u1)



Your message dated Sat, 15 Mar 2025 09:44:44 +0000
with message-id <E1ttO4S-005Kkp-GS@coccia.debian.org>
and subject line Close 1098489
has caused the Debian Bug report #1098489,
regarding bookworm-pu: package monero/0.18.0.0+~0+20200826-1+deb12u1
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
1098489: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1098489
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
Package: release.debian.org
Tags: bookworm
User: release.debian.org@packages.debian.org
Usertags: pu
Control: affects -1 src:monero

[ Reason ]
Fix security issue that is fixed in unstable/trixie: CVE-2025-26819

[ Impact ]
Two backported upstream patches that are not released yet.

[ Tests ]
Compiled pkg and ran monerod on amd64. No problem seen so far.

[ Checklist ]
  [x] *all* changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the pkg in stable
  [x] the issue is verified as fixed in unstable

[ Changes ]
Backport the two patches for the CVE fix from unstable.
One of them is slightly modified for the older codebase.
diff -Nru monero-0.18.0.0+~0+20200826/debian/changelog monero-0.18.0.0+~0+20200826/debian/changelog
--- monero-0.18.0.0+~0+20200826/debian/changelog	2022-07-26 20:15:22.000000000 +0000
+++ monero-0.18.0.0+~0+20200826/debian/changelog	2025-02-21 07:52:42.000000000 +0000
@@ -1,3 +1,10 @@
+monero (0.18.0.0+~0+20200826-1+deb12u1) bookworm; urgency=medium
+
+  * Backport CVE-2025-26819 fix
+  * Use fully qualified names for epee (fix compilation fail)
+
+ -- Bastian Germann <bage@debian.org>  Fri, 21 Feb 2025 07:52:42 +0000
+
 monero (0.18.0.0+~0+20200826-1) unstable; urgency=medium
 
   * New upstream version 0.18.0.0+~0+20200826
diff -Nru monero-0.18.0.0+~0+20200826/debian/patches/1003_tcp_throttling.patch monero-0.18.0.0+~0+20200826/debian/patches/1003_tcp_throttling.patch
--- monero-0.18.0.0+~0+20200826/debian/patches/1003_tcp_throttling.patch	1970-01-01 00:00:00.000000000 +0000
+++ monero-0.18.0.0+~0+20200826/debian/patches/1003_tcp_throttling.patch	2025-02-18 17:40:17.000000000 +0000
@@ -0,0 +1,357 @@
+Origin: upstream, 7e766e13c3790856fee440dcf8d47dab0bed5ea6
+From: Lee *!* Clagett <code@leeclagett.com>
+Date: Tue, 27 Aug 2024 14:07:52 -0400
+Subject: Cleanup TCP throttling code (performance) + move connection checks
+
+---
+ .../epee/include/net/abstract_tcp_server2.h   | 11 ++++-
+ .../epee/include/net/abstract_tcp_server2.inl | 24 ++++++++---
+ .../include/net/network_throttle-detail.hpp   |  4 +-
+ contrib/epee/src/network_throttle-detail.cpp  | 28 +++++++++----
+ src/p2p/net_node.h                            |  6 ++-
+ src/p2p/net_node.inl                          | 42 +++++++++++--------
+ tests/unit_tests/node_server.cpp              | 12 ++++++
+ 7 files changed, 93 insertions(+), 34 deletions(-)
+
+diff --git a/contrib/epee/include/net/abstract_tcp_server2.h b/contrib/epee/include/net/abstract_tcp_server2.h
+index bc0da66e299..be9999203c6 100644
+--- a/contrib/epee/include/net/abstract_tcp_server2.h
++++ b/contrib/epee/include/net/abstract_tcp_server2.h
+@@ -76,6 +76,13 @@ namespace net_utils
+   protected:
+     virtual ~i_connection_filter(){}
+   };
++
++  struct i_connection_limit
++  {
++    virtual bool is_host_limit(const epee::net_utils::network_address &address)=0;
++  protected:
++    virtual ~i_connection_limit(){}
++  };
+   
+ 
+   /************************************************************************/
+@@ -260,10 +267,11 @@ namespace net_utils
+     struct shared_state : connection_basic_shared_state, t_protocol_handler::config_type
+     {
+       shared_state()
+-        : connection_basic_shared_state(), t_protocol_handler::config_type(), pfilter(nullptr), stop_signal_sent(false)
++        : connection_basic_shared_state(), t_protocol_handler::config_type(), pfilter(nullptr), plimit(nullptr), stop_signal_sent(false)
+       {}
+ 
+       i_connection_filter* pfilter;
++      i_connection_limit* plimit;
+       bool stop_signal_sent;
+     };
+ 
+@@ -369,6 +377,7 @@ namespace net_utils
+     size_t get_threads_count(){return m_threads_count;}
+ 
+     void set_connection_filter(i_connection_filter* pfilter);
++    void set_connection_limit(i_connection_limit* plimit);
+ 
+     void set_default_remote(epee::net_utils::network_address remote)
+     {
+diff --git a/contrib/epee/include/net/abstract_tcp_server2.inl b/contrib/epee/include/net/abstract_tcp_server2.inl
+index d88f1819421..8a3a8299c47 100644
+--- a/contrib/epee/include/net/abstract_tcp_server2.inl
++++ b/contrib/epee/include/net/abstract_tcp_server2.inl
+@@ -328,7 +328,7 @@ namespace net_utils
+       return;
+     }
+     auto self = connection<T>::shared_from_this();
+-    if (m_connection_type != e_connection_type_RPC) {
++    if (speed_limit_is_enabled()) {
+       auto calc_duration = []{
+         CRITICAL_REGION_LOCAL(
+           network_throttle_manager_t::m_lock_get_global_throttle_in
+@@ -382,7 +382,7 @@ namespace net_utils
+             m_conn_context.m_max_speed_down,
+             speed
+           );
+-          {
++          if (speed_limit_is_enabled()) {
+             CRITICAL_REGION_LOCAL(
+               network_throttle_manager_t::m_lock_get_global_throttle_in
+             );
+@@ -454,7 +454,7 @@ namespace net_utils
+       return;
+     }
+     auto self = connection<T>::shared_from_this();
+-    if (m_connection_type != e_connection_type_RPC) {
++    if (speed_limit_is_enabled()) {
+       auto calc_duration = [this]{
+         CRITICAL_REGION_LOCAL(
+           network_throttle_manager_t::m_lock_get_global_throttle_out
+@@ -513,7 +513,7 @@ namespace net_utils
+             m_conn_context.m_max_speed_down,
+             speed
+           );
+-          {
++          if (speed_limit_is_enabled()) {
+             CRITICAL_REGION_LOCAL(
+               network_throttle_manager_t::m_lock_get_global_throttle_out
+             );
+@@ -873,6 +873,13 @@ namespace net_utils
+     ).pfilter;
+     if (filter && !filter->is_remote_host_allowed(*real_remote))
+       return false;
++
++    auto *limit = static_cast<shared_state&>(
++      connection_basic::get_state()
++    ).plimit;
++    if (limit && limit->is_host_limit(*real_remote))
++      return false;
++
+     ec_t ec;
+     #if !defined(_WIN32) || !defined(__i686)
+     connection_basic::socket_.next_layer().set_option(
+@@ -1022,7 +1029,7 @@ namespace net_utils
+   template<typename T>
+   bool connection<T>::speed_limit_is_enabled() const
+   {
+-    return m_connection_type != e_connection_type_RPC;
++    return m_connection_type == e_connection_type_P2P;
+   }
+ 
+   template<typename T>
+@@ -1349,6 +1356,13 @@ namespace net_utils
+   }
+   //---------------------------------------------------------------------------------
+   template<class t_protocol_handler>
++  void boosted_tcp_server<t_protocol_handler>::set_connection_limit(i_connection_limit* plimit)
++  {
++    assert(m_state != nullptr); // always set in constructor
++    m_state->plimit = plimit;
++  }
++  //---------------------------------------------------------------------------------
++  template<class t_protocol_handler>
+   bool boosted_tcp_server<t_protocol_handler>::run_server(size_t threads_count, bool wait, const boost::thread::attributes& attrs)
+   {
+     TRY_ENTRY();
+diff --git a/contrib/epee/include/net/network_throttle-detail.hpp b/contrib/epee/include/net/network_throttle-detail.hpp
+index d97cb9d8857..48af0d95b0a 100644
+--- a/contrib/epee/include/net/network_throttle-detail.hpp
++++ b/contrib/epee/include/net/network_throttle-detail.hpp
+@@ -46,13 +46,13 @@ namespace net_utils
+ 
+ 
+ class network_throttle : public i_network_throttle {
+-	private:
++	public:
+ 		struct packet_info {
+ 			size_t m_size; // octets sent. Summary for given small-window (e.g. for all packaged in 1 second)
+ 			packet_info();
+ 		};
+ 
+-
++	private:
+ 		network_speed_bps m_target_speed;
+ 		size_t m_network_add_cost; // estimated add cost of headers 
+ 		size_t m_network_minimal_segment; // estimated minimal cost of sending 1 byte to round up to
+diff --git a/contrib/epee/src/network_throttle-detail.cpp b/contrib/epee/src/network_throttle-detail.cpp
+index 5812679abfa..1cf001ffa9c 100644
+--- a/contrib/epee/src/network_throttle-detail.cpp
++++ b/contrib/epee/src/network_throttle-detail.cpp
+@@ -46,7 +46,7 @@
+ #include "misc_log_ex.h" 
+ #include <boost/chrono.hpp>
+ #include "misc_language.h"
+-#include <sstream>
++#include <fstream>
+ #include <iomanip>
+ #include <algorithm>
+ 
+@@ -186,6 +186,23 @@ void network_throttle::handle_trafic_exact(size_t packet_size)
+ 	_handle_trafic_exact(packet_size, packet_size);
+ }
+ 
++namespace
++{
++	struct output_history
++	{
++		const boost::circular_buffer< network_throttle::packet_info >& history;
++	};
++
++	std::ostream& operator<<(std::ostream& out, const output_history& source)
++	{
++		out << '[';
++		for (auto sample: source.history)
++			out << sample.m_size << ' ';
++		out << ']';
++		return out;
++	}
++}
++
+ void network_throttle::_handle_trafic_exact(size_t packet_size, size_t orginal_size)
+ {
+ 	tick();
+@@ -196,14 +213,11 @@ void network_throttle::_handle_trafic_exact(size_t packet_size, size_t orginal_s
+ 	m_total_packets++;
+ 	m_total_bytes += packet_size;
+ 
+-	std::ostringstream oss; oss << "["; 	for (auto sample: m_history) oss << sample.m_size << " ";	 oss << "]" << std::ends;
+-	std::string history_str = oss.str();
+-
+ 	MTRACE("Throttle " << m_name << ": packet of ~"<<packet_size<<"b " << " (from "<<orginal_size<<" b)"
+         << " Speed AVG=" << std::setw(4) <<  ((long int)(cts .average/1024)) <<"[w="<<cts .window<<"]"
+         <<           " " << std::setw(4) <<  ((long int)(cts2.average/1024)) <<"[w="<<cts2.window<<"]"
+ 				<<" / " << " Limit="<< ((long int)(m_target_speed/1024)) <<" KiB/sec "
+-				<< " " << history_str
++				<< " " << output_history{m_history}
+ 		);
+ }
+ 
+@@ -289,8 +303,6 @@ void network_throttle::calculate_times(size_t packet_size, calculate_times_struc
+     }
+ 
+ 	if (dbg) {
+-		std::ostringstream oss; oss << "["; 	for (auto sample: m_history) oss << sample.m_size << " ";	 oss << "]" << std::ends;
+-		std::string history_str = oss.str();
+ 		MTRACE((cts.delay > 0 ? "SLEEP" : "")
+ 			<< "dbg " << m_name << ": " 
+ 			<< "speed is A=" << std::setw(8) <<cts.average<<" vs "
+@@ -300,7 +312,7 @@ void network_throttle::calculate_times(size_t packet_size, calculate_times_struc
+ 			<< "E="<< std::setw(8) << E << " (Enow="<<std::setw(8)<<Enow<<") "
+             << "M=" << std::setw(8) << M <<" W="<< std::setw(8) << cts.window << " "
+             << "R=" << std::setw(8) << cts.recomendetDataSize << " Wgood" << std::setw(8) << Wgood << " "
+-			<< "History: " << std::setw(8) << history_str << " "
++			<< "History: " << std::setw(8) << output_history{m_history} << " "
+ 			<< "m_last_sample_time=" << std::setw(8) << m_last_sample_time
+ 		);
+ 
+diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h
+index 7b3477e1f7d..0a2fd372be4 100644
+--- a/src/p2p/net_node.h
++++ b/src/p2p/net_node.h
+@@ -124,7 +124,8 @@ namespace nodetool
+   template<class t_payload_net_handler>
+   class node_server: public epee::levin::levin_commands_handler<p2p_connection_context_t<typename t_payload_net_handler::connection_context> >,
+                      public i_p2p_endpoint<typename t_payload_net_handler::connection_context>,
+-                     public epee::net_utils::i_connection_filter
++                     public epee::net_utils::i_connection_filter,
++                     public epee::net_utils::i_connection_limit
+   {
+     struct by_conn_id{};
+     struct by_peer_id{};
+@@ -350,7 +351,10 @@ namespace nodetool
+     virtual bool add_host_fail(const epee::net_utils::network_address &address, unsigned int score = 1);
+     //----------------- i_connection_filter  --------------------------------------------------------
+     virtual bool is_remote_host_allowed(const epee::net_utils::network_address &address, time_t *t = NULL);
++    //----------------- i_connection_limit  ---------------------------------------------------------
++    virtual bool is_host_limit(const epee::net_utils::network_address &address);
+     //-----------------------------------------------------------------------------------------------
++
+     bool parse_peer_from_string(epee::net_utils::network_address& pe, const std::string& node_addr, uint16_t default_port = 0);
+     bool handle_command_line(
+         const boost::program_options::variables_map& vm
+diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl
+index 662e598e8a1..b3ba315e325 100644
+--- a/src/p2p/net_node.inl
++++ b/src/p2p/net_node.inl
+@@ -222,6 +222,26 @@ namespace nodetool
+     // not found in hosts or subnets, allowed
+     return true;
+   }
++  //-----------------------------------------------------------------------------------
++  template<class t_payload_net_handler>
++  bool node_server<t_payload_net_handler>::is_host_limit(const epee::net_utils::network_address &address)
++  {
++    const network_zone& zone = m_network_zones.at(address.get_zone());
++    if (zone.m_current_number_of_in_peers >= zone.m_config.m_net_config.max_in_connection_count) // in peers limit
++    {
++      MWARNING("Exceeded max incoming connections, so dropping this one.");
++      return true;
++    }
++
++    if(has_too_many_connections(address))
++    {
++      MWARNING("CONNECTION FROM " << address.host_str() << " REFUSED, too many connections from the same address");
++      return true;
++    }
++
++    return false;
++  }
++
+   //-----------------------------------------------------------------------------------
+   template<class t_payload_net_handler>
+   bool node_server<t_payload_net_handler>::block_host(epee::net_utils::network_address addr, time_t seconds, bool add_only)
+@@ -967,6 +987,7 @@ namespace nodetool
+         std::string ipv6_addr = "";
+         std::string ipv6_port = "";
+         zone.second.m_net_server.set_connection_filter(this);
++        zone.second.m_net_server.set_connection_limit(this);
+         MINFO("Binding (IPv4) on " << zone.second.m_bind_ip << ":" << zone.second.m_port);
+         if (!zone.second.m_bind_ipv6_address.empty() && m_use_ipv6)
+         {
+@@ -2543,13 +2564,6 @@ namespace nodetool
+       return 1;
+     }
+ 
+-    if (zone.m_current_number_of_in_peers >= zone.m_config.m_net_config.max_in_connection_count) // in peers limit
+-    {
+-      LOG_WARNING_CC(context, "COMMAND_HANDSHAKE came, but already have max incoming connections, so dropping this one.");
+-      drop_connection(context);
+-      return 1;
+-    }
+-
+     if(!m_payload_handler.process_payload_sync_data(arg.payload_data, context, true))
+     {
+       LOG_WARNING_CC(context, "COMMAND_HANDSHAKE came, but process_payload_sync_data returned false, dropping connection.");
+@@ -2559,13 +2573,6 @@ namespace nodetool
+ 
+     zone.m_notifier.on_handshake_complete(context.m_connection_id, context.m_is_income);
+ 
+-    if(has_too_many_connections(context.m_remote_address))
+-    {
+-      LOG_PRINT_CCONTEXT_L1("CONNECTION FROM " << context.m_remote_address.host_str() << " REFUSED, too many connections from the same address");
+-      drop_connection(context);
+-      return 1;
+-    }
+-
+     //associate peer_id with this connection
+     context.peer_id = arg.node_data.peer_id;
+     context.m_in_timedsync = false;
+@@ -2885,15 +2892,16 @@ namespace nodetool
+       if (cntxt.m_is_income && cntxt.m_remote_address.is_same_host(address)) {
+         count++;
+ 
+-        if (count > max_connections) {
++        // the only call location happens BEFORE foreach_connection list is updated
++        if (count >= max_connections) {
+           return false;
+         }
+       }
+ 
+       return true;
+     });
+-
+-    return count > max_connections;
++    // the only call location happens BEFORE foreach_connection list is updated
++    return count >= max_connections;
+   }
+ 
+   template<class t_payload_net_handler>
+diff --git a/tests/unit_tests/node_server.cpp b/tests/unit_tests/node_server.cpp
+index 39178884c87..cc9434157bb 100644
+--- a/tests/unit_tests/node_server.cpp
++++ b/tests/unit_tests/node_server.cpp
+@@ -224,6 +224,18 @@ TEST(ban, subnet)
+   test_core pr_core;
+   cryptonote::t_cryptonote_protocol_handler<test_core> cprotocol(pr_core, NULL);
+   Server server(cprotocol);
++  {
++    boost::program_options::options_description opts{};
++    Server::init_options(opts);
++    cryptonote::core::init_options(opts);
++
++    char** args = nullptr;
++    boost::program_options::variables_map vm;
++    boost::program_options::store(
++      boost::program_options::parse_command_line(0, args, opts), vm
++    );
++    server.init(vm);
++  }
+   cprotocol.set_p2p_endpoint(&server);
+ 
+   ASSERT_TRUE(server.block_subnet(MAKE_IPV4_SUBNET(1,2,3,4,24), 10));
diff -Nru monero-0.18.0.0+~0+20200826/debian/patches/1004_http_response_limits.patch monero-0.18.0.0+~0+20200826/debian/patches/1004_http_response_limits.patch
--- monero-0.18.0.0+~0+20200826/debian/patches/1004_http_response_limits.patch	1970-01-01 00:00:00.000000000 +0000
+++ monero-0.18.0.0+~0+20200826/debian/patches/1004_http_response_limits.patch	2025-02-21 07:52:42.000000000 +0000
@@ -0,0 +1,822 @@
+Origin: upstream, ec74ff4a3d3ca38b7912af680209a45fd1701c3d
+From: Lee *!* Clagett <code@leeclagett.com>
+Date: Tue, 21 Jan 2025 09:56:52 -0500
+Subject: Set response limits on http server connections
+
+---
+ .../epee/include/net/abstract_tcp_server2.h   |  11 +-
+ .../epee/include/net/abstract_tcp_server2.inl |  57 ++++-
+ .../epee/include/net/http_protocol_handler.h  |  18 +-
+ .../include/net/http_protocol_handler.inl     |  37 +++-
+ .../epee/include/net/http_server_impl_base.h  |  39 +++-
+ src/cryptonote_config.h                       |   4 +
+ src/p2p/net_node.cpp                          |   2 +-
+ src/rpc/core_rpc_server.cpp                   |  47 +++-
+ src/rpc/core_rpc_server.h                     |   5 +-
+ src/wallet/wallet_rpc_server.cpp              |  27 ++-
+ .../functional_tests/functional_tests_rpc.py  |   2 +-
+ tests/unit_tests/CMakeLists.txt               |   1 +
+ tests/unit_tests/epee_http_server.cpp         | 200 ++++++++++++++++++
+ 13 files changed, 423 insertions(+), 27 deletions(-)
+ create mode 100644 tests/unit_tests/epee_http_server.cpp
+
+diff --git a/contrib/epee/include/net/abstract_tcp_server2.h b/contrib/epee/include/net/abstract_tcp_server2.h
+index 3f9b95033f1..1e45e68096e 100644
+--- a/contrib/epee/include/net/abstract_tcp_server2.h
++++ b/contrib/epee/include/net/abstract_tcp_server2.h
+@@ -65,6 +65,7 @@
+ #define MONERO_DEFAULT_LOG_CATEGORY "net"
+ 
+ #define ABSTRACT_SERVER_SEND_QUE_MAX_COUNT 1000
++#define ABSTRACT_SERVER_SEND_QUE_MAX_BYTES_DEFAULT 100 * 1024 * 1024
+ 
+ namespace epee
+ {
+@@ -170,6 +171,7 @@ namespace net_utils
+         } read;
+         struct {
+           std::deque<epee::byte_slice> queue;
++          std::size_t total_bytes;
+           bool wait_consume;
+         } write;
+       };
+@@ -268,11 +270,17 @@ namespace net_utils
+     struct shared_state : connection_basic_shared_state, t_protocol_handler::config_type
+     {
+       shared_state()
+-        : connection_basic_shared_state(), t_protocol_handler::config_type(), pfilter(nullptr), plimit(nullptr), stop_signal_sent(false)
++        : connection_basic_shared_state(),
++          t_protocol_handler::config_type(),
++          pfilter(nullptr),
++          plimit(nullptr),
++          response_soft_limit(ABSTRACT_SERVER_SEND_QUE_MAX_BYTES_DEFAULT), 
++          stop_signal_sent(false)
+       {}
+ 
+       i_connection_filter* pfilter;
+       i_connection_limit* plimit;
++      std::size_t response_soft_limit;
+       bool stop_signal_sent;
+     };
+ 
+@@ -380,6 +388,7 @@ namespace net_utils
+ 
+     void set_connection_filter(i_connection_filter* pfilter);
+     void set_connection_limit(i_connection_limit* plimit);
++    void set_response_soft_limit(std::size_t limit);
+ 
+     void set_default_remote(epee::net_utils::network_address remote)
+     {
+diff --git a/contrib/epee/include/net/abstract_tcp_server2.inl b/contrib/epee/include/net/abstract_tcp_server2.inl
+index 53c0d852c9c..39a58d1b261 100644
+--- a/contrib/epee/include/net/abstract_tcp_server2.inl
++++ b/contrib/epee/include/net/abstract_tcp_server2.inl
+@@ -497,10 +497,12 @@ namespace net_utils
+       if (m_state.socket.cancel_write) {
+         m_state.socket.cancel_write = false;
+         m_state.data.write.queue.clear();
++        m_state.data.write.total_bytes = 0;
+         state_status_check();
+       }
+       else if (ec.value()) {
+         m_state.data.write.queue.clear();
++        m_state.data.write.total_bytes = 0;
+         interrupt();
+       }
+       else {
+@@ -525,8 +527,11 @@ namespace net_utils
+ 
+           start_timer(get_default_timeout(), true);
+         }
+-        assert(bytes_transferred == m_state.data.write.queue.back().size());
++        const std::size_t byte_count = m_state.data.write.queue.back().size();
++        assert(bytes_transferred == byte_count);
+         m_state.data.write.queue.pop_back();
++        m_state.data.write.total_bytes -=
++          std::min(m_state.data.write.total_bytes, byte_count);
+         m_state.condition.notify_all();
+         start_write();
+       }
+@@ -670,8 +675,9 @@ namespace net_utils
+       return;
+     if (m_state.timers.throttle.out.wait_expire)
+       return;
+-    if (m_state.socket.wait_write)
+-      return;
++    // \NOTE See on_terminating() comments
++    //if (m_state.socket.wait_write)
++    // return;
+     if (m_state.socket.wait_shutdown)
+       return;
+     if (m_state.protocol.wait_init)
+@@ -729,8 +735,13 @@ namespace net_utils
+       return;
+     if (m_state.timers.throttle.out.wait_expire)
+       return;
+-    if (m_state.socket.wait_write)
+-      return;
++    // Writes cannot be canceled due to `async_write` being a "composed"
++    // handler. ASIO has new cancellation routines, not available in 1.66, to
++    // handle this situation. The problem is that if cancel is called after an
++    // intermediate handler is queued, the op will not check the cancel flag in
++    // our code, and will instead queue up another write.
++    //if (m_state.socket.wait_write)
++    //  return;
+     if (m_state.socket.wait_shutdown)
+       return;
+     if (m_state.protocol.wait_init)
+@@ -757,6 +768,8 @@ namespace net_utils
+     std::lock_guard<std::mutex> guard(m_state.lock);
+     if (m_state.status != status_t::RUNNING || m_state.socket.wait_handshake)
+       return false;
++    if (std::numeric_limits<std::size_t>::max() - m_state.data.write.total_bytes < message.size())
++      return false;
+ 
+     // Wait for the write queue to fall below the max. If it doesn't after a
+     // randomized delay, drop the connection.
+@@ -774,7 +787,14 @@ namespace net_utils
+           std::uniform_int_distribution<>(5000, 6000)(rng)
+         );
+       };
+-      if (m_state.data.write.queue.size() <= ABSTRACT_SERVER_SEND_QUE_MAX_COUNT)
++
++      // The bytes check intentionally does not include incoming message size.
++      // This allows for a soft overflow; a single http response will never fail
++      // this check, but multiple responses could. Clients can avoid this case
++      // by reading the entire response before making another request. P2P
++      // should never hit the MAX_BYTES check (when using default values).
++      if (m_state.data.write.queue.size() <= ABSTRACT_SERVER_SEND_QUE_MAX_COUNT &&
++          m_state.data.write.total_bytes <= static_cast<shared_state&>(connection_basic::get_state()).response_soft_limit)
+         return true;
+       m_state.data.write.wait_consume = true;
+       bool success = m_state.condition.wait_for(
+@@ -783,14 +803,23 @@ namespace net_utils
+         [this]{
+           return (
+             m_state.status != status_t::RUNNING ||
+-            m_state.data.write.queue.size() <=
+-              ABSTRACT_SERVER_SEND_QUE_MAX_COUNT
++            (
++              m_state.data.write.queue.size() <=
++                ABSTRACT_SERVER_SEND_QUE_MAX_COUNT &&
++              m_state.data.write.total_bytes <=
++                static_cast<shared_state&>(connection_basic::get_state()).response_soft_limit
++            )
+           );
+         }
+       );
+       m_state.data.write.wait_consume = false;
+       if (!success) {
+-        terminate();
++        // synchronize with intermediate writes on `m_strand`
++        auto self = connection<T>::shared_from_this();
++        boost::asio::post(m_strand, [this, self] {
++          std::lock_guard<std::mutex> guard(m_state.lock);
++          terminate();
++        });
+         return false;
+       }
+       else
+@@ -816,7 +845,9 @@ namespace net_utils
+     ) {
+       if (!wait_consume())
+         return false;
++      const std::size_t byte_count = message.size();
+       m_state.data.write.queue.emplace_front(std::move(message));
++      m_state.data.write.total_bytes += byte_count;
+       start_write();
+     }
+     else {
+@@ -826,6 +857,7 @@ namespace net_utils
+         m_state.data.write.queue.emplace_front(
+           message.take_slice(CHUNK_SIZE)
+         );
++        m_state.data.write.total_bytes += m_state.data.write.queue.front().size();
+         start_write();
+       }
+     }
+@@ -1369,6 +1401,13 @@ namespace net_utils
+   }
+   //---------------------------------------------------------------------------------
+   template<class t_protocol_handler>
++  void boosted_tcp_server<t_protocol_handler>::set_response_soft_limit(const std::size_t limit)
++  {
++    assert(m_state != nullptr); // always set in constructor
++    m_state->response_soft_limit = limit;
++  }
++  //---------------------------------------------------------------------------------
++  template<class t_protocol_handler>
+   bool boosted_tcp_server<t_protocol_handler>::run_server(size_t threads_count, bool wait, const boost::thread::attributes& attrs)
+   {
+     TRY_ENTRY();
+diff --git a/contrib/epee/include/net/http_protocol_handler.h b/contrib/epee/include/net/http_protocol_handler.h
+index 258b07e2c5a..8b73964dd2b 100644
+--- a/contrib/epee/include/net/http_protocol_handler.h
++++ b/contrib/epee/include/net/http_protocol_handler.h
+@@ -32,6 +32,7 @@
+ 
+ #include <boost/optional/optional.hpp>
+ #include <string>
++#include <unordered_map>
+ #include "net_utils_base.h"
+ #include "http_auth.h"
+ #include "http_base.h"
+@@ -54,8 +55,13 @@ namespace net_utils
+ 		{
+ 			std::string m_folder;
+ 			std::vector<std::string> m_access_control_origins;
++			std::unordered_map<std::string, std::size_t> m_connections;
+ 			boost::optional<login> m_user;
+ 			size_t m_max_content_length{std::numeric_limits<size_t>::max()};
++			std::size_t m_connection_count{0};
++			std::size_t m_max_public_ip_connections{3};
++			std::size_t m_max_private_ip_connections{25};
++			std::size_t m_max_connections{100};
+ 			critical_section m_lock;
+ 		};
+ 
+@@ -70,7 +76,7 @@ namespace net_utils
+ 			typedef http_server_config config_type;
+ 
+ 			simple_http_connection_handler(i_service_endpoint* psnd_hndlr, config_type& config, t_connection_context& conn_context);
+-			virtual ~simple_http_connection_handler(){}
++			virtual ~simple_http_connection_handler();
+ 
+ 			bool release_protocol()
+ 			{
+@@ -86,10 +92,7 @@ namespace net_utils
+ 			{
+ 				return true;
+ 			}
+-			bool after_init_connection()
+-			{
+-				return true;
+-			}
++			bool after_init_connection();
+ 			virtual bool handle_recv(const void* ptr, size_t cb);
+ 			virtual bool handle_request(const http::http_request_info& query_info, http_response_info& response);
+ 
+@@ -146,6 +149,7 @@ namespace net_utils
+ 		protected:
+ 			i_service_endpoint* m_psnd_hndlr; 
+ 			t_connection_context& m_conn_context;
++			bool m_initialized;
+ 		};
+ 
+ 		template<class t_connection_context>
+@@ -212,10 +216,6 @@ namespace net_utils
+ 			}
+ 			void handle_qued_callback()
+ 			{}
+-			bool after_init_connection()
+-			{
+-				return true;
+-			}
+ 
+ 		private:
+ 			//simple_http_connection_handler::config_type m_stub_config;
+diff --git a/contrib/epee/include/net/http_protocol_handler.inl b/contrib/epee/include/net/http_protocol_handler.inl
+index f7d2074b2e7..6647d1f15c5 100644
+--- a/contrib/epee/include/net/http_protocol_handler.inl
++++ b/contrib/epee/include/net/http_protocol_handler.inl
+@@ -208,11 +208,46 @@ namespace net_utils
+ 		m_newlines(0),
+ 		m_bytes_read(0),
+ 		m_psnd_hndlr(psnd_hndlr),
+-		m_conn_context(conn_context)
++		m_conn_context(conn_context),
++		m_initialized(false)
+ 	{
+ 
+ 	}
+ 	//--------------------------------------------------------------------------------------------
++	template<class t_connection_context>
++	simple_http_connection_handler<t_connection_context>::~simple_http_connection_handler()
++	{
++	  try
++	  {
++	    if (m_initialized)
++	    {
++	      CRITICAL_REGION_LOCAL(m_config.m_lock);
++	      if (m_config.m_connection_count)
++	        --m_config.m_connection_count;
++	      auto elem = m_config.m_connections.find(m_conn_context.m_remote_address.host_str());
++	      if (elem != m_config.m_connections.end())
++	      {
++	        if (elem->second == 1 || elem->second == 0)
++	          m_config.m_connections.erase(elem);
++	        else
++	          --(elem->second);
++	      }
++	    }
++	  }
++	  catch (...)
++	  {}
++	}
++	//--------------------------------------------------------------------------------------------
++	template<class t_connection_context>
++	bool simple_http_connection_handler<t_connection_context>::after_init_connection()
++	{
++	  CRITICAL_REGION_LOCAL(m_config.m_lock);
++	  ++m_config.m_connections[m_conn_context.m_remote_address.host_str()];
++	  ++m_config.m_connection_count;
++	  m_initialized = true;
++	  return true;
++	}
++	//--------------------------------------------------------------------------------------------
+     template<class t_connection_context>
+ 	bool simple_http_connection_handler<t_connection_context>::set_ready_state()
+ 	{
+diff --git a/contrib/epee/include/net/http_server_impl_base.h b/contrib/epee/include/net/http_server_impl_base.h
+index 024f141b4ca..e2439167462 100644
+--- a/contrib/epee/include/net/http_server_impl_base.h
++++ b/contrib/epee/include/net/http_server_impl_base.h
+@@ -33,6 +33,7 @@
+ #include <boost/thread.hpp>
+ #include <boost/bind/bind.hpp>
+ 
++#include "cryptonote_config.h"
+ #include "net/abstract_tcp_server2.h"
+ #include "http_protocol_handler.h"
+ #include "net/http_server_handlers_map2.h"
+@@ -44,7 +45,8 @@ namespace epee
+ {
+ 
+   template<class t_child_class, class t_connection_context = epee::net_utils::connection_context_base>
+-  class http_server_impl_base: public net_utils::http::i_http_server_handler<t_connection_context>
++  class http_server_impl_base: public net_utils::http::i_http_server_handler<t_connection_context>,
++                                      net_utils::i_connection_limit
+   {
+ 
+   public:
+@@ -60,8 +62,16 @@ namespace epee
+       const std::string& bind_ipv6_address = "::", bool use_ipv6 = false, bool require_ipv4 = true,
+       std::vector<std::string> access_control_origins = std::vector<std::string>(),
+       boost::optional<net_utils::http::login> user = boost::none,
+-      net_utils::ssl_options_t ssl_options = net_utils::ssl_support_t::e_ssl_support_autodetect)
++      net_utils::ssl_options_t ssl_options = net_utils::ssl_support_t::e_ssl_support_autodetect,
++      const std::size_t max_public_ip_connections = DEFAULT_RPC_MAX_CONNECTIONS_PER_PUBLIC_IP,
++      const std::size_t max_private_ip_connections = DEFAULT_RPC_MAX_CONNECTIONS_PER_PRIVATE_IP,
++      const std::size_t max_connections = DEFAULT_RPC_MAX_CONNECTIONS,
++      const std::size_t response_soft_limit = DEFAULT_RPC_SOFT_LIMIT_SIZE)
+     {
++      if (max_connections < max_public_ip_connections)
++        throw std::invalid_argument{"Max public IP connections cannot be more than max connections"};
++      if (max_connections < max_private_ip_connections)
++        throw std::invalid_argument{"Max private IP connections cannot be more than max connections"};
+ 
+       //set self as callback handler
+       m_net_server.get_config_object().m_phandler = static_cast<t_child_class*>(this);
+@@ -75,6 +85,11 @@ namespace epee
+       m_net_server.get_config_object().m_access_control_origins = std::move(access_control_origins);
+ 
+       m_net_server.get_config_object().m_user = std::move(user);
++      m_net_server.get_config_object().m_max_public_ip_connections = max_public_ip_connections;
++      m_net_server.get_config_object().m_max_private_ip_connections = max_private_ip_connections;
++      m_net_server.get_config_object().m_max_connections = max_connections;
++      m_net_server.set_response_soft_limit(response_soft_limit);
++      m_net_server.set_connection_limit(this);
+ 
+       MGINFO("Binding on " << bind_ip << " (IPv4):" << bind_port);
+       if (use_ipv6)
+@@ -131,6 +146,26 @@ namespace epee
+     }
+ 
+   protected: 
++
++    virtual bool is_host_limit(const net_utils::network_address& na) override final
++    {
++      auto& config = m_net_server.get_config_object();
++      CRITICAL_REGION_LOCAL(config.m_lock);
++      if (config.m_max_connections <= config.m_connection_count)
++        return true;
++
++      const bool is_private = na.is_loopback() || na.is_local();
++      const auto elem = config.m_connections.find(na.host_str());
++      if (elem != config.m_connections.end())
++      {
++        if (is_private)
++          return config.m_max_private_ip_connections <= elem->second;
++        else
++          return config.m_max_public_ip_connections <= elem->second;
++      }
++      return false;
++    }
++
+     net_utils::boosted_tcp_server<net_utils::http::http_custom_handler<t_connection_context> > m_net_server;
+   };
+ }
+diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h
+index c32c399ec21..19b6f9ab867 100644
+--- a/src/cryptonote_config.h
++++ b/src/cryptonote_config.h
+@@ -127,6 +127,10 @@
+ 
+ #define COMMAND_RPC_GET_BLOCKS_FAST_MAX_BLOCK_COUNT     1000
+ #define COMMAND_RPC_GET_BLOCKS_FAST_MAX_TX_COUNT        20000
++#define DEFAULT_RPC_MAX_CONNECTIONS_PER_PUBLIC_IP       3
++#define DEFAULT_RPC_MAX_CONNECTIONS_PER_PRIVATE_IP      25
++#define DEFAULT_RPC_MAX_CONNECTIONS                     100
++#define DEFAULT_RPC_SOFT_LIMIT_SIZE                     25 * 1024 * 1024 // 25 MiB
+ #define MAX_RPC_CONTENT_LENGTH                          1048576 // 1 MB
+ 
+ #define P2P_LOCAL_WHITE_PEERLIST_LIMIT                  1000
+diff --git a/src/p2p/net_node.cpp b/src/p2p/net_node.cpp
+index 5cba55b4b1e..0f758937c79 100644
+--- a/src/p2p/net_node.cpp
++++ b/src/p2p/net_node.cpp
+@@ -169,7 +169,7 @@ namespace nodetool
+     const command_line::arg_descriptor<bool> arg_pad_transactions = {
+       "pad-transactions", "Pad relayed transactions to help defend against traffic volume analysis", false
+     };
+-    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};
++    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};
+ 
+     boost::optional<std::vector<proxy>> get_proxies(boost::program_options::variables_map const& vm)
+     {
+diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp
+index 426d8b295f6..8a29edfb207 100644
+--- a/src/rpc/core_rpc_server.cpp
++++ b/src/rpc/core_rpc_server.cpp
+@@ -163,6 +163,10 @@ namespace cryptonote
+     command_line::add_arg(desc, arg_rpc_payment_difficulty);
+     command_line::add_arg(desc, arg_rpc_payment_credits);
+     command_line::add_arg(desc, arg_rpc_payment_allow_free_loopback);
++    command_line::add_arg(desc, arg_rpc_max_connections_per_public_ip);
++    command_line::add_arg(desc, arg_rpc_max_connections_per_private_ip);
++    command_line::add_arg(desc, arg_rpc_max_connections);
++    command_line::add_arg(desc, arg_rpc_response_soft_limit);
+   }
+   //------------------------------------------------------------------------------------------------------------------------------
+   core_rpc_server::core_rpc_server(
+@@ -396,11 +400,28 @@ namespace cryptonote
+       }
+     }
+ 
++    const auto max_connections_public = command_line::get_arg(vm, arg_rpc_max_connections_per_public_ip);
++    const auto max_connections_private = command_line::get_arg(vm, arg_rpc_max_connections_per_private_ip);
++    const auto max_connections = command_line::get_arg(vm, arg_rpc_max_connections);
++
++    if (max_connections < max_connections_public)
++    {
++      MFATAL(arg_rpc_max_connections_per_public_ip.name << " is bigger than " << arg_rpc_max_connections.name);
++      return false;
++    }
++    if (max_connections < max_connections_private)
++    {
++      MFATAL(arg_rpc_max_connections_per_private_ip.name << " is bigger than " << arg_rpc_max_connections.name);
++      return false;
++    }
++
+     auto rng = [](size_t len, uint8_t *ptr){ return crypto::rand(len, ptr); };
+     const bool inited = epee::http_server_impl_base<core_rpc_server, connection_context>::init(
+       rng, std::move(port), std::move(bind_ip_str),
+       std::move(bind_ipv6_str), std::move(rpc_config->use_ipv6), std::move(rpc_config->require_ipv4),
+-      std::move(rpc_config->access_control_origins), std::move(http_login), std::move(rpc_config->ssl_options)
++      std::move(rpc_config->access_control_origins), std::move(http_login), std::move(rpc_config->ssl_options),
++      max_connections_public, max_connections_private, max_connections,
++      command_line::get_arg(vm, arg_rpc_response_soft_limit)
+     );
+ 
+     m_net_server.get_config_object().m_max_content_length = MAX_RPC_CONTENT_LENGTH;
+@@ -3885,4 +3906,28 @@ namespace cryptonote
+     , "Allow free access from the loopback address (ie, the local host)"
+     , false
+     };
++
++  const command_line::arg_descriptor<std::size_t> core_rpc_server::arg_rpc_max_connections_per_public_ip = {
++      "rpc-max-connections-per-public-ip"
++    , "Max RPC connections per public IP permitted"
++    , DEFAULT_RPC_MAX_CONNECTIONS_PER_PUBLIC_IP
++  };
++
++  const command_line::arg_descriptor<std::size_t> core_rpc_server::arg_rpc_max_connections_per_private_ip = {
++      "rpc-max-connections-per-private-ip"
++    , "Max RPC connections per private and localhost IP permitted"
++    , DEFAULT_RPC_MAX_CONNECTIONS_PER_PRIVATE_IP
++  };
++
++  const command_line::arg_descriptor<std::size_t> core_rpc_server::arg_rpc_max_connections = {
++      "rpc-max-connections"
++    , "Max RPC connections permitted"
++    , DEFAULT_RPC_MAX_CONNECTIONS
++  };
++
++  const command_line::arg_descriptor<std::size_t> core_rpc_server::arg_rpc_response_soft_limit = {
++      "rpc-response-soft-limit"
++    , "Max response bytes that can be queued, enforced at next response attempt"
++    , DEFAULT_RPC_SOFT_LIMIT_SIZE
++  };
+ }  // namespace cryptonote
+diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h
+index 6bd0fc25b69..ce87730052d 100644
+--- a/src/rpc/core_rpc_server.h
++++ b/src/rpc/core_rpc_server.h
+@@ -56,7 +56,6 @@ namespace cryptonote
+   {
+   public:
+ 
+-    static const command_line::arg_descriptor<bool> arg_public_node;
+     static const command_line::arg_descriptor<std::string, false, true, 2> arg_rpc_bind_port;
+     static const command_line::arg_descriptor<std::string> arg_rpc_restricted_bind_port;
+     static const command_line::arg_descriptor<bool> arg_restricted_rpc;
+@@ -73,6 +72,10 @@ namespace cryptonote
+     static const command_line::arg_descriptor<uint64_t> arg_rpc_payment_difficulty;
+     static const command_line::arg_descriptor<uint64_t> arg_rpc_payment_credits;
+     static const command_line::arg_descriptor<bool> arg_rpc_payment_allow_free_loopback;
++    static const command_line::arg_descriptor<std::size_t> arg_rpc_max_connections_per_public_ip;
++    static const command_line::arg_descriptor<std::size_t> arg_rpc_max_connections_per_private_ip;
++    static const command_line::arg_descriptor<std::size_t> arg_rpc_max_connections;
++    static const command_line::arg_descriptor<std::size_t> arg_rpc_response_soft_limit;
+ 
+     typedef epee::net_utils::connection_context_base connection_context;
+ 
+diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
+index 33437606228..b3d495a3038 100644
+--- a/src/wallet/wallet_rpc_server.cpp
++++ b/src/wallet/wallet_rpc_server.cpp
+@@ -129,6 +129,10 @@ namespace
+   const command_line::arg_descriptor<bool> arg_restricted = {"restricted-rpc", "Restricts to view-only commands", false};
+   const command_line::arg_descriptor<std::string> arg_wallet_dir = {"wallet-dir", "Directory for newly created wallets"};
+   const command_line::arg_descriptor<bool> arg_prompt_for_password = {"prompt-for-password", "Prompts for password when not provided", false};
++  const command_line::arg_descriptor<std::size_t> arg_rpc_max_connections_per_public_ip = {"rpc-max-connections-per-public-ip", "Max RPC connections per public IP permitted", DEFAULT_RPC_MAX_CONNECTIONS_PER_PUBLIC_IP};
++  const command_line::arg_descriptor<std::size_t> arg_rpc_max_connections_per_private_ip = {"rpc-max-connections-per-private-ip", "Max RPC connections per private and localhost IP permitted", DEFAULT_RPC_MAX_CONNECTIONS_PER_PRIVATE_IP};
++  const command_line::arg_descriptor<std::size_t> arg_rpc_max_connections = {"rpc-max-connections", "Max RPC connections permitted", DEFAULT_RPC_MAX_CONNECTIONS};
++  const command_line::arg_descriptor<std::size_t> arg_rpc_response_soft_limit = {"rpc-response-soft-limit", "Max response bytes that can be queued, enforced at next response attempt", DEFAULT_RPC_SOFT_LIMIT_SIZE};
+ 
+   constexpr const char default_rpc_username[] = "monero";
+ 
+@@ -325,13 +329,30 @@ namespace tools
+ 
+     check_background_mining();
+ 
++    const auto max_connections_public = command_line::get_arg(vm, arg_rpc_max_connections_per_public_ip);
++    const auto max_connections_private = command_line::get_arg(vm, arg_rpc_max_connections_per_private_ip);
++    const auto max_connections = command_line::get_arg(vm, arg_rpc_max_connections);
++
++    if (max_connections < max_connections_public)
++    {
++      MFATAL(arg_rpc_max_connections_per_public_ip.name << " is bigger than " << arg_rpc_max_connections.name);
++      return false;
++    }
++    if (max_connections < max_connections_private)
++    {
++      MFATAL(arg_rpc_max_connections_per_private_ip.name << " is bigger than " << arg_rpc_max_connections.name);
++      return false;
++    }
++
+     m_net_server.set_threads_prefix("RPC");
+     auto rng = [](size_t len, uint8_t *ptr) { return crypto::rand(len, ptr); };
+     return epee::http_server_impl_base<wallet_rpc_server, connection_context>::init(
+       rng, std::move(bind_port), std::move(rpc_config->bind_ip),
+       std::move(rpc_config->bind_ipv6_address), std::move(rpc_config->use_ipv6), std::move(rpc_config->require_ipv4),
+       std::move(rpc_config->access_control_origins), std::move(http_login),
+-      std::move(rpc_config->ssl_options)
++      std::move(rpc_config->ssl_options),
++      max_connections_public, max_connections_private, max_connections,
++      command_line::get_arg(vm, arg_rpc_response_soft_limit)
+     );
+   }
+   //------------------------------------------------------------------------------------------------------------------------------
+@@ -4771,6 +4771,10 @@ int main(int argc, char** argv) {
+   command_line::add_arg(desc_params, arg_wallet_dir);
+   command_line::add_arg(desc_params, arg_prompt_for_password);
+   command_line::add_arg(desc_params, arg_rpc_client_secret_key);
++  command_line::add_arg(desc_params, arg_rpc_max_connections_per_public_ip);
++  command_line::add_arg(desc_params, arg_rpc_max_connections_per_private_ip);
++  command_line::add_arg(desc_params, arg_rpc_max_connections);
++  command_line::add_arg(desc_params, arg_rpc_response_soft_limit);
+
+   daemonizer::init_options(hidden_options, desc_params);
+   desc_params.add(hidden_options);
+diff --git a/tests/functional_tests/functional_tests_rpc.py b/tests/functional_tests/functional_tests_rpc.py
+index 520ee596105..3881bf39f25 100755
+--- a/tests/functional_tests/functional_tests_rpc.py
++++ b/tests/functional_tests/functional_tests_rpc.py
+@@ -52,7 +52,7 @@
+ FUNCTIONAL_TESTS_DIRECTORY = builddir + "/tests/functional_tests"
+ DIFFICULTY = 10
+ 
+-monerod_base = [builddir + "/bin/monerod", "--regtest", "--fixed-difficulty", str(DIFFICULTY), "--no-igd", "--p2p-bind-port", "monerod_p2p_port", "--rpc-bind-port", "monerod_rpc_port", "--zmq-rpc-bind-port", "monerod_zmq_port", "--non-interactive", "--disable-dns-checkpoints", "--check-updates", "disabled", "--rpc-ssl", "disabled", "--data-dir", "monerod_data_dir", "--log-level", "1"]
++monerod_base = [builddir + "/bin/monerod", "--regtest", "--fixed-difficulty", str(DIFFICULTY), "--no-igd", "--p2p-bind-port", "monerod_p2p_port", "--rpc-bind-port", "monerod_rpc_port", "--zmq-rpc-bind-port", "monerod_zmq_port", "--non-interactive", "--disable-dns-checkpoints", "--check-updates", "disabled", "--rpc-ssl", "disabled", "--data-dir", "monerod_data_dir", "--log-level", "1", "--rpc-max-connections-per-private-ip", "100", "--rpc-max-connections", "100"]
+ monerod_extra = [
+   ["--offline"],
+   ["--rpc-payment-address", "44SKxxLQw929wRF6BA9paQ1EWFshNnKhXM3qz6Mo3JGDE2YG3xyzVutMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUiqhcwR", "--rpc-payment-difficulty", str(DIFFICULTY), "--rpc-payment-credits", "5000", "--offline"],
+diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt
+index e329b7506fa..8e9b7a531f9 100644
+--- a/tests/unit_tests/CMakeLists.txt
++++ b/tests/unit_tests/CMakeLists.txt
+@@ -48,6 +48,7 @@ set(unit_tests_sources
+   dns_resolver.cpp
+   epee_boosted_tcp_server.cpp
+   epee_levin_protocol_handler_async.cpp
++  epee_http_server.cpp
+   epee_serialization.cpp
+   epee_utils.cpp
+   expect.cpp
+diff --git a/tests/unit_tests/epee_http_server.cpp b/tests/unit_tests/epee_http_server.cpp
+new file mode 100644
+index 00000000000..1d3b60d5485
+--- /dev/null
++++ b/tests/unit_tests/epee_http_server.cpp
+@@ -0,0 +1,200 @@
++// Copyright (c) 2014-2024, The Monero Project
++// 
++// All rights reserved.
++// 
++// Redistribution and use in source and binary forms, with or without modification, are
++// permitted provided that the following conditions are met:
++// 
++// 1. Redistributions of source code must retain the above copyright notice, this list of
++//    conditions and the following disclaimer.
++// 
++// 2. Redistributions in binary form must reproduce the above copyright notice, this list
++//    of conditions and the following disclaimer in the documentation and/or other
++//    materials provided with the distribution.
++// 
++// 3. Neither the name of the copyright holder nor the names of its contributors may be
++//    used to endorse or promote products derived from this software without specific
++//    prior written permission.
++// 
++// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
++// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
++// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
++// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
++// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
++// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
++// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
++// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
++// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
++// 
++
++#include <atomic>
++#include <boost/asio/connect.hpp>
++#include <boost/asio/ip/tcp.hpp>
++#include <boost/beast/core.hpp>
++#include <boost/beast/http.hpp>
++#include <boost/beast/version.hpp>
++#include "gtest/gtest.h"
++#include "net/http_server_handlers_map2.h"
++#include "net/http_server_impl_base.h"
++#include "storages/portable_storage_template_helper.h"
++
++namespace
++{
++  constexpr const std::size_t payload_size = 26 * 1024 * 1024;
++  constexpr const std::size_t max_private_ips = 25;
++  struct dummy
++  {
++    struct request
++    {
++      BEGIN_KV_SERIALIZE_MAP()
++      END_KV_SERIALIZE_MAP()
++    };
++
++    struct response
++    {
++      BEGIN_KV_SERIALIZE_MAP()
++        KV_SERIALIZE(payload)
++      END_KV_SERIALIZE_MAP()
++
++      std::string payload;
++    };
++  };
++
++  std::string make_payload()
++  {
++    dummy::request body{};
++    const auto body_serialized = epee::serialization::store_t_to_binary(body);
++    return std::string{
++      reinterpret_cast<const char*>(body_serialized.data()),
++      body_serialized.size()
++    };
++  }
++
++  struct http_server :  epee::http_server_impl_base<http_server>
++  {
++    using connection_context =  epee::net_utils::connection_context_base;
++
++    http_server()
++      : epee::http_server_impl_base<http_server>(),
++        dummy_size(payload_size)
++    {}
++
++    CHAIN_HTTP_TO_MAP2(connection_context); //forward http requests to uri map
++
++    BEGIN_URI_MAP2()
++      MAP_URI_AUTO_BIN2("/dummy", on_dummy, dummy)
++    END_URI_MAP2()
++
++    bool on_dummy(const dummy::request&, dummy::response& res, const connection_context *ctx = NULL)
++    {
++      res.payload.resize(dummy_size.load(), 'f');
++      return true;
++    }
++
++    std::atomic<std::size_t> dummy_size;
++  };
++} // anonymous
++
++TEST(http_server, response_soft_limit)
++{
++  namespace http = boost::beast::http;
++
++  http_server server{};
++  server.init(nullptr, "8080");
++  server.run(1, false);
++
++  boost::system::error_code error{};
++  boost::asio::io_context context{};
++  boost::asio::ip::tcp::socket stream{context};
++  stream.connect(
++    boost::asio::ip::tcp::endpoint{
++      boost::asio::ip::make_address("127.0.0.1"), 8080
++    },
++    error
++  );
++  EXPECT_FALSE(bool(error));
++
++  http::request<http::string_body> req{http::verb::get, "/dummy", 11};
++  req.set(http::field::host, "127.0.0.1");
++  req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
++  req.body() = make_payload();
++  req.prepare_payload();
++  http::write(stream, req, error);
++  EXPECT_FALSE(bool(error));
++
++  {
++    dummy::response payload{};
++    boost::beast::flat_buffer buffer;
++    http::response<http::basic_string_body<char>> res;
++    http::read(stream, buffer, res, error);
++    EXPECT_FALSE(bool(error));
++    EXPECT_EQ(200u, res.result_int());
++    EXPECT_TRUE(epee::serialization::load_t_from_binary(payload, res.body()));
++    EXPECT_EQ(payload_size, std::count(payload.payload.begin(), payload.payload.end(), 'f'));
++  }
++
++  while (!error)
++    http::write(stream, req, error);
++  server.send_stop_signal();
++}
++
++TEST(http_server, private_ip_limit)
++{
++  namespace http = boost::beast::http;
++
++  http_server server{};
++  server.dummy_size = 1;
++  server.init(nullptr, "8080");
++  server.run(1, false);
++
++  boost::system::error_code error{};
++  boost::asio::io_context context{};
++
++  http::request<http::string_body> req{http::verb::get, "/dummy", 11};
++  req.set(http::field::host, "127.0.0.1");
++  req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
++  req.body() = make_payload();
++  req.prepare_payload();
++
++  std::vector<boost::asio::ip::tcp::socket> streams{};
++  for (std::size_t i = 0; i < max_private_ips; ++i)
++  {
++    streams.emplace_back(context);
++    streams.back().connect(
++      boost::asio::ip::tcp::endpoint{
++        boost::asio::ip::make_address("127.0.0.1"), 8080
++      },
++      error
++    );
++    http::write(streams.back(), req, error);
++    EXPECT_FALSE(bool(error));
++
++    dummy::response payload{};
++    boost::beast::flat_buffer buffer;
++    http::response<http::basic_string_body<char>> res;
++
++    http::read(streams.back(), buffer, res, error);
++    EXPECT_FALSE(bool(error));
++  }
++
++  boost::asio::ip::tcp::socket stream{context};
++  stream.connect(
++    boost::asio::ip::tcp::endpoint{
++      boost::asio::ip::make_address("127.0.0.1"), 8080
++    },
++    error
++  );
++  bool failed = bool(error);
++  http::write(stream, req, error);
++  failed |= bool(error);
++  {
++    dummy::response payload{};
++    boost::beast::flat_buffer buffer;
++    http::response<http::basic_string_body<char>> res;
++
++    // make sure server ran async_accept code
++    http::read(stream, buffer, res, error);
++  }
++  failed |= bool(error);
++  EXPECT_TRUE(failed);
++}
diff -Nru monero-0.18.0.0+~0+20200826/debian/patches/1005_fully_qualified_names.patch monero-0.18.0.0+~0+20200826/debian/patches/1005_fully_qualified_names.patch
--- monero-0.18.0.0+~0+20200826/debian/patches/1005_fully_qualified_names.patch	1970-01-01 00:00:00.000000000 +0000
+++ monero-0.18.0.0+~0+20200826/debian/patches/1005_fully_qualified_names.patch	2025-02-18 17:40:17.000000000 +0000
@@ -0,0 +1,94 @@
+Origin: upstream, a4cb77f9f3e3b37e5f9a87cee53392c8114a1b37
+From: Jeffrey Ryan <jeffreyryan@tutanota.com>
+Date: Sun, 22 May 2022 23:41:41 -0500
+Subject: epee: update 'http_server_handlers_map2.h' macros to use fully qualified names
+
+quick patch which fixes the issue where if you use some macros from `http_server_handlers_map2.h` you have to be in the `epee` namespace or it doesn't compile. Now can remove `using namespace epee;` from header file `core_rpc_server.h`, which caused a couple of name qualifying mistakes
+---
+ contrib/epee/include/net/http_server_handlers_map2.h | 8 ++++----
+ src/daemon/main.cpp                                  | 2 +-
+ src/daemon/rpc_command_executor.cpp                  | 2 +-
+ src/rpc/core_rpc_server.h                            | 4 ----
+ 4 files changed, 6 insertions(+), 10 deletions(-)
+
+diff --git a/contrib/epee/include/net/http_server_handlers_map2.h b/contrib/epee/include/net/http_server_handlers_map2.h
+index ffb3f3b7ea5..f52e61817f0 100644
+--- a/contrib/epee/include/net/http_server_handlers_map2.h
++++ b/contrib/epee/include/net/http_server_handlers_map2.h
+@@ -71,7 +71,7 @@
+     else if((query_info.m_URI == s_pattern) && (cond)) \
+     { \
+       handled = true; \
+-      uint64_t ticks = misc_utils::get_tick_count(); \
++      uint64_t ticks = epee::misc_utils::get_tick_count(); \
+       boost::value_initialized<command_type::request> req; \
+       bool parse_res = epee::serialization::load_t_from_json(static_cast<command_type::request&>(req), query_info.m_body); \
+       if (!parse_res) \
+@@ -107,7 +107,7 @@
+     else if(query_info.m_URI == s_pattern) \
+     { \
+       handled = true; \
+-      uint64_t ticks = misc_utils::get_tick_count(); \
++      uint64_t ticks = epee::misc_utils::get_tick_count(); \
+       boost::value_initialized<command_type::request> req; \
+       bool parse_res = epee::serialization::load_t_from_binary(static_cast<command_type::request&>(req), epee::strspan<uint8_t>(query_info.m_body)); \
+       if (!parse_res) \
+@@ -117,7 +117,7 @@
+          response_info.m_response_comment = "Bad request"; \
+          return true; \
+       } \
+-      uint64_t ticks1 = misc_utils::get_tick_count(); \
++      uint64_t ticks1 = epee::misc_utils::get_tick_count(); \
+       boost::value_initialized<command_type::response> resp;\
+       MINFO(m_conn_context << "calling " << s_pattern); \
+       bool res = false; \
+@@ -129,7 +129,7 @@
+         response_info.m_response_comment = "Internal Server Error"; \
+         return true; \
+       } \
+-      uint64_t ticks2 = misc_utils::get_tick_count(); \
++      uint64_t ticks2 = epee::misc_utils::get_tick_count(); \
+       epee::byte_slice buffer; \
+       epee::serialization::store_t_to_binary(static_cast<command_type::response&>(resp), buffer, 64 * 1024); \
+       uint64_t ticks3 = epee::misc_utils::get_tick_count(); \
+diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp
+index 73d9ebce1b3..3d90e0855fc 100644
+--- a/src/daemon/main.cpp
++++ b/src/daemon/main.cpp
+@@ -83,7 +83,7 @@ uint16_t parse_public_rpc_port(const po::variables_map &vm)
+   }
+ 
+   uint16_t rpc_port;
+-  if (!string_tools::get_xtype_from_string(rpc_port, rpc_port_str))
++  if (!epee::string_tools::get_xtype_from_string(rpc_port, rpc_port_str))
+   {
+     throw std::runtime_error("invalid RPC port " + rpc_port_str);
+   }
+diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp
+index b6364ff7702..0d3688c7655 100644
+--- a/src/daemon/rpc_command_executor.cpp
++++ b/src/daemon/rpc_command_executor.cpp
+@@ -1063,7 +1063,7 @@ bool t_rpc_command_executor::print_transaction(crypto::hash transaction_hash,
+       cryptonote::blobdata blob;
+       std::string source = as_hex.empty() ? pruned_as_hex + prunable_as_hex : as_hex;
+       bool pruned = !pruned_as_hex.empty() && prunable_as_hex.empty();
+-      if (!string_tools::parse_hexstr_to_binbuff(source, blob))
++      if (!epee::string_tools::parse_hexstr_to_binbuff(source, blob))
+       {
+         tools::fail_msg_writer() << "Failed to parse tx to get json format";
+       }
+diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h
+index 0274f4db84b..b87412ca67d 100644
+--- a/src/rpc/core_rpc_server.h
++++ b/src/rpc/core_rpc_server.h
+@@ -47,10 +47,6 @@
+ #undef MONERO_DEFAULT_LOG_CATEGORY
+ #define MONERO_DEFAULT_LOG_CATEGORY "daemon.rpc"
+ 
+-// yes, epee doesn't properly use its full namespace when calling its
+-// functions from macros.  *sigh*
+-using namespace epee;
+-
+ namespace cryptonote
+ {
+   /************************************************************************/
diff -Nru monero-0.18.0.0+~0+20200826/debian/patches/series monero-0.18.0.0+~0+20200826/debian/patches/series
--- monero-0.18.0.0+~0+20200826/debian/patches/series	2022-07-26 20:15:22.000000000 +0000
+++ monero-0.18.0.0+~0+20200826/debian/patches/series	2025-02-21 07:52:42.000000000 +0000
@@ -1,2 +1,5 @@
+1003_tcp_throttling.patch
+1004_http_response_limits.patch
+1005_fully_qualified_names.patch
 2001_system_shared_libs.patch
 2002_privacy.patch

--- End Message ---
--- Begin Message ---
Version: 12.10
This update has been released as part of 12.10. Thank you for your contribution.

--- End Message ---

Reply to: