Merge bitcoin/bitcoin#25096: [net] Minor improvements to addr caching

292828cd7744ec7eadede4ad54aa2117087c5435 [test] Test addr cache for multiple onion binds (dergoegge)
3382905befd23364989d941038bf7b1530fea0dc [net] Seed addr cache randomizer with port from binding address (dergoegge)
f10e80b6e4fbc151abbf1c20fbdcc3581d3688f0 [net] Use ConnectedThroughNetwork() instead of GetNetwork() to seed addr cache randomizer (dergoegge)

Pull request description:

  The addr cache id randomizer is currently supposed to be seeded with the network of the inbound connection and the local socket (only the address is used not the port):  a8098f2cef/src/net.cpp (L2800-L2804)

  For inbound onion connections `CNode::addr.GetNetwork()` returns `NET_UNROUTABLE` and `CNode::addrBind` is set to `127.0.0.1:<onion bind port>`. This results in the same addr cache for all inbound connections on 127.0.0.1 binds.

  To avoid the same addr cache across all onion and other 127.0.0.1 binds, we should seed the addr cache randomizer with the correct network for inbound onion connections (using `CNode::ConnectedThroughNetwork()`) as well as the port of `CNode::addrBind`.

ACKs for top commit:
  sipa:
    utACK 292828cd7744ec7eadede4ad54aa2117087c5435
  mzumsande:
    Code Review ACK 292828cd7744ec7eadede4ad54aa2117087c5435
  naumenkogs:
    utACK 292828cd7744ec7eadede4ad54aa2117087c5435

Tree-SHA512: d0be13bab6bc121c2926d4b168687f6c2ed4ce0c9dd19be71eb4886adeba8afc3daacdc4e232a0ba3b03a89d69b618abc5595b69abd1ad0c476d825bc6ea1f9f
This commit is contained in:
fanquake 2022-06-08 11:19:28 +01:00 committed by pasta
parent e556af3ef8
commit 7c723d88c6
No known key found for this signature in database
GPG Key ID: 52527BEDABE87984
2 changed files with 57 additions and 14 deletions

View File

