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 "libtorrent/socket.hpp"
36 #include "libtorrent/upnp.hpp"
37 #include "libtorrent/io.hpp"
38 #include "libtorrent/parse_url.hpp"
39 #include "libtorrent/xml_parse.hpp"
40 #include "libtorrent/connection_queue.hpp"
41 #include "libtorrent/enum_net.hpp"
43 #include <boost/bind.hpp>
44 #include <boost/ref.hpp>
45 #if BOOST_VERSION < 103500
46 #include <asio/ip/host_name.hpp>
47 #include <asio/ip/multicast.hpp>
49 #include <boost/asio/ip/host_name.hpp>
50 #include <boost/asio/ip/multicast.hpp>
52 #include <boost/thread/mutex.hpp>
55 #if (defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)) && !defined(TORRENT_UPNP_LOGGING)
56 #define TORRENT_UPNP_LOGGING
60 using namespace libtorrent
;
62 upnp::upnp(io_service
& ios
, connection_queue
& cc
63 , address
const& listen_interface
, std::string
const& user_agent
64 , portmap_callback_t
const& cb
, bool ignore_nonrouters
, void* state
)
65 : m_user_agent(user_agent
)
69 , m_socket(ios
, udp::endpoint(address_v4::from_string("239.255.255.250"), 1900)
70 , bind(&upnp::on_reply
, self(), _1
, _2
, _3
), false)
71 , m_broadcast_timer(ios
)
72 , m_refresh_timer(ios
)
75 , m_ignore_non_routers(ignore_nonrouters
)
78 #ifdef TORRENT_UPNP_LOGGING
79 m_log
.open("upnp.log", std::ios::in
| std::ios::out
| std::ios::trunc
);
85 upnp_state_t
* s
= (upnp_state_t
*)state
;
86 m_devices
.swap(s
->devices
);
87 m_mappings
.swap(s
->mappings
);
92 void* upnp::drain_state()
94 upnp_state_t
* s
= new upnp_state_t
;
95 s
->mappings
.swap(m_mappings
);
97 for (std::set
<rootdevice
>::iterator i
= m_devices
.begin()
98 , end(m_devices
.end()); i
!= end
; ++i
)
99 i
->upnp_connection
.reset();
100 s
->devices
.swap(m_devices
);
108 void upnp::discover_device()
110 mutex_t::scoped_lock
l(m_mutex
);
112 discover_device_impl();
115 void upnp::discover_device_impl()
117 const char msearch
[] =
118 "M-SEARCH * HTTP/1.1\r\n"
119 "HOST: 239.255.255.250:1900\r\n"
120 "ST:upnp:rootdevice\r\n"
121 "MAN:\"ssdp:discover\"\r\n"
126 #ifdef TORRENT_DEBUG_UPNP
127 // simulate packet loss
128 if (m_retry_count
& 1)
130 m_socket
.send(msearch
, sizeof(msearch
) - 1, ec
);
134 #ifdef TORRENT_UPNP_LOGGING
135 m_log
<< time_now_string()
136 << " ==> Broadcast FAILED: " << ec
.message() << std::endl
137 << "aborting" << std::endl
;
139 disable(ec
.message().c_str());
144 m_broadcast_timer
.expires_from_now(milliseconds(250 * m_retry_count
), ec
);
145 m_broadcast_timer
.async_wait(bind(&upnp::resend_request
148 #ifdef TORRENT_UPNP_LOGGING
149 m_log
<< time_now_string()
150 << " ==> Broadcasting search for rootdevice" << std::endl
;
154 // returns a reference to a mapping or -1 on failure
155 int upnp::add_mapping(upnp::protocol_type p
, int external_port
, int local_port
)
157 mutex_t::scoped_lock
l(m_mutex
);
159 #ifdef TORRENT_UPNP_LOGGING
160 m_log
<< time_now_string()
161 << " *** add mapping [ proto: " << (p
== tcp
?"tcp":"udp")
162 << " ext_port: " << external_port
163 << " local_port :" << local_port
<< " ]";
164 if (m_disabled
) m_log
<< " DISABLED";
167 if (m_disabled
) return -1;
169 std::vector
<global_mapping_t
>::iterator i
= std::find_if(
170 m_mappings
.begin(), m_mappings
.end()
171 , boost::bind(&global_mapping_t::protocol
, _1
) == int(none
));
173 if (i
== m_mappings
.end())
175 m_mappings
.push_back(global_mapping_t());
176 i
= m_mappings
.end() - 1;
180 i
->external_port
= external_port
;
181 i
->local_port
= local_port
;
183 int mapping_index
= i
- m_mappings
.begin();
185 for (std::set
<rootdevice
>::iterator i
= m_devices
.begin()
186 , end(m_devices
.end()); i
!= end
; ++i
)
188 rootdevice
& d
= const_cast<rootdevice
&>(*i
);
189 TORRENT_ASSERT(d
.magic
== 1337);
191 if (int(d
.mapping
.size()) <= mapping_index
)
192 d
.mapping
.resize(mapping_index
+ 1);
193 mapping_t
& m
= d
.mapping
[mapping_index
];
195 m
.action
= mapping_t::action_add
;
197 m
.external_port
= external_port
;
198 m
.local_port
= local_port
;
200 if (d
.service_namespace
) update_map(d
, mapping_index
);
203 return mapping_index
;
206 void upnp::delete_mapping(int mapping
)
208 mutex_t::scoped_lock
l(m_mutex
);
210 if (mapping
<= int(m_mappings
.size())) return;
212 global_mapping_t
& m
= m_mappings
[mapping
];
214 #ifdef TORRENT_UPNP_LOGGING
215 m_log
<< time_now_string()
216 << " *** delete mapping [ proto: " << (m
.protocol
== tcp
?"tcp":"udp")
217 << " ext_port:" << m
.external_port
218 << " local_port:" << m
.local_port
<< " ]";
222 if (m
.protocol
== none
) return;
224 for (std::set
<rootdevice
>::iterator i
= m_devices
.begin()
225 , end(m_devices
.end()); i
!= end
; ++i
)
227 rootdevice
& d
= const_cast<rootdevice
&>(*i
);
228 TORRENT_ASSERT(d
.magic
== 1337);
230 TORRENT_ASSERT(mapping
< int(d
.mapping
.size()));
231 d
.mapping
[mapping
].action
= mapping_t::action_delete
;
233 if (d
.service_namespace
) update_map(d
, mapping
);
237 void upnp::resend_request(error_code
const& e
)
241 mutex_t::scoped_lock
l(m_mutex
);
243 if (m_retry_count
< 9
244 && (m_devices
.empty() || m_retry_count
< 4))
246 discover_device_impl();
250 if (m_devices
.empty())
252 #ifdef TORRENT_UPNP_LOGGING
253 m_log
<< time_now_string()
254 << " *** Got no response in 9 retries. Giving up, "
255 "disabling UPnP." << std::endl
;
257 disable("no UPnP router found");
261 for (std::set
<rootdevice
>::iterator i
= m_devices
.begin()
262 , end(m_devices
.end()); i
!= end
; ++i
)
264 if (i
->control_url
.empty() && !i
->upnp_connection
&& !i
->disabled
)
266 // we don't have a WANIP or WANPPP url for this device,
268 rootdevice
& d
= const_cast<rootdevice
&>(*i
);
269 TORRENT_ASSERT(d
.magic
== 1337);
272 #ifdef TORRENT_UPNP_LOGGING
273 m_log
<< time_now_string()
274 << " ==> connecting to " << d
.url
<< std::endl
;
276 if (d
.upnp_connection
) d
.upnp_connection
->close();
277 d
.upnp_connection
.reset(new http_connection(m_io_service
278 , m_cc
, bind(&upnp::on_upnp_xml
, self(), _1
, _2
279 , boost::ref(d
), _5
)));
280 d
.upnp_connection
->get(d
.url
, seconds(30), 1);
282 catch (std::exception
& e
)
285 #ifdef TORRENT_UPNP_LOGGING
286 m_log
<< time_now_string()
287 << " *** Connection failed to: " << d
.url
288 << " " << e
.what() << std::endl
;
296 void upnp::on_reply(udp::endpoint
const& from
, char* buffer
297 , std::size_t bytes_transferred
)
299 mutex_t::scoped_lock
l(m_mutex
);
301 using namespace libtorrent::detail
;
303 // parse out the url for the device
306 the response looks like this:
310 USN:uuid:000f-66d6-7296000099dc::upnp:rootdevice
311 Location: http://192.168.1.1:5431/dyndev/uuid:000f-66d6-7296000099dc
312 Server: Custom/1.0 UPnP/1.0 Proc/Ver
314 Cache-Control:max-age=180
315 DATE: Fri, 02 Jan 1970 08:10:38 GMT
317 a notification looks like this:
320 Host:239.255.255.250:1900
321 NT:urn:schemas-upnp-org:device:MediaServer:1
323 Location:http://10.0.3.169:2869/upnphost/udhisapi.dll?content=uuid:c17f0c32-d19b-4938-ae94-65f945c3a26e
324 USN:uuid:c17f0c32-d19b-4938-ae94-65f945c3a26e::urn:schemas-upnp-org:device:MediaServer:1
325 Cache-Control:max-age=900
326 Server:Microsoft-Windows-NT/5.1 UPnP/1.0 UPnP-Device-Host/1.0
330 if (!in_local_network(m_io_service
, from
.address(), ec
))
332 #ifdef TORRENT_UPNP_LOGGING
335 m_log
<< time_now_string() << " <== (" << from
<< ") error: "
336 << ec
.message() << std::endl
;
340 m_log
<< time_now_string() << " <== (" << from
<< ") UPnP device "
341 "ignored because it's not on our local network ";
342 std::vector
<ip_interface
> net
= enum_net_interfaces(m_io_service
, ec
);
343 for (std::vector
<ip_interface
>::const_iterator i
= net
.begin()
344 , end(net
.end()); i
!= end
; ++i
)
346 m_log
<< "(" << i
->interface_address
<< ", " << i
->netmask
<< ") ";
354 std::vector
<ip_route
> routes
= enum_routes(m_io_service
, ec
);
355 if (m_ignore_non_routers
&& std::find_if(routes
.begin(), routes
.end()
356 , bind(&ip_route::gateway
, _1
) == from
.address()) == routes
.end())
358 // this upnp device is filtered because it's not in the
359 // list of configured routers
360 #ifdef TORRENT_UPNP_LOGGING
363 m_log
<< time_now_string() << " <== (" << from
<< ") error: "
364 << ec
.message() << std::endl
;
368 m_log
<< time_now_string() << " <== (" << from
<< ") UPnP device "
369 "ignored because it's not a router on our network ";
370 for (std::vector
<ip_route
>::const_iterator i
= routes
.begin()
371 , end(routes
.end()); i
!= end
; ++i
)
373 m_log
<< "(" << i
->gateway
<< ", " << i
->netmask
<< ") ";
383 p
.incoming(buffer::const_interval(buffer
384 , buffer
+ bytes_transferred
), error
);
387 #ifdef TORRENT_UPNP_LOGGING
388 m_log
<< time_now_string() << " <== (" << from
<< ") Rootdevice "
389 "responded with incorrect HTTP packet. Ignoring device" << std::endl
;
394 if (p
.status_code() != 200 && p
.method() != "notify")
396 #ifdef TORRENT_UPNP_LOGGING
397 if (p
.method().empty())
398 m_log
<< time_now_string()
399 << " <== (" << from
<< ") Device responded with HTTP status: " << p
.status_code()
400 << ". Ignoring device" << std::endl
;
402 m_log
<< time_now_string()
403 << " <== (" << from
<< ") Device with HTTP method: " << p
.method()
404 << ". Ignoring device" << std::endl
;
409 if (!p
.header_finished())
411 #ifdef TORRENT_UPNP_LOGGING
412 m_log
<< time_now_string()
413 << " <== (" << from
<< ") Rootdevice responded with incomplete HTTP "
414 "packet. Ignoring device" << std::endl
;
419 std::string url
= p
.header("location");
422 #ifdef TORRENT_UPNP_LOGGING
423 m_log
<< time_now_string()
424 << " <== (" << from
<< ") Rootdevice response is missing a location header. "
425 "Ignoring device" << std::endl
;
433 std::set
<rootdevice
>::iterator i
= m_devices
.find(d
);
435 if (i
== m_devices
.end())
438 std::string protocol
;
441 // we don't have this device in our list. Add it
442 boost::tie(protocol
, auth
, d
.hostname
, d
.port
, d
.path
, error
)
443 = parse_url_components(d
.url
);
447 #ifdef TORRENT_UPNP_LOGGING
448 m_log
<< time_now_string()
449 << " <== (" << from
<< ") Rootdevice advertized an invalid url: '" << d
.url
450 << "'. " << error
<< ". Ignoring device" << std::endl
;
455 // ignore the auth here. It will be re-parsed
456 // by the http connection later
458 if (protocol
!= "http")
460 #ifdef TORRENT_UPNP_LOGGING
461 m_log
<< time_now_string()
462 << " <== (" << from
<< ") Rootdevice uses unsupported protocol: '" << protocol
463 << "'. Ignoring device" << std::endl
;
470 #ifdef TORRENT_UPNP_LOGGING
471 m_log
<< time_now_string()
472 << " <== (" << from
<< ") Rootdevice responded with a url with port 0. "
473 "Ignoring device" << std::endl
;
477 #ifdef TORRENT_UPNP_LOGGING
478 m_log
<< time_now_string()
479 << " <== (" << from
<< ") Found rootdevice: " << d
.url
480 << " total: " << m_devices
.size() << std::endl
;
483 if (m_devices
.size() >= 50)
485 #ifdef TORRENT_UPNP_LOGGING
486 m_log
<< time_now_string()
487 << " <== (" << from
<< ") Too many devices (" << m_devices
.size() << "), "
488 "ignoring: " << d
.url
<< std::endl
;
493 TORRENT_ASSERT(d
.mapping
.empty());
494 for (std::vector
<global_mapping_t
>::iterator j
= m_mappings
.begin()
495 , end(m_mappings
.end()); j
!= end
; ++j
)
498 m
.action
= mapping_t::action_add
;
499 m
.local_port
= j
->local_port
;
500 m
.external_port
= j
->external_port
;
501 m
.protocol
= j
->protocol
;
502 d
.mapping
.push_back(m
);
504 boost::tie(i
, boost::tuples::ignore
) = m_devices
.insert(d
);
508 // since we're using udp, send the query 4 times
509 // just to make sure we find all devices
510 if (m_retry_count
>= 4 && !m_devices
.empty())
513 m_broadcast_timer
.cancel(ec
);
515 for (std::set
<rootdevice
>::iterator i
= m_devices
.begin()
516 , end(m_devices
.end()); i
!= end
; ++i
)
518 if (i
->control_url
.empty() && !i
->upnp_connection
&& !i
->disabled
)
520 // we don't have a WANIP or WANPPP url for this device,
522 rootdevice
& d
= const_cast<rootdevice
&>(*i
);
523 TORRENT_ASSERT(d
.magic
== 1337);
524 #ifndef BOOST_NO_EXCEPTIONS
528 #ifdef TORRENT_UPNP_LOGGING
529 m_log
<< time_now_string()
530 << " ==> connecting to " << d
.url
<< std::endl
;
532 if (d
.upnp_connection
) d
.upnp_connection
->close();
533 d
.upnp_connection
.reset(new http_connection(m_io_service
534 , m_cc
, bind(&upnp::on_upnp_xml
, self(), _1
, _2
535 , boost::ref(d
), _5
)));
536 d
.upnp_connection
->get(d
.url
, seconds(30), 1);
537 #ifndef BOOST_NO_EXCEPTIONS
539 catch (std::exception
& e
)
542 #ifdef TORRENT_UPNP_LOGGING
543 m_log
<< time_now_string()
544 << " *** Connection failed to: " << d
.url
545 << " " << e
.what() << std::endl
;
555 void upnp::post(upnp::rootdevice
const& d
, std::string
const& soap
556 , std::string
const& soap_action
)
558 TORRENT_ASSERT(d
.magic
== 1337);
559 TORRENT_ASSERT(d
.upnp_connection
);
561 std::stringstream header
;
563 header
<< "POST " << d
.control_url
<< " HTTP/1.1\r\n"
564 "Host: " << d
.hostname
<< ":" << d
.port
<< "\r\n"
565 "Content-Type: text/xml; charset=\"utf-8\"\r\n"
566 "Content-Length: " << soap
.size() << "\r\n"
567 "Soapaction: \"" << d
.service_namespace
<< "#" << soap_action
<< "\"\r\n\r\n" << soap
;
569 d
.upnp_connection
->sendbuffer
= header
.str();
571 #ifdef TORRENT_UPNP_LOGGING
572 m_log
<< time_now_string()
573 << " ==> sending: " << header
.str() << std::endl
;
578 void upnp::create_port_mapping(http_connection
& c
, rootdevice
& d
, int i
)
580 mutex_t::scoped_lock
l(m_mutex
);
582 TORRENT_ASSERT(d
.magic
== 1337);
584 if (!d
.upnp_connection
)
586 TORRENT_ASSERT(d
.disabled
);
587 #ifdef TORRENT_UPNP_LOGGING
588 m_log
<< time_now_string() << " *** mapping (" << i
589 << ") aborted" << std::endl
;
594 std::string soap_action
= "AddPortMapping";
596 std::stringstream soap
;
598 soap
<< "<?xml version=\"1.0\"?>\n"
599 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
600 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
601 "<s:Body><u:" << soap_action
<< " xmlns:u=\"" << d
.service_namespace
<< "\">";
604 soap
<< "<NewRemoteHost></NewRemoteHost>"
605 "<NewExternalPort>" << d
.mapping
[i
].external_port
<< "</NewExternalPort>"
606 "<NewProtocol>" << (d
.mapping
[i
].protocol
== udp
? "UDP" : "TCP") << "</NewProtocol>"
607 "<NewInternalPort>" << d
.mapping
[i
].local_port
<< "</NewInternalPort>"
608 "<NewInternalClient>" << c
.socket().local_endpoint(ec
).address() << "</NewInternalClient>"
609 "<NewEnabled>1</NewEnabled>"
610 "<NewPortMappingDescription>" << m_user_agent
<< "</NewPortMappingDescription>"
611 "<NewLeaseDuration>" << d
.lease_duration
<< "</NewLeaseDuration>";
612 soap
<< "</u:" << soap_action
<< "></s:Body></s:Envelope>";
614 post(d
, soap
.str(), soap_action
);
617 void upnp::next(rootdevice
& d
, int i
)
619 if (i
< num_mappings() - 1)
621 update_map(d
, i
+ 1);
625 std::vector
<mapping_t
>::iterator i
626 = std::find_if(d
.mapping
.begin(), d
.mapping
.end()
627 , boost::bind(&mapping_t::action
, _1
) != int(mapping_t::action_none
));
628 if (i
== d
.mapping
.end()) return;
630 update_map(d
, i
- d
.mapping
.begin());
634 void upnp::update_map(rootdevice
& d
, int i
)
636 TORRENT_ASSERT(d
.magic
== 1337);
637 TORRENT_ASSERT(i
< int(d
.mapping
.size()));
638 TORRENT_ASSERT(d
.mapping
.size() == m_mappings
.size());
640 if (d
.upnp_connection
) return;
642 mapping_t
& m
= d
.mapping
[i
];
644 if (m
.action
== mapping_t::action_none
645 || m
.protocol
== none
)
647 #ifdef TORRENT_UPNP_LOGGING
648 if (m
.protocol
!= none
)
649 m_log
<< time_now_string() << " *** mapping (" << i
650 << ") does not need update, skipping" << std::endl
;
656 TORRENT_ASSERT(!d
.upnp_connection
);
657 TORRENT_ASSERT(d
.service_namespace
);
659 #ifdef TORRENT_UPNP_LOGGING
660 m_log
<< time_now_string()
661 << " ==> connecting to " << d
.hostname
<< std::endl
;
663 if (m
.action
== mapping_t::action_add
)
672 if (d
.upnp_connection
) d
.upnp_connection
->close();
673 d
.upnp_connection
.reset(new http_connection(m_io_service
674 , m_cc
, bind(&upnp::on_upnp_map_response
, self(), _1
, _2
675 , boost::ref(d
), i
, _5
), true
676 , bind(&upnp::create_port_mapping
, self(), _1
, boost::ref(d
), i
)));
678 d
.upnp_connection
->start(d
.hostname
, boost::lexical_cast
<std::string
>(d
.port
)
681 else if (m
.action
== mapping_t::action_delete
)
683 if (d
.upnp_connection
) d
.upnp_connection
->close();
684 d
.upnp_connection
.reset(new http_connection(m_io_service
685 , m_cc
, bind(&upnp::on_upnp_unmap_response
, self(), _1
, _2
686 , boost::ref(d
), i
, _5
), true
687 , bind(&upnp::delete_port_mapping
, self(), boost::ref(d
), i
)));
688 d
.upnp_connection
->start(d
.hostname
, boost::lexical_cast
<std::string
>(d
.port
)
692 m
.action
= mapping_t::action_none
;
695 void upnp::delete_port_mapping(rootdevice
& d
, int i
)
697 mutex_t::scoped_lock
l(m_mutex
);
699 TORRENT_ASSERT(d
.magic
== 1337);
701 if (!d
.upnp_connection
)
703 TORRENT_ASSERT(d
.disabled
);
704 #ifdef TORRENT_UPNP_LOGGING
705 m_log
<< time_now_string() << " *** unmapping (" << i
706 << ") aborted" << std::endl
;
711 std::stringstream soap
;
713 std::string soap_action
= "DeletePortMapping";
715 soap
<< "<?xml version=\"1.0\"?>\n"
716 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
717 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
718 "<s:Body><u:" << soap_action
<< " xmlns:u=\"" << d
.service_namespace
<< "\">";
720 soap
<< "<NewRemoteHost></NewRemoteHost>"
721 "<NewExternalPort>" << d
.mapping
[i
].external_port
<< "</NewExternalPort>"
722 "<NewProtocol>" << (d
.mapping
[i
].protocol
== udp
? "UDP" : "TCP") << "</NewProtocol>";
723 soap
<< "</u:" << soap_action
<< "></s:Body></s:Envelope>";
725 post(d
, soap
.str(), soap_action
);
732 parse_state(): found_service(false), exit(false) {}
733 void reset(char const* st
)
735 found_service
= false;
742 std::string control_url
;
743 char const* service_type
;
747 void find_control_url(int type
, char const* string
, parse_state
& state
)
749 if (state
.exit
) return;
751 if (type
== xml_start_tag
)
753 if ((!state
.top_tag
.empty() && state
.top_tag
== "service")
754 || !strcmp(string
, "service"))
756 state
.top_tag
= string
;
758 else if (!strcmp(string
, "modelName"))
760 state
.top_tag
= string
;
763 else if (type
== xml_end_tag
)
765 if (!strcmp(string
, "service"))
767 state
.top_tag
.clear();
768 if (state
.found_service
) state
.exit
= true;
770 else if (!state
.top_tag
.empty() && state
.top_tag
!= "service")
771 state
.top_tag
= "service";
773 else if (type
== xml_string
)
775 if (state
.top_tag
== "serviceType")
777 if (!strcmp(string
, state
.service_type
))
778 state
.found_service
= true;
780 else if (state
.top_tag
== "controlURL")
782 state
.control_url
= string
;
783 if (state
.found_service
) state
.exit
= true;
785 else if (state
.top_tag
== "modelName")
787 state
.model
= string
;
794 void upnp::on_upnp_xml(error_code
const& e
795 , libtorrent::http_parser
const& p
, rootdevice
& d
796 , http_connection
& c
)
798 mutex_t::scoped_lock
l(m_mutex
);
800 TORRENT_ASSERT(d
.magic
== 1337);
801 if (d
.upnp_connection
&& d
.upnp_connection
.get() == &c
)
803 d
.upnp_connection
->close();
804 d
.upnp_connection
.reset();
807 if (e
&& e
!= asio::error::eof
)
809 #ifdef TORRENT_UPNP_LOGGING
810 m_log
<< time_now_string()
811 << " <== (" << d
.url
<< ") error while fetching control url: "
812 << e
.message() << std::endl
;
818 if (!p
.header_finished())
820 #ifdef TORRENT_UPNP_LOGGING
821 m_log
<< time_now_string()
822 << " <== (" << d
.url
<< ") error while fetching control url: incomplete http message" << std::endl
;
828 if (p
.status_code() != 200)
830 #ifdef TORRENT_UPNP_LOGGING
831 m_log
<< time_now_string()
832 << " <== (" << d
.url
<< ") error while fetching control url: " << p
.message() << std::endl
;
839 s
.reset("urn:schemas-upnp-org:service:WANIPConnection:1");
840 xml_parse((char*)p
.get_body().begin
, (char*)p
.get_body().end
841 , bind(&find_control_url
, _1
, _2
, boost::ref(s
)));
844 d
.service_namespace
= s
.service_type
;
845 if (!s
.model
.empty()) m_model
= s
.model
;
849 // we didn't find the WAN IP connection, look for
851 s
.reset("urn:schemas-upnp-org:service:WANPPPConnection:1");
852 xml_parse((char*)p
.get_body().begin
, (char*)p
.get_body().end
853 , bind(&find_control_url
, _1
, _2
, boost::ref(s
)));
856 d
.service_namespace
= s
.service_type
;
857 if (!s
.model
.empty()) m_model
= s
.model
;
861 #ifdef TORRENT_UPNP_LOGGING
862 m_log
<< time_now_string()
863 << " <== (" << d
.url
<< ") Rootdevice response, did not find "
864 "a port mapping interface" << std::endl
;
871 #ifdef TORRENT_UPNP_LOGGING
872 m_log
<< time_now_string()
873 << " <== (" << d
.url
<< ") Rootdevice response, found control URL: " << s
.control_url
874 << " namespace: " << d
.service_namespace
<< std::endl
;
877 d
.control_url
= s
.control_url
;
879 if (num_mappings() > 0) update_map(d
, 0);
882 void upnp::disable(char const* msg
)
887 for (std::vector
<global_mapping_t
>::iterator i
= m_mappings
.begin()
888 , end(m_mappings
.end()); i
!= end
; ++i
)
890 if (i
->protocol
== none
) continue;
892 m_callback(i
- m_mappings
.begin(), 0, msg
);
897 m_broadcast_timer
.cancel(ec
);
898 m_refresh_timer
.cancel(ec
);
904 struct error_code_parse_state
906 error_code_parse_state(): in_error_code(false), exit(false), error_code(-1) {}
912 void find_error_code(int type
, char const* string
, error_code_parse_state
& state
)
914 if (state
.exit
) return;
915 if (type
== xml_start_tag
&& !strcmp("errorCode", string
))
917 state
.in_error_code
= true;
919 else if (type
== xml_string
&& state
.in_error_code
)
921 state
.error_code
= std::atoi(string
);
935 error_code_t error_codes
[] =
937 {402, "Invalid Arguments"}
938 , {501, "Action Failed"}
939 , {714, "The specified value does not exist in the array"}
940 , {715, "The source IP address cannot be wild-carded"}
941 , {716, "The external port cannot be wild-carded"}
942 , {718, "The port mapping entry specified conflicts with "
943 "a mapping assigned previously to another client"}
944 , {724, "Internal and External port values must be the same"}
945 , {725, "The NAT implementation only supports permanent "
946 "lease times on port mappings"}
947 , {726, "RemoteHost must be a wildcard and cannot be a "
948 "specific IP address or DNS name"}
949 , {727, "ExternalPort must be a wildcard and cannot be a specific port "}
954 void upnp::on_upnp_map_response(error_code
const& e
955 , libtorrent::http_parser
const& p
, rootdevice
& d
, int mapping
956 , http_connection
& c
)
958 mutex_t::scoped_lock
l(m_mutex
);
960 TORRENT_ASSERT(d
.magic
== 1337);
961 if (d
.upnp_connection
&& d
.upnp_connection
.get() == &c
)
963 d
.upnp_connection
->close();
964 d
.upnp_connection
.reset();
967 if (e
&& e
!= asio::error::eof
)
969 #ifdef TORRENT_UPNP_LOGGING
970 m_log
<< time_now_string()
971 << " <== error while adding portmap: " << e
.message() << std::endl
;
977 if (m_closing
) return;
979 // error code response may look like this:
980 // <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
981 // s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
984 // <faultcode>s:Client</faultcode>
985 // <faultstring>UPnPError</faultstring>
987 // <UPnPErrorxmlns="urn:schemas-upnp-org:control-1-0">
988 // <errorCode>402</errorCode>
989 // <errorDescription>Invalid Args</errorDescription>
996 if (!p
.header_finished())
998 #ifdef TORRENT_UPNP_LOGGING
999 m_log
<< time_now_string()
1000 << " <== error while adding portmap: incomplete http message" << std::endl
;
1006 // We don't want to ignore responses with return codes other than 200
1007 // since those might contain valid UPnP error codes
1009 error_code_parse_state s
;
1010 xml_parse((char*)p
.get_body().begin
, (char*)p
.get_body().end
1011 , bind(&find_error_code
, _1
, _2
, boost::ref(s
)));
1013 #ifdef TORRENT_UPNP_LOGGING
1014 if (s
.error_code
!= -1)
1016 m_log
<< time_now_string()
1017 << " <== got error message: " << s
.error_code
<< std::endl
;
1021 mapping_t
& m
= d
.mapping
[mapping
];
1023 if (s
.error_code
== 725)
1025 // only permanent leases supported
1026 d
.lease_duration
= 0;
1027 m
.action
= mapping_t::action_add
;
1029 update_map(d
, mapping
);
1032 else if (s
.error_code
== 718 || s
.error_code
== 727)
1034 if (m
.external_port
!= 0)
1036 // conflict in mapping, set port to wildcard
1037 // and let the router decide
1038 m
.external_port
= 0;
1039 m
.action
= mapping_t::action_add
;
1041 update_map(d
, mapping
);
1044 return_error(mapping
, s
.error_code
);
1046 else if (s
.error_code
== 716)
1048 // The external port cannot be wildcarder
1049 // pick a random port
1050 m
.external_port
= 40000 + (rand() % 10000);
1051 m
.action
= mapping_t::action_add
;
1053 update_map(d
, mapping
);
1056 else if (s
.error_code
!= -1)
1058 return_error(mapping
, s
.error_code
);
1061 #ifdef TORRENT_UPNP_LOGGING
1062 m_log
<< time_now_string()
1063 << " <== map response: " << std::string(p
.get_body().begin
, p
.get_body().end
)
1067 if (s
.error_code
== -1)
1069 m_callback(mapping
, m
.external_port
, "");
1070 if (d
.lease_duration
> 0)
1072 m
.expires
= time_now()
1073 + seconds(int(d
.lease_duration
* 0.75f
));
1074 ptime next_expire
= m_refresh_timer
.expires_at();
1075 if (next_expire
< time_now()
1076 || next_expire
> m
.expires
)
1079 m_refresh_timer
.expires_at(m
.expires
, ec
);
1080 m_refresh_timer
.async_wait(bind(&upnp::on_expire
, self(), _1
));
1085 m
.expires
= max_time();
1093 void upnp::return_error(int mapping
, int code
)
1095 int num_errors
= sizeof(error_codes
) / sizeof(error_codes
[0]);
1096 error_code_t
* end
= error_codes
+ num_errors
;
1097 error_code_t tmp
= {code
, 0};
1098 error_code_t
* e
= std::lower_bound(error_codes
, end
, tmp
1099 , bind(&error_code_t::code
, _1
) < bind(&error_code_t::code
, _2
));
1100 std::string error_string
= "UPnP mapping error ";
1101 error_string
+= boost::lexical_cast
<std::string
>(code
);
1102 if (e
!= end
&& e
->code
== code
)
1104 error_string
+= ": ";
1105 error_string
+= e
->msg
;
1107 m_callback(mapping
, 0, error_string
);
1110 void upnp::on_upnp_unmap_response(error_code
const& e
1111 , libtorrent::http_parser
const& p
, rootdevice
& d
, int mapping
1112 , http_connection
& c
)
1114 mutex_t::scoped_lock
l(m_mutex
);
1116 TORRENT_ASSERT(d
.magic
== 1337);
1117 if (d
.upnp_connection
&& d
.upnp_connection
.get() == &c
)
1119 d
.upnp_connection
->close();
1120 d
.upnp_connection
.reset();
1123 if (e
&& e
!= asio::error::eof
)
1125 #ifdef TORRENT_UPNP_LOGGING
1126 m_log
<< time_now_string()
1127 << " <== error while deleting portmap: " << e
.message() << std::endl
;
1129 } else if (!p
.header_finished())
1131 #ifdef TORRENT_UPNP_LOGGING
1132 m_log
<< time_now_string()
1133 << " <== error while deleting portmap: incomplete http message" << std::endl
;
1136 else if (p
.status_code() != 200)
1138 #ifdef TORRENT_UPNP_LOGGING
1139 m_log
<< time_now_string()
1140 << " <== error while deleting portmap: " << p
.message() << std::endl
;
1146 #ifdef TORRENT_UPNP_LOGGING
1147 m_log
<< time_now_string()
1148 << " <== unmap response: " << std::string(p
.get_body().begin
, p
.get_body().end
)
1153 d
.mapping
[mapping
].protocol
= none
;
1158 void upnp::on_expire(error_code
const& e
)
1162 ptime now
= time_now();
1163 ptime next_expire
= max_time();
1165 mutex_t::scoped_lock
l(m_mutex
);
1167 for (std::set
<rootdevice
>::iterator i
= m_devices
.begin()
1168 , end(m_devices
.end()); i
!= end
; ++i
)
1170 rootdevice
& d
= const_cast<rootdevice
&>(*i
);
1171 TORRENT_ASSERT(d
.magic
== 1337);
1172 for (int m
= 0; m
< num_mappings(); ++m
)
1174 if (d
.mapping
[m
].expires
!= max_time())
1177 if (d
.mapping
[m
].expires
< now
)
1179 d
.mapping
[m
].expires
= max_time();
1182 else if (d
.mapping
[m
].expires
< next_expire
)
1184 next_expire
= d
.mapping
[m
].expires
;
1188 if (next_expire
!= max_time())
1191 m_refresh_timer
.expires_at(next_expire
, ec
);
1192 m_refresh_timer
.async_wait(bind(&upnp::on_expire
, self(), _1
));
1198 mutex_t::scoped_lock
l(m_mutex
);
1201 m_refresh_timer
.cancel(ec
);
1202 m_broadcast_timer
.cancel(ec
);
1206 for (std::set
<rootdevice
>::iterator i
= m_devices
.begin()
1207 , end(m_devices
.end()); i
!= end
; ++i
)
1209 rootdevice
& d
= const_cast<rootdevice
&>(*i
);
1210 TORRENT_ASSERT(d
.magic
== 1337);
1211 if (d
.control_url
.empty()) continue;
1212 for (std::vector
<mapping_t
>::iterator j
= d
.mapping
.begin()
1213 , end(d
.mapping
.end()); j
!= end
; ++j
)
1215 if (j
->protocol
== none
) continue;
1216 if (j
->action
== mapping_t::action_add
)
1218 j
->action
= mapping_t::action_none
;
1221 j
->action
= mapping_t::action_delete
;
1222 m_mappings
[j
- d
.mapping
.begin()].protocol
= none
;
1224 if (num_mappings() > 0) update_map(d
, 0);