Coverage Report

Created: 2026-01-10 06:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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