fixes bug where priorities where lost when force-rechecking.
[libtorrent.git] / src / natpmp.cpp
blob878cc6fbf3fa07ecc4face96685257852e11866b
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 m_socket.async_receive_from(asio::buffer(&m_response_buffer, 16)
109 , m_remote, bind(&natpmp::on_reply, self(), _1, _2));
111 for (std::vector<mapping_t>::iterator i = m_mappings.begin()
112 , end(m_mappings.end()); i != end; ++i)
114 if (i->protocol != none
115 || i->action != mapping_t::action_none)
116 continue;
117 i->action = mapping_t::action_add;
118 update_mapping(i - m_mappings.begin());
122 void natpmp::disable(char const* message)
124 m_disabled = true;
126 for (std::vector<mapping_t>::iterator i = m_mappings.begin()
127 , end(m_mappings.end()); i != end; ++i)
129 if (i->protocol == none) continue;
130 i->protocol = none;
131 m_callback(i - m_mappings.begin(), 0, message);
134 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
135 m_log << time_now_string() << " NAT-PMP disabled: " << message << std::endl;
136 #endif
137 close();
139 void natpmp::delete_mapping(int index)
141 TORRENT_ASSERT(index < int(m_mappings.size()) && index >= 0);
142 if (index >= int(m_mappings.size()) || index < 0) return;
143 mapping_t& m = m_mappings[index];
145 if (m.protocol == none) return;
147 m.action = mapping_t::action_delete;
148 update_mapping(index);
151 int natpmp::add_mapping(protocol_type p, int external_port, int local_port)
153 mutex_t::scoped_lock l(m_mutex);
155 if (m_disabled) return -1;
157 std::vector<mapping_t>::iterator i = std::find_if(m_mappings.begin()
158 , m_mappings.end(), boost::bind(&mapping_t::protocol, _1) == int(none));
159 if (i == m_mappings.end())
161 m_mappings.push_back(mapping_t());
162 i = m_mappings.end() - 1;
164 i->protocol = p;
165 i->external_port = external_port;
166 i->local_port = local_port;
167 i->action = mapping_t::action_add;
169 int mapping_index = i - m_mappings.begin();
171 update_mapping(mapping_index);
172 return mapping_index;
175 void natpmp::try_next_mapping(int i)
177 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
178 m_log << time_now_string() << " try_next_mapping [ " << i << " ]" << std::endl;
179 #endif
181 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
182 ptime now = time_now();
183 for (std::vector<mapping_t>::iterator m = m_mappings.begin()
184 , end(m_mappings.end()); m != end; ++m)
186 m_log << " " << (m - m_mappings.begin()) << " [ "
187 "proto: " << (m->protocol == none ? "none" : m->protocol == tcp ? "tcp" : "udp")
188 << " port: " << m->external_port
189 << " local-port: " << m->local_port
190 << " action: " << (m->action == mapping_t::action_none ? "none" : m->action == mapping_t::action_add ? "add" : "delete")
191 << " ttl: " << total_seconds(m->expires - now)
192 << " ]" << std::endl;
194 #endif
196 if (i < int(m_mappings.size()) - 1)
198 update_mapping(i + 1);
199 return;
202 std::vector<mapping_t>::iterator m = std::find_if(
203 m_mappings.begin(), m_mappings.end()
204 , boost::bind(&mapping_t::action, _1) != int(mapping_t::action_none));
206 if (m == m_mappings.end())
208 if (m_abort)
210 error_code ec;
211 m_send_timer.cancel(ec);
212 m_socket.close(ec);
214 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
215 m_log << " done" << (m_abort?" shutting down":"") << std::endl;
216 #endif
217 return;
220 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
221 m_log << " updating " << (m - m_mappings.begin()) << std::endl;
222 #endif
224 update_mapping(m - m_mappings.begin());
227 void natpmp::update_mapping(int i)
229 natpmp::mapping_t& m = m_mappings[i];
230 if (m.action == mapping_t::action_none
231 || m.protocol == none)
233 try_next_mapping(i);
234 return;
237 if (m_currently_mapping == -1)
239 // the socket is not currently in use
240 // send out a mapping request
241 m_retry_count = 0;
242 send_map_request(i);
246 void natpmp::send_map_request(int i)
248 using namespace libtorrent::detail;
250 TORRENT_ASSERT(m_currently_mapping == -1
251 || m_currently_mapping == i);
252 m_currently_mapping = i;
253 mapping_t& m = m_mappings[i];
254 TORRENT_ASSERT(m.action != mapping_t::action_none);
255 char buf[12];
256 char* out = buf;
257 write_uint8(0, out); // NAT-PMP version
258 write_uint8(m.protocol, out); // map "protocol"
259 write_uint16(0, out); // reserved
260 write_uint16(m.local_port, out); // private port
261 write_uint16(m.external_port, out); // requested public port
262 int ttl = m.action == mapping_t::action_add ? 3600 : 0;
263 write_uint32(ttl, out); // port mapping lifetime
265 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
266 m_log << time_now_string()
267 << " ==> port map ["
268 << " action: " << (m.action == mapping_t::action_add ? "add" : "delete") << " "
269 << " proto: " << (m.protocol == udp ? "udp" : "tcp")
270 << " local: " << m.local_port << " external: " << m.external_port
271 << " ttl: " << ttl << " ]" << std::endl;
272 #endif
274 error_code ec;
275 m_socket.send_to(asio::buffer(buf, 12), m_nat_endpoint, 0, ec);
276 // linear back-off instead of exponential
277 ++m_retry_count;
278 m_send_timer.expires_from_now(milliseconds(250 * m_retry_count), ec);
279 m_send_timer.async_wait(bind(&natpmp::resend_request, self(), i, _1));
282 void natpmp::resend_request(int i, error_code const& e)
284 if (e) return;
285 mutex_t::scoped_lock l(m_mutex);
286 if (m_currently_mapping != i) return;
288 // if we're shutting down, don't retry, just move on
289 // to the next mapping
290 if (m_retry_count >= 9 || m_abort)
292 m_currently_mapping = -1;
293 m_mappings[i].action = mapping_t::action_none;
294 // try again in two hours
295 m_mappings[i].expires = time_now() + hours(2);
296 try_next_mapping(i);
297 return;
299 send_map_request(i);
302 void natpmp::on_reply(error_code const& e
303 , std::size_t bytes_transferred)
305 using namespace libtorrent::detail;
306 if (e)
308 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
309 m_log << time_now_string()
310 << " <== on_receive ["
311 " error: " << e.message() << " ]" << std::endl;
312 #endif
313 return;
316 m_socket.async_receive_from(asio::buffer(&m_response_buffer, 16)
317 , m_remote, bind(&natpmp::on_reply, self(), _1, _2));
319 if (m_remote != m_nat_endpoint)
321 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
322 m_log << time_now_string()
323 << " <== received packet from the wrong IP ["
324 " ip: " << m_remote << " ]" << std::endl;
325 #endif
326 return;
329 mutex_t::scoped_lock l(m_mutex);
331 error_code ec;
332 m_send_timer.cancel(ec);
334 TORRENT_ASSERT(m_currently_mapping >= 0);
335 int i = m_currently_mapping;
336 mapping_t& m = m_mappings[i];
338 char* in = m_response_buffer;
339 int version = read_uint8(in);
340 int cmd = read_uint8(in);
341 int result = read_uint16(in);
342 int time = read_uint32(in);
343 int private_port = read_uint16(in);
344 int public_port = read_uint16(in);
345 int lifetime = read_uint32(in);
347 (void)time; // to remove warning
349 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
350 m_log << time_now_string()
351 << " <== port map ["
352 << " protocol: " << (cmd - 128 == 1 ? "udp" : "tcp")
353 << " local: " << private_port << " external: " << public_port
354 << " ttl: " << lifetime << " ]" << std::endl;
355 #endif
357 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
358 if (version != 0)
360 m_log << "*** unexpected version: " << version << std::endl;
362 #endif
364 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
365 if (private_port != m.local_port)
367 m_log << "*** unexpected local port: " << private_port << std::endl;
369 #endif
371 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
372 if (cmd != 128 + m.protocol)
374 m_log << "*** unexpected protocol: " << (cmd - 128) << std::endl;
376 #endif
378 if (public_port == 0 || lifetime == 0)
380 // this means the mapping was
381 // successfully closed
382 m.protocol = none;
384 else
386 m.expires = time_now() + seconds(int(lifetime * 0.7f));
387 m.external_port = public_port;
390 if (result != 0)
392 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
393 m_log << "*** ERROR: " << result << std::endl;
394 #endif
395 std::stringstream errmsg;
396 errmsg << "NAT router reports error (" << result << ") ";
397 switch (result)
399 case 1: errmsg << "Unsupported protocol version"; break;
400 case 2: errmsg << "Not authorized to create port map (enable NAT-PMP on your router)"; break;
401 case 3: errmsg << "Network failure"; break;
402 case 4: errmsg << "Out of resources"; break;
403 case 5: errmsg << "Unsupported opcode"; break;
405 m.expires = time_now() + hours(2);
406 m_callback(i, 0, errmsg.str());
408 else if (m.action == mapping_t::action_add)
410 m_callback(i, m.external_port, "");
413 m_currently_mapping = -1;
414 m.action = mapping_t::action_none;
415 m_send_timer.cancel(ec);
416 update_expiration_timer();
417 try_next_mapping(i);
420 void natpmp::update_expiration_timer()
422 if (m_abort) return;
424 ptime now = time_now();
425 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
426 m_log << time_now_string() << " update_expiration_timer " << std::endl;
427 for (std::vector<mapping_t>::iterator i = m_mappings.begin()
428 , end(m_mappings.end()); i != end; ++i)
430 m_log << " " << (i - m_mappings.begin()) << " [ "
431 "proto: " << (i->protocol == none ? "none" : i->protocol == tcp ? "tcp" : "udp")
432 << " port: " << i->external_port
433 << " local-port: " << i->local_port
434 << " action: " << (i->action == mapping_t::action_none ? "none" : i->action == mapping_t::action_add ? "add" : "delete")
435 << " ttl: " << total_seconds(i->expires - now)
436 << " ]" << std::endl;
438 #endif
440 ptime min_expire = now + seconds(3600);
441 int min_index = -1;
442 for (std::vector<mapping_t>::iterator i = m_mappings.begin()
443 , end(m_mappings.end()); i != end; ++i)
445 if (i->protocol == none
446 || i->action != mapping_t::action_none) continue;
447 if (i->expires < min_expire)
449 min_expire = i->expires;
450 min_index = i - m_mappings.begin();
454 // this is already the mapping we're waiting for
455 if (m_next_refresh == min_index) return;
457 if (min_index >= 0)
459 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
460 m_log << time_now_string() << " next expiration ["
461 " i: " << min_index
462 << " ttl: " << total_seconds(min_expire - time_now())
463 << " ]" << std::endl;
464 #endif
465 error_code ec;
466 if (m_next_refresh >= 0) m_refresh_timer.cancel(ec);
467 m_refresh_timer.expires_from_now(min_expire - now, ec);
468 m_refresh_timer.async_wait(bind(&natpmp::mapping_expired, self(), _1, min_index));
469 m_next_refresh = min_index;
473 void natpmp::mapping_expired(error_code const& e, int i)
475 if (e) return;
476 mutex_t::scoped_lock l(m_mutex);
477 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
478 m_log << time_now_string() << " mapping expired [ i: " << i << " ]" << std::endl;
479 #endif
480 m_mappings[i].action = mapping_t::action_add;
481 if (m_next_refresh == i) m_next_refresh = -1;
482 update_mapping(i);
485 void natpmp::close()
487 mutex_t::scoped_lock l(m_mutex);
488 m_abort = true;
489 error_code ec;
490 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
491 m_log << time_now_string() << " close" << std::endl;
492 #endif
493 if (m_disabled) return;
494 ptime now = time_now();
495 for (std::vector<mapping_t>::iterator i = m_mappings.begin()
496 , end(m_mappings.end()); i != end; ++i)
498 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
499 m_log << " " << (i - m_mappings.begin()) << " [ "
500 "proto: " << (i->protocol == none ? "none" : i->protocol == tcp ? "tcp" : "udp")
501 << " port: " << i->external_port
502 << " local-port: " << i->local_port
503 << " action: " << (i->action == mapping_t::action_none ? "none" : i->action == mapping_t::action_add ? "add" : "delete")
504 << " ttl: " << total_seconds(i->expires - now)
505 << " ]" << std::endl;
506 #endif
507 if (i->protocol == none) continue;
508 i->action = mapping_t::action_delete;
510 m_refresh_timer.cancel(ec);
511 update_mapping(0);