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 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
)
114 i
->action
= mapping_t::action_add
;
115 update_mapping(i
- m_mappings
.begin());
119 void natpmp::disable(char const* message
)
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;
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
;
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;
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
;
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
;
193 if (i
< int(m_mappings
.size()) - 1)
195 update_mapping(i
+ 1);
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())
208 m_send_timer
.cancel(ec
);
211 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
212 m_log
<< " done" << (m_abort
?" shutting down":"") << std::endl
;
217 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
218 m_log
<< " updating " << (m
- m_mappings
.begin()) << std::endl
;
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
)
234 if (m_currently_mapping
== -1)
236 // the socket is not currently in use
237 // send out a mapping request
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
);
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()
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
;
274 m_socket
.send_to(asio::buffer(buf
, 12), m_nat_endpoint
, 0, ec
);
275 // linear back-off instead of exponential
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
)
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);
300 void natpmp::on_reply(error_code
const& e
301 , std::size_t bytes_transferred
)
303 using namespace libtorrent::detail
;
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
));
313 mutex_t::scoped_lock
l(m_mutex
);
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()
336 << " protocol: " << (cmd
- 128 == 1 ? "udp" : "tcp")
337 << " local: " << private_port
<< " external: " << public_port
338 << " ttl: " << lifetime
<< " ]" << std::endl
;
341 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
344 m_log
<< "*** unexpected version: " << version
<< std::endl
;
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
;
355 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
356 if (cmd
!= 128 + m
.protocol
)
358 m_log
<< "*** unexpected protocol: " << (cmd
- 128) << std::endl
;
362 if (public_port
== 0 || lifetime
== 0)
364 // this means the mapping was
365 // successfully closed
370 m
.expires
= time_now() + seconds(int(lifetime
* 0.7f
));
371 m
.external_port
= public_port
;
376 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
377 m_log
<< "*** ERROR: " << result
<< std::endl
;
379 std::stringstream errmsg
;
380 errmsg
<< "NAT router reports error (" << 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();
404 void natpmp::update_expiration_timer()
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
;
424 ptime min_expire
= now
+ seconds(3600);
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;
443 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
444 m_log
<< time_now_string() << " next expiration ["
446 << " ttl: " << total_seconds(min_expire
- time_now())
447 << " ]" << std::endl
;
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
)
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
;
464 m_mappings
[i
].action
= mapping_t::action_add
;
465 if (m_next_refresh
== i
) m_next_refresh
= -1;
471 mutex_t::scoped_lock
l(m_mutex
);
474 #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
475 m_log
<< time_now_string() << " close" << std::endl
;
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
;
491 if (i
->protocol
== none
) continue;
492 i
->action
= mapping_t::action_delete
;
494 m_refresh_timer
.cancel(ec
);