3 Copyright (c) 2007, Arvid Norberg
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
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>
41 #include <boost/asio/ip/host_name.hpp>
44 #include "libtorrent/natpmp.hpp"
45 #include "libtorrent/io.hpp"
46 #include "libtorrent/assert.hpp"
47 #include "libtorrent/enum_net.hpp"
50 using namespace libtorrent
;
52 natpmp::natpmp(io_service
& ios
, address
const& listen_interface
, portmap_callback_t
const& cb
)
54 , m_currently_mapping(-1)
58 , m_refresh_timer(ios
)
62 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
63 m_log
.open("natpmp.log", std::ios::in
| std::ios::out
| std::ios::trunc
);
65 rebind(listen_interface
);
68 void natpmp::rebind(address
const& listen_interface
)
70 mutex_t::scoped_lock
l(m_mutex
);
73 address gateway
= get_default_gateway(m_socket
.get_io_service(), 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
;
80 disable("failed to find default router");
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
;
95 m_socket
.open(udp::v4(), ec
);
98 disable(ec
.message().c_str());
101 m_socket
.bind(udp::endpoint(address_v4::any(), 0), ec
);
104 disable(ec
.message().c_str());
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
)
117 i
->action
= mapping_t::action_add
;
118 update_mapping(i
- m_mappings
.begin());
122 void natpmp::disable(char const* message
)
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;
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
;
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;
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
;
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
;
196 if (i
< int(m_mappings
.size()) - 1)
198 update_mapping(i
+ 1);
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())
211 m_send_timer
.cancel(ec
);
214 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
215 m_log
<< " done" << (m_abort
?" shutting down":"") << std::endl
;
220 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
221 m_log
<< " updating " << (m
- m_mappings
.begin()) << std::endl
;
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
)
237 if (m_currently_mapping
== -1)
239 // the socket is not currently in use
240 // send out a mapping request
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
);
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()
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
;
275 m_socket
.send_to(asio::buffer(buf
, 12), m_nat_endpoint
, 0, ec
);
276 // linear back-off instead of exponential
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
)
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);
302 void natpmp::on_reply(error_code
const& e
303 , std::size_t bytes_transferred
)
305 using namespace libtorrent::detail
;
308 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
309 m_log
<< time_now_string()
310 << " <== on_receive ["
311 " error: " << e
.message() << " ]" << std::endl
;
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
;
329 mutex_t::scoped_lock
l(m_mutex
);
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()
352 << " protocol: " << (cmd
- 128 == 1 ? "udp" : "tcp")
353 << " local: " << private_port
<< " external: " << public_port
354 << " ttl: " << lifetime
<< " ]" << std::endl
;
357 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
360 m_log
<< "*** unexpected version: " << version
<< std::endl
;
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
;
371 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
372 if (cmd
!= 128 + m
.protocol
)
374 m_log
<< "*** unexpected protocol: " << (cmd
- 128) << std::endl
;
378 if (public_port
== 0 || lifetime
== 0)
380 // this means the mapping was
381 // successfully closed
386 m
.expires
= time_now() + seconds(int(lifetime
* 0.7f
));
387 m
.external_port
= public_port
;
392 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
393 m_log
<< "*** ERROR: " << result
<< std::endl
;
395 std::stringstream errmsg
;
396 errmsg
<< "NAT router reports error (" << 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();
420 void natpmp::update_expiration_timer()
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
;
440 ptime min_expire
= now
+ seconds(3600);
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;
459 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
460 m_log
<< time_now_string() << " next expiration ["
462 << " ttl: " << total_seconds(min_expire
- time_now())
463 << " ]" << std::endl
;
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
)
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
;
480 m_mappings
[i
].action
= mapping_t::action_add
;
481 if (m_next_refresh
== i
) m_next_refresh
= -1;
487 mutex_t::scoped_lock
l(m_mutex
);
490 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
491 m_log
<< time_now_string() << " close" << std::endl
;
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
;
507 if (i
->protocol
== none
) continue;
508 i
->action
= mapping_t::action_delete
;
510 m_refresh_timer
.cancel(ec
);