@@ -1414,7 +1414,7 @@ namespace nodetool
1414
1414
}
1415
1415
1416
1416
1417
- MDEBUG (" Connecting to " << na.str () << " (peer_type=" << peer_type << " , last_seen: "
1417
+ MDEBUG (" Connecting to " << na.str () << " (peer_type=" << peer_type << " , last_seen: "
1418
1418
<< (last_seen_stamp ? epee::misc_utils::get_time_interval_string (time (NULL ) - last_seen_stamp):" never" )
1419
1419
<< " )..." );
1420
1420
@@ -1571,33 +1571,78 @@ namespace nodetool
1571
1571
return false ;
1572
1572
}
1573
1573
// -----------------------------------------------------------------------------------
1574
+ // Find a single candidate from the given peer list in the given zone and connect to it if possible
1574
1575
template <class t_payload_net_handler >
1575
1576
bool node_server<t_payload_net_handler>::make_new_connection_from_peerlist(network_zone& zone, bool use_white_list)
1576
1577
{
1577
- size_t max_random_index = 0 ;
1578
1578
1579
- std::set<size_t > tried_peers;
1579
+ // Local helper method to get the host string, i.e. the pure IP address without port
1580
+ const auto get_host_string = [](const epee::net_utils::network_address &address) {
1581
+ if (address.get_type_id () == epee::net_utils::ipv6_network_address::get_type_id ())
1582
+ {
1583
+ const boost::asio::ip::address_v6 actual_ip = address.as <const epee::net_utils::ipv6_network_address>().ip ();
1584
+ if (actual_ip.is_v4_mapped ())
1585
+ {
1586
+ boost::asio::ip::address_v4 v4ip = make_address_v4_from_v6 (actual_ip);
1587
+ uint32_t actual_ipv4;
1588
+ memcpy (&actual_ipv4, v4ip.to_bytes ().data (), sizeof (actual_ipv4));
1589
+ return epee::net_utils::ipv4_network_address (actual_ipv4, 0 ).host_str ();
1590
+ }
1591
+ }
1592
+ return address.host_str ();
1593
+ };
1594
+
1595
+ // Get the current list of known peers of the desired kind, ordered by 'last_seen'.
1596
+ // Deduplicate ports right away, i.e. if we know several peers on the same host address but
1597
+ // with different ports, take only one of them, to avoid giving such peers undue weight
1598
+ // and make it impossible to game peer selection by advertising on a large number of ports.
1599
+ // Making this list also insulates us from any changes that may happen to the original list
1600
+ // while we are working here, and allows an easy retry in the inner try loop.
1601
+ std::vector<peerlist_entry> peers;
1602
+ std::unordered_set<std::string> hosts;
1603
+ size_t total_peers_size = 0 ;
1604
+ zone.m_peerlist .foreach (use_white_list, [&peers, &hosts, &total_peers_size, &get_host_string](const peerlist_entry &peer)
1605
+ {
1606
+ ++total_peers_size;
1607
+ const std::string host_string = get_host_string (peer.adr );
1608
+ const auto it = hosts.find (host_string);
1609
+ if (it == hosts.end ())
1610
+ {
1611
+ peers.push_back (peer);
1612
+ hosts.insert (host_string);
1613
+ }
1614
+ // else ignore this additional peer on the same IP number
1580
1615
1581
- size_t try_count = 0 ;
1616
+ return true ;
1617
+ });
1618
+
1619
+ const size_t peers_size = peers.size ();
1620
+ MDEBUG (" Looking at " << peers_size << " port-deduplicated peers out of " << total_peers_size
1621
+ << " , i.e. dropping " << (total_peers_size - peers_size));
1622
+
1623
+ std::set<uint64_t > tried_peers; // all peers ever tried
1624
+
1625
+ // Outer try loop, with up to 3 attempts to actually connect to a suitable randomly choosen candidate
1582
1626
size_t rand_count = 0 ;
1583
- while ( rand_count < (max_random_index+ 1 )* 3 && try_count < 10 && !zone.m_net_server .is_stop_signal_sent ())
1627
+ while (( rand_count < 3 ) && !zone.m_net_server .is_stop_signal_sent ())
1584
1628
{
1585
1629
++rand_count;
1586
- size_t random_index;
1630
+
1587
1631
const uint32_t next_needed_pruning_stripe = m_payload_handler.get_next_needed_pruning_stripe ().second ;
1588
1632
1589
- // build a set of all the /16 we're connected to, and prefer a peer that's not in that set
1590
- std::set<uint32_t > classB;
1591
- if (&zone == &m_network_zones.at (epee::net_utils::zone::public_)) // at returns reference, not copy
1633
+ // Build a list of all distinct /24 subnets we are connected to now right now; to catch
1634
+ // any connection changes, re-build the list for every outer try loop pass
1635
+ std::set<uint32_t > connected_subnets;
1636
+ const bool is_public_zone = &zone == &m_network_zones.at (epee::net_utils::zone::public_);
1637
+ if (is_public_zone)
1592
1638
{
1593
1639
zone.m_net_server .get_config_object ().foreach_connection ([&](const p2p_connection_context& cntxt)
1594
1640
{
1595
1641
if (cntxt.m_remote_address .get_type_id () == epee::net_utils::ipv4_network_address::get_type_id ())
1596
1642
{
1597
-
1598
1643
const epee::net_utils::network_address na = cntxt.m_remote_address ;
1599
1644
const uint32_t actual_ip = na.as <const epee::net_utils::ipv4_network_address>().ip ();
1600
- classB .insert (actual_ip & 0x0000ffff );
1645
+ connected_subnets .insert (actual_ip & 0x00ffffff );
1601
1646
}
1602
1647
else if (cntxt.m_remote_address .get_type_id () == epee::net_utils::ipv6_network_address::get_type_id ())
1603
1648
{
@@ -1608,100 +1653,128 @@ namespace nodetool
1608
1653
boost::asio::ip::address_v4 v4ip = make_address_v4_from_v6 (actual_ip);
1609
1654
uint32_t actual_ipv4;
1610
1655
memcpy (&actual_ipv4, v4ip.to_bytes ().data (), sizeof (actual_ipv4));
1611
- classB .insert (actual_ipv4 & ntohl (0xffff0000 ));
1656
+ connected_subnets .insert (actual_ipv4 & ntohl (0xffffff00 ));
1612
1657
}
1613
1658
}
1614
1659
return true ;
1615
1660
});
1616
1661
}
1617
1662
1618
- auto get_host_string = [](const epee::net_utils::network_address &address) {
1619
- if (address.get_type_id () == epee::net_utils::ipv6_network_address::get_type_id ())
1620
- {
1621
- boost::asio::ip::address_v6 actual_ip = address.as <const epee::net_utils::ipv6_network_address>().ip ();
1622
- if (actual_ip.is_v4_mapped ())
1623
- {
1624
- boost::asio::ip::address_v4 v4ip = make_address_v4_from_v6 (actual_ip);
1625
- uint32_t actual_ipv4;
1626
- memcpy (&actual_ipv4, v4ip.to_bytes ().data (), sizeof (actual_ipv4));
1627
- return epee::net_utils::ipv4_network_address (actual_ipv4, 0 ).host_str ();
1628
- }
1629
- }
1630
- return address.host_str ();
1631
- };
1632
- std::unordered_set<std::string> hosts_added;
1633
- std::deque<size_t > filtered;
1634
- const size_t limit = use_white_list ? 20 : std::numeric_limits<size_t >::max ();
1663
+ std::vector<peerlist_entry> *candidate_peers;
1664
+ std::vector<peerlist_entry> subnet_peers;
1665
+ std::vector<peerlist_entry> filtered;
1666
+
1667
+ // Inner try loop: Find candidates first with subnet deduplication, if none found again without.
1668
+ // Finding none happens if all candidates are from subnets we are already connected to and/or
1669
+ // they don't offer the needed stripe when pruning. Only actually loop and deduplicate if we are
1670
+ // in the public zone because private zones don't have subnets.
1635
1671
for (int step = 0 ; step < 2 ; ++step)
1636
1672
{
1637
- bool skip_duplicate_class_B = step == 0 ;
1638
- size_t idx = 0 , skipped = 0 ;
1639
- zone.m_peerlist .foreach (use_white_list, [&classB, &filtered, &idx, &skipped, skip_duplicate_class_B, limit, next_needed_pruning_stripe, &hosts_added, &get_host_string](const peerlist_entry &pe){
1640
- if (filtered.size () >= limit)
1641
- return false ;
1642
- bool skip = false ;
1643
- if (skip_duplicate_class_B && pe.adr .get_type_id () == epee::net_utils::ipv4_network_address::get_type_id ())
1644
- {
1645
- const epee::net_utils::network_address na = pe.adr ;
1646
- uint32_t actual_ip = na.as <const epee::net_utils::ipv4_network_address>().ip ();
1647
- skip = classB.find (actual_ip & 0x0000ffff ) != classB.end ();
1648
- }
1649
- else if (skip_duplicate_class_B && pe.adr .get_type_id () == epee::net_utils::ipv6_network_address::get_type_id ())
1673
+ if ((step == 1 ) && !is_public_zone)
1674
+ break ;
1675
+
1676
+ candidate_peers = &peers;
1677
+ if ((step == 0 ) && is_public_zone)
1678
+ {
1679
+ // Deduplicate subnets using 3 steps
1680
+
1681
+ // Step 1: Prepare to access the peers in a random order
1682
+ std::vector<size_t > shuffled_indexes (peers.size ());
1683
+ std::iota (shuffled_indexes.begin (), shuffled_indexes.end (), 0 );
1684
+ std::shuffle (shuffled_indexes.begin (), shuffled_indexes.end (), crypto::random_device{});
1685
+
1686
+ // Step 2: Deduplicate by only taking 1 candidate from each /24 subnet that occurs, the FIRST
1687
+ // candidate seen from each subnet within the now random order
1688
+ std::set<uint32_t > subnets = connected_subnets;
1689
+ for (size_t index : shuffled_indexes)
1650
1690
{
1651
- const epee::net_utils::network_address na = pe. adr ;
1652
- const boost::asio::ip::address_v6 &actual_ip = na. as < const epee::net_utils::ipv6_network_address>(). ip () ;
1653
- if (actual_ip. is_v4_mapped ())
1691
+ const peerlist_entry &peer = peers[index] ;
1692
+ bool take = true ;
1693
+ if (peer. adr . get_type_id () == epee::net_utils::ipv4_network_address::get_type_id ())
1654
1694
{
1655
- boost::asio::ip::address_v4 v4ip = make_address_v4_from_v6 (actual_ip);
1656
- uint32_t actual_ipv4;
1657
- memcpy (&actual_ipv4, v4ip.to_bytes ().data (), sizeof (actual_ipv4));
1658
- skip = classB.find (actual_ipv4 & ntohl (0xffff0000 )) != classB.end ();
1695
+ const epee::net_utils::network_address na = peer.adr ;
1696
+ const uint32_t actual_ip = na.as <const epee::net_utils::ipv4_network_address>().ip ();
1697
+ const uint32_t subnet = actual_ip & 0x00ffffff ;
1698
+ take = subnets.find (subnet) == subnets.end ();
1699
+ if (take)
1700
+ // This subnet is now "occupied", don't take any more candidates from this one
1701
+ subnets.insert (subnet);
1659
1702
}
1703
+ else if (peer.adr .get_type_id () == epee::net_utils::ipv6_network_address::get_type_id ())
1704
+ {
1705
+ const epee::net_utils::network_address na = peer.adr ;
1706
+ const boost::asio::ip::address_v6 &actual_ip = na.as <const epee::net_utils::ipv6_network_address>().ip ();
1707
+ if (actual_ip.is_v4_mapped ())
1708
+ {
1709
+ boost::asio::ip::address_v4 v4ip = make_address_v4_from_v6 (actual_ip);
1710
+ uint32_t actual_ipv4;
1711
+ memcpy (&actual_ipv4, v4ip.to_bytes ().data (), sizeof (actual_ipv4));
1712
+ uint32_t subnet = actual_ipv4 & ntohl (0xffffff00 );
1713
+ take = subnets.find (subnet) == subnets.end ();
1714
+ if (take)
1715
+ subnets.insert (subnet);
1716
+ }
1717
+ // else 'take' stays true, we will take an IPv6 address that is not V4 mapped
1718
+ }
1719
+ if (take)
1720
+ subnet_peers.push_back (peer);
1660
1721
}
1661
1722
1662
- // consider each host once, to avoid giving undue inflence to hosts running several nodes
1663
- if (!skip )
1723
+ // Step 3: Put back into order according to 'last_seen', i.e. most recently seen first
1724
+ std::sort (subnet_peers. begin (), subnet_peers. end (), []( const peerlist_entry &a, const peerlist_entry &b )
1664
1725
{
1665
- const auto i = hosts_added.find (get_host_string (pe.adr ));
1666
- if (i != hosts_added.end ())
1667
- skip = true ;
1668
- }
1726
+ return a.last_seen > b.last_seen ;
1727
+ });
1669
1728
1670
- if (skip)
1671
- ++skipped;
1672
- else if (next_needed_pruning_stripe == 0 || pe.pruning_seed == 0 )
1673
- filtered.push_back (idx);
1674
- else if (next_needed_pruning_stripe == tools::get_pruning_stripe (pe.pruning_seed ))
1675
- filtered.push_front (idx);
1676
- ++idx;
1677
- hosts_added.insert (get_host_string (pe.adr ));
1678
- return true ;
1679
- });
1680
- if (skipped == 0 || !filtered.empty ())
1729
+ const size_t subnet_peers_size = subnet_peers.size ();
1730
+ MDEBUG (" Looking at " << subnet_peers_size << " subnet-deduplicated peers out of " << peers_size
1731
+ << " , i.e. dropping " << (peers_size - subnet_peers_size));
1732
+
1733
+ candidate_peers = &subnet_peers;
1734
+ } // deduplicate
1735
+ // else, for step 1 / second pass of inner try loop, take all peers from all subnets
1736
+
1737
+ // Take as many candidates as we need and care about stripes if pruning
1738
+ const size_t limit = use_white_list ? 20 : std::numeric_limits<size_t >::max ();
1739
+ for (auto &peer : *candidate_peers) {
1740
+ if (filtered.size () >= limit)
1741
+ break ;
1742
+
1743
+ if (next_needed_pruning_stripe == 0 || peer.pruning_seed == 0 )
1744
+ filtered.push_back (peer);
1745
+ else if (next_needed_pruning_stripe == tools::get_pruning_stripe (peer.pruning_seed ))
1746
+ filtered.insert (filtered.begin (), peer);
1747
+ // else wrong stripe, skip
1748
+ }
1749
+
1750
+ if (!filtered.empty ())
1681
1751
break ;
1682
- if (skipped)
1683
- MDEBUG (" Skipping " << skipped << " possible peers as they share a class B with existing peers" );
1684
- }
1752
+ } // inner try loop
1753
+
1685
1754
if (filtered.empty ())
1686
1755
{
1687
1756
MINFO (" No available peer in " << (use_white_list ? " white" : " gray" ) << " list filtered by " << next_needed_pruning_stripe);
1688
1757
return false ;
1689
1758
}
1759
+
1760
+ size_t random_index;
1690
1761
if (use_white_list)
1691
1762
{
1692
- // if using the white list, we first pick in the set of peers we've already been using earlier
1693
- random_index = get_random_index_with_fixed_probability (std::min<uint64_t >(filtered.size () - 1 , 20 ));
1763
+ // If using the white list, we first pick in the set of peers we've already been using earlier;
1764
+ // that "fixed probability" heavily favors the peers most recently seen in the candidate list
1765
+ random_index = get_random_index_with_fixed_probability (filtered.size () - 1 );
1766
+
1694
1767
CRITICAL_REGION_LOCAL (m_used_stripe_peers_mutex);
1695
1768
if (next_needed_pruning_stripe > 0 && next_needed_pruning_stripe <= (1ul << CRYPTONOTE_PRUNING_LOG_STRIPES) && !m_used_stripe_peers[next_needed_pruning_stripe-1 ].empty ())
1696
1769
{
1697
1770
const epee::net_utils::network_address na = m_used_stripe_peers[next_needed_pruning_stripe-1 ].front ();
1698
1771
m_used_stripe_peers[next_needed_pruning_stripe-1 ].pop_front ();
1699
1772
for (size_t i = 0 ; i < filtered.size (); ++i)
1700
1773
{
1701
- peerlist_entry pe ;
1702
- if (zone. m_peerlist . get_white_peer_by_index (pe, filtered[i]) && pe .adr == na)
1774
+ const peerlist_entry &peer = filtered[i] ;
1775
+ if (peer .adr == na)
1703
1776
{
1704
- MDEBUG (" Reusing stripe " << next_needed_pruning_stripe << " peer " << pe .adr .str ());
1777
+ MDEBUG (" Reusing stripe " << next_needed_pruning_stripe << " peer " << peer .adr .str ());
1705
1778
random_index = i;
1706
1779
break ;
1707
1780
}
@@ -1710,52 +1783,54 @@ namespace nodetool
1710
1783
}
1711
1784
else
1712
1785
random_index = crypto::rand_idx (filtered.size ());
1713
-
1714
1786
CHECK_AND_ASSERT_MES (random_index < filtered.size (), false , " random_index < filtered.size() failed!!" );
1715
- random_index = filtered[random_index];
1716
- CHECK_AND_ASSERT_MES (random_index < (use_white_list ? zone.m_peerlist .get_white_peers_count () : zone.m_peerlist .get_gray_peers_count ()),
1717
- false , " random_index < peers size failed!!" );
1718
1787
1719
- if (tried_peers.count (random_index))
1720
- continue ;
1721
-
1722
- tried_peers.insert (random_index);
1723
- peerlist_entry pe = AUTO_VAL_INIT (pe);
1724
- bool r = use_white_list ? zone.m_peerlist .get_white_peer_by_index (pe, random_index):zone.m_peerlist .get_gray_peer_by_index (pe, random_index);
1725
- CHECK_AND_ASSERT_MES (r, false , " Failed to get random peer from peerlist(white:" << use_white_list << " )" );
1788
+ // We have our final candidate for this pass of the outer try loop
1789
+ const peerlist_entry &candidate = filtered[random_index];
1726
1790
1727
- ++try_count;
1791
+ if (tried_peers.count (candidate.id ))
1792
+ // Already tried, don't try that one again
1793
+ continue ;
1794
+ tried_peers.insert (candidate.id );
1728
1795
1729
1796
_note (" Considering connecting (out) to " << (use_white_list ? " white" : " gray" ) << " list peer: " <<
1730
- peerid_to_string (pe .id ) << " " << pe .adr .str () << " , pruning seed " << epee::string_tools::to_string_hex (pe .pruning_seed ) <<
1731
- " (stripe " << next_needed_pruning_stripe << " needed)" );
1797
+ peerid_to_string (candidate .id ) << " " << candidate .adr .str () << " , pruning seed " << epee::string_tools::to_string_hex (candidate .pruning_seed ) <<
1798
+ " (stripe " << next_needed_pruning_stripe << " needed), in loop pass " << rand_count );
1732
1799
1733
- if (zone.m_our_address == pe.adr )
1800
+ if (zone.m_our_address == candidate.adr )
1801
+ // It's ourselves, obviously don't take that
1734
1802
continue ;
1735
1803
1736
- if (is_peer_used (pe )) {
1804
+ if (is_peer_used (candidate )) {
1737
1805
_note (" Peer is used" );
1738
1806
continue ;
1739
1807
}
1740
1808
1741
- if (!is_remote_host_allowed (pe.adr ))
1809
+ if (!is_remote_host_allowed (candidate.adr )) {
1810
+ _note (" Not allowed" );
1742
1811
continue ;
1812
+ }
1743
1813
1744
- if (is_addr_recently_failed (pe.adr ))
1814
+ if (is_addr_recently_failed (candidate.adr )) {
1815
+ _note (" Recently failed" );
1745
1816
continue ;
1817
+ }
1746
1818
1747
- MDEBUG (" Selected peer: " << peerid_to_string (pe .id ) << " " << pe .adr .str ()
1748
- << " , pruning seed " << epee::string_tools::to_string_hex (pe .pruning_seed ) << " "
1749
- << " [peer_list=" << (use_white_list ? white : gray)
1750
- << " ] last_seen: " << (pe .last_seen ? epee::misc_utils::get_time_interval_string (time (NULL ) - pe .last_seen ) : " never" ));
1819
+ MDEBUG (" Selected peer: " << peerid_to_string (candidate .id ) << " " << candidate .adr .str ()
1820
+ << " , pruning seed " << epee::string_tools::to_string_hex (candidate .pruning_seed ) << " "
1821
+ << " [peer_list=" << (use_white_list ? white : gray)
1822
+ << " ] last_seen: " << (candidate .last_seen ? epee::misc_utils::get_time_interval_string (time (NULL ) - candidate .last_seen ) : " never" ));
1751
1823
1752
- if (!try_to_connect_and_handshake_with_new_peer (pe.adr , false , pe.last_seen , use_white_list ? white : gray)) {
1753
- _note (" Handshake failed" );
1824
+ const time_t begin_connect = time (NULL );
1825
+ if (!try_to_connect_and_handshake_with_new_peer (candidate.adr , false , candidate.last_seen , use_white_list ? white : gray)) {
1826
+ time_t fail_connect = time (NULL );
1827
+ _note (" Handshake failed after " << epee::misc_utils::get_time_interval_string (fail_connect - begin_connect));
1754
1828
continue ;
1755
1829
}
1756
1830
1757
1831
return true ;
1758
- }
1832
+ } // outer try loop
1833
+
1759
1834
return false ;
1760
1835
}
1761
1836
// -----------------------------------------------------------------------------------
@@ -1974,6 +2049,14 @@ namespace nodetool
1974
2049
++count;
1975
2050
return true ;
1976
2051
});
2052
+
2053
+ // Refresh the counter in the zone. The thread that the 'run' method sets up for the
2054
+ // job to update the counters runs only once every second. If we only rely on that,
2055
+ // 'try_to_connect_and_handshake_with_new_peer' called in 'make_new_connection_from_peerlist'
2056
+ // will often fail right away because it thinks there are still enough connections, with
2057
+ // perfectly good new peer candidates totally wasted, and a bad success rate choosing peers
2058
+ zone.m_current_number_of_out_peers = count;
2059
+
1977
2060
return count;
1978
2061
}
1979
2062
// -----------------------------------------------------------------------------------
0 commit comments