Merge pull request #961 from ssiloti/dht-sanitizing

remove DHT nodes which change their id
This commit is contained in:
Arvid Norberg 2016-07-29 18:00:22 -04:00 committed by GitHub
commit 1d514b3f99
3 changed files with 113 additions and 66 deletions

View File

@ -293,6 +293,10 @@ private:
node_entry* find_node(udp::endpoint const& ep node_entry* find_node(udp::endpoint const& ep
, routing_table::table_t::iterator* bucket); , routing_table::table_t::iterator* bucket);
// if the bucket is not full, try to fill it with nodes from the
// replacement list
void fill_from_replacements(table_t::iterator bucket);
dht_settings const& m_settings; dht_settings const& m_settings;
// (k-bucket, replacement cache) pairs // (k-bucket, replacement cache) pairs

View File

@ -547,6 +547,29 @@ node_entry* routing_table::find_node(udp::endpoint const& ep
return nullptr; return nullptr;
} }
void routing_table::fill_from_replacements(table_t::iterator bucket)
{
bucket_t& b = bucket->live_nodes;
bucket_t& rb = bucket->replacements;
int const bucket_size = bucket_limit(std::distance(m_buckets.begin(), bucket));
if (b.size() >= bucket_size) return;
// sort by RTT first, to find the node with the lowest
// RTT that is pinged
std::sort(rb.begin(), rb.end()
, [](node_entry const& lhs, node_entry const& rhs)
{ return lhs.rtt < rhs.rtt; });
while (b.size() < bucket_size && !rb.empty())
{
bucket_t::iterator j = std::find_if(rb.begin(), rb.end(), std::bind(&node_entry::pinged, _1));
if (j == rb.end()) j = rb.begin();
b.push_back(*j);
rb.erase(j);
}
}
void routing_table::remove_node(node_entry* n void routing_table::remove_node(node_entry* n
, routing_table::table_t::iterator bucket) , routing_table::table_t::iterator bucket)
{ {
@ -625,16 +648,12 @@ routing_table::add_node_status_t routing_table::add_node_impl(node_entry e)
if (m_router_nodes.find(e.ep()) != m_router_nodes.end()) if (m_router_nodes.find(e.ep()) != m_router_nodes.end())
return failed_to_add; return failed_to_add;
// don't add ourself
if (e.id == m_id) return failed_to_add;
// do we already have this IP in the table? // do we already have this IP in the table?
if (m_ips.count(e.addr()) > 0) if (m_ips.count(e.addr()) > 0)
{ {
// this exact IP already exists in the table. It might be the case // This exact IP already exists in the table. A node with the same IP and
// that the node changed IP. If pinged is true, and the port also // port but a different ID may be a sign of a malicious node. To be
// matches then we assume it's in fact the same node, and just update // conservative in this case the node is removed.
// the routing table
// pinged means that we have sent a message to the IP, port and received // pinged means that we have sent a message to the IP, port and received
// a response with a correct transaction ID, i.e. it is verified to not // a response with a correct transaction ID, i.e. it is verified to not
// be the result of a poisoned routing table // be the result of a poisoned routing table
@ -651,10 +670,13 @@ routing_table::add_node_status_t routing_table::add_node_impl(node_entry e)
if (m_settings.restrict_routing_ips) if (m_settings.restrict_routing_ips)
{ {
#ifndef TORRENT_DISABLE_LOGGING #ifndef TORRENT_DISABLE_LOGGING
char hex_id[41]; if (m_log)
aux::to_hex(reinterpret_cast<char const*>(&e.id[0]), 20, hex_id); {
m_log->log(dht_logger::routing_table, "ignoring node (duplicate IP): %s %s" char hex_id[41];
, hex_id, print_address(e.addr()).c_str()); aux::to_hex(reinterpret_cast<char const*>(&e.id[0]), 20, hex_id);
m_log->log(dht_logger::routing_table, "ignoring node (duplicate IP): %s %s"
, hex_id, print_address(e.addr()).c_str());
}
#endif #endif
return failed_to_add; return failed_to_add;
} }
@ -682,13 +704,40 @@ routing_table::add_node_status_t routing_table::add_node_impl(node_entry e)
else else
{ {
TORRENT_ASSERT(existing->id != e.id); TORRENT_ASSERT(existing->id != e.id);
// this is the same IP and port, but with // This is the same IP and port, but with a new node ID.
// a new node ID. remove the old entry and // This may indicate a malicious node so remove the entry.
// replace it with this new ID #ifndef TORRENT_DISABLE_LOGGING
if (m_log)
{
char hex_id_new[41];
char hex_id_old[41];
aux::to_hex(e.id.data(), 20, hex_id_new);
aux::to_hex(existing->id.data(), 20, hex_id_old);
m_log->log(dht_logger::routing_table, "evicting node (changed ID): old: %s new: %s %s"
, hex_id_old, hex_id_new, print_address(e.addr()).c_str());
}
#endif
remove_node(existing, existing_bucket); remove_node(existing, existing_bucket);
fill_from_replacements(existing_bucket);
// when we detect possible malicious activity in a bucket,
// schedule the other nodes in the bucket to be pinged soon
// to clean out any other malicious nodes
auto now = aux::time_now();
for (auto& node : existing_bucket->live_nodes)
{
if (now - node.last_queried > minutes(5))
node.last_queried = min_time();
}
return failed_to_add;
} }
} }
// don't add ourself
if (e.id == m_id) return failed_to_add;
table_t::iterator i = find_bucket(e.id); table_t::iterator i = find_bucket(e.id);
bucket_t& b = i->live_nodes; bucket_t& b = i->live_nodes;
bucket_t& rb = i->replacements; bucket_t& rb = i->replacements;
@ -755,13 +804,16 @@ routing_table::add_node_status_t routing_table::add_node_impl(node_entry e)
// close to this one. We know that it's not the same, because // close to this one. We know that it's not the same, because
// it claims a different node-ID. Ignore this to avoid attacks // it claims a different node-ID. Ignore this to avoid attacks
#ifndef TORRENT_DISABLE_LOGGING #ifndef TORRENT_DISABLE_LOGGING
char hex_id1[41]; if (m_log)
aux::to_hex(e.id.data(), 20, hex_id1); {
char hex_id2[41]; char hex_id1[41];
aux::to_hex(j->id.data(), 20, hex_id2); aux::to_hex(e.id.data(), 20, hex_id1);
m_log->log(dht_logger::routing_table, "ignoring node: %s %s existing node: %s %s" char hex_id2[41];
, hex_id1, print_address(e.addr()).c_str() aux::to_hex(j->id.data(), 20, hex_id2);
, hex_id2, print_address(j->addr()).c_str()); m_log->log(dht_logger::routing_table, "ignoring node: %s %s existing node: %s %s"
, hex_id1, print_address(e.addr()).c_str()
, hex_id2, print_address(j->addr()).c_str());
}
#endif #endif
return failed_to_add; return failed_to_add;
} }
@ -1155,13 +1207,16 @@ void routing_table::node_failed(node_id const& nid, udp::endpoint const& ep)
j->timed_out(); j->timed_out();
#ifndef TORRENT_DISABLE_LOGGING #ifndef TORRENT_DISABLE_LOGGING
char hex_id[41]; if (m_log)
aux::to_hex(nid.data(), 20, hex_id); {
m_log->log(dht_logger::routing_table, "NODE FAILED id: %s ip: %s fails: %d pinged: %d up-time: %d" char hex_id[41];
, hex_id, print_endpoint(j->ep()).c_str() aux::to_hex(nid.data(), 20, hex_id);
, int(j->fail_count()) m_log->log(dht_logger::routing_table, "NODE FAILED id: %s ip: %s fails: %d pinged: %d up-time: %d"
, int(j->pinged()) , hex_id, print_endpoint(j->ep()).c_str()
, int(total_seconds(aux::time_now() - j->first_seen))); , int(j->fail_count())
, int(j->pinged())
, int(total_seconds(aux::time_now() - j->first_seen)));
}
#endif #endif
return; return;
} }
@ -1176,13 +1231,16 @@ void routing_table::node_failed(node_id const& nid, udp::endpoint const& ep)
j->timed_out(); j->timed_out();
#ifndef TORRENT_DISABLE_LOGGING #ifndef TORRENT_DISABLE_LOGGING
char hex_id[41]; if (m_log)
aux::to_hex(nid.data(), 20, hex_id); {
m_log->log(dht_logger::routing_table, "NODE FAILED id: %s ip: %s fails: %d pinged: %d up-time: %d" char hex_id[41];
, hex_id, print_endpoint(j->ep()).c_str() aux::to_hex(nid.data(), 20, hex_id);
, int(j->fail_count()) m_log->log(dht_logger::routing_table, "NODE FAILED id: %s ip: %s fails: %d pinged: %d up-time: %d"
, int(j->pinged()) , hex_id, print_endpoint(j->ep()).c_str()
, int(total_seconds(aux::time_now() - j->first_seen))); , int(j->fail_count())
, int(j->pinged())
, int(total_seconds(aux::time_now() - j->first_seen)));
}
#endif #endif
// if this node has failed too many times, or if this node // if this node has failed too many times, or if this node
@ -1198,16 +1256,7 @@ void routing_table::node_failed(node_id const& nid, udp::endpoint const& ep)
m_ips.erase(j->addr()); m_ips.erase(j->addr());
b.erase(j); b.erase(j);
// sort by RTT first, to find the node with the lowest fill_from_replacements(i);
// RTT that is pinged
std::sort(rb.begin(), rb.end()
, [](node_entry const& lhs, node_entry const& rhs)
{ return lhs.rtt < rhs.rtt; });
j = std::find_if(rb.begin(), rb.end(), std::bind(&node_entry::pinged, _1));
if (j == rb.end()) j = rb.begin();
b.push_back(*j);
rb.erase(j);
} }
void routing_table::add_router_node(udp::endpoint router) void routing_table::add_router_node(udp::endpoint router)

