From 5a199af7a68614cf53bbe3782a2a6f5d82bd1e9a Mon Sep 17 00:00:00 2001 From: selsta Date: Fri, 10 Jul 2026 15:40:02 +0200 Subject: [PATCH] p2p: group outbound IPv6 peers by /32 --- src/p2p/net_node.inl | 35 +++++++++++++++++++++++++++++--- tests/unit_tests/node_server.cpp | 21 +++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index b19674cd0..01c19577b 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -126,6 +127,21 @@ namespace nodetool } } + using ipv6_peer_group = std::array; + + inline boost::optional get_ipv6_peer_group(const epee::net_utils::network_address& address) + { + if (address.get_type_id() != epee::net_utils::ipv6_network_address::get_type_id()) + return boost::none; + + const boost::asio::ip::address_v6 ip = address.as().ip(); + if (ip.is_v4_mapped()) + return boost::none; + + const boost::asio::ip::address_v6::bytes_type bytes = ip.to_bytes(); + return ipv6_peer_group{{bytes[0], bytes[1], bytes[2], bytes[3]}}; + } + //----------------------------------------------------------------------------------- template node_server::~node_server() { @@ -1687,9 +1703,10 @@ namespace nodetool const uint32_t next_needed_pruning_stripe = m_payload_handler.get_next_needed_pruning_stripe().second; - // Build a list of all distinct /24 subnets we are connected to now right now; to catch + // Build a list of all distinct IPv4 /24 and IPv6 /32 groups we are connected to right now; to catch // any connection changes, re-build the list for every outer try loop pass std::set connected_subnets; + std::set connected_ipv6_groups; const uint32_t subnet_mask = ntohl(0xffffff00); const bool is_public_zone = &zone == &m_network_zones.at(epee::net_utils::zone::public_); if (is_public_zone) @@ -1713,6 +1730,12 @@ namespace nodetool memcpy(&actual_ipv4, v4ip.to_bytes().data(), sizeof(actual_ipv4)); connected_subnets.insert(actual_ipv4 & subnet_mask); } + else + { + const boost::optional group = get_ipv6_peer_group(na); + if (group) + connected_ipv6_groups.insert(*group); + } } return true; }); @@ -1742,8 +1765,9 @@ namespace nodetool std::shuffle(shuffled_indexes.begin(), shuffled_indexes.end(), crypto::random_device{}); // Step 2: Deduplicate by only taking 1 candidate from each /24 subnet that occurs, the FIRST - // candidate seen from each subnet within the now random order + // candidate seen from each subnet within the now random order. Native IPv6 peers use /32 groups. std::set subnets = connected_subnets; + std::set ipv6_groups = connected_ipv6_groups; for (size_t index : shuffled_indexes) { const peerlist_entry &peer = peers.at(index); @@ -1772,7 +1796,12 @@ namespace nodetool if (take) subnets.insert(subnet); } - // else 'take' stays true, we will take an IPv6 address that is not V4 mapped + else + { + const boost::optional group = get_ipv6_peer_group(na); + if (group) + take = ipv6_groups.insert(*group).second; + } } if (take) subnet_peers.push_back(peer); diff --git a/tests/unit_tests/node_server.cpp b/tests/unit_tests/node_server.cpp index e2e00d21d..2733b9c31 100644 --- a/tests/unit_tests/node_server.cpp +++ b/tests/unit_tests/node_server.cpp @@ -119,6 +119,27 @@ public: typedef nodetool::node_server> Server; +TEST(node_server, ipv6_peer_group) +{ + const auto make_ipv6 = [](const char* ip) { + return epee::net_utils::network_address{ + epee::net_utils::ipv6_network_address{boost::asio::ip::make_address_v6(ip), 18080} + }; + }; + + const boost::optional first = nodetool::get_ipv6_peer_group(make_ipv6("2001:db8:1::1")); + const boost::optional same = nodetool::get_ipv6_peer_group(make_ipv6("2001:db8:ffff::1")); + const boost::optional different = nodetool::get_ipv6_peer_group(make_ipv6("2001:db9::1")); + + ASSERT_TRUE(first); + ASSERT_TRUE(same); + ASSERT_TRUE(different); + EXPECT_EQ(*first, *same); + EXPECT_NE(*first, *different); + EXPECT_FALSE(nodetool::get_ipv6_peer_group(make_ipv6("::ffff:192.0.2.1"))); + EXPECT_FALSE(nodetool::get_ipv6_peer_group(MAKE_IPV4_ADDRESS(192, 0, 2, 1))); +} + static bool is_blocked(Server &server, const epee::net_utils::network_address &address, time_t *t = NULL) { std::map hosts = server.get_blocked_hosts();