p2p, rpc: group public IPv6 connection limits by /64

(cherry picked from commit f277d328cf1f7e64423b5eb7dc0e21649f71e47a)
This commit is contained in:
selsta
2026-10-04 18:50:22 -04:00
committed by such-gitea
parent 881dcfbd43
commit f26ac02df9
13 changed files with 275 additions and 8 deletions
@@ -36,6 +36,7 @@
#include "net_utils_base.h"
#include "http_auth.h"
#include "http_base.h"
#include "syncobj.h"
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "net.http"
@@ -65,6 +66,9 @@ namespace net_utils
critical_section m_lock;
};
// RPC limits groupable IPv6 clients by /64 to avoid per-address limit bypasses.
std::string get_rpc_connection_limit_key(const net_utils::network_address& address);
/************************************************************************/
/* */
/************************************************************************/
@@ -225,7 +225,7 @@ namespace net_utils
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());
auto elem = m_config.m_connections.find(get_rpc_connection_limit_key(m_conn_context.m_remote_address));
if (elem != m_config.m_connections.end())
{
if (elem->second == 1 || elem->second == 0)
@@ -243,7 +243,7 @@ namespace net_utils
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_connections[get_rpc_connection_limit_key(m_conn_context.m_remote_address)];
++m_config.m_connection_count;
m_initialized = true;
return true;
@@ -155,7 +155,7 @@ namespace epee
return true;
const bool is_private = na.is_loopback() || na.is_local();
const auto elem = config.m_connections.find(na.host_str());
const auto elem = config.m_connections.find(net_utils::http::get_rpc_connection_limit_key(na));
if (elem != config.m_connections.end())
{
if (is_private)
@@ -32,6 +32,7 @@
#include <boost/uuid/uuid.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/address_v6.hpp>
#include <cstddef>
#include <typeinfo>
#include <type_traits>
#include "byte_slice.h"
@@ -208,6 +209,9 @@ namespace net_utils
END_KV_SERIALIZE_MAP()
};
bool should_group_ipv6_by_prefix(const boost::asio::ip::address_v6& ip);
boost::asio::ip::address_v6 get_ipv6_subnet_address(const boost::asio::ip::address_v6& ip, const std::size_t prefix_bits);
inline bool operator==(const ipv6_network_address& lhs, const ipv6_network_address& rhs) noexcept
{ return lhs.equal(rhs); }
inline bool operator!=(const ipv6_network_address& lhs, const ipv6_network_address& rhs) noexcept
+1 -1
View File
@@ -31,7 +31,7 @@ set(EPEE_INCLUDE_DIR_BASE "${CMAKE_CURRENT_SOURCE_DIR}/../include")
# Add headers to the file list, to be able to search for them and autosave in IDEs.
monero_find_all_headers(EPEE_HEADERS_PUBLIC "${EPEE_INCLUDE_DIR_BASE}")
monero_add_library(epee byte_slice.cpp byte_stream.cpp hex.cpp abstract_http_client.cpp http_auth.cpp mlog.cpp net_helper.cpp net_utils_base.cpp string_tools.cpp parserse_base_utils.cpp
monero_add_library(epee byte_slice.cpp byte_stream.cpp hex.cpp abstract_http_client.cpp http_auth.cpp http_protocol_handler.cpp mlog.cpp net_helper.cpp net_utils_base.cpp string_tools.cpp parserse_base_utils.cpp
wipeable_string.cpp levin_base.cpp memwipe.c connection_basic.cpp network_throttle.cpp network_throttle-detail.cpp mlocker.cpp buffer.cpp net_ssl.cpp
int-util.cpp portable_storage.cpp
misc_language.cpp
@@ -0,0 +1,44 @@
// Copyright (c) 2026, 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 "net/http_protocol_handler.h"
namespace epee { namespace net_utils { namespace http
{
std::string get_rpc_connection_limit_key(const net_utils::network_address& address)
{
if (address.get_type_id() == net_utils::ipv6_network_address::get_type_id())
{
const boost::asio::ip::address_v6 ip = address.as<const net_utils::ipv6_network_address>().ip();
if (net_utils::should_group_ipv6_by_prefix(ip))
return net_utils::get_ipv6_subnet_address(ip, 64).to_string() + "/64";
}
return address.host_str();
}
}}}
+30
View File
@@ -1,6 +1,8 @@
#include "net/net_utils_base.h"
#include <algorithm>
#include <boost/uuid/uuid_io.hpp>
#include "string_tools.h"
@@ -45,6 +47,34 @@ namespace epee { namespace net_utils
bool ipv6_network_address::is_loopback() const { return m_address.is_loopback(); }
bool ipv6_network_address::is_local() const { return m_address.is_link_local(); }
static bool is_ipv6_unique_local(const boost::asio::ip::address_v6& ip)
{
const boost::asio::ip::address_v6::bytes_type bytes = ip.to_bytes();
return (bytes[0] & 0xfe) == 0xfc;
}
bool should_group_ipv6_by_prefix(const boost::asio::ip::address_v6& ip)
{
return !ip.is_unspecified() && !ip.is_loopback() && !ip.is_multicast() &&
!ip.is_link_local() && !ip.is_site_local() && !is_ipv6_unique_local(ip) &&
!ip.is_v4_mapped();
}
boost::asio::ip::address_v6 get_ipv6_subnet_address(const boost::asio::ip::address_v6& ip, const std::size_t prefix_bits)
{
boost::asio::ip::address_v6::bytes_type bytes = ip.to_bytes();
const std::size_t bits = std::min(prefix_bits, std::size_t{128});
if (bits < 128)
{
const std::size_t full_bytes = bits / 8;
const std::size_t remainder_bits = bits % 8;
if (remainder_bits != 0)
bytes[full_bytes] &= uint8_t(0xffu << (8 - remainder_bits));
std::fill(bytes.begin() + full_bytes + (remainder_bits != 0), bytes.end(), 0);
}
return boost::asio::ip::address_v6(bytes);
}
bool ipv4_network_subnet::equal(const ipv4_network_subnet& other) const noexcept
{ return is_same_host(other) && m_mask == other.m_mask; }