diff --git a/src/net.cpp b/src/net.cpp index 171358bb5f8..bf29d928a1a 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -2539,6 +2539,17 @@ std::vector CConnman::GetAddresses() 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); diff --git a/src/net.h b/src/net.h index 3492a784cc3..1b9ed9dff42 100644 --- a/src/net.h +++ b/src/net.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -254,6 +255,13 @@ public: void MarkAddressGood(const CAddress& addr); 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. @@ -418,6 +426,29 @@ private: 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_processing.cpp b/src/net_processing.cpp index a591836f2e0..a9f6ed960fc 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -3477,7 +3477,7 @@ void ProcessMessage( pfrom.fSentAddr = true; pfrom.vAddrToSend.clear(); - std::vector vAddr = connman.GetAddresses(); + std::vector vAddr = connman.GetAddresses(pfrom.addr.GetNetwork()); FastRandomContext insecure_rand; for (const CAddress &addr : vAddr) { pfrom.PushAddress(addr, insecure_rand);