/src/libtorrent/src/lsd.cpp
Line | Count | Source |
1 | | /* |
2 | | |
3 | | Copyright (c) 2007-2012, 2014-2020, Arvid Norberg |
4 | | Copyright (c) 2016-2017, Alden Torres |
5 | | Copyright (c) 2016, Pavel Pimenov |
6 | | All rights reserved. |
7 | | |
8 | | Redistribution and use in source and binary forms, with or without |
9 | | modification, are permitted provided that the following conditions |
10 | | are met: |
11 | | |
12 | | * Redistributions of source code must retain the above copyright |
13 | | notice, this list of conditions and the following disclaimer. |
14 | | * Redistributions in binary form must reproduce the above copyright |
15 | | notice, this list of conditions and the following disclaimer in |
16 | | the documentation and/or other materials provided with the distribution. |
17 | | * Neither the name of the author nor the names of its |
18 | | contributors may be used to endorse or promote products derived |
19 | | from this software without specific prior written permission. |
20 | | |
21 | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
22 | | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
23 | | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
24 | | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
25 | | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
26 | | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
27 | | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
28 | | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
29 | | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
30 | | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
31 | | POSSIBILITY OF SUCH DAMAGE. |
32 | | |
33 | | */ |
34 | | |
35 | | #include <cstdlib> |
36 | | #include <cstdarg> |
37 | | #include <functional> |
38 | | #include <cstdio> // for vsnprintf |
39 | | |
40 | | #include "libtorrent/lsd.hpp" |
41 | | #include "libtorrent/time.hpp" |
42 | | #include "libtorrent/random.hpp" |
43 | | #include "libtorrent/http_parser.hpp" |
44 | | #include "libtorrent/socket_io.hpp" // for print_address |
45 | | #include "libtorrent/debug.hpp" |
46 | | #include "libtorrent/hex.hpp" // to_hex, from_hex |
47 | | #include "libtorrent/aux_/numeric_cast.hpp" |
48 | | #include "libtorrent/enum_net.hpp" |
49 | | #include "libtorrent/aux_/scope_end.hpp" |
50 | | |
51 | | #include "libtorrent/aux_/disable_warnings_push.hpp" |
52 | | #include <boost/asio/ip/multicast.hpp> |
53 | | #include "libtorrent/aux_/disable_warnings_pop.hpp" |
54 | | |
55 | | using namespace std::placeholders; |
56 | | |
57 | | namespace libtorrent { |
58 | | |
59 | | namespace { |
60 | | |
61 | | int render_lsd_packet(char* dst, int const len, int const listen_port |
62 | | , char const* info_hash_hex, std::uint32_t const cookie, char const* host) |
63 | 0 | { |
64 | 0 | TORRENT_ASSERT(len > 0); |
65 | 0 | return std::snprintf(dst, aux::numeric_cast<std::size_t>(len), |
66 | 0 | "BT-SEARCH * HTTP/1.1\r\n" |
67 | 0 | "Host: %s:6771\r\n" |
68 | 0 | "Port: %d\r\n" |
69 | 0 | "Infohash: %s\r\n" |
70 | 0 | "cookie: %x\r\n" |
71 | 0 | "\r\n\r\n", host, listen_port, info_hash_hex, cookie); |
72 | 0 | } |
73 | | } // anonymous namespace |
74 | | |
75 | | lsd::lsd(io_context& ios, aux::lsd_callback& cb |
76 | | , address const listen_address, address const netmask) |
77 | 0 | : m_callback(cb) |
78 | 0 | , m_listen_address(listen_address) |
79 | 0 | , m_netmask(netmask) |
80 | 0 | , m_socket(ios) |
81 | 0 | , m_broadcast_timer(ios) |
82 | 0 | , m_cookie((random(0x7fffffff) ^ std::uintptr_t(this)) & 0x7fffffff) |
83 | 0 | { |
84 | 0 | } |
85 | | |
86 | | #ifndef TORRENT_DISABLE_LOGGING |
87 | | bool lsd::should_log() const |
88 | | { |
89 | | return m_callback.should_log_lsd(); |
90 | | } |
91 | | |
92 | | TORRENT_FORMAT(2, 3) |
93 | | void lsd::debug_log(char const* fmt, ...) const |
94 | | { |
95 | | if (!should_log()) return; |
96 | | va_list v; |
97 | | va_start(v, fmt); |
98 | | |
99 | | char buf[1024]; |
100 | | std::vsnprintf(buf, sizeof(buf), fmt, v); |
101 | | va_end(v); |
102 | | m_callback.log_lsd(buf); |
103 | | } |
104 | | #endif |
105 | | |
106 | | namespace { |
107 | | address_v4 const lsd_multicast_addr4 = make_address_v4("239.192.152.143"); |
108 | | address_v6 const lsd_multicast_addr6 = make_address_v6("ff15::efc0:988f"); |
109 | | int const lsd_port = 6771; |
110 | | } |
111 | | |
112 | | void lsd::start(error_code& ec) |
113 | 0 | { |
114 | 0 | using namespace boost::asio::ip::multicast; |
115 | 0 | bool const v4 = m_listen_address.is_v4(); |
116 | 0 | m_socket.open(v4 ? udp::v4() : udp::v6(), ec); |
117 | 0 | if (ec) return; |
118 | | |
119 | 0 | m_socket.set_option(udp::socket::reuse_address(true), ec); |
120 | 0 | if (ec) return; |
121 | | |
122 | 0 | m_socket.bind(udp::endpoint(v4 ? address(address_v4::any()) : address(address_v6::any()), lsd_port), ec); |
123 | 0 | if (ec) return; |
124 | 0 | if (v4) |
125 | 0 | m_socket.set_option(join_group(lsd_multicast_addr4, m_listen_address.to_v4()), ec); |
126 | 0 | else |
127 | 0 | m_socket.set_option(join_group(lsd_multicast_addr6, m_listen_address.to_v6().scope_id()), ec); |
128 | 0 | if (ec) return; |
129 | 0 | m_socket.set_option(hops(32), ec); |
130 | 0 | if (ec) return; |
131 | 0 | m_socket.set_option(enable_loopback(true), ec); |
132 | 0 | if (ec) return; |
133 | 0 | if (v4) |
134 | 0 | { |
135 | 0 | m_socket.set_option(outbound_interface(m_listen_address.to_v4()), ec); |
136 | 0 | if (ec) return; |
137 | 0 | } |
138 | | |
139 | 0 | ADD_OUTSTANDING_ASYNC("lsd::on_announce"); |
140 | 0 | m_socket.async_receive_from(boost::asio::buffer(m_buffer), m_remote |
141 | 0 | , std::bind(&lsd::on_announce, self(), _1, _2)); |
142 | 0 | } |
143 | | |
144 | 0 | lsd::~lsd() = default; |
145 | | |
146 | | void lsd::announce(sha1_hash const& ih, int listen_port) |
147 | 0 | { |
148 | 0 | announce_impl(ih, listen_port, 0); |
149 | 0 | } |
150 | | |
151 | | void lsd::announce_impl(sha1_hash const& ih, int const listen_port |
152 | | , int retry_count) |
153 | 0 | { |
154 | 0 | if (m_disabled) return; |
155 | | |
156 | 0 | char msg[200]; |
157 | |
|
158 | 0 | error_code ec; |
159 | 0 | if (!m_disabled) |
160 | 0 | { |
161 | 0 | bool const v4 = m_listen_address.is_v4(); |
162 | 0 | char const* v4_address = "239.192.152.143"; |
163 | 0 | char const* v6_address = "[ff15::efc0:988f]"; |
164 | |
|
165 | 0 | int const msg_len = render_lsd_packet(msg, sizeof(msg), listen_port, aux::to_hex(ih).c_str() |
166 | 0 | , m_cookie, v4 ? v4_address : v6_address); |
167 | |
|
168 | 0 | udp::endpoint const to(v4 ? address(lsd_multicast_addr4) : address(lsd_multicast_addr6) |
169 | 0 | , lsd_port); |
170 | |
|
171 | | #ifndef TORRENT_DISABLE_LOGGING |
172 | | debug_log("==> LSD: ih: %s port: %d [iface: %s]", aux::to_hex(ih).c_str() |
173 | | , listen_port, m_listen_address.to_string().c_str()); |
174 | | #endif |
175 | |
|
176 | 0 | m_socket.send_to(boost::asio::buffer(msg, static_cast<std::size_t>(msg_len)) |
177 | 0 | , to, {}, ec); |
178 | 0 | if (ec) |
179 | 0 | { |
180 | 0 | m_disabled = true; |
181 | | #ifndef TORRENT_DISABLE_LOGGING |
182 | | if (should_log()) |
183 | | { |
184 | | debug_log("*** LSD: failed to send message: (%d) %s", ec.value() |
185 | | , ec.message().c_str()); |
186 | | } |
187 | | #endif |
188 | 0 | } |
189 | 0 | } |
190 | |
|
191 | 0 | ++retry_count; |
192 | 0 | if (retry_count >= 3) return; |
193 | | |
194 | 0 | if (m_disabled) return; |
195 | | |
196 | 0 | ADD_OUTSTANDING_ASYNC("lsd::resend_announce"); |
197 | 0 | m_broadcast_timer.expires_after(seconds(2 * retry_count)); |
198 | 0 | m_broadcast_timer.async_wait(std::bind(&lsd::resend_announce, self(), _1 |
199 | 0 | , ih, listen_port, retry_count)); |
200 | 0 | } |
201 | | |
202 | | void lsd::resend_announce(error_code const& e, sha1_hash const& info_hash |
203 | | , int listen_port, int retry_count) |
204 | 0 | { |
205 | 0 | COMPLETE_ASYNC("lsd::resend_announce"); |
206 | 0 | if (e) return; |
207 | | |
208 | 0 | announce_impl(info_hash, listen_port, retry_count); |
209 | 0 | } |
210 | | |
211 | | void lsd::on_announce(error_code const& ec, std::size_t len) |
212 | 0 | { |
213 | 0 | COMPLETE_ASYNC("lsd::on_announce"); |
214 | 0 | if (ec) |
215 | 0 | { |
216 | | #ifndef TORRENT_DISABLE_LOGGING |
217 | | debug_log("<== LSD: receive error: %s", ec.message().c_str()); |
218 | | #endif |
219 | 0 | return; |
220 | 0 | } |
221 | | |
222 | 0 | udp::endpoint const from = m_remote; |
223 | | |
224 | | // reissue the async receive as we exit the function. We can't do this |
225 | | // earlier because we still need to use m_buffer |
226 | 0 | auto reissue_receive = aux::scope_end([&] { |
227 | 0 | ADD_OUTSTANDING_ASYNC("lsd::on_announce"); |
228 | 0 | m_socket.async_receive_from(boost::asio::buffer(m_buffer), m_remote |
229 | 0 | , std::bind(&lsd::on_announce, self(), _1, _2)); |
230 | 0 | }); |
231 | |
|
232 | 0 | if (!match_addr_mask(from.address(), m_listen_address, m_netmask)) |
233 | 0 | { |
234 | | // we don't care about this network. Ignore this packet |
235 | | #ifndef TORRENT_DISABLE_LOGGING |
236 | | debug_log("<== LSD: receive from out of network: %s" |
237 | | , from.address().to_string().c_str()); |
238 | | #endif |
239 | 0 | return; |
240 | 0 | } |
241 | | |
242 | 0 | http_parser p; |
243 | |
|
244 | 0 | bool error = false; |
245 | 0 | p.incoming(span<char const>{m_buffer.data(), std::ptrdiff_t(len)}, error); |
246 | |
|
247 | 0 | if (!p.header_finished() || error) |
248 | 0 | { |
249 | | #ifndef TORRENT_DISABLE_LOGGING |
250 | | debug_log("<== LSD: incomplete HTTP message"); |
251 | | #endif |
252 | 0 | return; |
253 | 0 | } |
254 | | |
255 | 0 | if (p.method() != "bt-search") |
256 | 0 | { |
257 | | #ifndef TORRENT_DISABLE_LOGGING |
258 | | debug_log("<== LSD: invalid HTTP method: %s", p.method().c_str()); |
259 | | #endif |
260 | 0 | return; |
261 | 0 | } |
262 | | |
263 | 0 | std::string const& port_str = p.header("port"); |
264 | 0 | if (port_str.empty()) |
265 | 0 | { |
266 | | #ifndef TORRENT_DISABLE_LOGGING |
267 | | debug_log("<== LSD: invalid BT-SEARCH, missing port"); |
268 | | #endif |
269 | 0 | return; |
270 | 0 | } |
271 | | |
272 | 0 | long const port = std::strtol(port_str.c_str(), nullptr, 10); |
273 | 0 | if (port <= 0 || port >= int(std::numeric_limits<std::uint16_t>::max())) |
274 | 0 | { |
275 | | #ifndef TORRENT_DISABLE_LOGGING |
276 | | debug_log("<== LSD: invalid BT-SEARCH port value: %s", port_str.c_str()); |
277 | | #endif |
278 | 0 | return; |
279 | 0 | } |
280 | | |
281 | 0 | auto const& headers = p.headers(); |
282 | |
|
283 | 0 | auto const cookie_iter = headers.find("cookie"); |
284 | 0 | if (cookie_iter != headers.end()) |
285 | 0 | { |
286 | | // we expect it to be hexadecimal |
287 | | // if it isn't, it's not our cookie anyway |
288 | 0 | unsigned long const cookie = std::strtoul(cookie_iter->second.c_str(), nullptr, 16); |
289 | 0 | if (cookie == m_cookie) |
290 | 0 | { |
291 | | #ifndef TORRENT_DISABLE_LOGGING |
292 | | debug_log("<== LSD: ignoring packet (cookie matched our own): %x" |
293 | | , m_cookie); |
294 | | #endif |
295 | 0 | return; |
296 | 0 | } |
297 | 0 | } |
298 | | |
299 | 0 | auto const ihs = headers.equal_range("infohash"); |
300 | 0 | for (auto i = ihs.first; i != ihs.second; ++i) |
301 | 0 | { |
302 | 0 | std::string const& ih_str = i->second; |
303 | 0 | if (ih_str.size() != 40) |
304 | 0 | { |
305 | | #ifndef TORRENT_DISABLE_LOGGING |
306 | | debug_log("<== LSD: invalid BT-SEARCH, invalid infohash: %s" |
307 | | , ih_str.c_str()); |
308 | | #endif |
309 | 0 | continue; |
310 | 0 | } |
311 | | |
312 | 0 | sha1_hash ih; |
313 | 0 | aux::from_hex(ih_str, ih.data()); |
314 | |
|
315 | 0 | if (!ih.is_all_zeros()) |
316 | 0 | { |
317 | | #ifndef TORRENT_DISABLE_LOGGING |
318 | | if (should_log()) |
319 | | { |
320 | | debug_log("<== LSD: %s:%d ih: %s" |
321 | | , print_address(from.address()).c_str() |
322 | | , int(port), ih_str.c_str()); |
323 | | } |
324 | | #endif |
325 | | // we got an announce, pass it on through the callback |
326 | 0 | m_callback.on_lsd_peer(tcp::endpoint(from.address(), std::uint16_t(port)), ih); |
327 | 0 | } |
328 | 0 | } |
329 | 0 | } |
330 | | |
331 | | void lsd::close() |
332 | 0 | { |
333 | 0 | error_code ec; |
334 | 0 | m_socket.close(ec); |
335 | 0 | m_broadcast_timer.cancel(); |
336 | 0 | m_disabled = true; |
337 | 0 | } |
338 | | |
339 | | } // libtorrent namespace |