From 28a7d31565cb722e1a388743e2cbc492b6ad3bed Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Tue, 23 Apr 2019 11:07:44 +0000 Subject: [PATCH] p2p: do not send last_seen timestamp to peers This can be used for fingerprinting and working out the network topology. Instead of sending the first N (which are sorted by last seen time), we sent a random subset of the first N+N/5, which ensures reasonably recent peers are used, while preventing repeated calls from deducing new entries are peers the target node just connected to. The list is also randomly shuffled so the original set of timestamps cannot be approximated. --- src/p2p/net_node.inl | 4 ++-- src/p2p/net_peerlist.h | 33 +++++++++++++++++++++++++-------- src/p2p/p2p_protocol_defs.h | 5 +++-- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index be97edbe5..ba29d92c9 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -1955,7 +1955,7 @@ namespace nodetool const epee::net_utils::zone zone_type = context.m_remote_address.get_zone(); network_zone& zone = m_network_zones.at(zone_type); - zone.m_peerlist.get_peerlist_head(rsp.local_peerlist_new); + zone.m_peerlist.get_peerlist_head(rsp.local_peerlist_new, true); m_payload_handler.get_payload_sync_data(rsp.payload_data); /* Tor/I2P nodes receiving connections via forwarding (from tor/i2p daemon) @@ -2058,7 +2058,7 @@ namespace nodetool }); //fill response - zone.m_peerlist.get_peerlist_head(rsp.local_peerlist_new); + zone.m_peerlist.get_peerlist_head(rsp.local_peerlist_new, true); get_local_node_data(rsp.node_data, zone); m_payload_handler.get_payload_sync_data(rsp.payload_data); LOG_DEBUG_CC(context, "COMMAND_HANDSHAKE"); diff --git a/src/p2p/net_peerlist.h b/src/p2p/net_peerlist.h index 52814af94..f4fa921e2 100644 --- a/src/p2p/net_peerlist.h +++ b/src/p2p/net_peerlist.h @@ -102,7 +102,7 @@ namespace nodetool size_t get_white_peers_count(){CRITICAL_REGION_LOCAL(m_peerlist_lock); return m_peers_white.size();} size_t get_gray_peers_count(){CRITICAL_REGION_LOCAL(m_peerlist_lock); return m_peers_gray.size();} bool merge_peerlist(const std::vector& outer_bs); - bool get_peerlist_head(std::vector& bs_head, uint32_t depth = P2P_DEFAULT_PEERS_IN_HANDSHAKE); + bool get_peerlist_head(std::vector& bs_head, bool anonymize, uint32_t depth = P2P_DEFAULT_PEERS_IN_HANDSHAKE); void get_peerlist(std::vector& pl_gray, std::vector& pl_white); void get_peerlist(peerlist_types& peers); bool get_white_peer_by_index(peerlist_entry& p, size_t i); @@ -263,23 +263,40 @@ namespace nodetool } //-------------------------------------------------------------------------------------------------- inline - bool peerlist_manager::get_peerlist_head(std::vector& bs_head, uint32_t depth) + bool peerlist_manager::get_peerlist_head(std::vector& bs_head, bool anonymize, uint32_t depth) { - CRITICAL_REGION_LOCAL(m_peerlist_lock); peers_indexed::index::type& by_time_index=m_peers_white.get(); uint32_t cnt = 0; - bs_head.reserve(depth); + + // picks a random set of peers within the first 120%, rather than a set of the first 100%. + // The intent is that if someone asks twice, they can't easily tell: + // - this address was not in the first list, but is in the second, so the only way this can be + // is if its last_seen was recently reset, so this means the target node recently had a new + // connection to that address + // - this address was in the first list, and not in the second, which means either the address + // was moved to the gray list (if it's not accessibe, which the attacker can check if + // the address accepts incoming connections) or it was the oldest to still fit in the 250 items, + // so its last_seen is old. + const uint32_t pick_depth = anonymize ? depth + depth / 5 : depth; + bs_head.reserve(pick_depth); for(const peers_indexed::value_type& vl: boost::adaptors::reverse(by_time_index)) { - if(!vl.last_seen) - continue; - - if(cnt++ >= depth) + if(cnt++ >= pick_depth) break; bs_head.push_back(vl); } + + if (anonymize) + { + std::random_shuffle(bs_head.begin(), bs_head.end()); + if (bs_head.size() > depth) + bs_head.resize(depth); + for (auto &e: bs_head) + e.last_seen = 0; + } + return true; } //-------------------------------------------------------------------------------------------------- diff --git a/src/p2p/p2p_protocol_defs.h b/src/p2p/p2p_protocol_defs.h index 59c6099d5..85774fcd5 100644 --- a/src/p2p/p2p_protocol_defs.h +++ b/src/p2p/p2p_protocol_defs.h @@ -81,7 +81,8 @@ namespace nodetool BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(adr) KV_SERIALIZE(id) - KV_SERIALIZE(last_seen) + if (!is_store || this_ref.last_seen != 0) + KV_SERIALIZE_OPT(last_seen, (int64_t)0) KV_SERIALIZE_OPT(pruning_seed, (uint32_t)0) KV_SERIALIZE_OPT(rpc_port, (uint16_t)0) END_KV_SERIALIZE_MAP() @@ -132,7 +133,7 @@ namespace nodetool ss << pe.id << "\t" << pe.adr.str() << " \trpc port " << (pe.rpc_port > 0 ? std::to_string(pe.rpc_port) : "-") << " \tpruning seed " << pe.pruning_seed - << " \tlast_seen: " << epee::misc_utils::get_time_interval_string(now_time - pe.last_seen) + << " \tlast_seen: " << (pe.last_seen == 0 ? std::string("never") : epee::misc_utils::get_time_interval_string(now_time - pe.last_seen)) << std::endl; } return ss.str();