added some precautionary checks in bdecoder
[libtorrent.git] / src / natpmp.cpp
blobb751b083d38dc3375dc0e36cce7fcf4ab8621990
1 /*
3 Copyright (c) 2007, Arvid Norberg
4 All rights reserved.
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
8 are met:
10 * Redistributions of source code must retain the above copyright
11 notice, this list of conditions and the following disclaimer.
12 * Redistributions in binary form must reproduce the above copyright
13 notice, this list of conditions and the following disclaimer in
14 the documentation and/or other materials provided with the distribution.
15 * Neither the name of the author nor the names of its
16 contributors may be used to endorse or promote products derived
17 from this software without specific prior written permission.
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 POSSIBILITY OF SUCH DAMAGE.
33 #include "libtorrent/pch.hpp"
35 #include <boost/version.hpp>
36 #include <boost/bind.hpp>
38 #if BOOST_VERSION < 103500
39 #include <asio/ip/host_name.hpp>
40 #else
41 #include <boost/asio/ip/host_name.hpp>
42 #endif
44 #include "libtorrent/natpmp.hpp"
45 #include "libtorrent/io.hpp"
46 #include "libtorrent/assert.hpp"
47 #include "libtorrent/enum_net.hpp"
49 using boost::bind;
50 using namespace libtorrent;
52 natpmp::natpmp(io_service& ios, address const& listen_interface, portmap_callback_t const& cb)
53 : m_callback(cb)
54 , m_currently_mapping(-1)
55 , m_retry_count(0)
56 , m_socket(ios)
57 , m_send_timer(ios)
58 , m_refresh_timer(ios)
59 , m_next_refresh(-1)
60 , m_disabled(false)
62 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
63 m_log.open("natpmp.log", std::ios::in | std::ios::out | std::ios::trunc);
64 #endif
65 rebind(listen_interface);
68 void natpmp::rebind(address const& listen_interface)
70 mutex_t::scoped_lock l(m_mutex);
72 error_code ec;
73 address gateway = get_default_gateway(m_socket.get_io_service(), ec);
74 if (ec)
76 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
77 m_log << time_now_string() << " failed to find default router: "
78 << ec.message() << std::endl;
79 #endif
80 disable("failed to find default router");
81 return;
84 m_disabled = false;
86 udp::endpoint nat_endpoint(gateway, 5351);
87 if (nat_endpoint == m_nat_endpoint) return;
88 m_nat_endpoint = nat_endpoint;
90 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
91 m_log << time_now_string() << " found router at: "
92 << m_nat_endpoint.address() << std::endl;
93 #endif
95 m_socket.open(udp::v4(), ec);
96 if (ec)
98 disable(ec.message().c_str());
99 return;
101 m_socket.bind(udp::endpoint(address_v4::any(), 0), ec);
102 if (ec)
104 disable(ec.message().c_str());
105 return;
108 for (std::vector<mapping_t>::iterator i = m_mappings.begin()
109 , end(m_mappings.end()); i != end; ++i)
111 if (i->protocol != none
112 || i->action != mapping_t::action_none)
113 continue;
114 i->action = mapping_t::action_add;
115 update_mapping(i - m_mappings.begin());
119 void natpmp::disable(char const* message)
121 m_disabled = true;
123 for (std::vector<mapping_t>::iterator i = m_mappings.begin()
124 , end(m_mappings.end()); i != end; ++i)
126 if (i->protocol == none) continue;
127 i->protocol = none;
128 m_callback(i - m_mappings.begin(), 0, message);
131 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
132 m_log << time_now_string() << " NAT-PMP disabled: " << message << std::endl;
133 #endif
134 close();
136 void natpmp::delete_mapping(int index)
138 TORRENT_ASSERT(index < int(m_mappings.size()) && index >= 0);
139 if (index >= int(m_mappings.size()) || index < 0) return;
140 mapping_t& m = m_mappings[index];
142 if (m.protocol == none) return;
144 m.action = mapping_t::action_delete;
145 update_mapping(index);
148 int natpmp::add_mapping(protocol_type p, int external_port, int local_port)
150 mutex_t::scoped_lock l(m_mutex);
152 if (m_disabled) return -1;
154 std::vector<mapping_t>::iterator i = std::find_if(m_mappings.begin()
155 , m_mappings.end(), boost::bind(&mapping_t::protocol, _1) == int(none));
156 if (i == m_mappings.end())
158 m_mappings.push_back(mapping_t());
159 i = m_mappings.end() - 1;
161 i->protocol = p;
162 i->external_port = external_port;
163 i->local_port = local_port;
164 i->action = mapping_t::action_add;
166 int mapping_index = i - m_mappings.begin();
168 update_mapping(mapping_index);
169 return mapping_index;
172 void natpmp::try_next_mapping(int i)
174 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
175 m_log << time_now_string() << " try_next_mapping [ " << i << " ]" << std::endl;
176 #endif
178 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
179 ptime now = time_now();
180 for (std::vector<mapping_t>::iterator m = m_mappings.begin()
181 , end(m_mappings.end()); m != end; ++m)
183 m_log << " " << (m - m_mappings.begin()) << " [ "
184 "proto: " << (m->protocol == none ? "none" : m->protocol == tcp ? "tcp" : "udp")
185 << " port: " << m->external_port
186 << " local-port: " << m->local_port
187 << " action: " << (m->action == mapping_t::action_none ? "none" : m->action == mapping_t::action_add ? "add" : "delete")
188 << " ttl: " << total_seconds(m->expires - now)
189 << " ]" << std::endl;
191 #endif
193 if (i < int(m_mappings.size()) - 1)
195 update_mapping(i + 1);
196 return;
199 std::vector<mapping_t>::iterator m = std::find_if(
200 m_mappings.begin(), m_mappings.end()
201 , boost::bind(&mapping_t::action, _1) != int(mapping_t::action_none));
203 if (m == m_mappings.end())
205 if (m_abort)
207 error_code ec;
208 m_send_timer.cancel(ec);
209 m_socket.close(ec);
211 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
212 m_log << " done" << (m_abort?" shutting down":"") << std::endl;
213 #endif
214 return;
217 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
218 m_log << " updating " << (m - m_mappings.begin()) << std::endl;
219 #endif
221 update_mapping(m - m_mappings.begin());
224 void natpmp::update_mapping(int i)
226 natpmp::mapping_t& m = m_mappings[i];
227 if (m.action == mapping_t::action_none
228 || m.protocol == none)
230 try_next_mapping(i);
231 return;
234 if (m_currently_mapping == -1)
236 // the socket is not currently in use
237 // send out a mapping request
238 m_retry_count = 0;
239 send_map_request(i);
240 m_socket.async_receive_from(asio::buffer(&m_response_buffer, 16)
241 , m_remote, bind(&natpmp::on_reply, self(), _1, _2));
245 void natpmp::send_map_request(int i)
247 using namespace libtorrent::detail;
249 TORRENT_ASSERT(m_currently_mapping == -1
250 || m_currently_mapping == i);
251 m_currently_mapping = i;
252 mapping_t& m = m_mappings[i];
253 TORRENT_ASSERT(m.action != mapping_t::action_none);
254 char buf[12];
255 char* out = buf;
256 write_uint8(0, out); // NAT-PMP version
257 write_uint8(m.protocol, out); // map "protocol"
258 write_uint16(0, out); // reserved
259 write_uint16(m.local_port, out); // private port
260 write_uint16(m.external_port, out); // requested public port
261 int ttl = m.action == mapping_t::action_add ? 3600 : 0;
262 write_uint32(ttl, out); // port mapping lifetime
264 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
265 m_log << time_now_string()
266 << " ==> port map ["
267 << " action: " << (m.action == mapping_t::action_add ? "add" : "delete") << " "
268 << " proto: " << (m.protocol == udp ? "udp" : "tcp")
269 << " local: " << m.local_port << " external: " << m.external_port
270 << " ttl: " << ttl << " ]" << std::endl;
271 #endif
273 error_code ec;
274 m_socket.send_to(asio::buffer(buf, 12), m_nat_endpoint, 0, ec);
275 // linear back-off instead of exponential
276 ++m_retry_count;
277 m_send_timer.expires_from_now(milliseconds(250 * m_retry_count), ec);
278 m_send_timer.async_wait(bind(&natpmp::resend_request, self(), i, _1));
281 void natpmp::resend_request(int i, error_code const& e)
283 if (e) return;
284 if (m_abort) return;
286 mutex_t::scoped_lock l(m_mutex);
287 if (m_currently_mapping != i) return;
288 if (m_retry_count >= 9)
290 m_currently_mapping = -1;
291 m_mappings[i].action = mapping_t::action_none;
292 // try again in two hours
293 m_mappings[i].expires = time_now() + hours(2);
294 try_next_mapping(i);
295 return;
297 send_map_request(i);
300 void natpmp::on_reply(error_code const& e
301 , std::size_t bytes_transferred)
303 using namespace libtorrent::detail;
304 if (e) return;
306 if (m_remote != m_nat_endpoint)
308 m_socket.async_receive_from(asio::buffer(&m_response_buffer, 16)
309 , m_remote, bind(&natpmp::on_reply, self(), _1, _2));
310 return;
313 mutex_t::scoped_lock l(m_mutex);
315 error_code ec;
316 m_send_timer.cancel(ec);
318 TORRENT_ASSERT(m_currently_mapping >= 0);
319 int i = m_currently_mapping;
320 mapping_t& m = m_mappings[i];
322 char* in = m_response_buffer;
323 int version = read_uint8(in);
324 int cmd = read_uint8(in);
325 int result = read_uint16(in);
326 int time = read_uint32(in);
327 int private_port = read_uint16(in);
328 int public_port = read_uint16(in);
329 int lifetime = read_uint32(in);
331 (void)time; // to remove warning
333 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
334 m_log << time_now_string()
335 << " <== port map ["
336 << " protocol: " << (cmd - 128 == 1 ? "udp" : "tcp")
337 << " local: " << private_port << " external: " << public_port
338 << " ttl: " << lifetime << " ]" << std::endl;
339 #endif
341 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
342 if (version != 0)
344 m_log << "*** unexpected version: " << version << std::endl;
346 #endif
348 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
349 if (private_port != m.local_port)
351 m_log << "*** unexpected local port: " << private_port << std::endl;
353 #endif
355 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
356 if (cmd != 128 + m.protocol)
358 m_log << "*** unexpected protocol: " << (cmd - 128) << std::endl;
360 #endif
362 if (public_port == 0 || lifetime == 0)
364 // this means the mapping was
365 // successfully closed
366 m.protocol = none;
368 else
370 m.expires = time_now() + seconds(int(lifetime * 0.7f));
371 m.external_port = public_port;
374 if (result != 0)
376 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
377 m_log << "*** ERROR: " << result << std::endl;
378 #endif
379 std::stringstream errmsg;
380 errmsg << "NAT router reports error (" << result << ") ";
381 switch (result)
383 case 1: errmsg << "Unsupported protocol version"; break;
384 case 2: errmsg << "Not authorized to create port map (enable NAT-PMP on your router)"; break;
385 case 3: errmsg << "Network failure"; break;
386 case 4: errmsg << "Out of resources"; break;
387 case 5: errmsg << "Unsupported opcode"; break;
389 m.expires = time_now() + hours(2);
390 m_callback(i, 0, errmsg.str());
392 else if (m.action == mapping_t::action_add)
394 m_callback(i, m.external_port, "");
397 m_currently_mapping = -1;
398 m.action = mapping_t::action_none;
399 m_send_timer.cancel(ec);
400 update_expiration_timer();
401 try_next_mapping(i);
404 void natpmp::update_expiration_timer()
406 if (m_abort) return;
408 ptime now = time_now();
409 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
410 m_log << time_now_string() << " update_expiration_timer " << std::endl;
411 for (std::vector<mapping_t>::iterator i = m_mappings.begin()
412 , end(m_mappings.end()); i != end; ++i)
414 m_log << " " << (i - m_mappings.begin()) << " [ "
415 "proto: " << (i->protocol == none ? "none" : i->protocol == tcp ? "tcp" : "udp")
416 << " port: " << i->external_port
417 << " local-port: " << i->local_port
418 << " action: " << (i->action == mapping_t::action_none ? "none" : i->action == mapping_t::action_add ? "add" : "delete")
419 << " ttl: " << total_seconds(i->expires - now)
420 << " ]" << std::endl;
422 #endif
424 ptime min_expire = now + seconds(3600);
425 int min_index = -1;
426 for (std::vector<mapping_t>::iterator i = m_mappings.begin()
427 , end(m_mappings.end()); i != end; ++i)
429 if (i->protocol == none
430 || i->action != mapping_t::action_none) continue;
431 if (i->expires < min_expire)
433 min_expire = i->expires;
434 min_index = i - m_mappings.begin();
438 // this is already the mapping we're waiting for
439 if (m_next_refresh == min_index) return;
441 if (min_index >= 0)
443 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
444 m_log << time_now_string() << " next expiration ["
445 " i: " << min_index
446 << " ttl: " << total_seconds(min_expire - time_now())
447 << " ]" << std::endl;
448 #endif
449 error_code ec;
450 if (m_next_refresh >= 0) m_refresh_timer.cancel(ec);
451 m_refresh_timer.expires_from_now(min_expire - now, ec);
452 m_refresh_timer.async_wait(bind(&natpmp::mapping_expired, self(), _1, min_index));
453 m_next_refresh = min_index;
457 void natpmp::mapping_expired(error_code const& e, int i)
459 if (e) return;
460 mutex_t::scoped_lock l(m_mutex);
461 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
462 m_log << time_now_string() << " mapping expired [ i: " << i << " ]" << std::endl;
463 #endif
464 m_mappings[i].action = mapping_t::action_add;
465 if (m_next_refresh == i) m_next_refresh = -1;
466 update_mapping(i);
469 void natpmp::close()
471 mutex_t::scoped_lock l(m_mutex);
472 m_abort = true;
473 error_code ec;
474 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
475 m_log << time_now_string() << " close" << std::endl;
476 #endif
477 if (m_disabled) return;
478 ptime now = time_now();
479 for (std::vector<mapping_t>::iterator i = m_mappings.begin()
480 , end(m_mappings.end()); i != end; ++i)
482 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
483 m_log << " " << (i - m_mappings.begin()) << " [ "
484 "proto: " << (i->protocol == none ? "none" : i->protocol == tcp ? "tcp" : "udp")
485 << " port: " << i->external_port
486 << " local-port: " << i->local_port
487 << " action: " << (i->action == mapping_t::action_none ? "none" : i->action == mapping_t::action_add ? "add" : "delete")
488 << " ttl: " << total_seconds(i->expires - now)
489 << " ]" << std::endl;
490 #endif
491 if (i->protocol == none) continue;
492 i->action = mapping_t::action_delete;
494 m_refresh_timer.cancel(ec);
495 update_mapping(0);