@ -3469,8 +3469,11 @@ std::vector<CAddress> CConnman::GetAddresses(CNode& requestor, size_t max_addres
{
auto local_socket_bytes = requestor.addrBind.GetAddrBytes();
uint64_t cache_id = GetDeterministicRandomizer(RANDOMIZER_ID_ADDRCACHE)
.Write(requestor.addr.GetNetwork())
.Write(requestor.ConnectedThroughNetwork())
.Write(local_socket_bytes.data(), local_socket_bytes.size())
// For outbound connections, the port of the bound address is randomly
// assigned by the OS and would therefore not be useful for seeding.
.Write(requestor.IsInboundConn() ? requestor.addrBind.GetPort() : 0)
.Finalize();
const auto current_time = GetTime<std::chrono::microseconds>();
auto r = m_addr_response_caches.emplace(cache_id, CachedAddrResponse{});

View File

@ -12,6 +12,8 @@ from test_framework.mininode import (
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
PORT_MIN,
PORT_RANGE,
)
# As defined in net_processing.
@ -40,6 +42,13 @@ class AddrReceiver(P2PInterface):
class AddrTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
# Start onion ports after p2p and rpc ports.
port = PORT_MIN + 2 * PORT_RANGE
self.onion_port1 = port
self.onion_port2 = port + 1
self.extra_args = [
[f"-bind=127.0.0.1:{self.onion_port1}=onion", f"-bind=127.0.0.1:{self.onion_port2}=onion"],
]
def run_test(self):
self.log.info('Fill peer AddrMan with a lot of records')
@ -53,35 +62,66 @@ class AddrTest(BitcoinTestFramework):
# only a fraction of all known addresses can be cached and returned.
assert(len(self.nodes[0].getnodeaddresses(0)) > int(MAX_ADDR_TO_SEND / (MAX_PCT_ADDR_TO_SEND / 100)))
responses = []
last_response_on_local_bind = None
last_response_on_onion_bind1 = None
last_response_on_onion_bind2 = None
self.log.info('Send many addr requests within short time to receive same response')
N = 5
cur_mock_time = self.mocktime
for i in range(N):
addr_receiver = self.nodes[0].add_p2p_connection(AddrReceiver())
addr_receiver.send_and_ping(msg_getaddr())
addr_receiver_local = self.nodes[0].add_p2p_connection(AddrReceiver())
addr_receiver_local.send_and_ping(msg_getaddr())
addr_receiver_onion1 = self.nodes[0].add_p2p_connection(AddrReceiver(), dstport=self.onion_port1)
addr_receiver_onion1.send_and_ping(msg_getaddr())
addr_receiver_onion2 = self.nodes[0].add_p2p_connection(AddrReceiver(), dstport=self.onion_port2)
addr_receiver_onion2.send_and_ping(msg_getaddr())
# Trigger response
cur_mock_time += 5 * 60
self.nodes[0].setmocktime(cur_mock_time)
addr_receiver.wait_until(addr_receiver.addr_received)
responses.append(addr_receiver.get_received_addrs())
for response in responses[1:]:
assert_equal(response, responses[0])
assert(len(response) == MAX_ADDR_TO_SEND)
addr_receiver_local.wait_until(addr_receiver_local.addr_received)
addr_receiver_onion1.wait_until(addr_receiver_onion1.addr_received)
addr_receiver_onion2.wait_until(addr_receiver_onion2.addr_received)
if i > 0:
# Responses from different binds should be unique
assert(last_response_on_local_bind != addr_receiver_onion1.get_received_addrs())
assert(last_response_on_local_bind != addr_receiver_onion2.get_received_addrs())
assert(last_response_on_onion_bind1 != addr_receiver_onion2.get_received_addrs())
# Responses on from the same bind should be the same
assert_equal(last_response_on_local_bind, addr_receiver_local.get_received_addrs())
assert_equal(last_response_on_onion_bind1, addr_receiver_onion1.get_received_addrs())
assert_equal(last_response_on_onion_bind2, addr_receiver_onion2.get_received_addrs())
last_response_on_local_bind = addr_receiver_local.get_received_addrs()
last_response_on_onion_bind1 = addr_receiver_onion1.get_received_addrs()
last_response_on_onion_bind2 = addr_receiver_onion2.get_received_addrs()
for response in [last_response_on_local_bind, last_response_on_onion_bind1, last_response_on_onion_bind2]:
assert_equal(len(response), MAX_ADDR_TO_SEND)
cur_mock_time += 3 * 24 * 60 * 60
self.nodes[0].setmocktime(cur_mock_time)
self.log.info('After time passed, see a new response to addr request')
last_addr_receiver = self.nodes[0].add_p2p_connection(AddrReceiver())
last_addr_receiver.send_and_ping(msg_getaddr())
addr_receiver_local = self.nodes[0].add_p2p_connection(AddrReceiver())
addr_receiver_local.send_and_ping(msg_getaddr())
addr_receiver_onion1 = self.nodes[0].add_p2p_connection(AddrReceiver(), dstport=self.onion_port1)
addr_receiver_onion1.send_and_ping(msg_getaddr())
addr_receiver_onion2 = self.nodes[0].add_p2p_connection(AddrReceiver(), dstport=self.onion_port2)
addr_receiver_onion2.send_and_ping(msg_getaddr())
# Trigger response
cur_mock_time += 5 * 60
self.nodes[0].setmocktime(cur_mock_time)
last_addr_receiver.wait_until(last_addr_receiver.addr_received)
# new response is different
assert(set(responses[0]) != set(last_addr_receiver.get_received_addrs()))
addr_receiver_local.wait_until(addr_receiver_local.addr_received)
addr_receiver_onion1.wait_until(addr_receiver_onion1.addr_received)
addr_receiver_onion2.wait_until(addr_receiver_onion2.addr_received)
# new response is different
assert(set(last_response_on_local_bind) != set(addr_receiver_local.get_received_addrs()))
assert(set(last_response_on_onion_bind1) != set(addr_receiver_onion1.get_received_addrs()))
assert(set(last_response_on_onion_bind2) != set(addr_receiver_onion2.get_received_addrs()))
if __name__ == '__main__':
AddrTest().main()