diff --git a/src/net.h b/src/net.h --- a/src/net.h +++ b/src/net.h @@ -348,8 +348,8 @@ * A non-malicious call (from RPC or a peer with addr permission) should * call the function without a parameter to avoid using the cache. */ - std::vector GetAddresses(Network requestor_network, - size_t max_addresses, size_t max_pct); + std::vector GetAddresses(CNode &requestor, size_t max_addresses, + size_t max_pct); // This allows temporarily exceeding m_max_outbound_full_relay, with the // goal of finding a peer that is better than all our current peers. @@ -544,15 +544,19 @@ /** * Addr responses stored in different caches - * per network prevent cross-network node identification. + * per (network, local socket) prevent cross-network node identification. * If a node for example is multi-homed under Tor and IPv6, * a single cache (or no cache at all) would let an attacker * to easily detect that it is the same node by comparing responses. + * Indexing by local socket prevents leakage when a node has multiple + * listening addresses on the same network. + * * The used memory equals to 1000 CAddress records (or around 32 bytes) per * distinct Network (up to 5) we have/had an inbound peer from, - * resulting in at most ~160 KB. + * resulting in at most ~160 KB. Every separate local socket may + * add up to ~160 KB extra. */ - std::map m_addr_response_caches; + std::map m_addr_response_caches; /** * Services this instance offers. diff --git a/src/net.cpp b/src/net.cpp --- a/src/net.cpp +++ b/src/net.cpp @@ -109,6 +109,8 @@ static const uint64_t RANDOMIZER_ID_LOCALHOSTNONCE = 0xd93e69e2bbfa5735ULL; // SHA256("localhostnonce")[8:16] static const uint64_t RANDOMIZER_ID_EXTRAENTROPY = 0x94b05d41679a4ff7ULL; +// SHA256("addrcache")[0:8] +static const uint64_t RANDOMIZER_ID_ADDRCACHE = 0x1cf2e4ddd306dda9ULL; // // Global state variables // @@ -2879,15 +2881,21 @@ return addresses; } -std::vector CConnman::GetAddresses(Network requestor_network, - size_t max_addresses, - size_t max_pct) { +std::vector +CConnman::GetAddresses(CNode &requestor, size_t max_addresses, size_t max_pct) { + SOCKET socket; + WITH_LOCK(requestor.cs_hSocket, socket = requestor.hSocket); + auto local_socket_bytes = GetBindAddress(socket).GetAddrBytes(); + uint64_t cache_id = + GetDeterministicRandomizer(RANDOMIZER_ID_ADDRCACHE) + .Write(requestor.addr.GetNetwork()) + .Write(local_socket_bytes.data(), local_socket_bytes.size()) + .Finalize(); const auto current_time = GetTime(); - if (m_addr_response_caches.find(requestor_network) == - m_addr_response_caches.end() || - m_addr_response_caches[requestor_network].m_update_addr_response < + if (m_addr_response_caches.find(cache_id) == m_addr_response_caches.end() || + m_addr_response_caches[cache_id].m_update_addr_response < current_time) { - m_addr_response_caches[requestor_network].m_addrs_response_cache = + m_addr_response_caches[cache_id].m_addrs_response_cache = GetAddresses(max_addresses, max_pct); // Choosing a proper cache lifetime is a trade-off between the privacy @@ -2918,11 +2926,11 @@ // are older than 30 days, max. 24 hours of "penalty" due to cache // shouldn't make any meaningful difference in terms of the freshness of // the response. - m_addr_response_caches[requestor_network].m_update_addr_response = + m_addr_response_caches[cache_id].m_update_addr_response = current_time + std::chrono::hours(21) + GetRandMillis(std::chrono::hours(6)); } - return m_addr_response_caches[requestor_network].m_addrs_response_cache; + return m_addr_response_caches[cache_id].m_addrs_response_cache; } bool CConnman::AddNode(const std::string &strNode) { diff --git a/src/net_processing.cpp b/src/net_processing.cpp --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -4507,9 +4507,8 @@ vAddr = m_connman.GetAddresses(MAX_ADDR_TO_SEND, MAX_PCT_ADDR_TO_SEND); } else { - vAddr = - m_connman.GetAddresses(pfrom.addr.GetNetwork(), - MAX_ADDR_TO_SEND, MAX_PCT_ADDR_TO_SEND); + vAddr = m_connman.GetAddresses(pfrom, MAX_ADDR_TO_SEND, + MAX_PCT_ADDR_TO_SEND); } FastRandomContext insecure_rand; for (const CAddress &addr : vAddr) {