/src/libtorrent/src/natpmp.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | |
3 | | Copyright (c) 2007-2010, 2012, 2015-2020, 2022, Arvid Norberg |
4 | | Copyright (c) 2016-2017, 2019, Alden Torres |
5 | | Copyright (c) 2017, Pavel Pimenov |
6 | | Copyright (c) 2018, Steven Siloti |
7 | | All rights reserved. |
8 | | |
9 | | Redistribution and use in source and binary forms, with or without |
10 | | modification, are permitted provided that the following conditions |
11 | | are met: |
12 | | |
13 | | * Redistributions of source code must retain the above copyright |
14 | | notice, this list of conditions and the following disclaimer. |
15 | | * Redistributions in binary form must reproduce the above copyright |
16 | | notice, this list of conditions and the following disclaimer in |
17 | | the documentation and/or other materials provided with the distribution. |
18 | | * Neither the name of the author nor the names of its |
19 | | contributors may be used to endorse or promote products derived |
20 | | from this software without specific prior written permission. |
21 | | |
22 | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
23 | | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
24 | | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
25 | | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
26 | | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
27 | | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
28 | | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
29 | | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
30 | | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
31 | | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
32 | | POSSIBILITY OF SUCH DAMAGE. |
33 | | |
34 | | */ |
35 | | #include "libtorrent/config.hpp" |
36 | | |
37 | | #include "libtorrent/aux_/disable_warnings_push.hpp" |
38 | | |
39 | | #if defined TORRENT_OS2 |
40 | | #include <pthread.h> |
41 | | #endif |
42 | | |
43 | | #include <boost/asio/ip/host_name.hpp> |
44 | | |
45 | | #include "libtorrent/aux_/disable_warnings_pop.hpp" |
46 | | |
47 | | #include <cstdio> // for snprintf |
48 | | #include <cinttypes> // for PRId64 et.al. |
49 | | #include <cstdarg> |
50 | | #include <functional> |
51 | | #include <cstring> // for memcpy |
52 | | |
53 | | #include "libtorrent/natpmp.hpp" |
54 | | #include "libtorrent/io.hpp" |
55 | | #include "libtorrent/assert.hpp" |
56 | | #include "libtorrent/enum_net.hpp" |
57 | | #include "libtorrent/socket_io.hpp" |
58 | | #include "libtorrent/io_context.hpp" |
59 | | #include "libtorrent/aux_/time.hpp" |
60 | | #include "libtorrent/debug.hpp" |
61 | | #include "libtorrent/random.hpp" |
62 | | #include "libtorrent/aux_/ip_helpers.hpp" // for is_local |
63 | | #include "libtorrent/aux_/escape_string.hpp" |
64 | | #include "libtorrent/aux_/numeric_cast.hpp" |
65 | | |
66 | | namespace libtorrent { |
67 | | |
68 | | struct pcp_error_category final : boost::system::error_category |
69 | | { |
70 | | const char* name() const BOOST_SYSTEM_NOEXCEPT override |
71 | 0 | { return "pcp error"; } |
72 | | std::string message(int ev) const override |
73 | 0 | { |
74 | 0 | static char const* msgs[] = |
75 | 0 | { |
76 | 0 | "success", |
77 | 0 | "unsupported version", |
78 | 0 | "not authorized", |
79 | 0 | "malformed request", |
80 | 0 | "unsupported opcode", |
81 | 0 | "unsupported option", |
82 | 0 | "malformed option", |
83 | 0 | "network failure", |
84 | 0 | "no resources", |
85 | 0 | "unsupported protocol", |
86 | 0 | "user exceeded quota", |
87 | 0 | "cannot provide external", |
88 | 0 | "address mismatch", |
89 | 0 | "excessive remote peers", |
90 | 0 | }; |
91 | 0 | if (ev < 0 || ev >= int(sizeof(msgs)/sizeof(msgs[0]))) |
92 | 0 | return "Unknown error"; |
93 | 0 | return msgs[ev]; |
94 | 0 | } |
95 | | boost::system::error_condition default_error_condition( |
96 | | int ev) const BOOST_SYSTEM_NOEXCEPT override |
97 | 0 | { return {ev, *this}; } |
98 | | }; |
99 | | |
100 | | boost::system::error_category& pcp_category() |
101 | 0 | { |
102 | 0 | static pcp_error_category pcp_category; |
103 | 0 | return pcp_category; |
104 | 0 | } |
105 | | |
106 | | namespace errors |
107 | | { |
108 | | // hidden |
109 | | boost::system::error_code make_error_code(pcp_errors e) |
110 | 0 | { |
111 | 0 | return {e, pcp_category()}; |
112 | 0 | } |
113 | | } |
114 | | |
115 | | error_code natpmp::from_result_code(int const version, int result) |
116 | 0 | { |
117 | 0 | if (version == version_natpmp) |
118 | 0 | { |
119 | | // a few nat-pmp result codes map to different codes |
120 | | // in pcp |
121 | 0 | switch (result) |
122 | 0 | { |
123 | 0 | case 3:result = 7; break; |
124 | 0 | case 4:result = 8; break; |
125 | 0 | case 5:result = 4; break; |
126 | 0 | } |
127 | 0 | } |
128 | 0 | return errors::pcp_errors(result); |
129 | 0 | } |
130 | | |
131 | | char const* natpmp::version_to_string(protocol_version version) |
132 | 0 | { |
133 | 0 | return version == version_natpmp ? "NAT-PMP" : "PCP"; |
134 | 0 | } |
135 | | |
136 | | using namespace aux; |
137 | | using namespace std::placeholders; |
138 | | |
139 | | natpmp::natpmp(io_context& ios |
140 | | , aux::portmap_callback& cb |
141 | | , listen_socket_handle ls) |
142 | 0 | : m_callback(cb) |
143 | 0 | , m_socket(ios) |
144 | 0 | , m_send_timer(ios) |
145 | 0 | , m_refresh_timer(ios) |
146 | 0 | , m_ioc(ios) |
147 | 0 | , m_listen_handle(std::move(ls)) |
148 | 0 | { |
149 | | // unfortunately async operations rely on the storage |
150 | | // for this array not to be reallocated, by passing |
151 | | // around pointers to its elements. so reserve size for now |
152 | 0 | m_mappings.reserve(10); |
153 | 0 | } |
154 | | |
155 | | void natpmp::start(ip_interface const& ip) |
156 | 0 | { |
157 | 0 | TORRENT_ASSERT(is_single_thread()); |
158 | | |
159 | | // assume servers support PCP and fall back to NAT-PMP |
160 | | // if necessary |
161 | 0 | m_version = version_pcp; |
162 | |
|
163 | 0 | address const& local_address = ip.interface_address; |
164 | |
|
165 | 0 | error_code ec; |
166 | 0 | auto const routes = enum_routes(m_ioc, ec); |
167 | 0 | if (ec) |
168 | 0 | { |
169 | | #ifndef TORRENT_DISABLE_LOGGING |
170 | | if (should_log()) |
171 | | { |
172 | | log("failed to enumerate routes: %s" |
173 | | , convert_from_native(ec.message()).c_str()); |
174 | | } |
175 | | #endif |
176 | 0 | disable(ec); |
177 | 0 | } |
178 | |
|
179 | 0 | auto const route = get_gateway(ip, routes); |
180 | |
|
181 | 0 | if (!route) |
182 | 0 | { |
183 | | #ifndef TORRENT_DISABLE_LOGGING |
184 | | if (should_log()) |
185 | | { |
186 | | log("failed to find default route for \"%s\" %s" |
187 | | , ip.name, local_address.to_string().c_str()); |
188 | | } |
189 | | #endif |
190 | 0 | disable(ec); |
191 | 0 | return; |
192 | 0 | } |
193 | | |
194 | 0 | m_disabled = false; |
195 | |
|
196 | 0 | udp::endpoint const nat_endpoint(*route, 5351); |
197 | 0 | if (nat_endpoint == m_nat_endpoint) return; |
198 | 0 | m_nat_endpoint = nat_endpoint; |
199 | |
|
200 | | #ifndef TORRENT_DISABLE_LOGGING |
201 | | if (should_log()) |
202 | | { |
203 | | log("found gateway at: %s" |
204 | | , print_address(m_nat_endpoint.address()).c_str()); |
205 | | } |
206 | | #endif |
207 | |
|
208 | 0 | m_socket.open(local_address.is_v4() ? udp::v4() : udp::v6(), ec); |
209 | 0 | if (ec) |
210 | 0 | { |
211 | 0 | disable(ec); |
212 | 0 | return; |
213 | 0 | } |
214 | 0 | m_socket.bind({local_address, 0}, ec); |
215 | 0 | if (ec) |
216 | 0 | { |
217 | 0 | disable(ec); |
218 | 0 | return; |
219 | 0 | } |
220 | | |
221 | 0 | ADD_OUTSTANDING_ASYNC("natpmp::on_reply"); |
222 | 0 | m_socket.async_receive_from(boost::asio::buffer(&m_response_buffer[0] |
223 | 0 | , sizeof(m_response_buffer)) |
224 | 0 | , m_remote, std::bind(&natpmp::on_reply, self(), _1, _2)); |
225 | 0 | if (m_version == version_natpmp) |
226 | 0 | send_get_ip_address_request(); |
227 | |
|
228 | 0 | for (auto i = m_mappings.begin(), end(m_mappings.end()); i != end; ++i) |
229 | 0 | { |
230 | 0 | if (i->protocol == portmap_protocol::none |
231 | 0 | || i->act != portmap_action::none) |
232 | 0 | continue; |
233 | 0 | i->act = portmap_action::add; |
234 | 0 | update_mapping(port_mapping_t(int(i - m_mappings.begin()))); |
235 | 0 | } |
236 | 0 | } |
237 | | |
238 | | void natpmp::send_get_ip_address_request() |
239 | 0 | { |
240 | 0 | TORRENT_ASSERT(is_single_thread()); |
241 | 0 | using namespace libtorrent::aux; |
242 | | |
243 | | // this opcode only exists in NAT-PMP |
244 | | // PCP routers report the external IP in the response to a MAP operation |
245 | 0 | TORRENT_ASSERT(m_version == version_natpmp); |
246 | 0 | if (m_version != version_natpmp) |
247 | 0 | return; |
248 | | |
249 | 0 | char buf[2]; |
250 | 0 | char* out = buf; |
251 | 0 | write_uint8(version_natpmp, out); |
252 | 0 | write_uint8(0, out); // public IP address request opcode |
253 | | #ifndef TORRENT_DISABLE_LOGGING |
254 | | log("==> get public IP address"); |
255 | | #endif |
256 | |
|
257 | 0 | error_code ec; |
258 | 0 | m_socket.send_to(boost::asio::buffer(buf, sizeof(buf)), m_nat_endpoint, 0, ec); |
259 | 0 | } |
260 | | |
261 | | bool natpmp::get_mapping(port_mapping_t const index, int& local_port |
262 | | , int& external_port, portmap_protocol& protocol) const |
263 | 0 | { |
264 | 0 | TORRENT_ASSERT(is_single_thread()); |
265 | |
|
266 | 0 | TORRENT_ASSERT(index < m_mappings.end_index() && index >= port_mapping_t{}); |
267 | 0 | if (index >= m_mappings.end_index() || index < port_mapping_t{}) return false; |
268 | 0 | mapping_t const& m = m_mappings[index]; |
269 | 0 | if (m.protocol == portmap_protocol::none) return false; |
270 | 0 | local_port = m.local_port; |
271 | 0 | external_port = m.external_port; |
272 | 0 | protocol = m.protocol; |
273 | 0 | return true; |
274 | 0 | } |
275 | | |
276 | | #ifndef TORRENT_DISABLE_LOGGING |
277 | | bool natpmp::should_log() const |
278 | | { |
279 | | return m_callback.should_log_portmap(portmap_transport::natpmp); |
280 | | } |
281 | | |
282 | | void natpmp::mapping_log(char const* op, mapping_t const& m) const |
283 | | { |
284 | | if (should_log()) |
285 | | { |
286 | | log("%s-mapping: proto: %s port: %d local-port: %d action: %s ttl: %" PRId64 |
287 | | , op |
288 | | , m.protocol == portmap_protocol::none |
289 | | ? "none" : to_string(m.protocol) |
290 | | , m.external_port |
291 | | , m.local_port |
292 | | , to_string(m.act) |
293 | | , (m.expires.time_since_epoch() != seconds(0)) |
294 | | ? total_seconds(m.expires - aux::time_now()) |
295 | | : std::int64_t(0)); |
296 | | } |
297 | | } |
298 | | |
299 | | TORRENT_FORMAT(2, 3) |
300 | | void natpmp::log(char const* fmt, ...) const |
301 | | { |
302 | | TORRENT_ASSERT(is_single_thread()); |
303 | | if (!should_log()) return; |
304 | | char msg[200]; |
305 | | va_list v; |
306 | | va_start(v, fmt); |
307 | | std::vsnprintf(msg, sizeof(msg), fmt, v); |
308 | | va_end(v); |
309 | | m_callback.log_portmap(portmap_transport::natpmp, msg, m_listen_handle); |
310 | | } |
311 | | #endif |
312 | | |
313 | | void natpmp::disable(error_code const& ec) |
314 | 0 | { |
315 | 0 | TORRENT_ASSERT(is_single_thread()); |
316 | 0 | m_disabled = true; |
317 | |
|
318 | 0 | for (auto i = m_mappings.begin(), end(m_mappings.end()); i != end; ++i) |
319 | 0 | { |
320 | 0 | if (i->protocol == portmap_protocol::none) continue; |
321 | 0 | portmap_protocol const proto = i->protocol; |
322 | 0 | i->protocol = portmap_protocol::none; |
323 | 0 | port_mapping_t const index(static_cast<int>(i - m_mappings.begin())); |
324 | 0 | m_callback.on_port_mapping(index, address(), 0, proto, ec |
325 | 0 | , portmap_transport::natpmp, m_listen_handle); |
326 | 0 | } |
327 | 0 | close_impl(); |
328 | 0 | } |
329 | | |
330 | | void natpmp::delete_mapping(port_mapping_t const index) |
331 | 0 | { |
332 | 0 | TORRENT_ASSERT(is_single_thread()); |
333 | |
|
334 | 0 | TORRENT_ASSERT(index < m_mappings.end_index() && index >= port_mapping_t{}); |
335 | 0 | if (index >= m_mappings.end_index() || index < port_mapping_t{}) return; |
336 | 0 | mapping_t& m = m_mappings[index]; |
337 | |
|
338 | 0 | if (m.protocol == portmap_protocol::none) return; |
339 | 0 | if (!m.map_sent) |
340 | 0 | { |
341 | 0 | m.act = portmap_action::none; |
342 | 0 | m.protocol = portmap_protocol::none; |
343 | 0 | return; |
344 | 0 | } |
345 | | |
346 | 0 | m.act = portmap_action::del; |
347 | 0 | update_mapping(index); |
348 | 0 | } |
349 | | |
350 | | port_mapping_t natpmp::add_mapping(portmap_protocol const p, int const external_port |
351 | | , tcp::endpoint const local_ep, std::string const&) |
352 | 0 | { |
353 | 0 | TORRENT_ASSERT(is_single_thread()); |
354 | |
|
355 | 0 | if (m_disabled) return port_mapping_t{-1}; |
356 | | |
357 | 0 | auto i = std::find_if(m_mappings.begin() |
358 | 0 | , m_mappings.end(), [] (mapping_t const& m) { return m.protocol == portmap_protocol::none; }); |
359 | 0 | if (i == m_mappings.end()) |
360 | 0 | { |
361 | 0 | m_mappings.push_back(mapping_t()); |
362 | 0 | i = m_mappings.end() - 1; |
363 | 0 | } |
364 | 0 | aux::crypto_random_bytes(i->nonce); |
365 | 0 | i->protocol = p; |
366 | 0 | i->external_port = external_port; |
367 | 0 | i->local_port = local_ep.port(); |
368 | 0 | i->act = portmap_action::add; |
369 | |
|
370 | 0 | port_mapping_t const mapping_index(static_cast<int>(i - m_mappings.begin())); |
371 | | #ifndef TORRENT_DISABLE_LOGGING |
372 | | mapping_log("add",*i); |
373 | | #endif |
374 | |
|
375 | 0 | update_mapping(mapping_index); |
376 | 0 | return port_mapping_t{mapping_index}; |
377 | 0 | } |
378 | | |
379 | | void natpmp::try_next_mapping(port_mapping_t const i) |
380 | 0 | { |
381 | 0 | TORRENT_ASSERT(is_single_thread()); |
382 | 0 | if (i < prev(m_mappings.end_index())) |
383 | 0 | { |
384 | 0 | update_mapping(next(i)); |
385 | 0 | return; |
386 | 0 | } |
387 | | |
388 | 0 | auto const m = std::find_if( |
389 | 0 | m_mappings.begin(), m_mappings.end() |
390 | 0 | , [] (mapping_t const& ma) { return ma.act != portmap_action::none |
391 | 0 | && ma.protocol != portmap_protocol::none; }); |
392 | |
|
393 | 0 | if (m == m_mappings.end()) |
394 | 0 | { |
395 | 0 | if (m_abort) |
396 | 0 | { |
397 | 0 | m_send_timer.cancel(); |
398 | 0 | error_code ec; |
399 | 0 | m_socket.close(ec); |
400 | 0 | } |
401 | 0 | return; |
402 | 0 | } |
403 | | |
404 | 0 | update_mapping(port_mapping_t(static_cast<int>(m - m_mappings.begin()))); |
405 | 0 | } |
406 | | |
407 | | void natpmp::update_mapping(port_mapping_t const i) |
408 | 0 | { |
409 | 0 | TORRENT_ASSERT(is_single_thread()); |
410 | 0 | if (i == m_mappings.end_index()) |
411 | 0 | { |
412 | 0 | if (m_abort) |
413 | 0 | { |
414 | 0 | m_send_timer.cancel(); |
415 | 0 | error_code ec; |
416 | 0 | m_socket.close(ec); |
417 | 0 | } |
418 | 0 | return; |
419 | 0 | } |
420 | | |
421 | 0 | mapping_t const& m = m_mappings[i]; |
422 | |
|
423 | | #ifndef TORRENT_DISABLE_LOGGING |
424 | | mapping_log("update", m); |
425 | | #endif |
426 | |
|
427 | 0 | if (m.act == portmap_action::none |
428 | 0 | || m.protocol == portmap_protocol::none) |
429 | 0 | { |
430 | 0 | try_next_mapping(i); |
431 | 0 | return; |
432 | 0 | } |
433 | | |
434 | 0 | if (m_currently_mapping == port_mapping_t{-1}) |
435 | 0 | { |
436 | | // the socket is not currently in use |
437 | | // send out a mapping request |
438 | 0 | m_retry_count = 0; |
439 | 0 | send_map_request(i); |
440 | 0 | } |
441 | 0 | } |
442 | | |
443 | | void natpmp::send_map_request(port_mapping_t const i) |
444 | 0 | { |
445 | 0 | TORRENT_ASSERT(is_single_thread()); |
446 | 0 | using namespace libtorrent::aux; |
447 | |
|
448 | 0 | TORRENT_ASSERT(m_currently_mapping == port_mapping_t{-1} |
449 | 0 | || m_currently_mapping == i); |
450 | 0 | m_currently_mapping = i; |
451 | 0 | mapping_t& m = m_mappings[i]; |
452 | 0 | TORRENT_ASSERT(m.act != portmap_action::none); |
453 | 0 | char buf[60]; |
454 | 0 | char* out = buf; |
455 | 0 | int ttl = m.act == portmap_action::add ? 3600 : 0; |
456 | 0 | if (m_version == version_natpmp) |
457 | 0 | { |
458 | 0 | write_uint8(m_version, out); |
459 | 0 | write_uint8(m.protocol == portmap_protocol::udp ? 1 : 2, out); // map "protocol" |
460 | 0 | write_uint16(0, out); // reserved |
461 | 0 | write_uint16(m.local_port, out); // private port |
462 | 0 | write_uint16(m.external_port, out); // requested public port |
463 | 0 | write_uint32(ttl, out); // port mapping lifetime |
464 | 0 | } |
465 | 0 | else if (m_version == version_pcp) |
466 | 0 | { |
467 | | // PCP requires the use of IPv6 addresses even for IPv4 messages |
468 | 0 | write_uint8(m_version, out); |
469 | 0 | write_uint8(opcode_map, out); |
470 | 0 | write_uint16(0, out); // reserved |
471 | 0 | write_uint32(ttl, out); |
472 | 0 | error_code ec; |
473 | 0 | address const local_addr = m_socket.local_endpoint(ec).address(); |
474 | 0 | if (ec) |
475 | 0 | { |
476 | | #ifndef TORRENT_DISABLE_LOGGING |
477 | | if ( should_log()) |
478 | | { |
479 | | log("*** port map, local_endpoint [ ec: %s:%d %s ]" |
480 | | , ec.category().name() |
481 | | , ec.value() |
482 | | , ec.message().c_str()); |
483 | | } |
484 | | #endif |
485 | 0 | m_currently_mapping = port_mapping_t{-1}; |
486 | 0 | m.act = portmap_action::none; |
487 | 0 | return; |
488 | 0 | } |
489 | 0 | auto const local_bytes = local_addr.is_v4() |
490 | 0 | ? make_address_v6(v4_mapped, local_addr.to_v4()).to_bytes() |
491 | 0 | : local_addr.to_v6().to_bytes(); |
492 | 0 | out = std::copy(local_bytes.begin(), local_bytes.end(), out); |
493 | 0 | out = std::copy(m.nonce.begin(), m.nonce.end(), out); |
494 | | // translate portmap_protocol to an IANA protocol number |
495 | 0 | int const protocol = |
496 | 0 | (m.protocol == portmap_protocol::tcp) ? 6 |
497 | 0 | : (m.protocol == portmap_protocol::udp) ? 17 |
498 | 0 | : 0; |
499 | 0 | write_int8(protocol, out); |
500 | 0 | write_uint8(0, out); // reserved |
501 | 0 | write_uint16(0, out); // reserved |
502 | 0 | write_uint16(m.local_port, out); |
503 | 0 | write_uint16(m.external_port, out); |
504 | 0 | address_v6 external_addr; |
505 | 0 | if (!m.external_address.is_unspecified()) |
506 | 0 | { |
507 | 0 | external_addr = m.external_address.is_v4() |
508 | 0 | ? make_address_v6(v4_mapped, m.external_address.to_v4()) |
509 | 0 | : m.external_address.to_v6(); |
510 | 0 | } |
511 | 0 | else if (aux::is_local(local_addr)) |
512 | 0 | { |
513 | 0 | external_addr = local_addr.is_v4() |
514 | 0 | ? make_address_v6(v4_mapped, address_v4()) |
515 | 0 | : address_v6(); |
516 | 0 | } |
517 | 0 | else if (local_addr.is_v4()) |
518 | 0 | { |
519 | 0 | external_addr = make_address_v6(v4_mapped, local_addr.to_v4()); |
520 | 0 | } |
521 | 0 | else |
522 | 0 | { |
523 | 0 | external_addr = local_addr.to_v6(); |
524 | 0 | } |
525 | 0 | write_address(external_addr, out); |
526 | 0 | } |
527 | 0 | else |
528 | 0 | { |
529 | 0 | TORRENT_ASSERT_FAIL(); |
530 | 0 | } |
531 | | |
532 | | #ifndef TORRENT_DISABLE_LOGGING |
533 | | if (should_log()) |
534 | | { |
535 | | log("==> port map [ mapping: %d action: %s" |
536 | | " transport: %s proto: %s local: %d external: %d ttl: %d ]" |
537 | | , static_cast<int>(i), to_string(m.act) |
538 | | , version_to_string(m_version) |
539 | | , to_string(m.protocol) |
540 | | , m.local_port, m.external_port, ttl); |
541 | | } |
542 | | #endif |
543 | | |
544 | 0 | error_code ec; |
545 | 0 | m_socket.send_to(boost::asio::buffer(buf, std::size_t(out - buf)), m_nat_endpoint, 0, ec); |
546 | | #ifndef TORRENT_DISABLE_LOGGING |
547 | | if (ec && should_log()) |
548 | | { |
549 | | log("*** port map [ ec: %s:%d %s ]" |
550 | | , ec.category().name() |
551 | | , ec.value() |
552 | | , ec.message().c_str()); |
553 | | } |
554 | | #endif |
555 | 0 | m.map_sent = true; |
556 | 0 | m.outstanding_request = true; |
557 | |
|
558 | 0 | if (m_abort) |
559 | 0 | { |
560 | | // when we're shutting down, ignore the |
561 | | // responses and just remove all mappings |
562 | | // immediately |
563 | 0 | m_currently_mapping = port_mapping_t{-1}; |
564 | 0 | m.act = portmap_action::none; |
565 | 0 | try_next_mapping(i); |
566 | 0 | } |
567 | 0 | else |
568 | 0 | { |
569 | 0 | ADD_OUTSTANDING_ASYNC("natpmp::resend_request"); |
570 | | // linear back-off instead of exponential |
571 | 0 | ++m_retry_count; |
572 | 0 | m_send_timer.expires_after(milliseconds(250 * m_retry_count)); |
573 | 0 | m_send_timer.async_wait(std::bind(&natpmp::on_resend_request, self(), i, _1)); |
574 | 0 | } |
575 | 0 | } |
576 | | |
577 | | void natpmp::on_resend_request(port_mapping_t const i, error_code const& e) |
578 | 0 | { |
579 | 0 | TORRENT_ASSERT(is_single_thread()); |
580 | 0 | COMPLETE_ASYNC("natpmp::resend_request"); |
581 | 0 | if (e) return; |
582 | 0 | resend_request(i); |
583 | 0 | } |
584 | | |
585 | | void natpmp::resend_request(port_mapping_t const i) |
586 | 0 | { |
587 | 0 | if (m_currently_mapping != i) return; |
588 | | |
589 | | // if we're shutting down, don't retry, just move on |
590 | | // to the next mapping |
591 | 0 | if (m_retry_count >= 9 || m_abort) |
592 | 0 | { |
593 | 0 | m_currently_mapping = port_mapping_t{-1}; |
594 | 0 | m_mappings[i].act = portmap_action::none; |
595 | | // try again in two hours |
596 | 0 | m_mappings[i].expires = aux::time_now() + hours(2); |
597 | 0 | try_next_mapping(i); |
598 | 0 | return; |
599 | 0 | } |
600 | 0 | send_map_request(i); |
601 | 0 | } |
602 | | |
603 | | void natpmp::on_reply(error_code const& e |
604 | | , std::size_t const bytes_transferred) |
605 | 0 | { |
606 | 0 | TORRENT_ASSERT(is_single_thread()); |
607 | |
|
608 | 0 | COMPLETE_ASYNC("natpmp::on_reply"); |
609 | |
|
610 | 0 | using namespace libtorrent::aux; |
611 | 0 | if (e) |
612 | 0 | { |
613 | | #ifndef TORRENT_DISABLE_LOGGING |
614 | | if (should_log()) |
615 | | { |
616 | | log("error on receiving reply: %s" |
617 | | , convert_from_native(e.message()).c_str()); |
618 | | } |
619 | | #endif |
620 | 0 | return; |
621 | 0 | } |
622 | | |
623 | 0 | if (m_abort) return; |
624 | | |
625 | 0 | ADD_OUTSTANDING_ASYNC("natpmp::on_reply"); |
626 | | // make a copy of the response packet buffer |
627 | | // to avoid overwriting it in the next receive call |
628 | 0 | std::array<char, sizeof(m_response_buffer)> msg_buf; |
629 | 0 | std::memcpy(msg_buf.data(), m_response_buffer, bytes_transferred); |
630 | |
|
631 | 0 | m_socket.async_receive_from(boost::asio::buffer(&m_response_buffer[0] |
632 | 0 | , sizeof(m_response_buffer)) |
633 | 0 | , m_remote, std::bind(&natpmp::on_reply, self(), _1, _2)); |
634 | |
|
635 | 0 | if (m_remote != m_nat_endpoint) |
636 | 0 | { |
637 | | #ifndef TORRENT_DISABLE_LOGGING |
638 | | if (should_log()) |
639 | | { |
640 | | log("received packet from wrong IP: %s" |
641 | | , print_endpoint(m_remote).c_str()); |
642 | | } |
643 | | #endif |
644 | 0 | return; |
645 | 0 | } |
646 | | |
647 | 0 | m_send_timer.cancel(); |
648 | |
|
649 | 0 | if (bytes_transferred < 4) |
650 | 0 | { |
651 | | #ifndef TORRENT_DISABLE_LOGGING |
652 | | log("received packet of invalid size: %d", int(bytes_transferred)); |
653 | | #endif |
654 | 0 | return; |
655 | 0 | } |
656 | | |
657 | 0 | char* in = msg_buf.data(); |
658 | 0 | int const version = read_uint8(in); |
659 | |
|
660 | 0 | if (version != version_natpmp && version != version_pcp) |
661 | 0 | { |
662 | | #ifndef TORRENT_DISABLE_LOGGING |
663 | | log("unexpected version: %d", version); |
664 | | #endif |
665 | 0 | return; |
666 | 0 | } |
667 | | |
668 | 0 | int cmd = read_uint8(in); |
669 | 0 | if (version == version_pcp) |
670 | 0 | { |
671 | 0 | cmd &= 0x7f; |
672 | 0 | } |
673 | 0 | int result; |
674 | 0 | if (version == version_pcp) |
675 | 0 | { |
676 | 0 | ++in; // reserved |
677 | 0 | result = read_uint8(in); |
678 | 0 | } |
679 | 0 | else |
680 | 0 | { |
681 | 0 | result = read_uint16(in); |
682 | 0 | } |
683 | |
|
684 | 0 | if (result == errors::pcp_unsupp_version) |
685 | 0 | { |
686 | | #ifndef TORRENT_DISABLE_LOGGING |
687 | | log("unsupported version"); |
688 | | #endif |
689 | | // ignore errors from local_endpoint |
690 | 0 | error_code ec; |
691 | 0 | if (m_version == version_pcp && !aux::is_v6(m_socket.local_endpoint(ec))) |
692 | 0 | { |
693 | 0 | m_version = version_natpmp; |
694 | 0 | resend_request(m_currently_mapping); |
695 | 0 | send_get_ip_address_request(); |
696 | 0 | } |
697 | 0 | return; |
698 | 0 | } |
699 | | |
700 | 0 | if ((version == version_natpmp && bytes_transferred < 12) |
701 | 0 | || (version == version_pcp && bytes_transferred < 24)) |
702 | 0 | { |
703 | | #ifndef TORRENT_DISABLE_LOGGING |
704 | | log("received packet of invalid size: %d", int(bytes_transferred)); |
705 | | #endif |
706 | 0 | return; |
707 | 0 | } |
708 | | |
709 | 0 | int lifetime = 0; |
710 | 0 | if (version == version_pcp) |
711 | 0 | { |
712 | 0 | lifetime = aux::numeric_cast<int>(read_uint32(in)); |
713 | 0 | } |
714 | 0 | int const time = aux::numeric_cast<int>(read_uint32(in)); |
715 | 0 | if (version == version_pcp) in += 12; // reserved |
716 | 0 | TORRENT_UNUSED(time); |
717 | |
|
718 | 0 | if (version == version_natpmp && cmd == 128) |
719 | 0 | { |
720 | | // public IP request response |
721 | 0 | m_external_ip = read_v4_address(in); |
722 | |
|
723 | | #ifndef TORRENT_DISABLE_LOGGING |
724 | | if (should_log()) |
725 | | { |
726 | | log("<== public IP address [ %s ]", print_address(m_external_ip).c_str()); |
727 | | } |
728 | | #endif |
729 | 0 | return; |
730 | |
|
731 | 0 | } |
732 | | |
733 | 0 | if ((version == version_natpmp && bytes_transferred != 16) |
734 | 0 | || (version == version_pcp && bytes_transferred != 60)) |
735 | 0 | { |
736 | | #ifndef TORRENT_DISABLE_LOGGING |
737 | | log("received packet of invalid size: %d", int(bytes_transferred)); |
738 | | #endif |
739 | 0 | return; |
740 | 0 | } |
741 | | |
742 | 0 | std::array<char, 12> nonce; |
743 | 0 | portmap_protocol protocol = portmap_protocol::none; |
744 | 0 | if (version == version_pcp) |
745 | 0 | { |
746 | 0 | std::memcpy(nonce.data(), in, nonce.size()); |
747 | 0 | in += nonce.size(); |
748 | 0 | int p = read_uint8(in); |
749 | 0 | protocol = p == 6 ? portmap_protocol::tcp |
750 | 0 | : portmap_protocol::udp; |
751 | 0 | in += 3; // reserved |
752 | 0 | } |
753 | 0 | int const private_port = read_uint16(in); |
754 | 0 | int const public_port = read_uint16(in); |
755 | 0 | if (version == version_natpmp) |
756 | 0 | lifetime = aux::numeric_cast<int>(read_uint32(in)); |
757 | 0 | address external_addr; |
758 | 0 | if (version == version_pcp) |
759 | 0 | { |
760 | 0 | external_addr = read_v6_address(in); |
761 | 0 | if (external_addr.to_v6().is_v4_mapped()) |
762 | 0 | external_addr = make_address_v4(v4_mapped, external_addr.to_v6()); |
763 | 0 | } |
764 | |
|
765 | 0 | if (version == version_natpmp) |
766 | 0 | { |
767 | 0 | protocol = (cmd - 128 == 1) |
768 | 0 | ? portmap_protocol::udp |
769 | 0 | : portmap_protocol::tcp; |
770 | 0 | } |
771 | |
|
772 | | #ifndef TORRENT_DISABLE_LOGGING |
773 | | char msg[200]; |
774 | | int const num_chars = std::snprintf(msg, sizeof(msg), "<== port map [" |
775 | | " transport: %s protocol: %s local: %d external: %d ttl: %d ]" |
776 | | , version_to_string(protocol_version(version)) |
777 | | , (protocol == portmap_protocol::udp ? "udp" : "tcp") |
778 | | , private_port, public_port, lifetime); |
779 | | #endif |
780 | |
|
781 | 0 | mapping_t* m = nullptr; |
782 | 0 | port_mapping_t index{-1}; |
783 | 0 | for (auto i = m_mappings.begin(), end(m_mappings.end()); i != end; ++i) |
784 | 0 | { |
785 | 0 | if (private_port != i->local_port) continue; |
786 | 0 | if (protocol != i->protocol) continue; |
787 | 0 | if (!i->map_sent) continue; |
788 | 0 | if (!i->outstanding_request) continue; |
789 | 0 | if (version == version_pcp && nonce != i->nonce) continue; |
790 | 0 | m = &*i; |
791 | 0 | index = port_mapping_t(static_cast<int>(i - m_mappings.begin())); |
792 | 0 | break; |
793 | 0 | } |
794 | |
|
795 | 0 | if (m == nullptr) |
796 | 0 | { |
797 | | #ifndef TORRENT_DISABLE_LOGGING |
798 | | snprintf(msg + num_chars, sizeof(msg) - aux::numeric_cast<std::size_t>(num_chars), " not found in map table"); |
799 | | log("%s", msg); |
800 | | #endif |
801 | 0 | return; |
802 | 0 | } |
803 | 0 | m->outstanding_request = false; |
804 | |
|
805 | | #ifndef TORRENT_DISABLE_LOGGING |
806 | | log("%s", msg); |
807 | | #endif |
808 | |
|
809 | 0 | if (public_port == 0 || lifetime == 0) |
810 | 0 | { |
811 | | // this means the mapping was |
812 | | // successfully closed |
813 | 0 | m->protocol = portmap_protocol::none; |
814 | 0 | } |
815 | 0 | else |
816 | 0 | { |
817 | 0 | m->expires = aux::time_now() + seconds(lifetime * 3 / 4); |
818 | 0 | m->external_port = public_port; |
819 | 0 | if (!external_addr.is_unspecified()) |
820 | 0 | m->external_address = external_addr; |
821 | 0 | } |
822 | |
|
823 | 0 | if (result != 0) |
824 | 0 | { |
825 | 0 | m->expires = aux::time_now() + hours(2); |
826 | 0 | portmap_protocol const proto = m->protocol; |
827 | 0 | m_callback.on_port_mapping(port_mapping_t{index}, address(), 0, proto |
828 | 0 | , from_result_code(version, result), portmap_transport::natpmp, m_listen_handle); |
829 | 0 | } |
830 | 0 | else if (m->act == portmap_action::add) |
831 | 0 | { |
832 | 0 | portmap_protocol const proto = m->protocol; |
833 | 0 | address const ext_ip = version == version_pcp ? m->external_address : m_external_ip; |
834 | 0 | m_callback.on_port_mapping(port_mapping_t{index}, ext_ip, m->external_port, proto |
835 | 0 | , errors::pcp_success, portmap_transport::natpmp, m_listen_handle); |
836 | 0 | } |
837 | |
|
838 | 0 | m_currently_mapping = port_mapping_t{-1}; |
839 | 0 | m->act = portmap_action::none; |
840 | 0 | m_send_timer.cancel(); |
841 | 0 | update_expiration_timer(); |
842 | 0 | try_next_mapping(index); |
843 | 0 | } |
844 | | |
845 | | void natpmp::update_expiration_timer() |
846 | 0 | { |
847 | 0 | TORRENT_ASSERT(is_single_thread()); |
848 | 0 | if (m_abort) return; |
849 | | |
850 | 0 | time_point const now = aux::time_now() + milliseconds(100); |
851 | 0 | time_point min_expire = now + seconds(3600); |
852 | 0 | port_mapping_t min_index{-1}; |
853 | 0 | for (auto i = m_mappings.begin(), end(m_mappings.end()); i != end; ++i) |
854 | 0 | { |
855 | 0 | if (i->protocol == portmap_protocol::none |
856 | 0 | || i->act != portmap_action::none) continue; |
857 | 0 | port_mapping_t const index(static_cast<int>(i - m_mappings.begin())); |
858 | 0 | if (i->expires < now) |
859 | 0 | { |
860 | | #ifndef TORRENT_DISABLE_LOGGING |
861 | | log("mapping %d expired", static_cast<int>(index)); |
862 | | #endif |
863 | 0 | i->act = portmap_action::add; |
864 | 0 | if (m_next_refresh == index) m_next_refresh = port_mapping_t{-1}; |
865 | 0 | update_mapping(index); |
866 | 0 | } |
867 | 0 | else if (i->expires < min_expire) |
868 | 0 | { |
869 | 0 | min_expire = i->expires; |
870 | 0 | min_index = index; |
871 | 0 | } |
872 | 0 | } |
873 | | |
874 | | // this is already the mapping we're waiting for |
875 | 0 | if (m_next_refresh == min_index) return; |
876 | | |
877 | 0 | if (min_index >= port_mapping_t{}) |
878 | 0 | { |
879 | | #ifndef TORRENT_DISABLE_LOGGING |
880 | | log("next expiration [ idx: %d ttl: %" PRId64 " ]" |
881 | | , static_cast<int>(min_index), total_seconds(min_expire - aux::time_now())); |
882 | | #endif |
883 | 0 | if (m_next_refresh >= port_mapping_t{}) m_refresh_timer.cancel(); |
884 | |
|
885 | 0 | ADD_OUTSTANDING_ASYNC("natpmp::mapping_expired"); |
886 | 0 | m_refresh_timer.expires_after(min_expire - now); |
887 | 0 | m_refresh_timer.async_wait(std::bind(&natpmp::mapping_expired, self(), _1, min_index)); |
888 | 0 | m_next_refresh = min_index; |
889 | 0 | } |
890 | 0 | } |
891 | | |
892 | | void natpmp::mapping_expired(error_code const& e, port_mapping_t const i) |
893 | 0 | { |
894 | 0 | TORRENT_ASSERT(is_single_thread()); |
895 | 0 | COMPLETE_ASYNC("natpmp::mapping_expired"); |
896 | 0 | if (e || m_abort) return; |
897 | | #ifndef TORRENT_DISABLE_LOGGING |
898 | | log("mapping %d expired", static_cast<int>(i)); |
899 | | #endif |
900 | 0 | m_mappings[i].act = portmap_action::add; |
901 | 0 | if (m_next_refresh == i) m_next_refresh = port_mapping_t{-1}; |
902 | 0 | update_mapping(i); |
903 | 0 | } |
904 | | |
905 | | void natpmp::close() |
906 | 0 | { |
907 | 0 | TORRENT_ASSERT(is_single_thread()); |
908 | 0 | close_impl(); |
909 | 0 | } |
910 | | |
911 | | void natpmp::close_impl() |
912 | 0 | { |
913 | 0 | TORRENT_ASSERT(is_single_thread()); |
914 | 0 | m_abort = true; |
915 | | #ifndef TORRENT_DISABLE_LOGGING |
916 | | log("closing"); |
917 | | #endif |
918 | 0 | if (m_disabled) return; |
919 | 0 | for (auto& m : m_mappings) |
920 | 0 | { |
921 | 0 | if (m.protocol == portmap_protocol::none) continue; |
922 | 0 | m.act = portmap_action::del; |
923 | 0 | } |
924 | 0 | m_refresh_timer.cancel(); |
925 | 0 | m_currently_mapping = port_mapping_t{-1}; |
926 | 0 | update_mapping(port_mapping_t{}); |
927 | 0 | } |
928 | | |
929 | | } // namespace libtorrent |