mirror of
https://github.com/wownero/wownero.git
synced 2026-10-17 23:51:37 +02:00
p2p: fix IPv4 subnet arithmetic
This commit is contained in:
@@ -32,6 +32,7 @@
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ip/address_v6.hpp>
|
||||
#include <stdexcept>
|
||||
#include <typeinfo>
|
||||
#include <type_traits>
|
||||
#include "byte_slice.h"
|
||||
@@ -128,19 +129,23 @@ namespace net_utils
|
||||
|
||||
public:
|
||||
constexpr ipv4_network_subnet() noexcept
|
||||
: ipv4_network_subnet(0, 0)
|
||||
: m_ip(0), m_mask(0)
|
||||
{}
|
||||
|
||||
constexpr ipv4_network_subnet(uint32_t ip, uint8_t mask) noexcept
|
||||
: m_ip(ip), m_mask(mask) {}
|
||||
ipv4_network_subnet(uint32_t ip, uint8_t mask)
|
||||
: m_ip(ip), m_mask(mask)
|
||||
{
|
||||
CHECK_AND_ASSERT_THROW_MES(mask <= 32, "invalid IPv4 subnet mask");
|
||||
}
|
||||
|
||||
bool equal(const ipv4_network_subnet& other) const noexcept;
|
||||
bool less(const ipv4_network_subnet& other) const noexcept;
|
||||
constexpr bool is_same_host(const ipv4_network_subnet& other) const noexcept
|
||||
bool is_same_host(const ipv4_network_subnet& other) const noexcept
|
||||
{ return subnet() == other.subnet(); }
|
||||
bool matches(const ipv4_network_address &address) const;
|
||||
|
||||
constexpr uint32_t subnet() const noexcept { return m_ip & ~(0xffffffffull << m_mask); }
|
||||
uint8_t mask() const noexcept { return m_mask; }
|
||||
uint32_t subnet() const noexcept;
|
||||
std::string str() const;
|
||||
std::string host_str() const;
|
||||
bool is_loopback() const;
|
||||
|
||||
@@ -17,6 +17,13 @@ static inline uint32_t make_address_v4_from_v6(const boost::asio::ip::address_v6
|
||||
return htonl(v4);
|
||||
}
|
||||
|
||||
static inline uint32_t make_ipv4_cidr_mask(const uint8_t mask)
|
||||
{
|
||||
if (mask == 0)
|
||||
return 0;
|
||||
return SWAP32BE(0xffffffffu << (32 - mask));
|
||||
}
|
||||
|
||||
namespace epee { namespace net_utils
|
||||
{
|
||||
bool ipv4_network_address::equal(const ipv4_network_address& other) const noexcept
|
||||
@@ -52,6 +59,9 @@ namespace epee { namespace net_utils
|
||||
bool ipv4_network_subnet::less(const ipv4_network_subnet& other) const noexcept
|
||||
{ return subnet() < other.subnet() ? true : (other.subnet() < subnet() ? false : (m_mask < other.m_mask)); }
|
||||
|
||||
uint32_t ipv4_network_subnet::subnet() const noexcept
|
||||
{ return m_ip & make_ipv4_cidr_mask(m_mask); }
|
||||
|
||||
std::string ipv4_network_subnet::str() const
|
||||
{ return string_tools::get_ip_string_from_int32(subnet()) + "/" + std::to_string(m_mask); }
|
||||
|
||||
@@ -60,7 +70,7 @@ namespace epee { namespace net_utils
|
||||
bool ipv4_network_subnet::is_local() const { return net_utils::is_ip_local(subnet()); }
|
||||
bool ipv4_network_subnet::matches(const ipv4_network_address &address) const
|
||||
{
|
||||
return (address.ip() & ~(0xffffffffull << m_mask)) == subnet();
|
||||
return (address.ip() & make_ipv4_cidr_mask(m_mask)) == subnet();
|
||||
}
|
||||
|
||||
|
||||
|
||||
+91
-4
@@ -77,6 +77,66 @@ static inline boost::asio::ip::address_v4 make_address_v4_from_v6(const boost::a
|
||||
|
||||
namespace nodetool
|
||||
{
|
||||
namespace
|
||||
{
|
||||
uint64_t ipv4_subnet_size(const uint8_t mask)
|
||||
{
|
||||
CHECK_AND_ASSERT_THROW_MES(mask <= 32, "invalid IPv4 subnet mask");
|
||||
return mask == 0 ? (uint64_t{1} << 32) : (uint64_t{1} << (32 - mask));
|
||||
}
|
||||
|
||||
uint64_t ipv4_subnet_first(const epee::net_utils::ipv4_network_subnet &subnet)
|
||||
{
|
||||
return SWAP32BE(subnet.subnet());
|
||||
}
|
||||
|
||||
uint64_t ipv4_subnet_last(const epee::net_utils::ipv4_network_subnet &subnet)
|
||||
{
|
||||
return ipv4_subnet_first(subnet) + ipv4_subnet_size(subnet.mask()) - 1;
|
||||
}
|
||||
|
||||
epee::net_utils::ipv4_network_subnet make_ipv4_subnet(const uint64_t first, const uint8_t mask)
|
||||
{
|
||||
return {SWAP32BE(static_cast<uint32_t>(first)), mask};
|
||||
}
|
||||
|
||||
void subtract_ipv4_subnet(
|
||||
const epee::net_utils::ipv4_network_subnet &blocked,
|
||||
const epee::net_utils::ipv4_network_subnet &removed,
|
||||
std::vector<epee::net_utils::ipv4_network_subnet> &result)
|
||||
{
|
||||
const uint64_t blocked_first = ipv4_subnet_first(blocked);
|
||||
const uint64_t blocked_last = ipv4_subnet_last(blocked);
|
||||
const uint64_t removed_first = ipv4_subnet_first(removed);
|
||||
const uint64_t removed_last = ipv4_subnet_last(removed);
|
||||
|
||||
if (removed_last < blocked_first || blocked_last < removed_first)
|
||||
{
|
||||
result.push_back(blocked);
|
||||
return;
|
||||
}
|
||||
if (removed_first <= blocked_first && blocked_last <= removed_last)
|
||||
return;
|
||||
|
||||
const uint8_t child_mask = blocked.mask() + 1;
|
||||
CHECK_AND_ASSERT_THROW_MES(child_mask <= 32, "invalid IPv4 subnet split");
|
||||
|
||||
const uint64_t child_size = ipv4_subnet_size(child_mask);
|
||||
subtract_ipv4_subnet(make_ipv4_subnet(blocked_first, child_mask), removed, result);
|
||||
subtract_ipv4_subnet(make_ipv4_subnet(blocked_first + child_size, child_mask), removed, result);
|
||||
}
|
||||
|
||||
void emplace_blocked_subnet(
|
||||
std::map<epee::net_utils::ipv4_network_subnet, time_t> &subnets,
|
||||
const epee::net_utils::ipv4_network_subnet &subnet,
|
||||
const time_t limit)
|
||||
{
|
||||
auto entry = subnets.find(subnet);
|
||||
if (entry == subnets.end() || entry->second < limit)
|
||||
subnets[subnet] = limit;
|
||||
}
|
||||
}
|
||||
|
||||
template<class t_payload_net_handler>
|
||||
node_server<t_payload_net_handler>::~node_server()
|
||||
{
|
||||
@@ -390,11 +450,38 @@ namespace nodetool
|
||||
bool node_server<t_payload_net_handler>::unblock_subnet(const epee::net_utils::ipv4_network_subnet &subnet)
|
||||
{
|
||||
CRITICAL_REGION_LOCAL(m_blocked_hosts_lock);
|
||||
auto i = m_blocked_subnets.find(subnet);
|
||||
if (i == m_blocked_subnets.end())
|
||||
bool unblocked = false;
|
||||
|
||||
for (auto i = m_blocked_hosts.begin(); i != m_blocked_hosts.end(); )
|
||||
{
|
||||
auto address = net::get_network_address(i->first, 0);
|
||||
if (address && address->get_type_id() == epee::net_utils::ipv4_network_address::get_type_id()
|
||||
&& subnet.matches(address->template as<epee::net_utils::ipv4_network_address>()))
|
||||
{
|
||||
i = m_blocked_hosts.erase(i);
|
||||
unblocked = true;
|
||||
}
|
||||
else
|
||||
++i;
|
||||
}
|
||||
|
||||
std::map<epee::net_utils::ipv4_network_subnet, time_t> blocked_subnets;
|
||||
std::vector<epee::net_utils::ipv4_network_subnet> remaining;
|
||||
remaining.reserve(32);
|
||||
for (const auto &blocked_subnet : m_blocked_subnets)
|
||||
{
|
||||
remaining.clear();
|
||||
subtract_ipv4_subnet(blocked_subnet.first, subnet, remaining);
|
||||
if (remaining.size() != 1 || remaining.front() != blocked_subnet.first)
|
||||
unblocked = true;
|
||||
for (const auto &entry : remaining)
|
||||
emplace_blocked_subnet(blocked_subnets, entry, blocked_subnet.second);
|
||||
}
|
||||
|
||||
if (!unblocked)
|
||||
return false;
|
||||
m_blocked_subnets.erase(i);
|
||||
MCLOG_CYAN(el::Level::Info, "global", "Subnet " << subnet.host_str() << " unblocked.");
|
||||
m_blocked_subnets.swap(blocked_subnets);
|
||||
MCLOG_CYAN(el::Level::Info, "global", "Subnet " << subnet.str() << " unblocked.");
|
||||
return true;
|
||||
}
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
@@ -877,6 +877,13 @@ TEST(get_network_address, ipv4subnet)
|
||||
|
||||
address = net::get_ipv4_subnet_address("12.34.56.78/16");
|
||||
EXPECT_STREQ("12.34.0.0/16", address->str().c_str());
|
||||
|
||||
address = net::get_ipv4_subnet_address("172.16.1.2/12");
|
||||
EXPECT_STREQ("172.16.0.0/12", address->str().c_str());
|
||||
EXPECT_TRUE(address->matches(epee::net_utils::ipv4_network_address{MAKE_IP(172,16,0,0), 0}));
|
||||
EXPECT_TRUE(address->matches(epee::net_utils::ipv4_network_address{MAKE_IP(172,31,255,255), 0}));
|
||||
EXPECT_FALSE(address->matches(epee::net_utils::ipv4_network_address{MAKE_IP(172,15,255,255), 0}));
|
||||
EXPECT_FALSE(address->matches(epee::net_utils::ipv4_network_address{MAKE_IP(172,32,0,0), 0}));
|
||||
}
|
||||
|
||||
namespace
|
||||
|
||||
@@ -315,18 +315,14 @@ 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);
|
||||
const auto node_dir = create_temp_dir("ban-subnet-%%%%%%%%%%%%%%%%");
|
||||
ASSERT_TRUE(!node_dir.empty());
|
||||
auto auto_remove_node_dir = epee::misc_utils::create_scope_leave_handler([&node_dir](){
|
||||
remove_tree(node_dir);
|
||||
});
|
||||
|
||||
const char *argv[] = {"ban_subnet_test"};
|
||||
boost::program_options::variables_map vm;
|
||||
boost::program_options::store(
|
||||
boost::program_options::parse_command_line(1, argv, opts), vm
|
||||
);
|
||||
server.init(vm);
|
||||
}
|
||||
const auto vm = make_regtest_options(node_dir);
|
||||
ASSERT_TRUE(server.init(vm));
|
||||
cprotocol.set_p2p_endpoint(&server);
|
||||
|
||||
ASSERT_TRUE(server.block_subnet(MAKE_IPV4_SUBNET(1,2,3,4,24), 10));
|
||||
@@ -345,14 +341,94 @@ TEST(ban, subnet)
|
||||
ASSERT_TRUE(server.get_blocked_subnets().size() == 1);
|
||||
ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(1,255,3,255), &seconds));
|
||||
ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(1,0,3,255), &seconds));
|
||||
ASSERT_FALSE(server.unblock_subnet(MAKE_IPV4_SUBNET(1,2,3,8,24)));
|
||||
ASSERT_TRUE(server.get_blocked_subnets().size() == 1);
|
||||
ASSERT_TRUE(server.block_subnet(MAKE_IPV4_SUBNET(1,2,3,4,8), 10));
|
||||
ASSERT_TRUE(server.get_blocked_subnets().size() == 1);
|
||||
ASSERT_TRUE(server.unblock_subnet(MAKE_IPV4_SUBNET(1,2,3,8,24)));
|
||||
ASSERT_TRUE(server.get_blocked_subnets().size() == 16);
|
||||
ASSERT_FALSE(server.is_host_blocked(MAKE_IPV4_ADDRESS(1,2,3,8), &seconds));
|
||||
ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(1,2,2,255), &seconds));
|
||||
ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(1,2,4,0), &seconds));
|
||||
ASSERT_TRUE(server.unblock_subnet(MAKE_IPV4_SUBNET(1,255,0,0,8)));
|
||||
ASSERT_TRUE(server.get_blocked_subnets().size() == 0);
|
||||
}
|
||||
|
||||
TEST(ban, subnet_arithmetic)
|
||||
{
|
||||
time_t seconds;
|
||||
test_core pr_core;
|
||||
cryptonote::t_cryptonote_protocol_handler<test_core> cprotocol(pr_core, NULL);
|
||||
Server server(cprotocol);
|
||||
const auto node_dir = create_temp_dir("ban-subnet-arithmetic-%%%%%%%%%%%%%%%%");
|
||||
ASSERT_TRUE(!node_dir.empty());
|
||||
auto auto_remove_node_dir = epee::misc_utils::create_scope_leave_handler([&node_dir](){
|
||||
remove_tree(node_dir);
|
||||
});
|
||||
|
||||
const auto vm = make_regtest_options(node_dir);
|
||||
ASSERT_TRUE(server.init(vm));
|
||||
cprotocol.set_p2p_endpoint(&server);
|
||||
|
||||
ASSERT_NO_THROW((void)(MAKE_IPV4_SUBNET(0,0,0,0,0)));
|
||||
ASSERT_NO_THROW((void)(MAKE_IPV4_SUBNET(203,0,113,9,32)));
|
||||
ASSERT_THROW((void)(MAKE_IPV4_SUBNET(203,0,113,9,33)), std::runtime_error);
|
||||
|
||||
ASSERT_TRUE(server.block_subnet(MAKE_IPV4_SUBNET(172,16,0,0,16), 10));
|
||||
ASSERT_TRUE(server.block_subnet(MAKE_IPV4_SUBNET(172,16,1,0,24), 10));
|
||||
ASSERT_TRUE(server.unblock_subnet(MAKE_IPV4_SUBNET(172,16,0,0,16)));
|
||||
ASSERT_FALSE(server.is_host_blocked(MAKE_IPV4_ADDRESS(172,16,1,1), &seconds));
|
||||
ASSERT_TRUE(server.get_blocked_subnets().empty());
|
||||
|
||||
ASSERT_TRUE(server.block_subnet(MAKE_IPV4_SUBNET(172,16,0,0,12), 10));
|
||||
ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(172,16,1,1), &seconds));
|
||||
ASSERT_TRUE(server.unblock_subnet(MAKE_IPV4_SUBNET(172,16,1,0,24)));
|
||||
ASSERT_FALSE(server.is_host_blocked(MAKE_IPV4_ADDRESS(172,16,1,1), &seconds));
|
||||
ASSERT_FALSE(server.is_host_blocked(MAKE_IPV4_ADDRESS(172,16,1,255), &seconds));
|
||||
ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(172,16,0,255), &seconds));
|
||||
ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(172,16,2,0), &seconds));
|
||||
ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(172,31,255,255), &seconds));
|
||||
ASSERT_FALSE(server.is_host_blocked(MAKE_IPV4_ADDRESS(172,15,255,255), &seconds));
|
||||
ASSERT_FALSE(server.is_host_blocked(MAKE_IPV4_ADDRESS(172,32,0,0), &seconds));
|
||||
ASSERT_TRUE(server.get_blocked_subnets().size() == 12);
|
||||
|
||||
ASSERT_TRUE(server.block_host(MAKE_IPV4_ADDRESS(172,16,2,42), 10));
|
||||
ASSERT_TRUE(server.unblock_subnet(MAKE_IPV4_SUBNET(172,16,2,0,24)));
|
||||
ASSERT_FALSE(server.is_host_blocked(MAKE_IPV4_ADDRESS(172,16,2,42), &seconds));
|
||||
ASSERT_FALSE(server.is_host_blocked(MAKE_IPV4_ADDRESS(172,16,2,255), &seconds));
|
||||
ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(172,16,3,0), &seconds));
|
||||
ASSERT_TRUE(server.get_blocked_subnets().size() == 12);
|
||||
|
||||
ASSERT_TRUE(server.unblock_subnet(MAKE_IPV4_SUBNET(172,16,3,7,32)));
|
||||
ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(172,16,3,6), &seconds));
|
||||
ASSERT_FALSE(server.is_host_blocked(MAKE_IPV4_ADDRESS(172,16,3,7), &seconds));
|
||||
ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(172,16,3,8), &seconds));
|
||||
|
||||
ASSERT_TRUE(server.block_subnet(MAKE_IPV4_SUBNET(203,0,113,9,32), 10));
|
||||
ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(203,0,113,9), &seconds));
|
||||
ASSERT_FALSE(server.is_host_blocked(MAKE_IPV4_ADDRESS(203,0,113,8), &seconds));
|
||||
ASSERT_FALSE(server.is_host_blocked(MAKE_IPV4_ADDRESS(203,0,113,10), &seconds));
|
||||
ASSERT_TRUE(server.unblock_subnet(MAKE_IPV4_SUBNET(203,0,113,9,32)));
|
||||
ASSERT_FALSE(server.is_host_blocked(MAKE_IPV4_ADDRESS(203,0,113,9), &seconds));
|
||||
|
||||
ASSERT_TRUE(server.block_host(MAKE_IPV4_ADDRESS(10,1,2,3), 10));
|
||||
ASSERT_TRUE(server.block_subnet(MAKE_IPV4_SUBNET(192,0,2,0,24), 10));
|
||||
ASSERT_TRUE(server.unblock_subnet(MAKE_IPV4_SUBNET(0,0,0,0,0)));
|
||||
ASSERT_FALSE(server.is_host_blocked(MAKE_IPV4_ADDRESS(10,1,2,3), &seconds));
|
||||
ASSERT_FALSE(server.is_host_blocked(MAKE_IPV4_ADDRESS(192,0,2,100), &seconds));
|
||||
ASSERT_TRUE(server.get_blocked_hosts().empty());
|
||||
ASSERT_TRUE(server.get_blocked_subnets().empty());
|
||||
|
||||
ASSERT_TRUE(server.block_subnet(MAKE_IPV4_SUBNET(0,0,0,0,0), 10));
|
||||
ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(0,0,0,0), &seconds));
|
||||
ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(255,255,255,255), &seconds));
|
||||
ASSERT_TRUE(server.unblock_subnet(MAKE_IPV4_SUBNET(203,0,113,9,32)));
|
||||
ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(203,0,113,8), &seconds));
|
||||
ASSERT_FALSE(server.is_host_blocked(MAKE_IPV4_ADDRESS(203,0,113,9), &seconds));
|
||||
ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(203,0,113,10), &seconds));
|
||||
ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(0,0,0,0), &seconds));
|
||||
ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(255,255,255,255), &seconds));
|
||||
ASSERT_TRUE(server.get_blocked_subnets().size() == 32);
|
||||
ASSERT_TRUE(server.unblock_subnet(MAKE_IPV4_SUBNET(0,0,0,0,0)));
|
||||
ASSERT_TRUE(server.get_blocked_subnets().empty());
|
||||
}
|
||||
|
||||
TEST(ban, ignores_port)
|
||||
{
|
||||
test_core pr_core;
|
||||
|
||||
Reference in New Issue
Block a user