View File

@ -1392,19 +1392,6 @@ void test_routing_table(address(&rand_addr)())
TEST_EQUAL(nodes[0].timeout_count, 0); TEST_EQUAL(nodes[0].timeout_count, 0);
} }
// test adding the same IP:port again with a new node ID (should replace the old one)
add_and_replace(tmp, diff);
table.node_seen(tmp, udp::endpoint(node_addr, 4), 10);
table.find_node(id, nodes, 0, 10);
TEST_EQUAL(table.bucket_size(0), 1);
TEST_EQUAL(nodes.size(), 1);
if (!nodes.empty())
{
TEST_EQUAL(nodes[0].id, tmp);
TEST_EQUAL(nodes[0].addr(), node_addr);
TEST_EQUAL(nodes[0].port(), 4);
}
// test adding the same node ID again with a different IP (should be ignored) // test adding the same node ID again with a different IP (should be ignored)
table.node_seen(tmp, udp::endpoint(node_addr, 5), 10); table.node_seen(tmp, udp::endpoint(node_addr, 5), 10);
table.find_node(id, nodes, 0, 10); table.find_node(id, nodes, 0, 10);
@ -1429,6 +1416,13 @@ void test_routing_table(address(&rand_addr)())
TEST_EQUAL(nodes[0].port(), 4); TEST_EQUAL(nodes[0].port(), 4);
} }
// test adding the same IP:port again with a new node ID (should remove the node)
add_and_replace(tmp, diff);
table.node_seen(tmp, udp::endpoint(node_addr, 4), 10);
table.find_node(id, nodes, 0, 10);
TEST_EQUAL(table.bucket_size(0), 0);
TEST_EQUAL(nodes.size(), 0);
s.restrict_routing_ips = false; s.restrict_routing_ips = false;
init_rand_address(); init_rand_address();
@ -3006,14 +3000,6 @@ TORRENT_TEST(dht_verify_node_address)
TEST_EQUAL(std::get<0>(table.size()), 1); TEST_EQUAL(std::get<0>(table.size()), 1);
TEST_EQUAL(nodes.size(), 1); TEST_EQUAL(nodes.size(), 1);
// incorrect data, wrong id
table.node_seen(to_hash("0123456789abcdef01232456789abcdef0123456")
, udp::endpoint(addr("4.4.4.4"), 4), 10);
table.find_node(id, nodes, 0, 10);
TEST_EQUAL(std::get<0>(table.size()), 1);
TEST_EQUAL(nodes.size(), 1);
// incorrect data, wrong IP // incorrect data, wrong IP
table.node_seen(tmp table.node_seen(tmp
, udp::endpoint(addr("4.4.4.6"), 4), 10); , udp::endpoint(addr("4.4.4.6"), 4), 10);
@ -3021,6 +3007,14 @@ TORRENT_TEST(dht_verify_node_address)
TEST_EQUAL(std::get<0>(table.size()), 1); TEST_EQUAL(std::get<0>(table.size()), 1);
TEST_EQUAL(nodes.size(), 1); TEST_EQUAL(nodes.size(), 1);
// incorrect data, wrong id, should cause node to be removed
table.node_seen(to_hash("0123456789abcdef01232456789abcdef0123456")
, udp::endpoint(addr("4.4.4.4"), 4), 10);
table.find_node(id, nodes, 0, 10);
TEST_EQUAL(std::get<0>(table.size()), 0);
TEST_EQUAL(nodes.size(), 0);
} }
TORRENT_TEST(generate_prefix_mask) TORRENT_TEST(generate_prefix_mask)