1 /*
2
3 Copyright (c) 2006-2018, Arvid Norberg & Daniel Wallin
4 All rights reserved.
5
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
8 are met:
9
10 * Redistributions of source code must retain the above copyright
11 notice, this list of conditions and the following disclaimer.
12 * Redistributions in binary form must reproduce the above copyright
13 notice, this list of conditions and the following disclaimer in
14 the documentation and/or other materials provided with the distribution.
15 * Neither the name of the author nor the names of its
16 contributors may be used to endorse or promote products derived
17 from this software without specific prior written permission.
18
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 POSSIBILITY OF SUCH DAMAGE.
30
31 */
32
33 #include <libtorrent/kademlia/get_peers.hpp>
34 #include <libtorrent/kademlia/node.hpp>
35 #include <libtorrent/kademlia/dht_observer.hpp>
36 #include <libtorrent/socket_io.hpp>
37 #include <libtorrent/performance_counters.hpp>
38 #include <libtorrent/broadcast_socket.hpp> // for is_v4
39
40 #ifndef TORRENT_DISABLE_LOGGING
41 #include <libtorrent/hex.hpp> // to_hex
42 #endif
43
44 namespace libtorrent { namespace dht {
45
reply(msg const & m)46 void get_peers_observer::reply(msg const& m)
47 {
48 bdecode_node const r = m.message.dict_find_dict("r");
49 if (!r)
50 {
51 #ifndef TORRENT_DISABLE_LOGGING
52 get_observer()->log(dht_logger::traversal, "[%u] missing response dict"
53 , algorithm()->id());
54 #endif
55 timeout();
56 return;
57 }
58
59 // look for peers
60 bdecode_node const n = r.dict_find_list("values");
61 if (n)
62 {
63 std::vector<tcp::endpoint> peer_list;
64 if (n.list_size() == 1 && n.list_at(0).type() == bdecode_node::string_t
65 && is_v4(m.addr))
66 {
67 // assume it's mainline format
68 char const* peers = n.list_at(0).string_ptr();
69 char const* end = peers + n.list_at(0).string_length();
70
71 #ifndef TORRENT_DISABLE_LOGGING
72 log_peers(m, r, int((end - peers) / 6));
73 #endif
74 while (end - peers >= 6)
75 peer_list.push_back(detail::read_v4_endpoint<tcp::endpoint>(peers));
76 }
77 else
78 {
79 // assume it's uTorrent/libtorrent format
80 peer_list = detail::read_endpoint_list<tcp::endpoint>(n);
81 #ifndef TORRENT_DISABLE_LOGGING
82 log_peers(m, r, n.list_size());
83 #endif
84 }
85 static_cast<get_peers*>(algorithm())->got_peers(peer_list);
86 }
87
88 find_data_observer::reply(m);
89 }
90 #ifndef TORRENT_DISABLE_LOGGING
log_peers(msg const & m,bdecode_node const & r,int const size) const91 void get_peers_observer::log_peers(msg const& m, bdecode_node const& r, int const size) const
92 {
93 auto logger = get_observer();
94 if (logger != nullptr && logger->should_log(dht_logger::traversal))
95 {
96 bdecode_node const id = r.dict_find_string("id");
97 if (id && id.string_length() == 20)
98 {
99 logger->log(dht_logger::traversal, "[%u] PEERS "
100 "invoke-count: %d branch-factor: %d addr: %s id: %s distance: %d p: %d"
101 , algorithm()->id()
102 , algorithm()->invoke_count()
103 , algorithm()->branch_factor()
104 , print_endpoint(m.addr).c_str()
105 , aux::to_hex({id.string_ptr(), id.string_length()}).c_str()
106 , distance_exp(algorithm()->target(), node_id(id.string_ptr()))
107 , size);
108 }
109 }
110 }
111 #endif
got_peers(std::vector<tcp::endpoint> const & peers)112 void get_peers::got_peers(std::vector<tcp::endpoint> const& peers)
113 {
114 if (m_data_callback) m_data_callback(peers);
115 }
116
get_peers(node & dht_node,node_id const & target,data_callback const & dcallback,nodes_callback const & ncallback,bool noseeds)117 get_peers::get_peers(
118 node& dht_node
119 , node_id const& target
120 , data_callback const& dcallback
121 , nodes_callback const& ncallback
122 , bool noseeds)
123 : find_data(dht_node, target, ncallback)
124 , m_data_callback(dcallback)
125 , m_noseeds(noseeds)
126 {
127 }
128
name() const129 char const* get_peers::name() const { return "get_peers"; }
130
invoke(observer_ptr o)131 bool get_peers::invoke(observer_ptr o)
132 {
133 if (m_done) return false;
134
135 entry e;
136 e["y"] = "q";
137 entry& a = e["a"];
138
139 e["q"] = "get_peers";
140 a["info_hash"] = target().to_string();
141 if (m_noseeds) a["noseed"] = 1;
142
143 if (m_node.observer() != nullptr)
144 {
145 m_node.observer()->outgoing_get_peers(target(), target(), o->target_ep());
146 }
147
148 m_node.stats_counters().inc_stats_counter(counters::dht_get_peers_out);
149
150 return m_node.m_rpc.invoke(e, o->target_ep(), o);
151 }
152
new_observer(udp::endpoint const & ep,node_id const & id)153 observer_ptr get_peers::new_observer(udp::endpoint const& ep
154 , node_id const& id)
155 {
156 auto o = m_node.m_rpc.allocate_observer<get_peers_observer>(self(), ep, id);
157 #if TORRENT_USE_ASSERTS
158 if (o) o->m_in_constructor = false;
159 #endif
160 return o;
161 }
162
obfuscated_get_peers(node & dht_node,node_id const & info_hash,data_callback const & dcallback,nodes_callback const & ncallback,bool noseeds)163 obfuscated_get_peers::obfuscated_get_peers(
164 node& dht_node
165 , node_id const& info_hash
166 , data_callback const& dcallback
167 , nodes_callback const& ncallback
168 , bool noseeds)
169 : get_peers(dht_node, info_hash, dcallback, ncallback, noseeds)
170 , m_obfuscated(true)
171 {
172 }
173
name() const174 char const* obfuscated_get_peers::name() const
175 { return !m_obfuscated ? get_peers::name() : "get_peers [obfuscated]"; }
176
new_observer(udp::endpoint const & ep,node_id const & id)177 observer_ptr obfuscated_get_peers::new_observer(udp::endpoint const& ep
178 , node_id const& id)
179 {
180 if (m_obfuscated)
181 {
182 auto o = m_node.m_rpc.allocate_observer<obfuscated_get_peers_observer>(self()
183 , ep, id);
184 #if TORRENT_USE_ASSERTS
185 if (o) o->m_in_constructor = false;
186 #endif
187 return o;
188 }
189 else
190 {
191 auto o = m_node.m_rpc.allocate_observer<get_peers_observer>(self()
192 , ep, id);
193 #if TORRENT_USE_ASSERTS
194 if (o) o->m_in_constructor = false;
195 #endif
196 return o;
197 }
198 }
199
invoke(observer_ptr o)200 bool obfuscated_get_peers::invoke(observer_ptr o)
201 {
202 if (!m_obfuscated) return get_peers::invoke(o);
203
204 node_id const& id = o->id();
205 int const shared_prefix = 160 - distance_exp(id, target());
206
207 // when we get close to the target zone in the DHT
208 // start using the correct info-hash, in order to
209 // start receiving peers
210 if (shared_prefix > m_node.m_table.depth() - 4)
211 {
212 m_obfuscated = false;
213 // clear the queried bits on all successful nodes in
214 // our node-list for this traversal algorithm, to
215 // allow the get_peers traversal to regress in case
216 // nodes further down end up being dead
217 for (auto const& node : m_results)
218 {
219 // don't re-request from nodes that didn't respond
220 if (node->flags & observer::flag_failed) continue;
221 // don't interrupt with queries that are already in-flight
222 if (!(node->flags & observer::flag_alive)) continue;
223 node->flags &= ~(observer::flag_queried | observer::flag_alive);
224 }
225 return get_peers::invoke(o);
226 }
227
228 entry e;
229 e["y"] = "q";
230 e["q"] = "get_peers";
231 entry& a = e["a"];
232
233 // This logic will obfuscate the target info-hash
234 // we're looking up, in order to preserve more privacy
235 // on the DHT. This is done by only including enough
236 // bits in the info-hash for the node we're querying to
237 // give a good answer, but not more.
238
239 // now, obfuscate the bits past shared_prefix + 3
240 node_id mask = generate_prefix_mask(shared_prefix + 3);
241 node_id obfuscated_target = generate_random_id() & ~mask;
242 obfuscated_target |= target() & mask;
243 a["info_hash"] = obfuscated_target.to_string();
244
245 if (m_node.observer() != nullptr)
246 {
247 m_node.observer()->outgoing_get_peers(target(), obfuscated_target
248 , o->target_ep());
249 }
250
251 m_node.stats_counters().inc_stats_counter(counters::dht_get_peers_out);
252
253 return m_node.m_rpc.invoke(e, o->target_ep(), o);
254 }
255
done()256 void obfuscated_get_peers::done()
257 {
258 if (!m_obfuscated) return get_peers::done();
259
260 // oops, we failed to switch over to the non-obfuscated
261 // mode early enough. do it now
262
263 auto ta = std::make_shared<get_peers>(m_node, target()
264 , m_data_callback, m_nodes_callback, m_noseeds);
265
266 // don't call these when the obfuscated_get_peers
267 // is done, we're passing them on to be called when
268 // ta completes.
269 m_data_callback = nullptr;
270 m_nodes_callback = nullptr;
271
272 #ifndef TORRENT_DISABLE_LOGGING
273 get_node().observer()->log(dht_logger::traversal, "[%u] obfuscated get_peers "
274 "phase 1 done, spawning get_peers [ %u ]"
275 , id(), ta->id());
276 #endif
277
278 int num_added = 0;
279 for (auto i = m_results.begin()
280 , end(m_results.end()); i != end && num_added < 16; ++i)
281 {
282 observer_ptr o = *i;
283
284 // only add nodes whose node ID we know and that
285 // we know are alive
286 if (o->flags & observer::flag_no_id) continue;
287 if (!(o->flags & observer::flag_alive)) continue;
288
289 ta->add_entry(o->id(), o->target_ep(), observer::flag_initial);
290 ++num_added;
291 }
292
293 ta->start();
294
295 get_peers::done();
296 }
297
reply(msg const & m)298 void obfuscated_get_peers_observer::reply(msg const& m)
299 {
300 bdecode_node const r = m.message.dict_find_dict("r");
301 if (!r)
302 {
303 #ifndef TORRENT_DISABLE_LOGGING
304 get_observer()->log(dht_logger::traversal, "[%u] missing response dict"
305 , algorithm()->id());
306 #endif
307 timeout();
308 return;
309 }
310
311 bdecode_node const id = r.dict_find_string("id");
312 if (!id || id.string_length() != 20)
313 {
314 #ifndef TORRENT_DISABLE_LOGGING
315 get_observer()->log(dht_logger::traversal, "[%u] invalid id in response"
316 , algorithm()->id());
317 #endif
318 timeout();
319 return;
320 }
321
322 traversal_observer::reply(m);
323
324 done();
325 }
326
327 } } // namespace libtorrent::dht
328