mirror of
https://github.com/wownero/wownero.git
synced 2026-10-17 15:41:44 +02:00
p2p: reject IPv4-mapped IPv6 peers
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 <boost/optional/optional.hpp>
|
||||
#include <stdexcept>
|
||||
#include <typeinfo>
|
||||
#include <type_traits>
|
||||
@@ -226,6 +227,8 @@ namespace net_utils
|
||||
inline bool operator>=(const ipv6_network_address& lhs, const ipv6_network_address& rhs) noexcept
|
||||
{ return !lhs.less(rhs); }
|
||||
|
||||
boost::optional<ipv4_network_address> get_ipv4_mapped_address(const ipv6_network_address& address);
|
||||
|
||||
class network_address
|
||||
{
|
||||
struct interface
|
||||
@@ -352,6 +355,8 @@ namespace net_utils
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
boost::optional<ipv4_network_address> get_ipv4_mapped_address(const network_address& address);
|
||||
|
||||
inline bool operator==(const network_address& lhs, const network_address& rhs)
|
||||
{ return lhs.equal(rhs); }
|
||||
inline bool operator!=(const network_address& lhs, const network_address& rhs)
|
||||
|
||||
@@ -1,22 +1,12 @@
|
||||
|
||||
#include "net/net_utils_base.h"
|
||||
|
||||
#include <boost/asio/ip/address_v4.hpp>
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
|
||||
#include "string_tools.h"
|
||||
#include "net/local_ip.h"
|
||||
|
||||
static inline uint32_t make_address_v4_from_v6(const boost::asio::ip::address_v6& a)
|
||||
{
|
||||
const auto &bytes = a.to_bytes();
|
||||
uint32_t v4 = 0;
|
||||
v4 = (v4 << 8) | bytes[12];
|
||||
v4 = (v4 << 8) | bytes[13];
|
||||
v4 = (v4 << 8) | bytes[14];
|
||||
v4 = (v4 << 8) | bytes[15];
|
||||
return htonl(v4);
|
||||
}
|
||||
|
||||
static inline uint32_t make_ipv4_cidr_mask(const uint8_t mask)
|
||||
{
|
||||
if (mask == 0)
|
||||
@@ -52,6 +42,17 @@ 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(); }
|
||||
|
||||
boost::optional<ipv4_network_address> get_ipv4_mapped_address(const ipv6_network_address& address)
|
||||
{
|
||||
const boost::asio::ip::address_v6 ip = address.ip();
|
||||
if (!ip.is_v4_mapped())
|
||||
return boost::none;
|
||||
|
||||
const boost::asio::ip::address_v4 ipv4 =
|
||||
boost::asio::ip::make_address_v4(boost::asio::ip::v4_mapped, ip);
|
||||
return ipv4_network_address{SWAP32BE(ipv4.to_uint()), address.port()};
|
||||
}
|
||||
|
||||
|
||||
bool ipv4_network_subnet::equal(const ipv4_network_subnet& other) const noexcept
|
||||
{ return is_same_host(other) && m_mask == other.m_mask; }
|
||||
@@ -109,25 +110,28 @@ namespace epee { namespace net_utils
|
||||
const auto this_id = get_type_id();
|
||||
if (this_id == ipv4_network_address::get_type_id() && other.get_type_id() == ipv6_network_address::get_type_id())
|
||||
{
|
||||
const boost::asio::ip::address_v6 &actual_ip = other.as<const epee::net_utils::ipv6_network_address>().ip();
|
||||
if (actual_ip.is_v4_mapped())
|
||||
{
|
||||
const uint32_t v4ip = make_address_v4_from_v6(actual_ip);
|
||||
return is_same_host(ipv4_network_address(v4ip, 0));
|
||||
}
|
||||
const boost::optional<ipv4_network_address> mapped =
|
||||
get_ipv4_mapped_address(other.as<const ipv6_network_address>());
|
||||
if (mapped)
|
||||
return is_same_host(*mapped);
|
||||
}
|
||||
else if (this_id == ipv6_network_address::get_type_id() && other.get_type_id() == ipv4_network_address::get_type_id())
|
||||
{
|
||||
const boost::asio::ip::address_v6 &actual_ip = this->as<const epee::net_utils::ipv6_network_address>().ip();
|
||||
if (actual_ip.is_v4_mapped())
|
||||
{
|
||||
const uint32_t v4ip = make_address_v4_from_v6(actual_ip);
|
||||
return other.is_same_host(ipv4_network_address(v4ip, 0));
|
||||
}
|
||||
const boost::optional<ipv4_network_address> mapped =
|
||||
get_ipv4_mapped_address(this->as<const ipv6_network_address>());
|
||||
if (mapped)
|
||||
return other.is_same_host(*mapped);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boost::optional<ipv4_network_address> get_ipv4_mapped_address(const network_address& address)
|
||||
{
|
||||
if (address.get_type_id() != ipv6_network_address::get_type_id())
|
||||
return boost::none;
|
||||
return get_ipv4_mapped_address(address.as<const ipv6_network_address>());
|
||||
}
|
||||
|
||||
std::string print_connection_context(const connection_context_base& ctx)
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
+6
-1
@@ -187,7 +187,12 @@ namespace net
|
||||
|
||||
if (ipv6)
|
||||
{
|
||||
return {epee::net_utils::ipv6_network_address{v6, port}};
|
||||
const epee::net_utils::ipv6_network_address address{v6, port};
|
||||
const boost::optional<epee::net_utils::ipv4_network_address> mapped =
|
||||
epee::net_utils::get_ipv4_mapped_address(address);
|
||||
if (mapped)
|
||||
return {std::move(*mapped)};
|
||||
return {address};
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+57
-29
@@ -142,6 +142,20 @@ namespace nodetool
|
||||
return ipv6_peer_group{{bytes[0], bytes[1], bytes[2], bytes[3]}};
|
||||
}
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
inline bool is_forbidden_ipv4_mapped_ipv6_address(const epee::net_utils::network_address& address)
|
||||
{
|
||||
return bool(epee::net_utils::get_ipv4_mapped_address(address));
|
||||
}
|
||||
|
||||
inline bool should_skip_connect_address(const epee::net_utils::network_address& address, bool use_ipv6)
|
||||
{
|
||||
if (address.get_type_id() == epee::net_utils::ipv6_network_address::get_type_id() && !use_ipv6)
|
||||
return true;
|
||||
|
||||
return is_forbidden_ipv4_mapped_ipv6_address(address);
|
||||
}
|
||||
|
||||
template<class t_payload_net_handler>
|
||||
node_server<t_payload_net_handler>::~node_server()
|
||||
{
|
||||
@@ -1474,6 +1488,9 @@ namespace nodetool
|
||||
if (zone.m_connect == nullptr) // outgoing connections in zone not possible
|
||||
return false;
|
||||
|
||||
if (should_skip_connect_address(na, m_use_ipv6))
|
||||
return false;
|
||||
|
||||
if (zone.m_our_address == na)
|
||||
return false;
|
||||
|
||||
@@ -1653,17 +1670,9 @@ namespace nodetool
|
||||
|
||||
// Local helper method to get the host string, i.e. the pure IP address without port
|
||||
const auto get_host_string = [](const epee::net_utils::network_address &address) {
|
||||
if (address.get_type_id() == epee::net_utils::ipv6_network_address::get_type_id())
|
||||
{
|
||||
const boost::asio::ip::address_v6 actual_ip = address.as<const epee::net_utils::ipv6_network_address>().ip();
|
||||
if (actual_ip.is_v4_mapped())
|
||||
{
|
||||
auto v4ip = boost::asio::ip::make_address_v4(boost::asio::ip::v4_mapped, actual_ip);
|
||||
uint32_t actual_ipv4;
|
||||
memcpy(&actual_ipv4, v4ip.to_bytes().data(), sizeof(actual_ipv4));
|
||||
return epee::net_utils::ipv4_network_address(actual_ipv4, 0).host_str();
|
||||
}
|
||||
}
|
||||
const boost::optional<epee::net_utils::ipv4_network_address> mapped = epee::net_utils::get_ipv4_mapped_address(address);
|
||||
if (mapped)
|
||||
return epee::net_utils::ipv4_network_address(mapped->ip(), 0).host_str();
|
||||
return address.host_str();
|
||||
};
|
||||
|
||||
@@ -1676,9 +1685,12 @@ namespace nodetool
|
||||
std::vector<peerlist_entry> peers;
|
||||
std::unordered_set<std::string> hosts;
|
||||
size_t total_peers_size = 0;
|
||||
zone.m_peerlist.foreach(use_white_list, [&peers, &hosts, &total_peers_size, &get_host_string](const peerlist_entry &peer)
|
||||
zone.m_peerlist.foreach(use_white_list, [this, &peers, &hosts, &total_peers_size, &get_host_string](const peerlist_entry &peer)
|
||||
{
|
||||
++total_peers_size;
|
||||
if (should_skip_connect_address(peer.adr, m_use_ipv6))
|
||||
return true;
|
||||
|
||||
const std::string host_string = get_host_string(peer.adr);
|
||||
if (hosts.insert(host_string).second)
|
||||
{
|
||||
@@ -1722,14 +1734,9 @@ namespace nodetool
|
||||
else if (cntxt.m_remote_address.get_type_id() == epee::net_utils::ipv6_network_address::get_type_id())
|
||||
{
|
||||
const epee::net_utils::network_address na = cntxt.m_remote_address;
|
||||
const boost::asio::ip::address_v6 &actual_ip = na.as<const epee::net_utils::ipv6_network_address>().ip();
|
||||
if (actual_ip.is_v4_mapped())
|
||||
{
|
||||
auto v4ip = boost::asio::ip::make_address_v4(boost::asio::ip::v4_mapped, actual_ip);
|
||||
uint32_t actual_ipv4;
|
||||
memcpy(&actual_ipv4, v4ip.to_bytes().data(), sizeof(actual_ipv4));
|
||||
connected_subnets.insert(actual_ipv4 & subnet_mask);
|
||||
}
|
||||
const boost::optional<epee::net_utils::ipv4_network_address> mapped = epee::net_utils::get_ipv4_mapped_address(na);
|
||||
if (mapped)
|
||||
connected_subnets.insert(mapped->ip() & subnet_mask);
|
||||
else
|
||||
{
|
||||
const boost::optional<ipv6_peer_group> group = get_ipv6_peer_group(na);
|
||||
@@ -1785,13 +1792,10 @@ namespace nodetool
|
||||
else if (peer.adr.get_type_id() == epee::net_utils::ipv6_network_address::get_type_id())
|
||||
{
|
||||
const epee::net_utils::network_address na = peer.adr;
|
||||
const boost::asio::ip::address_v6 &actual_ip = na.as<const epee::net_utils::ipv6_network_address>().ip();
|
||||
if (actual_ip.is_v4_mapped())
|
||||
const boost::optional<epee::net_utils::ipv4_network_address> mapped = epee::net_utils::get_ipv4_mapped_address(na);
|
||||
if (mapped)
|
||||
{
|
||||
auto v4ip = boost::asio::ip::make_address_v4(boost::asio::ip::v4_mapped, actual_ip);
|
||||
uint32_t actual_ipv4;
|
||||
memcpy(&actual_ipv4, v4ip.to_bytes().data(), sizeof(actual_ipv4));
|
||||
uint32_t subnet = actual_ipv4 & subnet_mask;
|
||||
uint32_t subnet = mapped->ip() & subnet_mask;
|
||||
take = subnets.find(subnet) == subnets.end();
|
||||
if (take)
|
||||
subnets.insert(subnet);
|
||||
@@ -2328,11 +2332,15 @@ namespace nodetool
|
||||
bool ignore = false;
|
||||
peerlist_entry &be = local_peerlist[i];
|
||||
epee::net_utils::network_address &na = be.adr;
|
||||
if (na.is_loopback() || na.is_local())
|
||||
if (is_forbidden_ipv4_mapped_ipv6_address(na))
|
||||
{
|
||||
ignore = true;
|
||||
}
|
||||
else if (be.adr.get_type_id() == epee::net_utils::ipv4_network_address::get_type_id())
|
||||
else if (na.is_loopback() || na.is_local())
|
||||
{
|
||||
ignore = true;
|
||||
}
|
||||
else if (na.get_type_id() == epee::net_utils::ipv4_network_address::get_type_id())
|
||||
{
|
||||
const epee::net_utils::ipv4_network_address &ipv4 = na.as<const epee::net_utils::ipv4_network_address>();
|
||||
if (ipv4.ip() == 0)
|
||||
@@ -2381,7 +2389,8 @@ namespace nodetool
|
||||
LOG_TRACE_CC(context, "REMOTE PEERLIST: " << ENDL << print_peerlist_to_string(peerlist_));
|
||||
CRITICAL_REGION_LOCAL(m_blocked_hosts_lock);
|
||||
return m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.merge_peerlist(peerlist_, [this](const peerlist_entry &pe) {
|
||||
return !is_addr_recently_failed(pe.adr) && is_remote_host_allowed(pe.adr);
|
||||
return !is_forbidden_ipv4_mapped_ipv6_address(pe.adr) &&
|
||||
!is_addr_recently_failed(pe.adr) && is_remote_host_allowed(pe.adr);
|
||||
});
|
||||
}
|
||||
//-----------------------------------------------------------------------------------
|
||||
@@ -2669,6 +2678,9 @@ namespace nodetool
|
||||
|
||||
std::vector<peerlist_entry> local_peerlist_new;
|
||||
zone.m_peerlist.get_peerlist_head(local_peerlist_new, true, max_peerlist_size);
|
||||
local_peerlist_new.erase(std::remove_if(local_peerlist_new.begin(), local_peerlist_new.end(), [](const peerlist_entry& peer) {
|
||||
return is_forbidden_ipv4_mapped_ipv6_address(peer.adr);
|
||||
}), local_peerlist_new.end());
|
||||
|
||||
/* Tor/I2P nodes receiving connections via forwarding (from tor/i2p daemon)
|
||||
do not know the address of the connecting peer. This is relayed to them,
|
||||
@@ -2793,6 +2805,9 @@ namespace nodetool
|
||||
|
||||
//fill response
|
||||
zone.m_peerlist.get_peerlist_head(rsp.local_peerlist_new, true);
|
||||
rsp.local_peerlist_new.erase(std::remove_if(rsp.local_peerlist_new.begin(), rsp.local_peerlist_new.end(), [](const peerlist_entry& peer) {
|
||||
return is_forbidden_ipv4_mapped_ipv6_address(peer.adr);
|
||||
}), rsp.local_peerlist_new.end());
|
||||
for (const auto &e: rsp.local_peerlist_new)
|
||||
context.sent_addresses.insert(e.adr);
|
||||
get_local_node_data(rsp.node_data, zone);
|
||||
@@ -3098,6 +3113,16 @@ namespace nodetool
|
||||
if (!zone.second.m_peerlist.get_random_gray_peer(pe))
|
||||
continue;
|
||||
|
||||
if (is_forbidden_ipv4_mapped_ipv6_address(pe.adr))
|
||||
{
|
||||
zone.second.m_peerlist.remove_from_peer_gray(pe);
|
||||
LOG_PRINT_L2("PEER EVICTED FROM GRAY PEER LIST: address: " << pe.adr.host_str() << " Peer ID: " << peerid_to_string(pe.id));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (should_skip_connect_address(pe.adr, m_use_ipv6))
|
||||
continue;
|
||||
|
||||
if (!check_connection_and_handshake_with_peer(pe.adr, pe.last_seen))
|
||||
{
|
||||
zone.second.m_peerlist.remove_from_peer_gray(pe);
|
||||
@@ -3182,6 +3207,9 @@ namespace nodetool
|
||||
}
|
||||
else if (is_ipv6)
|
||||
{
|
||||
if (epee::net_utils::get_ipv4_mapped_address(na))
|
||||
return boost::none;
|
||||
|
||||
const epee::net_utils::ipv6_network_address &ipv6 = na.as<const epee::net_utils::ipv6_network_address>();
|
||||
address = ipv6.ip().to_string();
|
||||
port = epee::string_tools::num_to_string_fast(ipv6.port());
|
||||
|
||||
@@ -1025,6 +1025,16 @@ TEST(get_network_address, ipv4)
|
||||
EXPECT_STREQ("23.0.0.254:2000", address->str().c_str());
|
||||
}
|
||||
|
||||
TEST(get_network_address, ipv4_mapped_ipv6)
|
||||
{
|
||||
expect<epee::net_utils::network_address> address =
|
||||
net::get_network_address("[::ffff:203.0.113.1]:18080", 0);
|
||||
ASSERT_TRUE(bool(address));
|
||||
EXPECT_EQ(epee::net_utils::address_type::ipv4, address->get_type_id());
|
||||
EXPECT_STREQ("203.0.113.1", address->host_str().c_str());
|
||||
EXPECT_STREQ("203.0.113.1:18080", address->str().c_str());
|
||||
}
|
||||
|
||||
TEST(get_network_address, ipv4subnet)
|
||||
{
|
||||
expect<epee::net_utils::ipv4_network_subnet> address = net::get_ipv4_subnet_address("0.0.0.0", true);
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "cryptonote_protocol/cryptonote_protocol_handler.h"
|
||||
#include "cryptonote_protocol/cryptonote_protocol_handler.inl"
|
||||
#include "unit_tests_utils.h"
|
||||
#include <algorithm>
|
||||
#include <condition_variable>
|
||||
#include <thread>
|
||||
|
||||
@@ -167,6 +168,36 @@ static bool is_blocked(Server &server, const epee::net_utils::network_address &a
|
||||
return false;
|
||||
}
|
||||
|
||||
TEST(node_server, ipv4_mapped_ipv6_address)
|
||||
{
|
||||
boost::asio::ip::address_v6::bytes_type bytes = {};
|
||||
bytes[10] = 0xff;
|
||||
bytes[11] = 0xff;
|
||||
bytes[12] = 45;
|
||||
bytes[13] = 155;
|
||||
bytes[14] = 69;
|
||||
bytes[15] = 10;
|
||||
|
||||
const epee::net_utils::network_address mapped{
|
||||
epee::net_utils::ipv6_network_address{boost::asio::ip::address_v6{bytes}, 18080}
|
||||
};
|
||||
const boost::optional<epee::net_utils::ipv4_network_address> ipv4 = epee::net_utils::get_ipv4_mapped_address(mapped);
|
||||
ASSERT_TRUE(bool(ipv4));
|
||||
EXPECT_EQ("45.155.69.10", ipv4->host_str());
|
||||
EXPECT_EQ(18080, ipv4->port());
|
||||
|
||||
const epee::net_utils::network_address ipv6{
|
||||
epee::net_utils::ipv6_network_address{boost::asio::ip::address_v6::loopback(), 18080}
|
||||
};
|
||||
EXPECT_FALSE(epee::net_utils::get_ipv4_mapped_address(ipv6));
|
||||
|
||||
EXPECT_TRUE(nodetool::is_forbidden_ipv4_mapped_ipv6_address(mapped));
|
||||
EXPECT_FALSE(nodetool::is_forbidden_ipv4_mapped_ipv6_address(ipv6));
|
||||
EXPECT_TRUE(nodetool::should_skip_connect_address(mapped, true));
|
||||
EXPECT_TRUE(nodetool::should_skip_connect_address(ipv6, false));
|
||||
EXPECT_FALSE(nodetool::should_skip_connect_address(ipv6, true));
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
using path_t = boost::filesystem::path;
|
||||
@@ -229,6 +260,44 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
TEST(node_server, peerlist_merge_rejects_ipv4_mapped_ipv6_address)
|
||||
{
|
||||
boost::asio::ip::address_v6::bytes_type bytes = {};
|
||||
bytes[10] = 0xff;
|
||||
bytes[11] = 0xff;
|
||||
bytes[12] = 45;
|
||||
bytes[13] = 155;
|
||||
bytes[14] = 69;
|
||||
bytes[15] = 10;
|
||||
|
||||
const epee::net_utils::network_address mapped{
|
||||
epee::net_utils::ipv6_network_address{boost::asio::ip::address_v6{bytes}, 18080}
|
||||
};
|
||||
const epee::net_utils::network_address ipv4{MAKE_IPV4_ADDRESS_PORT(11, 22, 33, 44, 18080)};
|
||||
|
||||
std::vector<nodetool::peerlist_entry> remote_peerlist;
|
||||
remote_peerlist.push_back(make_peer(mapped, 1, 100));
|
||||
remote_peerlist.push_back(make_peer(ipv4, 2, 200));
|
||||
|
||||
nodetool::peerlist_manager peerlist;
|
||||
ASSERT_TRUE(peerlist.init(nodetool::peerlist_types{}, false));
|
||||
ASSERT_TRUE(peerlist.merge_peerlist(remote_peerlist, [](const nodetool::peerlist_entry& pe) {
|
||||
return !nodetool::is_forbidden_ipv4_mapped_ipv6_address(pe.adr);
|
||||
}));
|
||||
|
||||
std::vector<nodetool::peerlist_entry> gray;
|
||||
std::vector<nodetool::peerlist_entry> white;
|
||||
peerlist.get_peerlist(gray, white);
|
||||
const auto contains_address = [](const std::vector<nodetool::peerlist_entry>& peers, const epee::net_utils::network_address& address) {
|
||||
return std::any_of(peers.begin(), peers.end(), [&address](const nodetool::peerlist_entry& peer) {
|
||||
return peer.adr == address;
|
||||
});
|
||||
};
|
||||
EXPECT_TRUE(contains_address(gray, ipv4));
|
||||
EXPECT_FALSE(contains_address(gray, mapped));
|
||||
EXPECT_FALSE(contains_address(white, mapped));
|
||||
}
|
||||
|
||||
TEST(ban, add)
|
||||
{
|
||||
test_core pr_core;
|
||||
|
||||
Reference in New Issue
Block a user