Coverage Report

Created: 2025-08-25 06:28

/src/libtorrent/src/upnp.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
3
Copyright (c) 2019, Amir Abrams
4
Copyright (c) 2007-2022, Arvid Norberg
5
Copyright (c) 2009, Andrew Resch
6
Copyright (c) 2015, Mike Tzou
7
Copyright (c) 2016-2019, Alden Torres
8
Copyright (c) 2016-2017, Andrei Kurushin
9
Copyright (c) 2016-2017, Pavel Pimenov
10
Copyright (c) 2016, Steven Siloti
11
Copyright (c) 2020, Paul-Louis Ageneau
12
All rights reserved.
13
14
Redistribution and use in source and binary forms, with or without
15
modification, are permitted provided that the following conditions
16
are met:
17
18
    * Redistributions of source code must retain the above copyright
19
      notice, this list of conditions and the following disclaimer.
20
    * Redistributions in binary form must reproduce the above copyright
21
      notice, this list of conditions and the following disclaimer in
22
      the documentation and/or other materials provided with the distribution.
23
    * Neither the name of the author nor the names of its
24
      contributors may be used to endorse or promote products derived
25
      from this software without specific prior written permission.
26
27
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
28
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
31
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37
POSSIBILITY OF SUCH DAMAGE.
38
39
*/
40
41
#include "libtorrent/config.hpp"
42
#include "libtorrent/socket.hpp"
43
#include "libtorrent/socket_io.hpp"
44
#include "libtorrent/upnp.hpp"
45
#include "libtorrent/io.hpp"
46
#include "libtorrent/parse_url.hpp"
47
#include "libtorrent/xml_parse.hpp"
48
#include "libtorrent/random.hpp"
49
#include "libtorrent/aux_/time.hpp" // for aux::time_now()
50
#include "libtorrent/aux_/escape_string.hpp" // for convert_from_native
51
#include "libtorrent/http_connection.hpp"
52
#include "libtorrent/ssl.hpp"
53
#include "libtorrent/aux_/scope_end.hpp"
54
55
#if defined TORRENT_ASIO_DEBUGGING
56
#include "libtorrent/debug.hpp"
57
#endif
58
59
#include "libtorrent/aux_/disable_warnings_push.hpp"
60
#include <boost/asio/ip/host_name.hpp>
61
#include <boost/asio/ip/multicast.hpp>
62
#include "libtorrent/aux_/disable_warnings_pop.hpp"
63
64
#include <cstdlib>
65
#include <cstdio> // for snprintf
66
#include <cstdarg>
67
#include <functional>
68
69
using namespace std::placeholders;
70
71
namespace libtorrent {
72
73
using namespace aux;
74
75
// due to the recursive nature of update_map, it's necessary to
76
// limit the internal list of global mappings to a small size
77
// this can be changed once the entire UPnP code is refactored
78
constexpr std::size_t max_global_mappings = 50;
79
80
namespace upnp_errors
81
{
82
  boost::system::error_code make_error_code(error_code_enum e)
83
0
  { return {e, upnp_category()}; }
84
85
} // upnp_errors namespace
86
87
static error_code ignore_error;
88
89
0
upnp::rootdevice::rootdevice() = default;
90
91
#if TORRENT_USE_ASSERTS
92
upnp::rootdevice::~rootdevice()
93
0
{
94
0
  TORRENT_ASSERT(magic == 1337);
95
0
  magic = 0;
96
0
}
97
#else
98
upnp::rootdevice::~rootdevice() = default;
99
#endif
100
101
0
upnp::rootdevice::rootdevice(rootdevice const&) = default;
102
0
upnp::rootdevice& upnp::rootdevice::operator=(rootdevice const&) & = default;
103
0
upnp::rootdevice::rootdevice(rootdevice&&) noexcept = default;
104
0
upnp::rootdevice& upnp::rootdevice::operator=(rootdevice&&) & = default;
105
106
// TODO: 2 use boost::asio::ip::network instead of netmask
107
upnp::upnp(io_context& ios
108
  , aux::session_settings const& settings
109
  , aux::portmap_callback& cb
110
  , address_v4 const listen_address
111
  , address_v4 const netmask
112
  , std::string listen_device
113
  , listen_socket_handle ls)
114
0
  : m_settings(settings)
115
0
  , m_callback(cb)
116
0
  , m_io_service(ios)
117
0
  , m_resolver(ios)
118
0
  , m_multicast(ios)
119
0
  , m_unicast(ios)
120
0
  , m_broadcast_timer(ios)
121
0
  , m_refresh_timer(ios)
122
0
  , m_map_timer(ios)
123
0
  , m_listen_address(listen_address)
124
0
  , m_netmask(netmask)
125
0
  , m_device(std::move(listen_device))
126
#if TORRENT_USE_SSL
127
0
  , m_ssl_ctx(ssl::context::sslv23_client)
128
#endif
129
0
  , m_listen_handle(std::move(ls))
130
0
{
131
0
#if TORRENT_USE_SSL
132
0
  m_ssl_ctx.set_verify_mode(ssl::context::verify_none);
133
0
#endif
134
0
}
135
136
void upnp::start()
137
0
{
138
0
  TORRENT_ASSERT(is_single_thread());
139
140
0
  error_code ec;
141
0
  open_multicast_socket(m_multicast, ec);
142
#ifndef TORRENT_DISABLE_LOGGING
143
  if (ec && should_log())
144
  {
145
    log("failed to open multicast socket: \"%s\""
146
      , convert_from_native(ec.message()).c_str());
147
    m_disabled = true;
148
    return;
149
  }
150
#endif
151
152
0
  open_unicast_socket(m_unicast, ec);
153
#ifndef TORRENT_DISABLE_LOGGING
154
  if (ec && should_log())
155
  {
156
    log("failed to open unicast socket: \"%s\""
157
      , convert_from_native(ec.message()).c_str());
158
    m_disabled = true;
159
    return;
160
  }
161
#endif
162
163
0
  m_mappings.reserve(2);
164
165
0
  discover_device_impl();
166
0
}
167
168
namespace {
169
  address_v4 const ssdp_multicast_addr = make_address_v4("239.255.255.250");
170
  int const ssdp_port = 1900;
171
172
}
173
174
void upnp::open_multicast_socket(aux::socket_package& s, error_code& ec)
175
0
{
176
0
  using namespace boost::asio::ip::multicast;
177
0
  s.socket.open(udp::v4(), ec);
178
0
  if (ec) return;
179
0
  s.socket.set_option(udp::socket::reuse_address(true), ec);
180
0
  if (ec) return;
181
0
  s.socket.bind(udp::endpoint(m_listen_address, ssdp_port), ec);
182
0
  if (ec) return;
183
0
  s.socket.set_option(join_group(ssdp_multicast_addr), ec);
184
0
  if (ec) return;
185
0
  s.socket.set_option(hops(255), ec);
186
0
  if (ec) return;
187
0
  s.socket.set_option(enable_loopback(true), ec);
188
0
  if (ec) return;
189
0
  s.socket.set_option(outbound_interface(m_listen_address), ec);
190
0
  if (ec) return;
191
192
0
  ADD_OUTSTANDING_ASYNC("upnp::on_reply");
193
0
  s.socket.async_receive_from(boost::asio::buffer(s.buffer), s.remote
194
0
    , std::bind(&upnp::on_reply, self(), std::ref(s), _1, _2));
195
0
}
196
197
void upnp::open_unicast_socket(aux::socket_package& s, error_code& ec)
198
0
{
199
0
  s.socket.open(udp::v4(), ec);
200
0
  if (ec) return;
201
0
  s.socket.bind(udp::endpoint(m_listen_address, 0), ec);
202
0
  if (ec) return;
203
204
0
  ADD_OUTSTANDING_ASYNC("upnp::on_reply");
205
0
  s.socket.async_receive_from(boost::asio::buffer(s.buffer), s.remote
206
0
    , std::bind(&upnp::on_reply, self(), std::ref(s), _1, _2));
207
0
}
208
209
0
upnp::~upnp() = default;
210
211
#ifndef TORRENT_DISABLE_LOGGING
212
bool upnp::should_log() const
213
{
214
  return m_callback.should_log_portmap(portmap_transport::upnp);
215
}
216
217
TORRENT_FORMAT(2,3)
218
void upnp::log(char const* fmt, ...) const
219
{
220
  TORRENT_ASSERT(is_single_thread());
221
  if (!should_log()) return;
222
  va_list v;
223
  va_start(v, fmt);
224
  char msg[1024];
225
  std::vsnprintf(msg, sizeof(msg), fmt, v);
226
  va_end(v);
227
  m_callback.log_portmap(portmap_transport::upnp, msg, m_listen_handle);
228
}
229
#endif
230
231
void upnp::discover_device_impl()
232
0
{
233
0
  TORRENT_ASSERT(is_single_thread());
234
0
  static const char msearch[] =
235
0
    "M-SEARCH * HTTP/1.1\r\n"
236
0
    "HOST: 239.255.255.250:1900\r\n"
237
0
    "ST:upnp:rootdevice\r\n"
238
0
    "MAN:\"ssdp:discover\"\r\n"
239
0
    "MX:3\r\n"
240
0
    "\r\n\r\n";
241
242
#ifdef TORRENT_DEBUG_UPNP
243
  // simulate packet loss
244
  if (m_retry_count & 1)
245
#endif
246
247
0
  error_code mcast_ec;
248
0
  error_code ucast_ec;
249
0
  m_multicast.socket.send_to(boost::asio::buffer(msearch, sizeof(msearch) - 1)
250
0
    , udp::endpoint(ssdp_multicast_addr, ssdp_port), 0, mcast_ec);
251
0
  m_unicast.socket.send_to(boost::asio::buffer(msearch, sizeof(msearch) - 1)
252
0
    , udp::endpoint(ssdp_multicast_addr, ssdp_port), 0, ucast_ec);
253
254
0
  if (mcast_ec && ucast_ec)
255
0
  {
256
#ifndef TORRENT_DISABLE_LOGGING
257
    if (should_log())
258
    {
259
      log("multicast send failed: \"%s\" and \"%s\". Aborting."
260
        , convert_from_native(mcast_ec.message()).c_str()
261
        , convert_from_native(ucast_ec.message()).c_str());
262
    }
263
#endif
264
0
    disable(mcast_ec);
265
0
    return;
266
0
  }
267
268
0
  ADD_OUTSTANDING_ASYNC("upnp::resend_request");
269
0
  ++m_retry_count;
270
0
  m_broadcast_timer.expires_after(seconds(2 * m_retry_count));
271
0
  m_broadcast_timer.async_wait(std::bind(&upnp::resend_request
272
0
    , self(), _1));
273
274
#ifndef TORRENT_DISABLE_LOGGING
275
  log("broadcasting search for rootdevice");
276
#endif
277
0
}
278
279
// returns a reference to a mapping or -1 on failure
280
port_mapping_t upnp::add_mapping(portmap_protocol const p, int const external_port
281
  , tcp::endpoint const local_ep, std::string const& device)
282
0
{
283
0
  TORRENT_ASSERT(is_single_thread());
284
  // external port 0 means _every_ port
285
0
  TORRENT_ASSERT(external_port != 0);
286
287
#ifndef TORRENT_DISABLE_LOGGING
288
  if (should_log())
289
  {
290
    log("adding port map: [ protocol: %s ext_port: %d "
291
      "local_ep: %s device: %s] %s", (p == portmap_protocol::tcp?"tcp":"udp")
292
      , external_port
293
      , print_endpoint(local_ep).c_str(), device.c_str()
294
      , m_disabled ? "DISABLED": "");
295
  }
296
#endif
297
0
  if (m_disabled) return port_mapping_t{-1};
298
299
0
  auto mapping_it = std::find_if(m_mappings.begin(), m_mappings.end()
300
0
    , [](global_mapping_t const& m) { return m.protocol == portmap_protocol::none; });
301
302
0
  if (mapping_it == m_mappings.end())
303
0
  {
304
0
    TORRENT_ASSERT(m_mappings.size() <= max_global_mappings);
305
0
    if (m_mappings.size() >= max_global_mappings)
306
0
    {
307
#ifndef TORRENT_DISABLE_LOGGING
308
      log("too many mappings registered");
309
#endif
310
0
      return port_mapping_t{-1};
311
0
    }
312
0
    m_mappings.push_back(global_mapping_t());
313
0
    mapping_it = m_mappings.end() - 1;
314
0
  }
315
316
0
  mapping_it->protocol = p;
317
0
  mapping_it->external_port = external_port;
318
0
  mapping_it->local_ep = local_ep;
319
0
  mapping_it->device = device;
320
321
0
  port_mapping_t const mapping_index{static_cast<int>(mapping_it - m_mappings.begin())};
322
323
0
  for (auto const& dev : m_devices)
324
0
  {
325
0
    auto& d = const_cast<rootdevice&>(dev);
326
0
    TORRENT_ASSERT(d.magic == 1337);
327
0
    if (d.disabled) continue;
328
329
0
    if (d.mapping.end_index() <= mapping_index)
330
0
      d.mapping.resize(static_cast<int>(mapping_index) + 1);
331
0
    mapping_t& m = d.mapping[mapping_index];
332
333
0
    m.act = portmap_action::add;
334
0
    m.protocol = p;
335
0
    m.external_port = external_port;
336
0
    m.local_ep = local_ep;
337
0
    m.device = device;
338
339
0
    if (!d.service_namespace.empty()) update_map(d, mapping_index);
340
0
  }
341
342
0
  return port_mapping_t{mapping_index};
343
0
}
344
345
void upnp::delete_mapping(port_mapping_t const mapping)
346
0
{
347
0
  TORRENT_ASSERT(is_single_thread());
348
349
0
  if (mapping >= m_mappings.end_index()) return;
350
351
0
  global_mapping_t const& m = m_mappings[mapping];
352
353
#ifndef TORRENT_DISABLE_LOGGING
354
  if (should_log())
355
  {
356
    log("deleting port map: [ protocol: %s ext_port: %d "
357
      "local_ep: %s device: %s]"
358
      , (m.protocol == portmap_protocol::tcp?"tcp":"udp"), m.external_port
359
      , print_endpoint(m.local_ep).c_str()
360
      , m.device.c_str());
361
  }
362
#endif
363
364
0
  if (m.protocol == portmap_protocol::none) return;
365
366
0
  for (auto const& dev : m_devices)
367
0
  {
368
0
    auto& d = const_cast<rootdevice&>(dev);
369
0
    TORRENT_ASSERT(d.magic == 1337);
370
0
    if (d.disabled) continue;
371
372
0
    TORRENT_ASSERT(mapping < d.mapping.end_index());
373
0
    d.mapping[mapping].act = portmap_action::del;
374
375
0
    if (!d.service_namespace.empty()) update_map(d, mapping);
376
0
  }
377
0
}
378
379
bool upnp::get_mapping(port_mapping_t const index
380
  , tcp::endpoint& local_ep
381
  , int& external_port
382
  , portmap_protocol& protocol) const
383
0
{
384
0
  TORRENT_ASSERT(is_single_thread());
385
0
  TORRENT_ASSERT(index < m_mappings.end_index() && index >= port_mapping_t{0});
386
0
  if (index >= m_mappings.end_index() || index < port_mapping_t{0}) return false;
387
0
  global_mapping_t const& m = m_mappings[index];
388
0
  if (m.protocol == portmap_protocol::none) return false;
389
0
  local_ep = m.local_ep;
390
0
  external_port = m.external_port;
391
0
  protocol = m.protocol;
392
0
  return true;
393
0
}
394
395
void upnp::resend_request(error_code const& ec)
396
0
{
397
0
  TORRENT_ASSERT(is_single_thread());
398
0
  COMPLETE_ASYNC("upnp::resend_request");
399
0
  if (ec) return;
400
401
0
  std::shared_ptr<upnp> me(self());
402
403
0
  if (m_closing) return;
404
405
0
  if (m_retry_count < 12
406
0
    && (m_devices.empty() || m_retry_count < 4))
407
0
  {
408
0
    discover_device_impl();
409
0
    return;
410
0
  }
411
412
0
  if (m_devices.empty())
413
0
  {
414
0
    disable(errors::no_router);
415
0
    return;
416
0
  }
417
418
0
  for (auto const& dev : m_devices)
419
0
  {
420
0
    if (!dev.control_url.empty()
421
0
      || dev.upnp_connection
422
0
      || dev.disabled)
423
0
    {
424
0
      continue;
425
0
    }
426
427
    // we don't have a WANIP or WANPPP url for this device,
428
    // ask for it
429
0
    connect(const_cast<rootdevice&>(dev));
430
0
  }
431
0
}
432
433
void upnp::connect(rootdevice& d)
434
0
{
435
0
  TORRENT_ASSERT(d.magic == 1337);
436
0
  try
437
0
  {
438
#ifndef TORRENT_DISABLE_LOGGING
439
    log("connecting to: %s", d.url.c_str());
440
#endif
441
0
    if (d.upnp_connection) d.upnp_connection->close();
442
0
    d.upnp_connection = std::make_shared<http_connection>(m_io_service
443
0
      , m_resolver
444
0
      , std::bind(&upnp::on_upnp_xml, self(), _1, _2
445
0
        , std::ref(d), _4), true, default_max_bottled_buffer_size
446
0
      , http_connect_handler()
447
0
      , http_filter_handler()
448
0
      , hostname_filter_handler()
449
0
#if TORRENT_USE_SSL
450
0
      , &m_ssl_ctx
451
0
#endif
452
0
      );
453
0
    d.upnp_connection->get(d.url, seconds(30));
454
0
  }
455
0
  catch (std::exception const& exc)
456
0
  {
457
0
    TORRENT_UNUSED(exc);
458
#ifndef TORRENT_DISABLE_LOGGING
459
    log("connection failed to: %s %s", d.url.c_str(), exc.what());
460
#endif
461
0
    d.disabled = true;
462
0
  }
463
0
}
464
465
void upnp::on_reply(aux::socket_package& s, error_code const& ec, std::size_t const len)
466
0
{
467
0
  TORRENT_ASSERT(is_single_thread());
468
0
  COMPLETE_ASYNC("upnp::on_reply");
469
470
0
  if (ec == boost::asio::error::operation_aborted) return;
471
0
  if (m_closing) return;
472
473
0
  std::shared_ptr<upnp> me(self());
474
0
  udp::endpoint const from = s.remote;
475
  // parse out the url for the device
476
477
/*
478
  the response looks like this:
479
480
  HTTP/1.1 200 OK
481
  ST:upnp:rootdevice
482
  USN:uuid:000f-66d6-7296000099dc::upnp:rootdevice
483
  Location: http://192.168.1.1:5431/dyndev/uuid:000f-66d6-7296000099dc
484
  Server: Custom/1.0 UPnP/1.0 Proc/Ver
485
  EXT:
486
  Cache-Control:max-age=180
487
  DATE: Fri, 02 Jan 1970 08:10:38 GMT
488
489
  a notification looks like this:
490
491
  NOTIFY * HTTP/1.1
492
  Host:239.255.255.250:1900
493
  NT:urn:schemas-upnp-org:device:MediaServer:1
494
  NTS:ssdp:alive
495
  Location:http://10.0.3.169:2869/upnphost/udhisapi.dll?content=uuid:c17f0c32-d19b-4938-ae94-65f945c3a26e
496
  USN:uuid:c17f0c32-d19b-4938-ae94-65f945c3a26e::urn:schemas-upnp-org:device:MediaServer:1
497
  Cache-Control:max-age=900
498
  Server:Microsoft-Windows-NT/5.1 UPnP/1.0 UPnP-Device-Host/1.0
499
500
*/
501
502
  // reissue the async receive as we exit the function. We can't do this
503
  // earlier because we still need to use s.buffer
504
0
  auto reissue_receive = scope_end([&] {
505
0
    ADD_OUTSTANDING_ASYNC("upnp::on_reply");
506
0
    s.socket.async_receive_from(boost::asio::buffer(s.buffer), s.remote
507
0
      , std::bind(&upnp::on_reply, self(), std::ref(s), _1, _2));
508
0
  });
509
510
0
  if (ec) return;
511
512
0
  if (m_settings.get_bool(settings_pack::upnp_ignore_nonrouters)
513
0
    && !match_addr_mask(m_listen_address, from.address(), m_netmask))
514
0
  {
515
#ifndef TORRENT_DISABLE_LOGGING
516
    if (should_log())
517
    {
518
      log("ignoring response from: %s. IP is not on local network. (addr: %s mask: %s)"
519
        , print_endpoint(from).c_str()
520
        , m_listen_address.to_string().c_str()
521
        , m_netmask.to_string().c_str());
522
    }
523
#endif
524
0
    return;
525
0
  }
526
527
0
  http_parser p;
528
0
  bool error = false;
529
0
  p.incoming({s.buffer.data(), std::ptrdiff_t(len)}, error);
530
0
  if (error)
531
0
  {
532
#ifndef TORRENT_DISABLE_LOGGING
533
    if (should_log())
534
    {
535
      log("received malformed HTTP from: %s"
536
        , print_endpoint(from).c_str());
537
    }
538
#endif
539
0
    return;
540
0
  }
541
542
0
  if (p.status_code() != 200 && p.method() != "notify")
543
0
  {
544
#ifndef TORRENT_DISABLE_LOGGING
545
    if (should_log())
546
    {
547
      if (p.method().empty())
548
      {
549
        log("HTTP status %d from %s"
550
          , p.status_code(), print_endpoint(from).c_str());
551
      }
552
      else
553
      {
554
        log("HTTP method %s from %s"
555
          , p.method().c_str(), print_endpoint(from).c_str());
556
      }
557
    }
558
#endif
559
0
    return;
560
0
  }
561
562
0
  if (!p.header_finished())
563
0
  {
564
#ifndef TORRENT_DISABLE_LOGGING
565
    if (should_log())
566
    {
567
      log("incomplete HTTP packet from %s"
568
        , print_endpoint(from).c_str());
569
    }
570
#endif
571
0
    return;
572
0
  }
573
574
0
  std::string url = p.header("location");
575
0
  if (url.empty())
576
0
  {
577
#ifndef TORRENT_DISABLE_LOGGING
578
    if (should_log())
579
    {
580
      log("missing location header from %s"
581
        , print_endpoint(from).c_str());
582
    }
583
#endif
584
0
    return;
585
0
  }
586
587
0
  rootdevice d;
588
0
  d.url = url;
589
590
0
  auto i = m_devices.find(d);
591
592
0
  if (i == m_devices.end())
593
0
  {
594
0
    std::string protocol;
595
0
    std::string auth;
596
0
    error_code err;
597
    // we don't have this device in our list. Add it
598
0
    std::tie(protocol, auth, d.hostname, d.port, d.path)
599
0
      = parse_url_components(d.url, err);
600
0
    if (d.port == -1) d.port = protocol == "http" ? 80 : 443;
601
602
0
    if (err)
603
0
    {
604
#ifndef TORRENT_DISABLE_LOGGING
605
      if (should_log())
606
      {
607
        log("invalid URL %s from %s: %s"
608
          , d.url.c_str(), print_endpoint(from).c_str(), convert_from_native(err.message()).c_str());
609
      }
610
#endif
611
0
      return;
612
0
    }
613
614
    // ignore the auth here. It will be re-parsed
615
    // by the http connection later
616
617
0
    if (protocol != "http")
618
0
    {
619
#ifndef TORRENT_DISABLE_LOGGING
620
      if (should_log())
621
      {
622
        log("unsupported protocol %s from %s"
623
          , protocol.c_str(), print_endpoint(from).c_str());
624
      }
625
#endif
626
0
      return;
627
0
    }
628
629
0
    if (d.port == 0)
630
0
    {
631
#ifndef TORRENT_DISABLE_LOGGING
632
      if (should_log())
633
      {
634
        log("URL with port 0 from %s", print_endpoint(from).c_str());
635
      }
636
#endif
637
0
      return;
638
0
    }
639
640
#ifndef TORRENT_DISABLE_LOGGING
641
    if (should_log())
642
    {
643
      log("found rootdevice: %s (%d)"
644
        , d.url.c_str(), int(m_devices.size()));
645
    }
646
#endif
647
648
0
    if (m_devices.size() >= 50)
649
0
    {
650
#ifndef TORRENT_DISABLE_LOGGING
651
      if (should_log())
652
      {
653
        log("too many rootdevices: (%d). Ignoring %s"
654
          , int(m_devices.size()), d.url.c_str());
655
      }
656
#endif
657
0
      return;
658
0
    }
659
660
0
    TORRENT_ASSERT(d.mapping.empty());
661
0
    for (auto const& j : m_mappings)
662
0
    {
663
0
      mapping_t m;
664
0
      m.act = portmap_action::add;
665
0
      m.local_ep = j.local_ep;
666
0
      m.device = j.device;
667
0
      m.external_port = j.external_port;
668
0
      m.protocol = j.protocol;
669
0
      d.mapping.push_back(m);
670
0
    }
671
0
    std::tie(i, std::ignore) = m_devices.insert(d);
672
0
  }
673
674
675
  // iterate over the devices we know and connect and issue the mappings
676
0
  try_map_upnp();
677
678
  // check back in a little bit to see if we have seen any
679
  // devices at one of our default routes. If not, we want to override
680
  // ignoring them and use them instead (better than not working).
681
0
  m_map_timer.expires_after(seconds(1));
682
0
  ADD_OUTSTANDING_ASYNC("upnp::map_timer");
683
0
  m_map_timer.async_wait(std::bind(&upnp::map_timer, self(), _1));
684
0
}
685
686
void upnp::map_timer(error_code const& ec)
687
0
{
688
0
  TORRENT_ASSERT(is_single_thread());
689
0
  COMPLETE_ASYNC("upnp::map_timer");
690
0
  if (ec) return;
691
0
  if (m_closing) return;
692
693
0
  try_map_upnp();
694
0
}
695
696
void upnp::try_map_upnp()
697
0
{
698
0
  TORRENT_ASSERT(is_single_thread());
699
0
  if (m_devices.empty()) return;
700
701
0
  for (auto i = m_devices.begin(), end(m_devices.end()); i != end; ++i)
702
0
  {
703
0
    if (i->control_url.empty() && !i->upnp_connection && !i->disabled)
704
0
    {
705
      // we don't have a WANIP or WANPPP url for this device,
706
      // ask for it
707
0
      connect(const_cast<rootdevice&>(*i));
708
0
    }
709
0
  }
710
0
}
711
712
void upnp::post(upnp::rootdevice const& d, char const* soap
713
  , char const* soap_action)
714
0
{
715
0
  TORRENT_ASSERT(is_single_thread());
716
0
  TORRENT_ASSERT(d.magic == 1337);
717
0
  TORRENT_ASSERT(d.upnp_connection);
718
0
  TORRENT_ASSERT(!d.disabled);
719
720
0
  char header[2048];
721
0
  std::snprintf(header, sizeof(header), "POST %s HTTP/1.1\r\n"
722
0
    "Host: %s:%d\r\n"
723
0
    "Content-Type: text/xml; charset=\"utf-8\"\r\n"
724
0
    "Content-Length: %d\r\n"
725
0
    "Soapaction: \"%s#%s\"\r\n\r\n"
726
0
    "%s"
727
0
    , d.path.c_str(), d.hostname.c_str(), d.port
728
0
    , int(strlen(soap)), d.service_namespace.c_str(), soap_action
729
0
    , soap);
730
731
0
  d.upnp_connection->m_sendbuffer = header;
732
733
#ifndef TORRENT_DISABLE_LOGGING
734
  log("sending: %s", header);
735
#endif
736
0
}
737
738
int upnp::lease_duration(rootdevice const& d) const
739
0
{
740
0
  return d.use_lease_duration
741
0
    ? m_settings.get_int(settings_pack::upnp_lease_duration)
742
0
    : 0;
743
0
}
744
745
void upnp::create_port_mapping(http_connection& c, rootdevice& d
746
  , port_mapping_t const i)
747
0
{
748
0
  TORRENT_ASSERT(is_single_thread());
749
750
0
  TORRENT_ASSERT(d.magic == 1337);
751
752
0
  if (!d.upnp_connection)
753
0
  {
754
0
    TORRENT_ASSERT(d.disabled);
755
#ifndef TORRENT_DISABLE_LOGGING
756
    log("mapping %d aborted", static_cast<int>(i));
757
#endif
758
0
    return;
759
0
  }
760
761
0
  char const* soap_action = "AddPortMapping";
762
763
0
  error_code ec;
764
0
  std::string local_endpoint = print_address(c.socket().local_endpoint(ec).address());
765
766
0
  char soap[1024];
767
0
  std::snprintf(soap, sizeof(soap), "<?xml version=\"1.0\"?>\n"
768
0
    "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
769
0
    "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
770
0
    "<s:Body><u:%s xmlns:u=\"%s\">"
771
0
    "<NewRemoteHost></NewRemoteHost>"
772
0
    "<NewExternalPort>%d</NewExternalPort>"
773
0
    "<NewProtocol>%s</NewProtocol>"
774
0
    "<NewInternalPort>%d</NewInternalPort>"
775
0
    "<NewInternalClient>%s</NewInternalClient>"
776
0
    "<NewEnabled>1</NewEnabled>"
777
0
    "<NewPortMappingDescription>%s</NewPortMappingDescription>"
778
0
    "<NewLeaseDuration>%d</NewLeaseDuration>"
779
0
    "</u:%s></s:Body></s:Envelope>"
780
0
    , soap_action, d.service_namespace.c_str(), d.mapping[i].external_port
781
0
    , to_string(d.mapping[i].protocol)
782
0
    , d.mapping[i].local_ep.port()
783
0
    , local_endpoint.c_str()
784
0
    , m_settings.get_bool(settings_pack::anonymous_mode)
785
0
      ? "" : m_settings.get_str(settings_pack::user_agent).c_str()
786
0
    , lease_duration(d), soap_action);
787
788
0
  post(d, soap, soap_action);
789
0
}
790
791
void upnp::next(rootdevice& d, port_mapping_t const i)
792
0
{
793
0
  TORRENT_ASSERT(is_single_thread());
794
0
  TORRENT_ASSERT(!d.disabled);
795
0
  if (i < prev(m_mappings.end_index()))
796
0
  {
797
0
    update_map(d, lt::next(i));
798
0
  }
799
0
  else
800
0
  {
801
0
    auto const j = std::find_if(d.mapping.begin(), d.mapping.end()
802
0
      , [](mapping_t const& m) { return m.act != portmap_action::none; });
803
0
    if (j == d.mapping.end()) return;
804
805
0
    update_map(d, port_mapping_t{static_cast<int>(j - d.mapping.begin())});
806
0
  }
807
0
}
808
809
void upnp::update_map(rootdevice& d, port_mapping_t const i)
810
0
{
811
0
  TORRENT_ASSERT(is_single_thread());
812
0
  TORRENT_ASSERT(d.magic == 1337);
813
0
  TORRENT_ASSERT(i < d.mapping.end_index());
814
0
  TORRENT_ASSERT(d.mapping.size() == m_mappings.size());
815
0
  TORRENT_ASSERT(!d.disabled);
816
817
0
  if (d.upnp_connection) return;
818
819
  // this should not happen, but in case it does, don't fail at runtime
820
0
  if (i >= d.mapping.end_index()) return;
821
822
0
  std::shared_ptr<upnp> me(self());
823
824
0
  mapping_t& m = d.mapping[i];
825
826
0
  if (m.act == portmap_action::none
827
0
    || m.protocol == portmap_protocol::none)
828
0
  {
829
#ifndef TORRENT_DISABLE_LOGGING
830
    log("mapping %d does not need updating, skipping", static_cast<int>(i));
831
#endif
832
0
    m.act = portmap_action::none;
833
0
    next(d, i);
834
0
    return;
835
0
  }
836
837
0
  TORRENT_ASSERT(!d.upnp_connection);
838
0
  TORRENT_ASSERT(!d.service_namespace.empty());
839
840
#ifndef TORRENT_DISABLE_LOGGING
841
  log("connecting to %s", d.hostname.c_str());
842
#endif
843
0
  if (m.act == portmap_action::add)
844
0
  {
845
0
    if (m.failcount > 5)
846
0
    {
847
0
      m.act = portmap_action::none;
848
      // giving up
849
0
      next(d, i);
850
0
      return;
851
0
    }
852
853
0
    if (d.upnp_connection) d.upnp_connection->close();
854
0
    d.upnp_connection = std::make_shared<http_connection>(m_io_service
855
0
      , m_resolver
856
0
      , std::bind(&upnp::on_upnp_map_response, self(), _1, _2
857
0
        , std::ref(d), i, _4), true, default_max_bottled_buffer_size
858
0
      , std::bind(&upnp::create_port_mapping, self(), _1, std::ref(d), i)
859
0
      , http_filter_handler()
860
0
      , hostname_filter_handler()
861
0
#if TORRENT_USE_SSL
862
0
      , &m_ssl_ctx
863
0
#endif
864
0
      );
865
866
0
    bind_info_t bi{m.device, m.local_ep.address()};
867
0
    d.upnp_connection->start(d.hostname, d.port
868
0
      , seconds(10), nullptr, false, 5, bi);
869
0
  }
870
0
  else if (m.act == portmap_action::del)
871
0
  {
872
0
    if (d.upnp_connection) d.upnp_connection->close();
873
0
    d.upnp_connection = std::make_shared<http_connection>(m_io_service
874
0
      , m_resolver
875
0
      , std::bind(&upnp::on_upnp_unmap_response, self(), _1, _2
876
0
        , std::ref(d), i, _4), true, default_max_bottled_buffer_size
877
0
      , std::bind(&upnp::delete_port_mapping, self(), std::ref(d), i)
878
0
      , http_filter_handler()
879
0
      , hostname_filter_handler()
880
0
#if TORRENT_USE_SSL
881
0
      , &m_ssl_ctx
882
0
#endif
883
0
      );
884
0
    bind_info_t bi{m.device, m.local_ep.address()};
885
0
    d.upnp_connection->start(d.hostname, d.port
886
0
      , seconds(10), nullptr, false, 5, bi);
887
0
  }
888
889
0
  m.act = portmap_action::none;
890
0
  m.expires = aux::time_now() + seconds(30);
891
0
}
892
893
void upnp::delete_port_mapping(rootdevice& d, port_mapping_t const i)
894
0
{
895
0
  TORRENT_ASSERT(is_single_thread());
896
897
0
  TORRENT_ASSERT(d.magic == 1337);
898
899
0
  if (!d.upnp_connection)
900
0
  {
901
0
    TORRENT_ASSERT(d.disabled);
902
#ifndef TORRENT_DISABLE_LOGGING
903
    log("unmapping %d aborted", static_cast<int>(i));
904
#endif
905
0
    return;
906
0
  }
907
908
0
  char const* soap_action = "DeletePortMapping";
909
910
0
  char soap[1024];
911
0
  std::snprintf(soap, sizeof(soap), "<?xml version=\"1.0\"?>\n"
912
0
    "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
913
0
    "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
914
0
    "<s:Body><u:%s xmlns:u=\"%s\">"
915
0
    "<NewRemoteHost></NewRemoteHost>"
916
0
    "<NewExternalPort>%d</NewExternalPort>"
917
0
    "<NewProtocol>%s</NewProtocol>"
918
0
    "</u:%s></s:Body></s:Envelope>"
919
0
    , soap_action, d.service_namespace.c_str()
920
0
    , d.mapping[i].external_port
921
0
    , to_string(d.mapping[i].protocol)
922
0
    , soap_action);
923
924
0
  post(d, soap, soap_action);
925
0
}
926
927
void find_control_url(int const type, string_view str, parse_state& state)
928
0
{
929
0
  if (type == xml_start_tag)
930
0
  {
931
0
    state.tag_stack.push_back(str);
932
0
  }
933
0
  else if (type == xml_end_tag)
934
0
  {
935
0
    if (!state.tag_stack.empty())
936
0
    {
937
0
      if (state.in_service && string_equal_no_case(state.tag_stack.back(), "service"))
938
0
        state.in_service = false;
939
0
      state.tag_stack.pop_back();
940
0
    }
941
0
  }
942
0
  else if (type == xml_string)
943
0
  {
944
0
    if (state.tag_stack.empty()) return;
945
    // default to the first (or only) control url in the router's listing
946
0
    if (!state.in_service
947
0
      && state.top_tags("service", "servicetype")
948
0
      && state.service_type.empty())
949
0
    {
950
0
      if (string_equal_no_case(str, "urn:schemas-upnp-org:service:WANIPConnection:1")
951
0
        || string_equal_no_case(str, "urn:schemas-upnp-org:service:WANIPConnection:2")
952
0
        || string_equal_no_case(str, "urn:schemas-upnp-org:service:WANPPPConnection:1"))
953
0
      {
954
0
        state.service_type.assign(str.begin(), str.end());
955
0
        state.in_service = true;
956
0
      }
957
0
    }
958
0
    else if (state.control_url.empty() && state.in_service
959
0
      && state.top_tags("service", "controlurl") && str.size() > 0)
960
0
    {
961
      // default to the first (or only) control url in the router's listing
962
0
      state.control_url.assign(str.begin(), str.end());
963
0
    }
964
0
    else if (state.model.empty() && state.top_tags("device", "modelname"))
965
0
    {
966
0
      state.model.assign(str.begin(), str.end());
967
0
    }
968
0
    else if (string_equal_no_case(state.tag_stack.back(), "urlbase"))
969
0
    {
970
0
      state.url_base.assign(str.begin(), str.end());
971
0
    }
972
0
  }
973
0
}
974
975
void upnp::on_upnp_xml(error_code const& e
976
  , libtorrent::http_parser const& p, rootdevice& d
977
  , http_connection& c)
978
0
{
979
0
  TORRENT_ASSERT(is_single_thread());
980
0
  std::shared_ptr<upnp> me(self());
981
982
0
  TORRENT_ASSERT(d.magic == 1337);
983
0
  if (d.upnp_connection && d.upnp_connection.get() == &c)
984
0
  {
985
0
    d.upnp_connection->close();
986
0
    d.upnp_connection.reset();
987
0
  }
988
989
0
  if (m_closing) return;
990
991
0
  if (e && e != boost::asio::error::eof)
992
0
  {
993
#ifndef TORRENT_DISABLE_LOGGING
994
    if (should_log())
995
    {
996
      log("error while fetching control url from: %s: %s"
997
        , d.url.c_str(), convert_from_native(e.message()).c_str());
998
    }
999
#endif
1000
0
    d.disabled = true;
1001
0
    return;
1002
0
  }
1003
1004
0
  if (!p.header_finished())
1005
0
  {
1006
#ifndef TORRENT_DISABLE_LOGGING
1007
    log("error while fetching control url from: %s: incomplete HTTP message"
1008
      , d.url.c_str());
1009
#endif
1010
0
    d.disabled = true;
1011
0
    return;
1012
0
  }
1013
1014
0
  if (p.status_code() != 200)
1015
0
  {
1016
#ifndef TORRENT_DISABLE_LOGGING
1017
    if (should_log())
1018
    {
1019
      log("error while fetching control url from: %s: %s"
1020
        , d.url.c_str(), convert_from_native(p.message()).c_str());
1021
    }
1022
#endif
1023
0
    d.disabled = true;
1024
0
    return;
1025
0
  }
1026
1027
0
  parse_state s;
1028
0
  auto body = p.get_body();
1029
0
  xml_parse({body.data(), std::size_t(body.size())}, std::bind(&find_control_url, _1, _2, std::ref(s)));
1030
0
  if (s.control_url.empty())
1031
0
  {
1032
#ifndef TORRENT_DISABLE_LOGGING
1033
    log("could not find a port mapping interface in response from: %s"
1034
      , d.url.c_str());
1035
#endif
1036
0
    d.disabled = true;
1037
0
    return;
1038
0
  }
1039
0
  d.service_namespace = s.service_type;
1040
1041
0
  TORRENT_ASSERT(!d.service_namespace.empty());
1042
1043
0
  if (!s.model.empty()) m_model = s.model;
1044
1045
0
  if (!s.url_base.empty() && s.control_url.substr(0, 7) != "http://")
1046
0
  {
1047
    // avoid double slashes in path
1048
0
    if (s.url_base[s.url_base.size()-1] == '/'
1049
0
      && !s.control_url.empty()
1050
0
      && s.control_url[0] == '/')
1051
0
      s.url_base.erase(s.url_base.end()-1);
1052
0
    d.control_url = s.url_base + s.control_url;
1053
0
  }
1054
0
  else d.control_url = s.control_url;
1055
1056
0
  std::string protocol;
1057
0
  std::string auth;
1058
0
  error_code ec;
1059
0
  if (!d.control_url.empty() && d.control_url[0] == '/')
1060
0
  {
1061
0
    std::tie(protocol, auth, d.hostname, d.port, d.path)
1062
0
      = parse_url_components(d.url, ec);
1063
0
    if (d.port == -1) d.port = protocol == "http" ? 80 : 443;
1064
0
    d.control_url = protocol + "://" + d.hostname + ":"
1065
0
      + to_string(d.port).data() + s.control_url;
1066
0
  }
1067
1068
#ifndef TORRENT_DISABLE_LOGGING
1069
  if (should_log())
1070
  {
1071
    log("found control URL: %s namespace %s "
1072
      "urlbase: %s in response from %s"
1073
      , d.control_url.c_str(), d.service_namespace.c_str()
1074
      , s.url_base.c_str(), d.url.c_str());
1075
  }
1076
#endif
1077
1078
0
  std::tie(protocol, auth, d.hostname, d.port, d.path)
1079
0
    = parse_url_components(d.control_url, ec);
1080
0
  if (d.port == -1) d.port = protocol == "http" ? 80 : 443;
1081
1082
0
  if (ec)
1083
0
  {
1084
#ifndef TORRENT_DISABLE_LOGGING
1085
    if (should_log())
1086
    {
1087
      log("failed to parse URL '%s': %s"
1088
        , d.control_url.c_str(), convert_from_native(ec.message()).c_str());
1089
    }
1090
#endif
1091
0
    d.disabled = true;
1092
0
    return;
1093
0
  }
1094
1095
0
  if (d.upnp_connection) d.upnp_connection->close();
1096
0
  d.upnp_connection = std::make_shared<http_connection>(m_io_service
1097
0
    , m_resolver
1098
0
    , std::bind(&upnp::on_upnp_get_ip_address_response, self(), _1, _2
1099
0
      , std::ref(d), _4), true, default_max_bottled_buffer_size
1100
0
    , std::bind(&upnp::get_ip_address, self(), std::ref(d))
1101
0
    , http_filter_handler()
1102
0
    , hostname_filter_handler()
1103
0
#if TORRENT_USE_SSL
1104
0
    , &m_ssl_ctx
1105
0
#endif
1106
0
    );
1107
0
  d.upnp_connection->start(d.hostname, d.port, seconds(10));
1108
0
}
1109
1110
void upnp::get_ip_address(rootdevice& d)
1111
0
{
1112
0
  TORRENT_ASSERT(is_single_thread());
1113
1114
0
  TORRENT_ASSERT(d.magic == 1337);
1115
1116
0
  if (!d.upnp_connection)
1117
0
  {
1118
0
    TORRENT_ASSERT(d.disabled);
1119
#ifndef TORRENT_DISABLE_LOGGING
1120
    log("getting external IP address");
1121
#endif
1122
0
    return;
1123
0
  }
1124
1125
0
  char const* soap_action = "GetExternalIPAddress";
1126
1127
0
  char soap[1024];
1128
0
  std::snprintf(soap, sizeof(soap), "<?xml version=\"1.0\"?>\n"
1129
0
    "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
1130
0
    "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
1131
0
    "<s:Body><u:%s xmlns:u=\"%s\">"
1132
0
    "</u:%s></s:Body></s:Envelope>"
1133
0
    , soap_action, d.service_namespace.c_str()
1134
0
    , soap_action);
1135
1136
0
  post(d, soap, soap_action);
1137
0
}
1138
1139
void upnp::disable(error_code const& ec)
1140
0
{
1141
0
  TORRENT_ASSERT(is_single_thread());
1142
0
  m_disabled = true;
1143
1144
  // kill all mappings
1145
0
  for (auto i = m_mappings.begin(), end(m_mappings.end()); i != end; ++i)
1146
0
  {
1147
0
    if (i->protocol == portmap_protocol::none) continue;
1148
0
    portmap_protocol const proto = i->protocol;
1149
0
    i->protocol = portmap_protocol::none;
1150
0
    m_callback.on_port_mapping(port_mapping_t(static_cast<int>(i - m_mappings.begin()))
1151
0
      , address(), 0, proto, ec, portmap_transport::upnp, m_listen_handle);
1152
0
  }
1153
1154
  // we cannot clear the devices since there
1155
  // might be outstanding requests relying on
1156
  // the device entry being present when they
1157
  // complete
1158
0
  m_broadcast_timer.cancel();
1159
0
  m_refresh_timer.cancel();
1160
0
  m_map_timer.cancel();
1161
0
  error_code e;
1162
0
  m_unicast.socket.close(e);
1163
0
  m_multicast.socket.close(e);
1164
0
}
1165
1166
void find_error_code(int const type, string_view string, error_code_parse_state& state)
1167
0
{
1168
0
  if (state.exit) return;
1169
0
  if (type == xml_start_tag && string == "errorCode")
1170
0
  {
1171
0
    state.in_error_code = true;
1172
0
  }
1173
0
  else if (type == xml_string && state.in_error_code)
1174
0
  {
1175
0
    state.error_code = std::atoi(string.to_string().c_str());
1176
0
    state.exit = true;
1177
0
  }
1178
0
}
1179
1180
void find_ip_address(int const type, string_view string, ip_address_parse_state& state)
1181
0
{
1182
0
  find_error_code(type, string, state);
1183
0
  if (state.exit) return;
1184
1185
0
  if (type == xml_start_tag && string == "NewExternalIPAddress")
1186
0
  {
1187
0
    state.in_ip_address = true;
1188
0
  }
1189
0
  else if (type == xml_string && state.in_ip_address)
1190
0
  {
1191
0
    state.ip_address.assign(string.begin(), string.end());
1192
0
    state.exit = true;
1193
0
  }
1194
0
}
1195
1196
namespace {
1197
1198
  struct error_code_t
1199
  {
1200
    int code;
1201
    char const* msg;
1202
  };
1203
1204
  error_code_t error_codes[] =
1205
  {
1206
    {0, "no error"}
1207
    , {402, "Invalid Arguments"}
1208
    , {501, "Action Failed"}
1209
    , {714, "The specified value does not exist in the array"}
1210
    , {715, "The source IP address cannot be wild-carded"}
1211
    , {716, "The external port cannot be wild-carded"}
1212
    , {718, "The port mapping entry specified conflicts with "
1213
      "a mapping assigned previously to another client"}
1214
    , {724, "Internal and External port values must be the same"}
1215
    , {725, "The NAT implementation only supports permanent "
1216
      "lease times on port mappings"}
1217
    , {726, "RemoteHost must be a wildcard and cannot be a "
1218
      "specific IP address or DNS name"}
1219
    , {727, "ExternalPort must be a wildcard and cannot be a specific port "}
1220
  };
1221
1222
}
1223
1224
struct upnp_error_category final : boost::system::error_category
1225
{
1226
  const char* name() const BOOST_SYSTEM_NOEXCEPT override
1227
0
  {
1228
0
    return "upnp";
1229
0
  }
1230
1231
  std::string message(int ev) const override
1232
0
  {
1233
0
    int num_errors = sizeof(error_codes) / sizeof(error_codes[0]);
1234
0
    error_code_t* end = error_codes + num_errors;
1235
0
    error_code_t tmp = {ev, nullptr};
1236
0
    error_code_t* e = std::lower_bound(error_codes, end, tmp
1237
0
      , [] (error_code_t const& lhs, error_code_t const& rhs)
1238
0
      { return lhs.code < rhs.code; });
1239
0
    if (e != end && e->code == ev)
1240
0
    {
1241
0
      return e->msg;
1242
0
    }
1243
0
    char msg[500];
1244
0
    std::snprintf(msg, sizeof(msg), "unknown UPnP error (%d)", ev);
1245
0
    return msg;
1246
0
  }
1247
1248
  boost::system::error_condition default_error_condition(
1249
    int ev) const BOOST_SYSTEM_NOEXCEPT override
1250
0
  {
1251
0
    return {ev, *this};
1252
0
  }
1253
};
1254
1255
boost::system::error_category& upnp_category()
1256
0
{
1257
0
  static upnp_error_category cat;
1258
0
  return cat;
1259
0
}
1260
1261
void upnp::on_upnp_get_ip_address_response(error_code const& e
1262
  , libtorrent::http_parser const& p, rootdevice& d
1263
  , http_connection& c)
1264
0
{
1265
0
  TORRENT_ASSERT(is_single_thread());
1266
0
  std::shared_ptr<upnp> me(self());
1267
1268
0
  TORRENT_ASSERT(d.magic == 1337);
1269
0
  if (d.upnp_connection && d.upnp_connection.get() == &c)
1270
0
  {
1271
0
    d.upnp_connection->close();
1272
0
    d.upnp_connection.reset();
1273
0
  }
1274
1275
0
  if (m_closing) return;
1276
1277
0
  if (e && e != boost::asio::error::eof)
1278
0
  {
1279
#ifndef TORRENT_DISABLE_LOGGING
1280
    if (should_log())
1281
    {
1282
      log("error while getting external IP address: %s"
1283
        , convert_from_native(e.message()).c_str());
1284
    }
1285
#endif
1286
0
    if (num_mappings() > 0) update_map(d, port_mapping_t{0});
1287
0
    return;
1288
0
  }
1289
1290
0
  if (!p.header_finished())
1291
0
  {
1292
#ifndef TORRENT_DISABLE_LOGGING
1293
    log("error while getting external IP address: incomplete http message");
1294
#endif
1295
0
    if (num_mappings() > 0) update_map(d, port_mapping_t{0});
1296
0
    return;
1297
0
  }
1298
1299
0
  if (p.status_code() != 200)
1300
0
  {
1301
#ifndef TORRENT_DISABLE_LOGGING
1302
    if (should_log())
1303
    {
1304
      log("error while getting external IP address: %s"
1305
        , convert_from_native(p.message()).c_str());
1306
    }
1307
#endif
1308
0
    if (num_mappings() > 0) update_map(d, port_mapping_t{0});
1309
0
    return;
1310
0
  }
1311
1312
  // response may look like
1313
  // <?xml version="1.0"?>
1314
  // <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
1315
  // <s:Body><u:GetExternalIPAddressResponse xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">
1316
  // <NewExternalIPAddress>192.168.160.19</NewExternalIPAddress>
1317
  // </u:GetExternalIPAddressResponse>
1318
  // </s:Body>
1319
  // </s:Envelope>
1320
1321
0
  span<char const> body = p.get_body();
1322
#ifndef TORRENT_DISABLE_LOGGING
1323
  if (should_log())
1324
  {
1325
    log("get external IP address response: %s"
1326
      , std::string(body.data(), static_cast<std::size_t>(body.size())).c_str());
1327
  }
1328
#endif
1329
1330
0
  ip_address_parse_state s;
1331
0
  xml_parse({body.data(), std::size_t(body.size())}, std::bind(&find_ip_address, _1, _2, std::ref(s)));
1332
#ifndef TORRENT_DISABLE_LOGGING
1333
  if (s.error_code != -1)
1334
  {
1335
    log("error while getting external IP address, code: %d", s.error_code);
1336
  }
1337
#endif
1338
1339
0
  if (!s.ip_address.empty())
1340
0
  {
1341
#ifndef TORRENT_DISABLE_LOGGING
1342
    log("got router external IP address %s", s.ip_address.c_str());
1343
#endif
1344
0
    d.external_ip = make_address(s.ip_address.c_str(), ignore_error);
1345
0
  }
1346
0
  else
1347
0
  {
1348
#ifndef TORRENT_DISABLE_LOGGING
1349
    log("failed to find external IP address in response");
1350
#endif
1351
0
  }
1352
1353
0
  if (num_mappings() > 0) update_map(d, port_mapping_t{0});
1354
0
}
1355
1356
void upnp::on_upnp_map_response(error_code const& e
1357
  , libtorrent::http_parser const& p, rootdevice& d, port_mapping_t const mapping
1358
  , http_connection& c)
1359
0
{
1360
0
  TORRENT_ASSERT(is_single_thread());
1361
0
  std::shared_ptr<upnp> me(self());
1362
1363
0
  TORRENT_ASSERT(d.magic == 1337);
1364
0
  if (d.upnp_connection && d.upnp_connection.get() == &c)
1365
0
  {
1366
0
    d.upnp_connection->close();
1367
0
    d.upnp_connection.reset();
1368
0
  }
1369
1370
0
  if (e && e != boost::asio::error::eof)
1371
0
  {
1372
#ifndef TORRENT_DISABLE_LOGGING
1373
    if (should_log())
1374
    {
1375
      log("error while adding port map: %s"
1376
        , convert_from_native(e.message()).c_str());
1377
    }
1378
#endif
1379
0
    d.disabled = true;
1380
0
    return;
1381
0
  }
1382
1383
0
  if (m_closing) return;
1384
1385
//   error code response may look like this:
1386
//  <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
1387
//    s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
1388
//   <s:Body>
1389
//    <s:Fault>
1390
//    <faultcode>s:Client</faultcode>
1391
//    <faultstring>UPnPError</faultstring>
1392
//    <detail>
1393
//     <UPnPErrorxmlns="urn:schemas-upnp-org:control-1-0">
1394
//      <errorCode>402</errorCode>
1395
//      <errorDescription>Invalid Args</errorDescription>
1396
//     </UPnPError>
1397
//    </detail>
1398
//    </s:Fault>
1399
//   </s:Body>
1400
//  </s:Envelope>
1401
1402
0
  if (!p.header_finished())
1403
0
  {
1404
#ifndef TORRENT_DISABLE_LOGGING
1405
    log("error while adding port map: incomplete http message");
1406
#endif
1407
0
    next(d, mapping);
1408
0
    return;
1409
0
  }
1410
1411
0
  std::string const& ct = p.header("content-type");
1412
0
  if (!ct.empty()
1413
0
    && ct.find_first_of("text/xml") == std::string::npos
1414
0
    && ct.find_first_of("text/soap+xml") == std::string::npos
1415
0
    && ct.find_first_of("application/xml") == std::string::npos
1416
0
    && ct.find_first_of("application/soap+xml") == std::string::npos
1417
0
    )
1418
0
  {
1419
#ifndef TORRENT_DISABLE_LOGGING
1420
    log("error while adding port map: invalid content-type, \"%s\". "
1421
      "Expected text/xml or application/soap+xml", ct.c_str());
1422
#endif
1423
0
    next(d, mapping);
1424
0
    return;
1425
0
  }
1426
1427
  // We don't want to ignore responses with return codes other than 200
1428
  // since those might contain valid UPnP error codes
1429
1430
0
  error_code_parse_state s;
1431
0
  span<char const> body = p.get_body();
1432
0
  xml_parse({body.data(), std::size_t(body.size())}, std::bind(&find_error_code, _1, _2, std::ref(s)));
1433
1434
0
  if (s.error_code != -1)
1435
0
  {
1436
#ifndef TORRENT_DISABLE_LOGGING
1437
    log("error while adding port map, code: %d", s.error_code);
1438
#endif
1439
0
  }
1440
1441
0
  mapping_t& m = d.mapping[mapping];
1442
1443
0
  if (s.error_code == 725)
1444
0
  {
1445
    // The gateway only supports permanent leases
1446
0
    d.use_lease_duration = false;
1447
0
    m.act = portmap_action::add;
1448
0
    ++m.failcount;
1449
0
    update_map(d, mapping);
1450
0
    return;
1451
0
  }
1452
0
  else if (s.error_code == 727)
1453
0
  {
1454
0
    return_error(mapping, s.error_code);
1455
0
  }
1456
0
  else if ((s.error_code == 718 || s.error_code == 501) && m.failcount < 4)
1457
0
  {
1458
    // some routers return 501 action failed, instead of 716
1459
    // The external port conflicts with another mapping
1460
    // pick a random port
1461
0
    m.external_port = 40000 + int(random(10000));
1462
0
    m.act = portmap_action::add;
1463
0
    ++m.failcount;
1464
0
    update_map(d, mapping);
1465
0
    return;
1466
0
  }
1467
0
  else if (s.error_code != -1)
1468
0
  {
1469
0
    return_error(mapping, s.error_code);
1470
0
  }
1471
1472
#ifndef TORRENT_DISABLE_LOGGING
1473
  if (should_log())
1474
  {
1475
    log("map response: %s"
1476
      , std::string(body.data(), static_cast<std::size_t>(body.size())).c_str());
1477
  }
1478
#endif
1479
1480
0
  if (s.error_code == -1)
1481
0
  {
1482
0
    m_callback.on_port_mapping(mapping, d.external_ip, m.external_port, m.protocol, error_code()
1483
0
      , portmap_transport::upnp, m_listen_handle);
1484
0
    if (d.use_lease_duration && m_settings.get_int(settings_pack::upnp_lease_duration) != 0)
1485
0
    {
1486
0
      time_point const now = aux::time_now();
1487
0
      m.expires = now + seconds(
1488
0
        m_settings.get_int(settings_pack::upnp_lease_duration) * 3 / 4);
1489
0
      time_point next_expire = m_refresh_timer.expiry();
1490
0
      if (next_expire < now || next_expire > m.expires)
1491
0
      {
1492
0
        ADD_OUTSTANDING_ASYNC("upnp::on_expire");
1493
0
        m_refresh_timer.expires_at(m.expires);
1494
0
        m_refresh_timer.async_wait(std::bind(&upnp::on_expire, self(), _1));
1495
0
      }
1496
0
    }
1497
0
    else
1498
0
    {
1499
0
      m.expires = max_time();
1500
0
    }
1501
0
    m.failcount = 0;
1502
0
  }
1503
1504
0
  next(d, mapping);
1505
0
}
1506
1507
void upnp::return_error(port_mapping_t const mapping, int const code)
1508
0
{
1509
0
  TORRENT_ASSERT(is_single_thread());
1510
0
  int num_errors = sizeof(error_codes) / sizeof(error_codes[0]);
1511
0
  error_code_t* end = error_codes + num_errors;
1512
0
  error_code_t tmp = {code, nullptr};
1513
0
  error_code_t* e = std::lower_bound(error_codes, end, tmp
1514
0
    , [] (error_code_t const& lhs, error_code_t const& rhs)
1515
0
    { return lhs.code < rhs.code; });
1516
1517
0
  std::string error_string = "UPnP mapping error ";
1518
0
  error_string += to_string(code).data();
1519
0
  if (e != end && e->code == code)
1520
0
  {
1521
0
    error_string += ": ";
1522
0
    error_string += e->msg;
1523
0
  }
1524
0
  portmap_protocol const proto = m_mappings[mapping].protocol;
1525
0
  m_callback.on_port_mapping(mapping, address(), 0, proto, error_code(code, upnp_category())
1526
0
    , portmap_transport::upnp, m_listen_handle);
1527
0
}
1528
1529
void upnp::on_upnp_unmap_response(error_code const& e
1530
  , libtorrent::http_parser const& p, rootdevice& d
1531
  , port_mapping_t const mapping
1532
  , http_connection& c)
1533
0
{
1534
0
  TORRENT_ASSERT(is_single_thread());
1535
0
  std::shared_ptr<upnp> me(self());
1536
1537
0
  TORRENT_ASSERT(d.magic == 1337);
1538
0
  if (d.upnp_connection && d.upnp_connection.get() == &c)
1539
0
  {
1540
0
    d.upnp_connection->close();
1541
0
    d.upnp_connection.reset();
1542
0
  }
1543
1544
0
  if (e && e != boost::asio::error::eof)
1545
0
  {
1546
#ifndef TORRENT_DISABLE_LOGGING
1547
    if (should_log())
1548
    {
1549
      log("error while deleting portmap: %s"
1550
        , convert_from_native(e.message()).c_str());
1551
    }
1552
#endif
1553
0
  }
1554
0
  else if (!p.header_finished())
1555
0
  {
1556
#ifndef TORRENT_DISABLE_LOGGING
1557
    log("error while deleting portmap: incomplete http message");
1558
#endif
1559
0
  }
1560
0
  else if (p.status_code() != 200)
1561
0
  {
1562
#ifndef TORRENT_DISABLE_LOGGING
1563
    if (should_log())
1564
    {
1565
      log("error while deleting portmap: %s"
1566
        , convert_from_native(p.message()).c_str());
1567
    }
1568
#endif
1569
0
  }
1570
0
  else
1571
0
  {
1572
#ifndef TORRENT_DISABLE_LOGGING
1573
    if (should_log())
1574
    {
1575
      span<char const> body = p.get_body();
1576
      log("unmap response: %s"
1577
        , std::string(body.data(), static_cast<std::size_t>(body.size())).c_str());
1578
    }
1579
#endif
1580
0
  }
1581
1582
0
  error_code_parse_state s;
1583
0
  if (p.header_finished())
1584
0
  {
1585
0
    span<char const> body = p.get_body();
1586
0
    xml_parse({body.data(), std::size_t(body.size())}, std::bind(&find_error_code, _1, _2, std::ref(s)));
1587
0
  }
1588
1589
0
  portmap_protocol const proto = m_mappings[mapping].protocol;
1590
1591
0
  m_callback.on_port_mapping(mapping, address(), 0, proto, p.status_code() != 200
1592
0
    ? error_code(p.status_code(), http_category())
1593
0
    : error_code(s.error_code, upnp_category())
1594
0
    , portmap_transport::upnp, m_listen_handle);
1595
1596
0
  d.mapping[mapping].protocol = portmap_protocol::none;
1597
1598
  // free the slot in global mappings
1599
0
  auto pred = [mapping](rootdevice const& rd)
1600
0
    { return rd.mapping.end_index() <= mapping || rd.mapping[mapping].protocol == portmap_protocol::none; };
1601
0
  if (std::all_of(m_devices.begin(), m_devices.end(), pred))
1602
0
  {
1603
0
    m_mappings[mapping].protocol = portmap_protocol::none;
1604
0
  }
1605
1606
0
  next(d, mapping);
1607
0
}
1608
1609
void upnp::on_expire(error_code const& ec)
1610
0
{
1611
0
  TORRENT_ASSERT(is_single_thread());
1612
0
  COMPLETE_ASYNC("upnp::on_expire");
1613
0
  if (ec) return;
1614
1615
0
  if (m_closing) return;
1616
1617
0
  time_point const now = aux::time_now();
1618
0
  time_point next_expire = max_time();
1619
1620
0
  for (auto& dev : m_devices)
1621
0
  {
1622
0
    auto& d = const_cast<rootdevice&>(dev);
1623
0
    TORRENT_ASSERT(d.magic == 1337);
1624
0
    if (d.disabled) continue;
1625
0
    for (port_mapping_t m{0}; m < m_mappings.end_index(); ++m)
1626
0
    {
1627
0
      if (d.mapping[m].expires == max_time())
1628
0
        continue;
1629
1630
0
      if (d.mapping[m].expires <= now)
1631
0
      {
1632
0
        d.mapping[m].act = portmap_action::add;
1633
0
        update_map(d, m);
1634
0
      }
1635
0
      if (d.mapping[m].expires < next_expire)
1636
0
      {
1637
0
        next_expire = d.mapping[m].expires;
1638
0
      }
1639
0
    }
1640
0
  }
1641
0
  if (next_expire != max_time())
1642
0
  {
1643
0
    ADD_OUTSTANDING_ASYNC("upnp::on_expire");
1644
0
    m_refresh_timer.expires_at(next_expire);
1645
0
    m_refresh_timer.async_wait(std::bind(&upnp::on_expire, self(), _1));
1646
0
  }
1647
0
}
1648
1649
void upnp::close()
1650
0
{
1651
0
  TORRENT_ASSERT(is_single_thread());
1652
1653
0
  m_refresh_timer.cancel();
1654
0
  m_broadcast_timer.cancel();
1655
0
  m_map_timer.cancel();
1656
0
  m_closing = true;
1657
0
  error_code ec;
1658
0
  m_unicast.socket.close(ec);
1659
0
  m_multicast.socket.close(ec);
1660
1661
0
  for (auto& dev : m_devices)
1662
0
  {
1663
0
    auto& d = const_cast<rootdevice&>(dev);
1664
0
    TORRENT_ASSERT(d.magic == 1337);
1665
0
    if (d.disabled || d.control_url.empty()) continue;
1666
0
    for (auto& m : d.mapping)
1667
0
    {
1668
0
      if (m.protocol == portmap_protocol::none) continue;
1669
0
      if (m.act == portmap_action::add)
1670
0
      {
1671
0
        m.act = portmap_action::none;
1672
0
        continue;
1673
0
      }
1674
0
      m.act = portmap_action::del;
1675
0
      m_mappings[port_mapping_t{static_cast<int>(&m - d.mapping.data())}].protocol = portmap_protocol::none;
1676
0
    }
1677
0
    if (num_mappings() > 0) update_map(d, port_mapping_t{0});
1678
0
  }
1679
0
}
1680
1681
}