From f7b8094d611531c6b41a94715dbc01f56257ccd2 Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Wed, 26 Jan 2022 09:01:42 +0100 Subject: p2p: extend inbound eviction protection by network to CJDNS peers This commit extends our inbound eviction protection to CJDNS peers to favorise the diversity of peer connections, as peers connected through the CJDNS network are otherwise disadvantaged by our eviction criteria for their higher latency (higher min ping times) relative to IPv4 and IPv6 peers. The `networks` array is order-dependent in the case of a tie in candidate counts between networks; earlier array members receive priority in the case of a tie. Therefore, we place CJDNS candidates before I2P, localhost, and onion ones in terms of opportunity to recover unused remaining protected slots from the previous iteration, estimating that most nodes allowing several inbound privacy networks will have more onion, localhost or I2P peers than CJDNS ones, as CJDNS support is only being added in the upcoming v23.0 release. --- src/net.cpp | 12 ++++++------ src/net.h | 2 ++ src/test/net_peer_eviction_tests.cpp | 14 +++++++------- 3 files changed, 15 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/net.cpp b/src/net.cpp index 0260e14da7..273163f25a 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -918,17 +918,17 @@ void ProtectEvictionCandidatesByRatio(std::vector& evicti // Protect the half of the remaining nodes which have been connected the longest. // This replicates the non-eviction implicit behavior, and precludes attacks that start later. // To favorise the diversity of our peer connections, reserve up to half of these protected - // spots for Tor/onion, localhost and I2P peers, even if they're not longest uptime overall. - // This helps protect these higher-latency peers that tend to be otherwise + // spots for Tor/onion, localhost, I2P, and CJDNS peers, even if they're not longest uptime + // overall. This helps protect these higher-latency peers that tend to be otherwise // disadvantaged under our eviction criteria. const size_t initial_size = eviction_candidates.size(); const size_t total_protect_size{initial_size / 2}; - // Disadvantaged networks to protect: I2P, localhost, Tor/onion. In case of equal counts, earlier - // array members have first opportunity to recover unused slots from the previous iteration. + // Disadvantaged networks to protect. In the case of equal counts, earlier array members + // have the first opportunity to recover unused slots from the previous iteration. struct Net { bool is_local; Network id; size_t count; }; - std::array networks{ - {{false, NET_I2P, 0}, {/* localhost */ true, NET_MAX, 0}, {false, NET_ONION, 0}}}; + std::array networks{ + {{false, NET_CJDNS, 0}, {false, NET_I2P, 0}, {/*localhost=*/true, NET_MAX, 0}, {false, NET_ONION, 0}}}; // Count and store the number of eviction candidates per network. for (Net& n : networks) { diff --git a/src/net.h b/src/net.h index 4301733525..74a18a6803 100644 --- a/src/net.h +++ b/src/net.h @@ -1307,6 +1307,8 @@ struct NodeEvictionCandidate * * - I2P peers * + * - CJDNS peers + * * This helps protect these privacy network peers, which tend to be otherwise * disadvantaged under our eviction criteria for their higher min ping times * relative to IPv4/IPv6 peers, and favorise the diversity of peer connections. diff --git a/src/test/net_peer_eviction_tests.cpp b/src/test/net_peer_eviction_tests.cpp index 78ad24a408..7abea0506d 100644 --- a/src/test/net_peer_eviction_tests.cpp +++ b/src/test/net_peer_eviction_tests.cpp @@ -90,7 +90,7 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) // Test protection of onion, localhost, and I2P peers... // Expect 1/4 onion peers to be protected from eviction, - // if no localhost or I2P peers. + // if no localhost, I2P, or CJDNS peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.m_is_local = false; @@ -101,7 +101,7 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) random_context)); // Expect 1/4 onion peers and 1/4 of the other peers to be protected, - // sorted by longest uptime (lowest m_connected), if no localhost or I2P peers. + // sorted by longest uptime (lowest m_connected), if no localhost, I2P or CJDNS peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.m_connected = std::chrono::seconds{c.id}; @@ -113,7 +113,7 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) random_context)); // Expect 1/4 localhost peers to be protected from eviction, - // if no onion or I2P peers. + // if no onion, I2P, or CJDNS peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.m_is_local = (c.id == 1 || c.id == 9 || c.id == 11); @@ -124,7 +124,7 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) random_context)); // Expect 1/4 localhost peers and 1/4 of the other peers to be protected, - // sorted by longest uptime (lowest m_connected), if no onion or I2P peers. + // sorted by longest uptime (lowest m_connected), if no onion, I2P, or CJDNS peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.m_connected = std::chrono::seconds{c.id}; @@ -136,7 +136,7 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) random_context)); // Expect 1/4 I2P peers to be protected from eviction, - // if no onion or localhost peers. + // if no onion, localhost, or CJDNS peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.m_is_local = false; @@ -146,8 +146,8 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) /*unprotected_peer_ids=*/{}, random_context)); - // Expect 1/4 I2P peers and 1/4 of the other peers to be protected, - // sorted by longest uptime (lowest m_connected), if no onion or localhost peers. + // Expect 1/4 I2P peers and 1/4 of the other peers to be protected, sorted + // by longest uptime (lowest m_connected), if no onion, localhost, or CJDNS peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.m_connected = std::chrono::seconds{c.id}; -- cgit v1.2.3 From 0c00c0c981fc0b6cec101e68e8c1aeda1ccf33bb Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Wed, 26 Jan 2022 10:34:30 +0100 Subject: test: fix off-by-one logic in an eviction protection test --- src/test/net_peer_eviction_tests.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/test/net_peer_eviction_tests.cpp b/src/test/net_peer_eviction_tests.cpp index 7abea0506d..14a53f3e70 100644 --- a/src/test/net_peer_eviction_tests.cpp +++ b/src/test/net_peer_eviction_tests.cpp @@ -289,16 +289,16 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) BOOST_CHECK(IsProtected( 4, [](NodeEvictionCandidate& c) { c.m_connected = std::chrono::seconds{c.id}; - c.m_is_local = (c.id == 3); - if (c.id == 4) { + c.m_is_local = (c.id == 2); + if (c.id == 3) { c.m_network = NET_I2P; - } else if (c.id == 2) { + } else if (c.id == 1) { c.m_network = NET_ONION; } else { c.m_network = NET_IPV6; } }, - /*protected_peer_ids=*/{0, 4}, + /*protected_peer_ids=*/{0, 3}, /*unprotected_peer_ids=*/{1, 2}, random_context)); -- cgit v1.2.3 From 0a1bb84770b403ab5cbd9d5474c76f91ce58e8f6 Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Wed, 26 Jan 2022 09:24:50 +0100 Subject: test: add tests for inbound eviction protection of CJDNS peers --- src/test/net_peer_eviction_tests.cpp | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/test/net_peer_eviction_tests.cpp b/src/test/net_peer_eviction_tests.cpp index 14a53f3e70..d064cc7028 100644 --- a/src/test/net_peer_eviction_tests.cpp +++ b/src/test/net_peer_eviction_tests.cpp @@ -158,6 +158,29 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) /*unprotected_peer_ids=*/{3, 5, 6, 7, 8, 11}, random_context)); + // Expect 1/4 CJDNS peers to be protected from eviction, + // if no onion, localhost, or I2P peers. + BOOST_CHECK(IsProtected( + num_peers, [](NodeEvictionCandidate& c) { + c.m_is_local = false; + c.m_network = (c.id == 2 || c.id == 7 || c.id == 10) ? NET_CJDNS : NET_IPV4; + }, + /*protected_peer_ids=*/{2, 7, 10}, + /*unprotected_peer_ids=*/{}, + random_context)); + + // Expect 1/4 CJDNS peers and 1/4 of the other peers to be protected, sorted + // by longest uptime (lowest m_connected), if no onion, localhost, or I2P peers. + BOOST_CHECK(IsProtected( + num_peers, [](NodeEvictionCandidate& c) { + c.m_connected = std::chrono::seconds{c.id}; + c.m_is_local = false; + c.m_network = (c.id == 4 || c.id > 8) ? NET_CJDNS : NET_IPV6; + }, + /*protected_peer_ids=*/{0, 1, 2, 4, 9, 10}, + /*unprotected_peer_ids=*/{3, 5, 6, 7, 8, 11}, + random_context)); + // Tests with 2 networks... // Combined test: expect having 1 localhost and 1 onion peer out of 4 to @@ -416,15 +439,15 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11, 16, 19, 20, 21, 22, 23}, random_context)); - // Combined test: expect having 8 localhost, 4 I2P, and 3 onion peers out of - // 24 to protect 2 of each (6 total), plus 6 others for 12/24 total, sorted - // by longest uptime. + // Combined test: expect having 8 localhost, 4 CJDNS, and 3 onion peers out + // of 24 to protect 2 of each (6 total), plus 6 others for 12/24 total, + // sorted by longest uptime. BOOST_CHECK(IsProtected( 24, [](NodeEvictionCandidate& c) { c.m_connected = std::chrono::seconds{c.id}; c.m_is_local = (c.id > 15); if (c.id > 10 && c.id < 15) { - c.m_network = NET_I2P; + c.m_network = NET_CJDNS; } else if (c.id > 6 && c.id < 10) { c.m_network = NET_ONION; } else { -- cgit v1.2.3 From b7be28cac50046b9f2ddfe63ecafccc80649a36c Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Wed, 26 Jan 2022 10:30:35 +0100 Subject: test: add combined CJDNS/I2P/localhost/onion eviction protection tests --- src/test/net_peer_eviction_tests.cpp | 110 +++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) (limited to 'src') diff --git a/src/test/net_peer_eviction_tests.cpp b/src/test/net_peer_eviction_tests.cpp index d064cc7028..6ec3fb0c6b 100644 --- a/src/test/net_peer_eviction_tests.cpp +++ b/src/test/net_peer_eviction_tests.cpp @@ -457,6 +457,116 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 7, 8, 11, 12, 16, 17}, /*unprotected_peer_ids=*/{6, 9, 10, 13, 14, 15, 18, 19, 20, 21, 22, 23}, random_context)); + + // Tests with 4 networks... + + // Combined test: expect having 1 CJDNS, 1 I2P, 1 localhost and 1 onion peer + // out of 5 to protect 1 CJDNS, 0 I2P, 0 localhost, 0 onion and 1 other peer + // (2 total), sorted by longest uptime; stable sort breaks tie with array + // order of CJDNS first. + BOOST_CHECK(IsProtected( + 5, [](NodeEvictionCandidate& c) { + c.m_connected = std::chrono::seconds{c.id}; + c.m_is_local = (c.id == 3); + if (c.id == 4) { + c.m_network = NET_CJDNS; + } else if (c.id == 1) { + c.m_network = NET_I2P; + } else if (c.id == 2) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV6; + } + }, + /* protected_peer_ids */ {0, 4}, + /* unprotected_peer_ids */ {1, 2, 3}, + random_context)); + + // Combined test: expect having 1 CJDNS, 1 I2P, 1 localhost and 1 onion peer + // out of 7 to protect 1 CJDNS, 0, I2P, 0 localhost, 0 onion and 2 other + // peers (3 total) sorted by longest uptime; stable sort breaks tie with + // array order of CJDNS first. + BOOST_CHECK(IsProtected( + 7, [](NodeEvictionCandidate& c) { + c.m_connected = std::chrono::seconds{c.id}; + c.m_is_local = (c.id == 4); + if (c.id == 6) { + c.m_network = NET_CJDNS; + } else if (c.id == 5) { + c.m_network = NET_I2P; + } else if (c.id == 3) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV4; + } + }, + /*protected_peer_ids=*/{0, 1, 6}, + /*unprotected_peer_ids=*/{2, 3, 4, 5}, + random_context)); + + // Combined test: expect having 1 CJDNS, 1 I2P, 1 localhost and 1 onion peer + // out of 8 to protect 1 CJDNS, 1 I2P, 0 localhost, 0 onion and 2 other + // peers (4 total) sorted by longest uptime; stable sort breaks tie with + // array order of CJDNS first. + BOOST_CHECK(IsProtected( + 8, [](NodeEvictionCandidate& c) { + c.m_connected = std::chrono::seconds{c.id}; + c.m_is_local = (c.id == 3); + if (c.id == 5) { + c.m_network = NET_CJDNS; + } else if (c.id == 6) { + c.m_network = NET_I2P; + } else if (c.id == 3) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV6; + } + }, + /*protected_peer_ids=*/{0, 1, 5, 6}, + /*unprotected_peer_ids=*/{2, 3, 4, 7}, + random_context)); + + // Combined test: expect having 2 CJDNS, 2 I2P, 4 localhost, and 2 onion + // peers out of 16 to protect 1 CJDNS, 1 I2P, 1 localhost, 1 onion (4/16 + // total), plus 4 others for 8 total, sorted by longest uptime. + BOOST_CHECK(IsProtected( + 16, [](NodeEvictionCandidate& c) { + c.m_connected = std::chrono::seconds{c.id}; + c.m_is_local = (c.id > 5); + if (c.id == 11 || c.id == 15) { + c.m_network = NET_CJDNS; + } else if (c.id == 10 || c.id == 14) { + c.m_network = NET_I2P; + } else if (c.id == 8 || c.id == 9) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV4; + } + }, + /*protected_peer_ids=*/{0, 1, 2, 3, 6, 8, 10, 11}, + /*unprotected_peer_ids=*/{4, 5, 7, 9, 12, 13, 14, 15}, + random_context)); + + // Combined test: expect having 6 CJDNS, 1 I2P, 1 localhost, and 4 onion + // peers out of 24 to protect 2 CJDNS, 1 I2P, 1 localhost, and 2 onions (6 + // total), plus 6 others for 12/24 total, sorted by longest uptime. + BOOST_CHECK(IsProtected( + 24, [](NodeEvictionCandidate& c) { + c.m_connected = std::chrono::seconds{c.id}; + c.m_is_local = (c.id == 13); + if (c.id > 17) { + c.m_network = NET_CJDNS; + } else if (c.id == 17) { + c.m_network = NET_I2P; + } else if (c.id == 12 || c.id == 14 || c.id == 15 || c.id == 16) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV6; + } + }, + /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 12, 13, 14, 17, 18, 19}, + /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11, 15, 16, 20, 21, 22, 23}, + random_context)); } // Returns true if any of the node ids in node_ids are selected for eviction. -- cgit v1.2.3