diff --git a/src/net.h b/src/net.h --- a/src/net.h +++ b/src/net.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -342,6 +343,13 @@ void AddNewAddresses(const std::vector &vAddr, const CAddress &addrFrom, int64_t nTimePenalty = 0); std::vector GetAddresses(); + /** + * Cache is used to minimize topology leaks, so it should + * be used for all non-trusted calls, for example, p2p. + * A non-malicious call (from RPC) should + * call the function without a parameter to avoid using the cache. + */ + std::vector GetAddresses(Network requestor_network); // This allows temporarily exceeding m_max_outbound_full_relay, with the // goal of finding a peer that is better than all our current peers. @@ -523,6 +531,29 @@ std::atomic nLastNodeId{0}; unsigned int nPrevNodeCount{0}; + /** + * Cache responses to addr requests to minimize privacy leak. + * Attack example: scraping addrs in real-time may allow an attacker + * to infer new connections of the victim by detecting new records + * with fresh timestamps (per self-announcement). + */ + struct CachedAddrResponse { + std::vector m_addrs_response_cache; + std::chrono::microseconds m_update_addr_response{0}; + }; + + /** + * Addr responses stored in different caches + * per network 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. + * 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. + */ + 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 @@ -2878,6 +2878,21 @@ return addresses; } +std::vector CConnman::GetAddresses(Network requestor_network) { + 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 < + current_time) { + m_addr_response_caches[requestor_network].m_addrs_response_cache = + GetAddresses(); + m_addr_response_caches[requestor_network].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; +} + bool CConnman::AddNode(const std::string &strNode) { LOCK(cs_vAddedNodes); for (const std::string &it : vAddedNodes) { diff --git a/src/net_processing.cpp b/src/net_processing.cpp --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -4472,7 +4472,8 @@ pfrom.fSentAddr = true; pfrom.vAddrToSend.clear(); - std::vector vAddr = m_connman.GetAddresses(); + std::vector vAddr = + m_connman.GetAddresses(pfrom.addr.GetNetwork()); FastRandomContext insecure_rand; for (const CAddress &addr : vAddr) { pfrom.PushAddress(addr, insecure_rand);