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.
This commit is contained in:
moneromooo-monero 2019-04-23 11:07:44 +00:00
parent 475481949a
commit 28a7d31565
No known key found for this signature in database
GPG key ID: 686F07454D6CEFC3
3 changed files with 30 additions and 12 deletions

View file

@ -1955,7 +1955,7 @@ namespace nodetool
const epee::net_utils::zone zone_type = context.m_remote_address.get_zone(); const epee::net_utils::zone zone_type = context.m_remote_address.get_zone();
network_zone& zone = m_network_zones.at(zone_type); 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); m_payload_handler.get_payload_sync_data(rsp.payload_data);
/* Tor/I2P nodes receiving connections via forwarding (from tor/i2p daemon) /* Tor/I2P nodes receiving connections via forwarding (from tor/i2p daemon)
@ -2058,7 +2058,7 @@ namespace nodetool
}); });
//fill response //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); get_local_node_data(rsp.node_data, zone);
m_payload_handler.get_payload_sync_data(rsp.payload_data); m_payload_handler.get_payload_sync_data(rsp.payload_data);
LOG_DEBUG_CC(context, "COMMAND_HANDSHAKE"); LOG_DEBUG_CC(context, "COMMAND_HANDSHAKE");

View file

@ -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_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();} size_t get_gray_peers_count(){CRITICAL_REGION_LOCAL(m_peerlist_lock); return m_peers_gray.size();}
bool merge_peerlist(const std::vector<peerlist_entry>& outer_bs); bool merge_peerlist(const std::vector<peerlist_entry>& outer_bs);
bool get_peerlist_head(std::vector<peerlist_entry>& bs_head, uint32_t depth = P2P_DEFAULT_PEERS_IN_HANDSHAKE); bool get_peerlist_head(std::vector<peerlist_entry>& bs_head, bool anonymize, uint32_t depth = P2P_DEFAULT_PEERS_IN_HANDSHAKE);
void get_peerlist(std::vector<peerlist_entry>& pl_gray, std::vector<peerlist_entry>& pl_white); void get_peerlist(std::vector<peerlist_entry>& pl_gray, std::vector<peerlist_entry>& pl_white);
void get_peerlist(peerlist_types& peers); void get_peerlist(peerlist_types& peers);
bool get_white_peer_by_index(peerlist_entry& p, size_t i); bool get_white_peer_by_index(peerlist_entry& p, size_t i);
@ -263,23 +263,40 @@ namespace nodetool
} }
//-------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------
inline inline
bool peerlist_manager::get_peerlist_head(std::vector<peerlist_entry>& bs_head, uint32_t depth) bool peerlist_manager::get_peerlist_head(std::vector<peerlist_entry>& bs_head, bool anonymize, uint32_t depth)
{ {
CRITICAL_REGION_LOCAL(m_peerlist_lock); CRITICAL_REGION_LOCAL(m_peerlist_lock);
peers_indexed::index<by_time>::type& by_time_index=m_peers_white.get<by_time>(); peers_indexed::index<by_time>::type& by_time_index=m_peers_white.get<by_time>();
uint32_t cnt = 0; 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)) for(const peers_indexed::value_type& vl: boost::adaptors::reverse(by_time_index))
{ {
if(!vl.last_seen) if(cnt++ >= pick_depth)
continue;
if(cnt++ >= depth)
break; break;
bs_head.push_back(vl); 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; return true;
} }
//-------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------

View file

@ -81,7 +81,8 @@ namespace nodetool
BEGIN_KV_SERIALIZE_MAP() BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(adr) KV_SERIALIZE(adr)
KV_SERIALIZE(id) 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(pruning_seed, (uint32_t)0)
KV_SERIALIZE_OPT(rpc_port, (uint16_t)0) KV_SERIALIZE_OPT(rpc_port, (uint16_t)0)
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
@ -132,7 +133,7 @@ namespace nodetool
ss << pe.id << "\t" << pe.adr.str() ss << pe.id << "\t" << pe.adr.str()
<< " \trpc port " << (pe.rpc_port > 0 ? std::to_string(pe.rpc_port) : "-") << " \trpc port " << (pe.rpc_port > 0 ? std::to_string(pe.rpc_port) : "-")
<< " \tpruning seed " << pe.pruning_seed << " \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; << std::endl;
} }
return ss.str(); return ss.str();