/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 | | } |