2006-08-01 17:27:08 +02:00
|
|
|
/*
|
|
|
|
|
2012-10-02 05:16:33 +02:00
|
|
|
Copyright (c) 2006-2012, Arvid Norberg & Daniel Wallin
|
2006-08-01 17:27:08 +02:00
|
|
|
All rights reserved.
|
|
|
|
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
|
|
modification, are permitted provided that the following conditions
|
|
|
|
are met:
|
|
|
|
|
|
|
|
* Redistributions of source code must retain the above copyright
|
|
|
|
notice, this list of conditions and the following disclaimer.
|
|
|
|
* Redistributions in binary form must reproduce the above copyright
|
|
|
|
notice, this list of conditions and the following disclaimer in
|
|
|
|
the documentation and/or other materials provided with the distribution.
|
|
|
|
* Neither the name of the author nor the names of its
|
|
|
|
contributors may be used to endorse or promote products derived
|
|
|
|
from this software without specific prior written permission.
|
|
|
|
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
|
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
|
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
|
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
|
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
|
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
|
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
|
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
|
POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
2007-03-17 18:15:16 +01:00
|
|
|
#include "libtorrent/pch.hpp"
|
|
|
|
|
2006-08-01 17:27:08 +02:00
|
|
|
#include <libtorrent/kademlia/find_data.hpp>
|
|
|
|
#include <libtorrent/kademlia/routing_table.hpp>
|
|
|
|
#include <libtorrent/kademlia/rpc_manager.hpp>
|
2008-09-20 19:42:25 +02:00
|
|
|
#include <libtorrent/kademlia/node.hpp>
|
2006-08-01 17:27:08 +02:00
|
|
|
#include <libtorrent/io.hpp>
|
2008-05-03 18:05:42 +02:00
|
|
|
#include <libtorrent/socket.hpp>
|
2009-09-20 02:23:36 +02:00
|
|
|
#include <libtorrent/socket_io.hpp>
|
|
|
|
#include <vector>
|
2006-08-01 17:27:08 +02:00
|
|
|
|
|
|
|
namespace libtorrent { namespace dht
|
|
|
|
{
|
|
|
|
|
2009-09-20 02:23:36 +02:00
|
|
|
#ifdef TORRENT_DHT_VERBOSE_LOGGING
|
2009-10-09 04:34:25 +02:00
|
|
|
TORRENT_DECLARE_LOG(traversal);
|
2009-09-20 02:23:36 +02:00
|
|
|
#endif
|
|
|
|
|
2009-10-19 04:43:50 +02:00
|
|
|
using detail::read_endpoint_list;
|
2009-09-20 02:23:36 +02:00
|
|
|
using detail::read_v4_endpoint;
|
2009-10-19 04:43:50 +02:00
|
|
|
#if TORRENT_USE_IPV6
|
2009-09-20 02:23:36 +02:00
|
|
|
using detail::read_v6_endpoint;
|
2009-10-19 04:43:50 +02:00
|
|
|
#endif
|
2009-09-20 02:23:36 +02:00
|
|
|
|
2006-08-01 17:27:08 +02:00
|
|
|
void find_data_observer::reply(msg const& m)
|
|
|
|
{
|
2009-09-20 02:23:36 +02:00
|
|
|
lazy_entry const* r = m.message.dict_find_dict("r");
|
|
|
|
if (!r)
|
|
|
|
{
|
|
|
|
#ifdef TORRENT_DHT_VERBOSE_LOGGING
|
2009-10-09 04:34:25 +02:00
|
|
|
TORRENT_LOG(traversal) << "[" << m_algorithm.get() << "] missing response dict";
|
2009-09-20 02:23:36 +02:00
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
lazy_entry const* id = r->dict_find_string("id");
|
|
|
|
if (!id || id->string_length() != 20)
|
|
|
|
{
|
|
|
|
#ifdef TORRENT_DHT_VERBOSE_LOGGING
|
2009-10-09 04:34:25 +02:00
|
|
|
TORRENT_LOG(traversal) << "[" << m_algorithm.get() << "] invalid id in response";
|
2009-09-20 02:23:36 +02:00
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
lazy_entry const* token = r->dict_find_string("token");
|
|
|
|
if (token)
|
|
|
|
{
|
2009-10-07 22:51:02 +02:00
|
|
|
static_cast<find_data*>(m_algorithm.get())->got_write_token(
|
|
|
|
node_id(id->string_ptr()), token->string_value());
|
2009-09-20 02:23:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// look for peers
|
|
|
|
lazy_entry const* n = r->dict_find_list("values");
|
|
|
|
if (n)
|
|
|
|
{
|
|
|
|
std::vector<tcp::endpoint> peer_list;
|
|
|
|
if (n->list_size() == 1 && n->list_at(0)->type() == lazy_entry::string_t)
|
|
|
|
{
|
|
|
|
// assume it's mainline format
|
|
|
|
char const* peers = n->list_at(0)->string_ptr();
|
|
|
|
char const* end = peers + n->list_at(0)->string_length();
|
|
|
|
|
|
|
|
#ifdef TORRENT_DHT_VERBOSE_LOGGING
|
2013-01-20 08:54:54 +01:00
|
|
|
TORRENT_LOG(traversal)
|
|
|
|
<< "[" << m_algorithm.get() << "] PEERS"
|
|
|
|
<< " invoke-count: " << m_algorithm->invoke_count()
|
|
|
|
<< " branch-factor: " << m_algorithm->branch_factor()
|
|
|
|
<< " addr: " << m.addr
|
|
|
|
<< " id: " << node_id(id->string_ptr())
|
|
|
|
<< " distance: " << distance_exp(m_algorithm->target(), node_id(id->string_ptr()))
|
|
|
|
<< " p: " << ((end - peers) / 6);
|
2009-09-20 02:23:36 +02:00
|
|
|
#endif
|
|
|
|
while (end - peers >= 6)
|
|
|
|
peer_list.push_back(read_v4_endpoint<tcp::endpoint>(peers));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// assume it's uTorrent/libtorrent format
|
|
|
|
read_endpoint_list<tcp::endpoint>(n, peer_list);
|
|
|
|
#ifdef TORRENT_DHT_VERBOSE_LOGGING
|
2013-01-20 08:54:54 +01:00
|
|
|
TORRENT_LOG(traversal)
|
|
|
|
<< "[" << m_algorithm.get() << "] PEERS"
|
|
|
|
<< " invoke-count: " << m_algorithm->invoke_count()
|
|
|
|
<< " branch-factor: " << m_algorithm->branch_factor()
|
|
|
|
<< " addr: " << m.addr
|
|
|
|
<< " id: " << node_id(id->string_ptr())
|
|
|
|
<< " distance: " << distance_exp(m_algorithm->target(), node_id(id->string_ptr()))
|
|
|
|
<< " p: " << n->list_size();
|
2009-09-20 02:23:36 +02:00
|
|
|
#endif
|
|
|
|
}
|
2009-10-07 22:51:02 +02:00
|
|
|
static_cast<find_data*>(m_algorithm.get())->got_peers(peer_list);
|
2009-09-20 02:23:36 +02:00
|
|
|
}
|
|
|
|
|
2013-12-15 00:25:38 +01:00
|
|
|
traversal_observer::reply(m);
|
2008-12-23 21:04:12 +01:00
|
|
|
|
2013-12-15 00:25:38 +01:00
|
|
|
done();
|
|
|
|
}
|
|
|
|
|
|
|
|
void obfuscated_find_data_observer::reply(msg const& m)
|
|
|
|
{
|
|
|
|
lazy_entry const* r = m.message.dict_find_dict("r");
|
|
|
|
if (!r)
|
|
|
|
{
|
|
|
|
#ifdef TORRENT_DHT_VERBOSE_LOGGING
|
|
|
|
TORRENT_LOG(traversal) << "[" << m_algorithm.get()
|
|
|
|
<< "] missing response dict";
|
|
|
|
#endif
|
|
|
|
return;
|
2009-09-20 02:23:36 +02:00
|
|
|
}
|
2008-12-23 21:04:12 +01:00
|
|
|
|
2013-12-15 00:25:38 +01:00
|
|
|
lazy_entry const* id = r->dict_find_string("id");
|
|
|
|
if (!id || id->string_length() != 20)
|
2006-08-01 17:27:08 +02:00
|
|
|
{
|
2013-12-15 00:25:38 +01:00
|
|
|
#ifdef TORRENT_DHT_VERBOSE_LOGGING
|
|
|
|
TORRENT_LOG(traversal) << "[" << m_algorithm.get()
|
|
|
|
<< "] invalid id in response";
|
2009-09-20 02:23:36 +02:00
|
|
|
#endif
|
2013-12-15 00:25:38 +01:00
|
|
|
return;
|
2006-08-01 17:27:08 +02:00
|
|
|
}
|
2013-12-15 00:25:38 +01:00
|
|
|
|
|
|
|
traversal_observer::reply(m);
|
|
|
|
|
2009-10-07 22:51:02 +02:00
|
|
|
done();
|
2006-08-01 17:27:08 +02:00
|
|
|
}
|
|
|
|
|
2010-01-03 12:08:39 +01:00
|
|
|
void add_entry_fun(void* userdata, node_entry const& e)
|
|
|
|
{
|
|
|
|
traversal_algorithm* f = (traversal_algorithm*)userdata;
|
2010-11-05 20:06:50 +01:00
|
|
|
f->add_entry(e.id, e.ep(), observer::flag_initial);
|
2010-01-03 12:08:39 +01:00
|
|
|
}
|
|
|
|
|
2006-08-01 17:27:08 +02:00
|
|
|
find_data::find_data(
|
2008-09-20 19:42:25 +02:00
|
|
|
node_impl& node
|
|
|
|
, node_id target
|
2008-12-23 21:04:12 +01:00
|
|
|
, data_callback const& dcallback
|
2011-05-23 02:45:36 +02:00
|
|
|
, nodes_callback const& ncallback
|
|
|
|
, bool noseeds)
|
2009-09-20 02:23:36 +02:00
|
|
|
: traversal_algorithm(node, target)
|
2008-12-23 21:04:12 +01:00
|
|
|
, m_data_callback(dcallback)
|
|
|
|
, m_nodes_callback(ncallback)
|
|
|
|
, m_target(target)
|
2006-08-01 17:27:08 +02:00
|
|
|
, m_done(false)
|
2009-10-07 22:51:02 +02:00
|
|
|
, m_got_peers(false)
|
2011-05-23 02:45:36 +02:00
|
|
|
, m_noseeds(noseeds)
|
2006-08-01 17:27:08 +02:00
|
|
|
{
|
2013-09-09 09:08:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void find_data::start()
|
|
|
|
{
|
|
|
|
// if the user didn't add seed-nodes manually, grab a bunch of nodes from the
|
|
|
|
// routing table
|
|
|
|
if (m_results.empty())
|
|
|
|
m_node.m_table.for_each_node(&add_entry_fun, 0, (traversal_algorithm*)this);
|
|
|
|
|
|
|
|
traversal_algorithm::start();
|
2006-08-01 17:27:08 +02:00
|
|
|
}
|
|
|
|
|
2010-11-05 20:06:50 +01:00
|
|
|
observer_ptr find_data::new_observer(void* ptr
|
|
|
|
, udp::endpoint const& ep, node_id const& id)
|
|
|
|
{
|
|
|
|
observer_ptr o(new (ptr) find_data_observer(this, ep, id));
|
2012-01-16 23:48:43 +01:00
|
|
|
#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS
|
2010-11-05 20:06:50 +01:00
|
|
|
o->m_in_constructor = false;
|
|
|
|
#endif
|
|
|
|
return o;
|
|
|
|
}
|
|
|
|
|
2013-09-09 09:08:02 +02:00
|
|
|
char const* find_data::name() const { return "get_peers"; }
|
|
|
|
|
2010-11-05 20:06:50 +01:00
|
|
|
bool find_data::invoke(observer_ptr o)
|
2006-08-01 17:27:08 +02:00
|
|
|
{
|
|
|
|
if (m_done)
|
|
|
|
{
|
|
|
|
m_invoke_count = -1;
|
2009-10-07 22:51:02 +02:00
|
|
|
return false;
|
2006-08-01 17:27:08 +02:00
|
|
|
}
|
|
|
|
|
2009-09-20 02:23:36 +02:00
|
|
|
entry e;
|
|
|
|
e["y"] = "q";
|
|
|
|
entry& a = e["a"];
|
2013-09-09 09:08:02 +02:00
|
|
|
|
|
|
|
e["q"] = "get_peers";
|
2009-10-09 04:34:25 +02:00
|
|
|
a["info_hash"] = m_target.to_string();
|
2011-05-23 02:45:36 +02:00
|
|
|
if (m_noseeds) a["noseed"] = 1;
|
2013-09-09 09:08:02 +02:00
|
|
|
|
2010-11-05 20:06:50 +01:00
|
|
|
return m_node.m_rpc.invoke(e, o->target_ep(), o);
|
2006-08-01 17:27:08 +02:00
|
|
|
}
|
|
|
|
|
2009-09-20 02:23:36 +02:00
|
|
|
void find_data::got_peers(std::vector<tcp::endpoint> const& peers)
|
2006-08-01 17:27:08 +02:00
|
|
|
{
|
2009-10-07 22:51:02 +02:00
|
|
|
if (!peers.empty()) m_got_peers = true;
|
2013-09-09 09:08:02 +02:00
|
|
|
if (m_data_callback) m_data_callback(peers);
|
2006-08-01 17:27:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void find_data::done()
|
|
|
|
{
|
|
|
|
if (m_invoke_count != 0) return;
|
2008-12-23 21:04:12 +01:00
|
|
|
|
2009-10-07 22:51:02 +02:00
|
|
|
m_done = true;
|
|
|
|
|
2010-11-05 20:06:50 +01:00
|
|
|
#ifdef TORRENT_DHT_VERBOSE_LOGGING
|
2013-01-20 08:54:54 +01:00
|
|
|
TORRENT_LOG(traversal) << "[" << this << "] get_peers DONE";
|
2010-11-05 20:06:50 +01:00
|
|
|
#endif
|
|
|
|
|
2008-12-23 21:04:12 +01:00
|
|
|
std::vector<std::pair<node_entry, std::string> > results;
|
|
|
|
int num_results = m_node.m_table.bucket_size();
|
2010-11-05 20:06:50 +01:00
|
|
|
for (std::vector<observer_ptr>::iterator i = m_results.begin()
|
2008-12-23 21:04:12 +01:00
|
|
|
, end(m_results.end()); i != end && num_results > 0; ++i)
|
|
|
|
{
|
2010-11-05 20:06:50 +01:00
|
|
|
observer_ptr const& o = *i;
|
|
|
|
if (o->flags & observer::flag_no_id) continue;
|
|
|
|
if ((o->flags & observer::flag_queried) == 0) continue;
|
|
|
|
std::map<node_id, std::string>::iterator j = m_write_tokens.find(o->id());
|
2008-12-23 21:04:12 +01:00
|
|
|
if (j == m_write_tokens.end()) continue;
|
2010-11-05 20:06:50 +01:00
|
|
|
results.push_back(std::make_pair(node_entry(o->id(), o->target_ep()), j->second));
|
2008-12-23 21:04:12 +01:00
|
|
|
--num_results;
|
|
|
|
}
|
2013-09-09 09:08:02 +02:00
|
|
|
if (m_nodes_callback) m_nodes_callback(results, m_got_peers);
|
2010-11-05 20:06:50 +01:00
|
|
|
|
|
|
|
traversal_algorithm::done();
|
2006-08-01 17:27:08 +02:00
|
|
|
}
|
|
|
|
|
2013-09-09 09:08:02 +02:00
|
|
|
obfuscated_get_peers::obfuscated_get_peers(
|
|
|
|
node_impl& node
|
|
|
|
, node_id info_hash
|
|
|
|
, data_callback const& dcallback
|
|
|
|
, nodes_callback const& ncallback
|
|
|
|
, bool noseeds)
|
|
|
|
: find_data(node, info_hash, dcallback, ncallback, noseeds)
|
2013-10-27 00:59:55 +02:00
|
|
|
, m_obfuscated(true)
|
2013-09-09 09:08:02 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2013-12-15 00:25:38 +01:00
|
|
|
char const* obfuscated_get_peers::name() const
|
|
|
|
{ return !m_obfuscated ? find_data::name() : "get_peers [obfuscated]"; }
|
2013-09-09 09:08:02 +02:00
|
|
|
|
|
|
|
observer_ptr obfuscated_get_peers::new_observer(void* ptr
|
|
|
|
, udp::endpoint const& ep, node_id const& id)
|
|
|
|
{
|
2013-12-15 00:25:38 +01:00
|
|
|
if (m_obfuscated)
|
|
|
|
{
|
|
|
|
observer_ptr o(new (ptr) obfuscated_find_data_observer(this, ep, id));
|
2013-09-09 09:08:02 +02:00
|
|
|
#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS
|
2013-12-15 00:25:38 +01:00
|
|
|
o->m_in_constructor = false;
|
2013-09-09 09:08:02 +02:00
|
|
|
#endif
|
2013-12-15 00:25:38 +01:00
|
|
|
return o;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
observer_ptr o(new (ptr) find_data_observer(this, ep, id));
|
|
|
|
#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS
|
|
|
|
o->m_in_constructor = false;
|
|
|
|
#endif
|
|
|
|
return o;
|
|
|
|
}
|
2013-09-09 09:08:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool obfuscated_get_peers::invoke(observer_ptr o)
|
|
|
|
{
|
2013-10-27 00:59:55 +02:00
|
|
|
if (!m_obfuscated) return find_data::invoke(o);
|
|
|
|
|
|
|
|
node_id id = o->id();
|
|
|
|
int shared_prefix = 160 - distance_exp(id, m_target);
|
|
|
|
|
|
|
|
// when we get close to the target zone in the DHT
|
|
|
|
// start using the correct info-hash, in order to
|
|
|
|
// start receiving peers
|
|
|
|
if (shared_prefix > m_node.m_table.depth() - 10)
|
|
|
|
{
|
|
|
|
m_obfuscated = false;
|
|
|
|
// clear the queried bits on all successful nodes in
|
|
|
|
// our node-list for this traversal algorithm, to
|
|
|
|
// allow the get_peers traversal to regress in case
|
|
|
|
// nodes further down end up being dead
|
|
|
|
for (std::vector<observer_ptr>::iterator i = m_results.begin()
|
|
|
|
, end(m_results.end()); i != end; ++i)
|
|
|
|
{
|
|
|
|
observer* o = i->get();
|
|
|
|
// don't re-request from nodes that didn't respond
|
|
|
|
if (o->flags & observer::flag_failed) continue;
|
|
|
|
// don't interrupt with queries that are already in-flight
|
|
|
|
if ((o->flags & observer::flag_alive) == 0) continue;
|
|
|
|
o->flags &= ~(observer::flag_queried | observer::flag_alive);
|
|
|
|
}
|
|
|
|
return find_data::invoke(o);
|
|
|
|
}
|
|
|
|
|
2013-09-09 09:08:02 +02:00
|
|
|
entry e;
|
|
|
|
e["y"] = "q";
|
|
|
|
e["q"] = "find_node";
|
|
|
|
entry& a = e["a"];
|
|
|
|
|
|
|
|
// This logic will obfuscate the target info-hash
|
|
|
|
// we're looking up, in order to preserve more privacy
|
|
|
|
// on the DHT. This is done by only including enough
|
|
|
|
// bits in the info-hash for the node we're querying to
|
|
|
|
// give a good answer, but not more.
|
|
|
|
|
|
|
|
// now, obfuscate the bits past shared_prefix + 5
|
|
|
|
node_id obfuscated_target = generate_random_id();
|
2013-10-17 01:19:18 +02:00
|
|
|
obfuscated_target >>= shared_prefix + 3;
|
2013-09-09 09:08:02 +02:00
|
|
|
obfuscated_target^= m_target;
|
|
|
|
a["target"] = obfuscated_target.to_string();
|
|
|
|
|
|
|
|
return m_node.m_rpc.invoke(e, o->target_ep(), o);
|
|
|
|
}
|
|
|
|
|
|
|
|
void obfuscated_get_peers::done()
|
|
|
|
{
|
2013-10-27 00:59:55 +02:00
|
|
|
if (!m_obfuscated) return find_data::done();
|
|
|
|
|
|
|
|
// oops, we failed to switch over to the non-obfuscated
|
|
|
|
// mode early enough. do it now
|
|
|
|
|
2013-09-09 09:08:02 +02:00
|
|
|
boost::intrusive_ptr<find_data> ta(new find_data(m_node, m_target
|
|
|
|
, m_data_callback
|
|
|
|
, m_nodes_callback
|
|
|
|
, m_noseeds));
|
|
|
|
|
|
|
|
// don't call these when the obfuscated_get_peers
|
|
|
|
// is done, we're passing them on to be called when
|
|
|
|
// ta completes.
|
|
|
|
m_data_callback.clear();
|
|
|
|
m_nodes_callback.clear();
|
|
|
|
|
|
|
|
#ifdef TORRENT_DHT_VERBOSE_LOGGING
|
|
|
|
TORRENT_LOG(traversal) << " [" << this << "]"
|
|
|
|
<< " obfuscated get_peers phase 1 done, spawning get_peers [" << ta.get() << "]";
|
|
|
|
#endif
|
|
|
|
|
|
|
|
int num_added = 0;
|
|
|
|
for (std::vector<observer_ptr>::iterator i = m_results.begin()
|
2013-10-27 00:59:55 +02:00
|
|
|
, end(m_results.end()); i != end && num_added < 16; ++i)
|
2013-09-09 09:08:02 +02:00
|
|
|
{
|
|
|
|
observer_ptr o = *i;
|
|
|
|
|
2013-10-27 00:59:55 +02:00
|
|
|
// only add nodes whose node ID we know and that
|
2013-09-09 09:08:02 +02:00
|
|
|
// we know are alive
|
|
|
|
if (o->flags & observer::flag_no_id) continue;
|
|
|
|
if ((o->flags & observer::flag_alive) == 0) continue;
|
|
|
|
|
|
|
|
ta->add_entry(o->id(), o->target_ep(), observer::flag_initial);
|
|
|
|
++num_added;
|
|
|
|
}
|
|
|
|
|
|
|
|
ta->start();
|
|
|
|
|
|
|
|
find_data::done();
|
|
|
|
}
|
|
|
|
|
2006-08-01 17:27:08 +02:00
|
|
|
} } // namespace libtorrent::dht
|
|
|
|
|