add option to enforce node ID in the DHT

This commit is contained in:
Arvid Norberg 2013-10-14 01:03:43 +00:00
parent 50f051433a
commit afd80cffb7
6 changed files with 114 additions and 34 deletions

View File

@ -192,7 +192,7 @@ typedef std::map<node_id, dht_mutable_item> dht_mutable_table_t;
public: public:
node_impl(alert_dispatcher* alert_disp, udp_socket_interface* sock node_impl(alert_dispatcher* alert_disp, udp_socket_interface* sock
, dht_settings const& settings, node_id nid, address const& external_address , libtorrent::dht_settings const& settings, node_id nid, address const& external_address
, dht_observer* observer); , dht_observer* observer);
virtual ~node_impl() {} virtual ~node_impl() {}
@ -268,7 +268,7 @@ public:
void status(libtorrent::session_status& s); void status(libtorrent::session_status& s);
dht_settings const& settings() const { return m_settings; } libtorrent::dht_settings const& settings() const { return m_settings; }
protected: protected:
@ -277,7 +277,7 @@ protected:
bool lookup_torrents(sha1_hash const& target, entry& reply bool lookup_torrents(sha1_hash const& target, entry& reply
, char* tags) const; , char* tags) const;
dht_settings const& m_settings; libtorrent::dht_settings const& m_settings;
private: private:
typedef libtorrent::mutex mutex_t; typedef libtorrent::mutex mutex_t;

View File

