From 48ef3b6bf7e77a30c59dcf996f04154b2e47c206 Mon Sep 17 00:00:00 2001 From: Alden Torres Date: Mon, 12 Jun 2017 05:54:11 -0400 Subject: [PATCH] implemented support for BEP 51 (#1652) --- CMakeLists.txt | 1 + ChangeLog | 1 + Jamfile | 1 + include/libtorrent/Makefile.am | 1 + include/libtorrent/alert_types.hpp | 47 +++++- include/libtorrent/aux_/session_impl.hpp | 1 + include/libtorrent/kademlia/dht_tracker.hpp | 5 + include/libtorrent/kademlia/node.hpp | 5 + .../libtorrent/kademlia/sample_infohashes.hpp | 79 ++++++++++ include/libtorrent/performance_counters.hpp | 3 + include/libtorrent/session_handle.hpp | 8 + include/libtorrent/socket_io.hpp | 2 +- src/Makefile.am | 1 + src/alert.cpp | 58 +++++++ src/kademlia/dht_tracker.cpp | 14 ++ src/kademlia/node.cpp | 57 +++++++ src/kademlia/rpc_manager.cpp | 2 + src/kademlia/sample_infohashes.cpp | 146 ++++++++++++++++++ src/session_handle.cpp | 10 ++ src/session_impl.cpp | 13 ++ src/session_stats.cpp | 3 + test/test_alert_types.cpp | 60 ++++++- test/test_dht.cpp | 90 +++++++++++ 23 files changed, 605 insertions(+), 3 deletions(-) create mode 100644 include/libtorrent/kademlia/sample_infohashes.hpp create mode 100644 src/kademlia/sample_infohashes.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c45d33f3..65ff679a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -151,6 +151,7 @@ set(kademlia_sources get_peers get_item ed25519 + sample_infohashes ) # -- ed25519 -- diff --git a/ChangeLog b/ChangeLog index 35a3ae734..255b98985 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,4 @@ + * implemented support for DHT infohash indexing, BEP51 * removed deprecated support for file_base in file_storage * added support for running separate DHT nodes on each network interface * added support for establishing UTP connections on any network interface diff --git a/Jamfile b/Jamfile index 000a1adee..92b6581ed 100644 --- a/Jamfile +++ b/Jamfile @@ -693,6 +693,7 @@ KADEMLIA_SOURCES = get_item put_data ed25519 + sample_infohashes ; ED25519_SOURCES = diff --git a/include/libtorrent/Makefile.am b/include/libtorrent/Makefile.am index 162a36858..a181d5513 100644 --- a/include/libtorrent/Makefile.am +++ b/include/libtorrent/Makefile.am @@ -235,4 +235,5 @@ nobase_include_HEADERS = \ kademlia/ed25519.hpp \ kademlia/item.hpp \ kademlia/get_item.hpp \ + kademlia/sample_infohashes.hpp \ kademlia/get_peers.hpp diff --git a/include/libtorrent/alert_types.hpp b/include/libtorrent/alert_types.hpp index 21d847db0..228c1ba5b 100644 --- a/include/libtorrent/alert_types.hpp +++ b/include/libtorrent/alert_types.hpp @@ -2590,11 +2590,56 @@ namespace libtorrent { virtual std::string message() const override; }; + struct TORRENT_EXPORT dht_sample_infohashes_alert final : alert + { + dht_sample_infohashes_alert(aux::stack_allocator& alloc + , udp::endpoint const& endp + , time_duration interval + , int num + , std::vector const& samples + , std::vector> const& nodes); + + static const int static_category = alert::dht_operation_notification; + TORRENT_DEFINE_ALERT(dht_sample_infohashes_alert, 93) + + virtual std::string message() const override; + + aux::noexcept_movable endpoint; + + time_duration const interval; + + // This field indicates how many infohash keys are currently in the node's storage. + // If the value is larger than the number of returned samples it indicates that the + // indexer may obtain additional samples after waiting out the interval. + int const num_infohashes; + + int num_samples() const; + std::vector samples() const; + + // The total number of nodes returned by ``nodes()``. + int num_nodes() const; + + // This is the set of more DHT nodes returned by the request. + // + // The information is included so that indexing nodes can perform a keyspace + // traversal with a single RPC per node by adjusting the target value for each RPC. + std::vector> nodes() const; + + private: + std::reference_wrapper m_alloc; + int const m_num_samples; + aux::allocation_slot m_samples_idx; + int m_v4_num_nodes = 0; + int m_v6_num_nodes = 0; + aux::allocation_slot m_v4_nodes_idx; + aux::allocation_slot m_v6_nodes_idx; + }; + #undef TORRENT_DEFINE_ALERT_IMPL #undef TORRENT_DEFINE_ALERT #undef TORRENT_DEFINE_ALERT_PRIO - enum { num_alert_types = 93 }; // this enum represents "max_alert_index" + 1 + enum { num_alert_types = 94 }; // this enum represents "max_alert_index" + 1 } #endif diff --git a/include/libtorrent/aux_/session_impl.hpp b/include/libtorrent/aux_/session_impl.hpp index b36978bc8..d9547d218 100644 --- a/include/libtorrent/aux_/session_impl.hpp +++ b/include/libtorrent/aux_/session_impl.hpp @@ -403,6 +403,7 @@ namespace aux { void dht_announce(sha1_hash const& info_hash, int port = 0, int flags = 0); void dht_live_nodes(sha1_hash const& nid); + void dht_sample_infohashes(udp::endpoint const& ep, sha1_hash const& target); void dht_direct_request(udp::endpoint const& ep, entry& e , void* userdata = nullptr); diff --git a/include/libtorrent/kademlia/dht_tracker.hpp b/include/libtorrent/kademlia/dht_tracker.hpp index 94ed7c6b1..523cc4d18 100644 --- a/include/libtorrent/kademlia/dht_tracker.hpp +++ b/include/libtorrent/kademlia/dht_tracker.hpp @@ -100,6 +100,11 @@ namespace libtorrent { namespace dht { void announce(sha1_hash const& ih, int listen_port, int flags , std::function const&)> f); + void sample_infohashes(udp::endpoint const& ep, sha1_hash const& target + , std::function + , std::vector>)> f); + void get_item(sha1_hash const& target , std::function cb); diff --git a/include/libtorrent/kademlia/node.hpp b/include/libtorrent/kademlia/node.hpp index e253b9e9b..89c80238a 100644 --- a/include/libtorrent/kademlia/node.hpp +++ b/include/libtorrent/kademlia/node.hpp @@ -149,6 +149,11 @@ public: , std::function f , std::function data_cb); + void sample_infohashes(udp::endpoint const& ep, sha1_hash const& target + , std::function + , std::vector>)> f); + bool verify_token(string_view token, sha1_hash const& info_hash , udp::endpoint const& addr) const; diff --git a/include/libtorrent/kademlia/sample_infohashes.hpp b/include/libtorrent/kademlia/sample_infohashes.hpp new file mode 100644 index 000000000..5a3f7a1ca --- /dev/null +++ b/include/libtorrent/kademlia/sample_infohashes.hpp @@ -0,0 +1,79 @@ +/* + +Copyright (c) 2017, Arvid Norberg, Alden Torres +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. + +*/ + +#ifndef TORRENT_SAMPLE_INFOHASHES_HPP +#define TORRENT_SAMPLE_INFOHASHES_HPP + +#include + +#include +#include + +namespace libtorrent { namespace dht +{ + +class sample_infohashes final : public traversal_algorithm +{ +public: + + using data_callback = std::function + , std::vector>)>; + + sample_infohashes(node& dht_node + , node_id const& target + , data_callback const& dcallback); + + virtual char const* name() const override; + + void got_samples(time_duration interval + , int num, std::vector samples + , std::vector> nodes); + +protected: + + data_callback m_data_callback; +}; + +class sample_infohashes_observer final : public traversal_observer +{ +public: + + sample_infohashes_observer(std::shared_ptr const& algorithm + , udp::endpoint const& ep, node_id const& id); + + virtual void reply(msg const&) override; +}; + +}} // namespace libtorrent::dht + +#endif // TORRENT_SAMPLE_INFOHASHES_HPP diff --git a/include/libtorrent/performance_counters.hpp b/include/libtorrent/performance_counters.hpp index c4bebff48..092b92d62 100644 --- a/include/libtorrent/performance_counters.hpp +++ b/include/libtorrent/performance_counters.hpp @@ -235,12 +235,15 @@ namespace libtorrent { dht_get_out, dht_put_in, dht_put_out, + dht_sample_infohashes_in, + dht_sample_infohashes_out, dht_invalid_announce, dht_invalid_get_peers, dht_invalid_find_node, dht_invalid_put, dht_invalid_get, + dht_invalid_sample_infohashes, // uTP counters. utp_packet_loss, diff --git a/include/libtorrent/session_handle.hpp b/include/libtorrent/session_handle.hpp index 75727b56c..2b4a606c8 100644 --- a/include/libtorrent/session_handle.hpp +++ b/include/libtorrent/session_handle.hpp @@ -452,6 +452,14 @@ namespace libtorrent { // posted, regardless of the alert mask. void dht_live_nodes(sha1_hash const& nid); + // Query the DHT node specified by ``ep`` to retrieve a sample of the + // infohashes that the node currently have in their storage. + // The ``target`` is included for iterative lookups so that indexing nodes + // can perform a keyspace traversal with a single RPC per node by adjusting + // the target value for each RPC. It has no effect on the returned sample value. + // The result is posted as a ``dht_sample_infohashes_alert``. + void dht_sample_infohashes(udp::endpoint const& ep, sha1_hash const& target); + // Send an arbitrary DHT request directly to the specified endpoint. This // function is intended for use by plugins. When a response is received // or the request times out, a dht_direct_response_alert will be posted diff --git a/include/libtorrent/socket_io.hpp b/include/libtorrent/socket_io.hpp index c2974618f..0568eeb8a 100644 --- a/include/libtorrent/socket_io.hpp +++ b/include/libtorrent/socket_io.hpp @@ -58,7 +58,7 @@ namespace libtorrent { namespace detail { template - size_t address_size(Proto p) + std::size_t address_size(Proto p) { TORRENT_UNUSED(p); #if TORRENT_USE_IPV6 diff --git a/src/Makefile.am b/src/Makefile.am index 526defb75..d99bb046e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -22,6 +22,7 @@ KADEMLIA_SOURCES = \ kademlia/get_item.cpp \ kademlia/item.cpp \ kademlia/ed25519.cpp \ + kademlia/sample_infohashes.cpp \ ../ed25519/src/add_scalar.cpp \ ../ed25519/src/fe.cpp \ ../ed25519/src/ge.cpp \ diff --git a/src/alert.cpp b/src/alert.cpp index 9862dedc3..58ee69827 100644 --- a/src/alert.cpp +++ b/src/alert.cpp @@ -2275,4 +2275,62 @@ namespace { return stats_header; } + dht_sample_infohashes_alert::dht_sample_infohashes_alert(aux::stack_allocator& alloc + , udp::endpoint const& endp + , time_duration _interval + , int _num + , std::vector const& samples + , std::vector> const& nodes) + : endpoint(endp) + , interval(_interval) + , num_infohashes(_num) + , m_alloc(alloc) + , m_num_samples(aux::numeric_cast(samples.size())) + { + m_samples_idx = alloc.allocate(m_num_samples * 20); + + char *ptr = alloc.ptr(m_samples_idx); + std::memcpy(ptr, samples.data(), samples.size() * 20); + + std::tie(m_v4_num_nodes, m_v4_nodes_idx, m_v6_num_nodes, m_v6_nodes_idx) + = write_nodes(alloc, nodes); + } + + std::string dht_sample_infohashes_alert::message() const + { + char msg[200]; + std::snprintf(msg, sizeof(msg) + , "incoming dht sample_infohashes reply from: %s, samples %d" + , print_endpoint(endpoint).c_str(), m_num_samples); + return msg; + } + + int dht_sample_infohashes_alert::num_samples() const + { + return m_num_samples; + } + + std::vector dht_sample_infohashes_alert::samples() const + { + aux::vector samples; + samples.resize(m_num_samples); + + const char *ptr = m_alloc.get().ptr(m_samples_idx); + std::memcpy(samples.data(), ptr, samples.size() * 20); + + return samples; + } + + int dht_sample_infohashes_alert::num_nodes() const + { + return m_v4_num_nodes + m_v6_num_nodes; + } + + std::vector> dht_sample_infohashes_alert::nodes() const + { + return read_nodes(m_alloc.get() + , m_v4_num_nodes, m_v4_nodes_idx + , m_v6_num_nodes, m_v6_nodes_idx); + } + } // namespace libtorrent diff --git a/src/kademlia/dht_tracker.cpp b/src/kademlia/dht_tracker.cpp index 78c1e4b62..ae04c7379 100644 --- a/src/kademlia/dht_tracker.cpp +++ b/src/kademlia/dht_tracker.cpp @@ -322,6 +322,20 @@ namespace libtorrent { namespace dht { n.second.dht.announce(ih, listen_port, flags, f); } + void dht_tracker::sample_infohashes(udp::endpoint const& ep, sha1_hash const& target + , std::function + , std::vector>)> f) + { + for (auto& n : m_nodes) + { + if (ep.protocol() != (n.first->get_external_address().is_v4() ? udp::v4() : udp::v6())) + continue; + n.second.dht.sample_infohashes(ep, target, f); + break; + } + } + namespace { struct get_immutable_item_ctx diff --git a/src/kademlia/node.cpp b/src/kademlia/node.cpp index d86306abf..63bdf4802 100644 --- a/src/kademlia/node.cpp +++ b/src/kademlia/node.cpp @@ -64,6 +64,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/kademlia/get_item.hpp" #include "libtorrent/kademlia/msg.hpp" #include +#include using namespace std::placeholders; @@ -565,6 +566,38 @@ void node::put_item(public_key const& pk, std::string const& salt ta->start(); } +void node::sample_infohashes(udp::endpoint const& ep, sha1_hash const& target + , std::function + , std::vector>)> f) +{ +#ifndef TORRENT_DISABLE_LOGGING + if (m_observer != nullptr && m_observer->should_log(dht_logger::node)) + { + m_observer->log(dht_logger::node, "starting sample_infohashes for [ node: %s, target: %s ]" + , print_endpoint(ep).c_str(), aux::to_hex(target).c_str()); + } +#endif + + // not an actual traversal + auto ta = std::make_shared(*this, node_id(), std::move(f)); + + auto o = m_rpc.allocate_observer(ta, ep, node_id()); + if (!o) return; +#if TORRENT_USE_ASSERTS + o->m_in_constructor = false; +#endif + + entry e; + + e["q"] = "sample_infohashes"; + e["a"]["target"] = target; + + stats_counters().inc_stats_counter(counters::dht_sample_infohashes_out); + + m_rpc.invoke(e, ep, o); +} + struct ping_observer : observer { ping_observer( @@ -1104,6 +1137,30 @@ void node::incoming_request(msg const& m, entry& e) , reply); } } + else if (query == "sample_infohashes") + { + key_desc_t const msg_desc[] = { + {"target", bdecode_node::string_t, 20, 0}, + {"want", bdecode_node::list_t, 0, key_desc_t::optional}, + }; + + bdecode_node msg_keys[2]; + if (!verify_message(arg_ent, msg_desc, msg_keys, error_string)) + { + m_counters.inc_stats_counter(counters::dht_invalid_sample_infohashes); + incoming_error(e, error_string); + return; + } + + m_counters.inc_stats_counter(counters::dht_sample_infohashes_in); + sha1_hash const target(msg_keys[0].string_ptr()); + + // TODO: keep the returned value to pass as a limit + // to write_nodes_entries when implemented + m_storage.get_infohashes_sample(reply); + + write_nodes_entries(target, msg_keys[1], reply); + } else { // if we don't recognize the message but there's a diff --git a/src/kademlia/rpc_manager.cpp b/src/kademlia/rpc_manager.cpp index dbf5669da..edc507ab7 100644 --- a/src/kademlia/rpc_manager.cpp +++ b/src/kademlia/rpc_manager.cpp @@ -43,6 +43,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include // for print_endpoint #include @@ -143,6 +144,7 @@ using observer_storage = aux::aligned_union<1 , get_item_observer , get_peers_observer , obfuscated_get_peers_observer + , sample_infohashes_observer , null_observer , traversal_observer>::type; diff --git a/src/kademlia/sample_infohashes.cpp b/src/kademlia/sample_infohashes.cpp new file mode 100644 index 000000000..c07322ec0 --- /dev/null +++ b/src/kademlia/sample_infohashes.cpp @@ -0,0 +1,146 @@ +/* + +Copyright (c) 2017, Arvid Norberg, Alden Torres +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. + +*/ + +#include +#include +#include +#include +#include +#include +#include + +namespace libtorrent { namespace dht +{ + +sample_infohashes::sample_infohashes(node& dht_node + , node_id const& target + , data_callback const& dcallback) + : traversal_algorithm(dht_node, target) + , m_data_callback(dcallback) {} + +char const* sample_infohashes::name() const { return "sample_infohashes"; } + +void sample_infohashes::got_samples(time_duration interval + , int num, std::vector samples + , std::vector> nodes) +{ + if (m_data_callback) + { + m_data_callback(interval, num, std::move(samples), std::move(nodes)); + m_data_callback = nullptr; + done(); + } +} + +sample_infohashes_observer::sample_infohashes_observer( + std::shared_ptr const& algorithm + , udp::endpoint const& ep, node_id const& id) + : traversal_observer(algorithm, ep, id) {} + +void sample_infohashes_observer::reply(msg const& m) +{ + bdecode_node r = m.message.dict_find_dict("r"); + if (!r) + { +#ifndef TORRENT_DISABLE_LOGGING + get_observer()->log(dht_logger::traversal, "[%u] missing response dict" + , algorithm()->id()); +#endif + timeout(); + return; + } + + // look for nodes + std::vector> nodes; + udp const protocol = algorithm()->get_node().protocol(); + int const protocol_size = int(detail::address_size(protocol)); + char const* nodes_key = algorithm()->get_node().protocol_nodes_key(); + bdecode_node const n = r.dict_find_string(nodes_key); + if (n) + { + char const* ptr = n.string_ptr(); + char const* end = ptr + n.string_length(); + + while (end - ptr >= 20 + protocol_size + 2) + { + node_endpoint nep = read_node_endpoint(protocol, ptr); + nodes.emplace_back(nep.id, nep.ep); + } + } + + std::int64_t const interval = r.dict_find_int_value("interval", -1); + if (interval < 0 || interval > 21600) // TODO: put constant in a common place + { +#ifndef TORRENT_DISABLE_LOGGING + get_observer()->log(dht_logger::traversal, "[%u] wrong or missing interval value" + , algorithm()->id()); +#endif + timeout(); + return; + } + + std::int64_t const num = r.dict_find_int_value("num", -1); + if (num < 0 || num > std::numeric_limits::max()) + { +#ifndef TORRENT_DISABLE_LOGGING + get_observer()->log(dht_logger::traversal, "[%u] wrong or missing num value" + , algorithm()->id()); +#endif + timeout(); + return; + } + + bdecode_node samples = r.dict_find_string("samples"); + if (samples && (samples.string_length() % 20 == 0)) + { + std::vector v(aux::numeric_cast(samples.string_length() / 20)); + std::memcpy(v.data(), samples.string_ptr(), v.size() * 20); + + static_cast(algorithm())->got_samples( + seconds(interval), int(num), std::move(v), std::move(nodes)); + } + else + { +#ifndef TORRENT_DISABLE_LOGGING + get_observer()->log(dht_logger::traversal, "[%u] wrong or missing samples value" + , algorithm()->id()); +#endif + timeout(); + } + + traversal_observer::reply(m); + // this is necessary to play nice with + // observer::abort(), observer::done() and observer::timeout() + flags |= flag_done; +} + +}} // namespace libtorrent::dht diff --git a/src/session_handle.cpp b/src/session_handle.cpp index bd1a34390..f12cf52df 100644 --- a/src/session_handle.cpp +++ b/src/session_handle.cpp @@ -623,6 +623,16 @@ namespace { #endif } + void session_handle::dht_sample_infohashes(udp::endpoint const& ep, sha1_hash const& target) + { +#ifndef TORRENT_DISABLE_DHT + async_call(&session_impl::dht_sample_infohashes, ep, target); +#else + TORRENT_UNUSED(ep); + TORRENT_UNUSED(target); +#endif + } + void session_handle::dht_direct_request(udp::endpoint const& ep, entry const& e, void* userdata) { #ifndef TORRENT_DISABLE_DHT diff --git a/src/session_impl.cpp b/src/session_impl.cpp index 43a9b49b6..eea9131cb 100644 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -5954,6 +5954,19 @@ namespace { m_alerts.emplace_alert(nid, nodes); } + void session_impl::dht_sample_infohashes(udp::endpoint const& ep, sha1_hash const& target) + { + if (!m_dht) return; + m_dht->sample_infohashes(ep, target, [this, &ep](time_duration interval + , int num, std::vector samples + , std::vector> nodes) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(ep + , interval, num, samples, nodes); + }); + } + void session_impl::dht_direct_request(udp::endpoint const& ep, entry& e, void* userdata) { if (!m_dht) return; diff --git a/src/session_stats.cpp b/src/session_stats.cpp index e4906ce59..3d56e1d50 100644 --- a/src/session_stats.cpp +++ b/src/session_stats.cpp @@ -472,6 +472,8 @@ namespace { METRIC(dht, dht_get_out) METRIC(dht, dht_put_in) METRIC(dht, dht_put_out) + METRIC(dht, dht_sample_infohashes_in) + METRIC(dht, dht_sample_infohashes_out) // the number of failed incoming DHT requests by kind of request METRIC(dht, dht_invalid_announce) @@ -479,6 +481,7 @@ namespace { METRIC(dht, dht_invalid_find_node) METRIC(dht, dht_invalid_put) METRIC(dht, dht_invalid_get) + METRIC(dht, dht_invalid_sample_infohashes) // uTP counters. Each counter represents the number of time each event // has occurred. diff --git a/test/test_alert_types.cpp b/test/test_alert_types.cpp index 9c3438a89..8af5de1b6 100644 --- a/test/test_alert_types.cpp +++ b/test/test_alert_types.cpp @@ -160,10 +160,11 @@ TORRENT_TEST(alerts_types) TEST_ALERT_TYPE(session_error_alert, 90, 0, alert::error_notification); TEST_ALERT_TYPE(dht_live_nodes_alert, 91, 0, alert::dht_notification); TEST_ALERT_TYPE(session_stats_header_alert, 92, 0, alert::stats_notification); + TEST_ALERT_TYPE(dht_sample_infohashes_alert, 93, 0, alert::dht_operation_notification); #undef TEST_ALERT_TYPE - TEST_EQUAL(num_alert_types, 93); + TEST_EQUAL(num_alert_types, 94); TEST_EQUAL(num_alert_types, count_alert_types); } @@ -263,3 +264,60 @@ TORRENT_TEST(session_stats_alert) TEST_CHECK(v != nullptr); TEST_CHECK(v->message().find("session stats (") != std::string::npos); } + +TORRENT_TEST(dht_sample_infohashes_alert) +{ + alert_manager mgr(1, dht_sample_infohashes_alert::static_category); + + TEST_EQUAL(mgr.should_post(), true); + + udp::endpoint const endpoint = rand_udp_ep(); + time_duration const interval = seconds(10); + int const num = 100; + sha1_hash const h1 = rand_hash(); + sha1_hash const h2 = rand_hash(); + sha1_hash const h3 = rand_hash(); + sha1_hash const h4 = rand_hash(); + sha1_hash const h5 = rand_hash(); + + std::vector const v = {h1, h2, h3, h4, h5}; + + sha1_hash const nh1 = rand_hash(); + sha1_hash const nh2 = rand_hash(); + sha1_hash const nh3 = rand_hash(); + sha1_hash const nh4 = rand_hash(); + sha1_hash const nh5 = rand_hash(); + udp::endpoint const nep1 = rand_udp_ep(rand_v4); + udp::endpoint const nep2 = rand_udp_ep(rand_v4); + udp::endpoint const nep3 = rand_udp_ep(rand_v4); +#if TORRENT_USE_IPV6 + udp::endpoint const nep4 = rand_udp_ep(rand_v6); + udp::endpoint const nep5 = rand_udp_ep(rand_v6); +#else + udp::endpoint const nep4 = rand_udp_ep(rand_v4); + udp::endpoint const nep5 = rand_udp_ep(rand_v4); +#endif + std::vector> nv; + nv.emplace_back(nh1, nep1); + nv.emplace_back(nh2, nep2); + nv.emplace_back(nh3, nep3); + nv.emplace_back(nh4, nep4); + nv.emplace_back(nh5, nep5); + + mgr.emplace_alert(endpoint, interval, num, v, nv); + + auto const* a = alert_cast(mgr.wait_for_alert(seconds(0))); + TEST_CHECK(a != nullptr); + + TEST_EQUAL(a->endpoint, endpoint); + TEST_CHECK(a->interval == interval); + TEST_EQUAL(a->num_infohashes, num); + TEST_EQUAL(a->num_samples(), 5); + TEST_CHECK(a->samples() == v); + TEST_EQUAL(a->num_nodes(), 5); + + auto nodes = a->nodes(); + std::sort(nv.begin(), nv.end()); + std::sort(nodes.begin(), nodes.end()); + TEST_CHECK(nv == nodes); +} diff --git a/test/test_dht.cpp b/test/test_dht.cpp index 8379531ae..f9cf1fcd8 100644 --- a/test/test_dht.cpp +++ b/test/test_dht.cpp @@ -246,6 +246,19 @@ struct msg_args msg_args& peers(std::set const& p) { if (!p.empty()) a.dict()["values"] = write_peers(p); return *this; } + msg_args& interval(time_duration interval) + { a["interval"] = total_seconds(interval); return *this; } + + msg_args& num(int num) + { a["num"] = num; return *this; } + + msg_args& samples(std::vector const& samples) + { + a["samples"] = span( + reinterpret_cast(samples.data()), samples.size() * 20); + return *this; + } + entry a; }; @@ -600,6 +613,15 @@ dht::key_desc_t const put_mutable_item_desc[] = { {"v", bdecode_node::none_t, 0, key_desc_t::last_child}, }; +dht::key_desc_t const sample_infohashes_desc[] = { + {"y", bdecode_node::string_t, 1, 0}, + {"t", bdecode_node::string_t, 2, 0}, + {"q", bdecode_node::string_t, 17, 0}, + {"a", bdecode_node::dict_t, 0, key_desc_t::parse_children}, + {"id", bdecode_node::string_t, 20, 0}, + {"target", bdecode_node::string_t, 20, key_desc_t::last_child}, +}; + void print_state(std::ostream& os, routing_table const& table) { std::vector buf(2048); @@ -3593,6 +3615,74 @@ TORRENT_TEST(dht_state) TEST_CHECK(s2.nodes.empty()); } +TORRENT_TEST(sample_infohashes) +{ + dht_test_setup t(rand_udp_ep()); + bdecode_node response; + + g_sent_packets.clear(); + + udp::endpoint initial_node = rand_udp_ep(); + t.dht_node.m_table.add_node(node_entry{initial_node}); + + // nodes + sha1_hash const h1 = rand_hash(); + sha1_hash const h2 = rand_hash(); + udp::endpoint const ep1 = rand_udp_ep(rand_v4); + udp::endpoint const ep2 = rand_udp_ep(rand_v4); + + t.dht_node.sample_infohashes(initial_node, items[0].target, + [h1, ep1, h2, ep2](time_duration interval, int num + , std::vector samples + , std::vector> const& nodes) + { + TEST_EQUAL(total_seconds(interval), 10); + TEST_EQUAL(num, 2); + TEST_EQUAL(samples.size(), 1); + TEST_EQUAL(samples[0], to_hash("1000000000000000000000000000000000000001")); + TEST_EQUAL(nodes.size(), 2); + TEST_EQUAL(nodes[0].first, h1); + TEST_EQUAL(nodes[0].second, ep1); + TEST_EQUAL(nodes[1].first, h2); + TEST_EQUAL(nodes[1].second, ep2); + }); + + TEST_EQUAL(g_sent_packets.size(), 1); + if (g_sent_packets.empty()) return; + TEST_EQUAL(g_sent_packets.front().first, initial_node); + + lazy_from_entry(g_sent_packets.front().second, response); + bdecode_node sample_infohashes_keys[6]; + bool const ret = verify_message(response + , sample_infohashes_desc, sample_infohashes_keys, t.error_string); + if (ret) + { + TEST_EQUAL(sample_infohashes_keys[0].string_value(), "q"); + TEST_EQUAL(sample_infohashes_keys[2].string_value(), "sample_infohashes"); + TEST_EQUAL(sample_infohashes_keys[5].string_value(), items[0].target.to_string()); + } + else + { + std::printf(" invalid sample_infohashes request: %s\n", print_entry(response).c_str()); + TEST_ERROR(t.error_string); + return; + } + + std::vector nodes; + nodes.emplace_back(h1, ep1); + nodes.emplace_back(h2, ep2); + + g_sent_packets.clear(); + send_dht_response(t.dht_node, response, initial_node + , msg_args() + .interval(seconds(10)) + .num(2) + .samples({to_hash("1000000000000000000000000000000000000001")}) + .nodes(nodes)); + + TEST_CHECK(g_sent_packets.empty()); +} + // TODO: test obfuscated_get_peers #else