@ -49,6 +49,8 @@ POSSIBILITY OF SUCH DAMAGE.
namespace libtorrent { namespace aux { struct session_impl; } } namespace libtorrent { namespace aux { struct session_impl; } }
namespace libtorrent { struct dht_settings; }
namespace libtorrent { namespace dht namespace libtorrent { namespace dht
{ {
@ -79,7 +81,7 @@ public:
// returns true if the node needs a refresh // returns true if the node needs a refresh
// if so, id is assigned the node id to refresh // if so, id is assigned the node id to refresh
bool incoming(msg const&, node_id* id); bool incoming(msg const&, node_id* id, libtorrent::dht_settings const& settings);
time_duration tick(); time_duration tick();
bool invoke(entry& e, udp::endpoint target bool invoke(entry& e, udp::endpoint target

View File

@ -1406,6 +1406,7 @@ namespace libtorrent
, extended_routing_table(true) , extended_routing_table(true)
, aggressive_lookups(true) , aggressive_lookups(true)
, privacy_lookups(false) , privacy_lookups(false)
, enforce_node_id(false)
{} {}
// the maximum number of peers to send in a // the maximum number of peers to send in a
@ -1475,6 +1476,11 @@ namespace libtorrent
// when set, perform lookups in a way that is slightly more expensive, but which // when set, perform lookups in a way that is slightly more expensive, but which
// minimizes the amount of information leaked about you. // minimizes the amount of information leaked about you.
bool privacy_lookups; bool privacy_lookups;
// when set, node's whose IDs that are not correctly generated based on its external
// IP are ignored. When a query arrives from such node, an error message is returned
// with a message saying "invalid node ID".
bool enforce_node_id;
}; };
#ifndef TORRENT_DISABLE_ENCRYPTION #ifndef TORRENT_DISABLE_ENCRYPTION

View File

@ -224,17 +224,8 @@ void node_impl::incoming(msg const& m)
char y = *(y_ent->string_ptr()); char y = *(y_ent->string_ptr());
lazy_entry const* ext_ip = m.message.dict_find_string("ip"); lazy_entry const* ext_ip = m.message.dict_find_string("ip");
if (ext_ip && ext_ip->string_length() == 4)
{
// this node claims we use the wrong node-ID!
address_v4::bytes_type b;
memcpy(&b[0], ext_ip->string_ptr(), 4);
if (m_observer)
m_observer->set_external_address(address_v4(b)
, m.addr.address());
}
#if TORRENT_USE_IPV6 #if TORRENT_USE_IPV6
else if (ext_ip && ext_ip->string_length() == 16) if (ext_ip && ext_ip->string_length() >= 16)
{ {
// this node claims we use the wrong node-ID! // this node claims we use the wrong node-ID!
address_v6::bytes_type b; address_v6::bytes_type b;
@ -242,15 +233,23 @@ void node_impl::incoming(msg const& m)
if (m_observer) if (m_observer)
m_observer->set_external_address(address_v6(b) m_observer->set_external_address(address_v6(b)
, m.addr.address()); , m.addr.address());
} } else
#endif #endif
if (ext_ip && ext_ip->string_length() >= 4)
{
address_v4::bytes_type b;
memcpy(&b[0], ext_ip->string_ptr(), 4);
if (m_observer)
m_observer->set_external_address(address_v4(b)
, m.addr.address());
}
switch (y) switch (y)
{ {
case 'r': case 'r':
{ {
node_id id; node_id id;
if (m_rpc.incoming(m, &id)) if (m_rpc.incoming(m, &id, m_settings))
refresh(id, boost::bind(&nop)); refresh(id, boost::bind(&nop));
break; break;
} }
@ -682,22 +681,22 @@ void node_impl::incoming_request(msg const& m, entry& e)
} }
e["ip"] = endpoint_to_bytes(m.addr); e["ip"] = endpoint_to_bytes(m.addr);
/*
// if this nodes ID doesn't match its IP, tell it what
// its IP is with an error
// don't enforce this yet
if (!verify_id(id, m.addr.address()))
{
incoming_error(e, "invalid node ID");
return;
}
*/
char const* query = top_level[0]->string_cstr(); char const* query = top_level[0]->string_cstr();
lazy_entry const* arg_ent = top_level[1]; lazy_entry const* arg_ent = top_level[1];
node_id id(top_level[2]->string_ptr()); node_id id(top_level[2]->string_ptr());
// if this nodes ID doesn't match its IP, tell it what
// its IP is with an error
// don't enforce this yet
if (m_settings.enforce_node_id && !verify_id(id, m.addr.address()))
{
incoming_error(e, "invalid node ID");
return;
}
m_table.heard_about(id, m.addr); m_table.heard_about(id, m.addr);
entry& reply = e["r"]; entry& reply = e["r"];

View File

@ -47,6 +47,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include <libtorrent/kademlia/node.hpp> #include <libtorrent/kademlia/node.hpp>
#include <libtorrent/kademlia/observer.hpp> #include <libtorrent/kademlia/observer.hpp>
#include <libtorrent/hasher.hpp> #include <libtorrent/hasher.hpp>
#include <libtorrent/session_settings.hpp> // for dht_settings
#include <libtorrent/time.hpp> #include <libtorrent/time.hpp>
#include <time.h> // time() #include <time.h> // time()
@ -267,7 +268,7 @@ void rpc_manager::unreachable(udp::endpoint const& ep)
// defined in node.cpp // defined in node.cpp
void incoming_error(entry& e, char const* msg, int error_code = 203); void incoming_error(entry& e, char const* msg, int error_code = 203);
bool rpc_manager::incoming(msg const& m, node_id* id) bool rpc_manager::incoming(msg const& m, node_id* id, libtorrent::dht_settings const& settings)
{ {
INVARIANT_CHECK; INVARIANT_CHECK;
@ -337,12 +338,21 @@ bool rpc_manager::incoming(msg const& m, node_id* id)
return false; return false;
} }
node_id nid = node_id(node_id_ent->string_ptr());
if (settings.enforce_node_id && !verify_id(nid, m.addr.address()))
{
entry e;
incoming_error(e, "invalid node ID");
m_sock->send_packet(e, m.addr, 0);
return false;
}
#ifdef TORRENT_DHT_VERBOSE_LOGGING #ifdef TORRENT_DHT_VERBOSE_LOGGING
TORRENT_LOG(rpc) << "[" << o->m_algorithm.get() << "] Reply with transaction id: " TORRENT_LOG(rpc) << "[" << o->m_algorithm.get() << "] Reply with transaction id: "
<< tid << " from " << m.addr; << tid << " from " << m.addr;
#endif #endif
o->reply(m); o->reply(m);
*id = node_id(node_id_ent->string_ptr()); *id = nid;
int rtt = total_milliseconds(now - o->sent()); int rtt = total_milliseconds(now - o->sent());

View File

@ -119,7 +119,7 @@ void send_dht_msg(node_impl& node, char const* msg, udp::endpoint const& ep
, char const* target = 0, entry const* value = 0 , char const* target = 0, entry const* value = 0
, bool scrape = false, bool seed = false , bool scrape = false, bool seed = false
, std::string const key = std::string(), std::string const sig = std::string() , std::string const key = std::string(), std::string const sig = std::string()
, int seq = -1, char const* cas = 0) , int seq = -1, char const* cas = 0, sha1_hash const* nid = NULL)
{ {
// we're about to clear out the backing buffer // we're about to clear out the backing buffer
// for this lazy_entry, so we better clear it now // for this lazy_entry, so we better clear it now
@ -129,7 +129,8 @@ void send_dht_msg(node_impl& node, char const* msg, udp::endpoint const& ep
e["t"] = t; e["t"] = t;
e["y"] = "q"; e["y"] = "q";
entry::dictionary_type& a = e["a"].dict(); entry::dictionary_type& a = e["a"].dict();
a["id"] = generate_next().to_string(); if (nid == NULL) a["id"] = generate_next().to_string();
else a["id"] = nid->to_string();
if (info_hash) a["info_hash"] = std::string(info_hash, 20); if (info_hash) a["info_hash"] = std::string(info_hash, 20);
if (name) a["n"] = name; if (name) a["n"] = name;
if (!token.empty()) a["token"] = token; if (!token.empty()) a["token"] = token;
@ -320,6 +321,7 @@ int test_main()
dht_settings sett; dht_settings sett;
sett.max_torrents = 4; sett.max_torrents = 4;
sett.max_dht_items = 4; sett.max_dht_items = 4;
sett.enforce_node_id = false;
address ext = address::from_string("236.0.0.1"); address ext = address::from_string("236.0.0.1");
mock_socket s; mock_socket s;
print_alert ad; print_alert ad;
@ -361,13 +363,11 @@ int test_main()
dht::key_desc_t err_desc[] = { dht::key_desc_t err_desc[] = {
{"y", lazy_entry::string_t, 1, 0}, {"y", lazy_entry::string_t, 1, 0},
{"e", lazy_entry::list_t, 2, 0}, {"e", lazy_entry::list_t, 2, 0}
{"r", lazy_entry::dict_t, 0, key_desc_t::parse_children},
{"id", lazy_entry::string_t, 20, key_desc_t::last_child},
}; };
fprintf(stderr, "msg: %s\n", print_entry(response).c_str()); fprintf(stderr, "msg: %s\n", print_entry(response).c_str());
ret = dht::verify_message(&response, err_desc, parsed, 4, error_string, sizeof(error_string)); ret = dht::verify_message(&response, err_desc, parsed, 2, error_string, sizeof(error_string));
TEST_CHECK(ret); TEST_CHECK(ret);
if (ret) if (ret)
{ {
@ -498,6 +498,69 @@ int test_main()
fprintf(stderr, " invalid get_peers response: %s\n", error_string); fprintf(stderr, " invalid get_peers response: %s\n", error_string);
} }
// ====== test node ID enforcement ======
// enable node_id enforcement
sett.enforce_node_id = true;
// this is one of the test vectors from:
// http://libtorrent.org/dht_sec.html
source = udp::endpoint(address::from_string("124.31.75.21"), 20);
node_id nid = to_hash("1712f6c70c5d6a4ec8a88e4c6ab4c28b95eee401");
send_dht_msg(node, "find_node", source, &response, "10", 0, 0, std::string()
, 0, "0101010101010101010101010101010101010101", 0, false, false, std::string(), std::string(), -1, 0, &nid);
dht::key_desc_t nodes_desc[] = {
{"y", lazy_entry::string_t, 1, 0},
{"r", lazy_entry::dict_t, 0, key_desc_t::parse_children},
{"id", lazy_entry::string_t, 20, key_desc_t::last_child},
};
fprintf(stderr, "msg: %s\n", print_entry(response).c_str());
ret = dht::verify_message(&response, nodes_desc, parsed, 3, error_string, sizeof(error_string));
TEST_CHECK(ret);
if (ret)
{
TEST_CHECK(parsed[0]->string_value() == "r");
}
else
{
fprintf(stderr, "msg: %s\n", print_entry(response).c_str());
fprintf(stderr, " invalid error response: %s\n", error_string);
}
// verify that we reject invalid node IDs
// this is now an invalid node-id for 'source'
nid[0] = 0x18;
send_dht_msg(node, "find_node", source, &response, "10", 0, 0, std::string()
, 0, "0101010101010101010101010101010101010101", 0, false, false, std::string(), std::string(), -1, 0, &nid);
ret = dht::verify_message(&response, err_desc, parsed, 2, error_string, sizeof(error_string));
TEST_CHECK(ret);
if (ret)
{
TEST_CHECK(parsed[0]->string_value() == "e");
if (parsed[1]->list_at(0)->type() == lazy_entry::int_t
&& parsed[1]->list_at(1)->type() == lazy_entry::string_t)
{
TEST_CHECK(parsed[1]->list_at(1)->string_value() == "invalid node ID");
}
else
{
fprintf(stderr, "msg: %s\n", print_entry(response).c_str());
TEST_ERROR("invalid error response");
}
}
else
{
fprintf(stderr, "msg: %s\n", print_entry(response).c_str());
fprintf(stderr, " invalid error response: %s\n", error_string);
}
sett.enforce_node_id = false;
// ===========================
bloom_filter<256> test; bloom_filter<256> test;
for (int i = 0; i < 256; ++i) for (int i = 0; i < 256; ++i)
{ {