fixes bug where priorities where lost when force-rechecking.
[libtorrent.git] / src / upnp.cpp
blobd98e952843982e11c1dd791d208d97824aa18355
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 "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>
48 #else
49 #include <boost/asio/ip/host_name.hpp>
50 #include <boost/asio/ip/multicast.hpp>
51 #endif
52 #include <boost/thread/mutex.hpp>
53 #include <cstdlib>
55 #if (defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)) && !defined(TORRENT_UPNP_LOGGING)
56 #define TORRENT_UPNP_LOGGING
57 #endif
59 using boost::bind;
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)
66 , m_callback(cb)
67 , m_retry_count(0)
68 , m_io_service(ios)
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)
73 , m_disabled(false)
74 , m_closing(false)
75 , m_ignore_non_routers(ignore_nonrouters)
76 , m_cc(cc)
78 #ifdef TORRENT_UPNP_LOGGING
79 m_log.open("upnp.log", std::ios::in | std::ios::out | std::ios::trunc);
80 #endif
81 m_retry_count = 0;
83 if (state)
85 upnp_state_t* s = (upnp_state_t*)state;
86 m_devices.swap(s->devices);
87 m_mappings.swap(s->mappings);
88 delete s;
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);
101 return s;
104 upnp::~upnp()
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"
122 "MX:3\r\n"
123 "\r\n\r\n";
125 error_code ec;
126 #ifdef TORRENT_DEBUG_UPNP
127 // simulate packet loss
128 if (m_retry_count & 1)
129 #endif
130 m_socket.send(msearch, sizeof(msearch) - 1, ec);
132 if (ec)
134 #ifdef TORRENT_UPNP_LOGGING
135 m_log << time_now_string()
136 << " ==> Broadcast FAILED: " << ec.message() << std::endl
137 << "aborting" << std::endl;
138 #endif
139 disable(ec.message().c_str());
140 return;
143 ++m_retry_count;
144 m_broadcast_timer.expires_from_now(milliseconds(250 * m_retry_count), ec);
145 m_broadcast_timer.async_wait(bind(&upnp::resend_request
146 , self(), _1));
148 #ifdef TORRENT_UPNP_LOGGING
149 m_log << time_now_string()
150 << " ==> Broadcasting search for rootdevice" << std::endl;
151 #endif
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";
165 m_log << std::endl;
166 #endif
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;
179 i->protocol = p;
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;
196 m.protocol = p;
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 << " ]";
219 m_log << std::endl;
220 #endif
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)
239 if (e) return;
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();
247 return;
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;
256 #endif
257 disable("no UPnP router found");
258 return;
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,
267 // ask for it
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;
275 #endif
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)
284 (void)e;
285 #ifdef TORRENT_UPNP_LOGGING
286 m_log << time_now_string()
287 << " *** Connection failed to: " << d.url
288 << " " << e.what() << std::endl;
289 #endif
290 d.disabled = true;
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:
308 HTTP/1.1 200 OK
309 ST:upnp:rootdevice
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
313 EXT:
314 Cache-Control:max-age=180
315 DATE: Fri, 02 Jan 1970 08:10:38 GMT
317 a notification looks like this:
319 NOTIFY * HTTP/1.1
320 Host:239.255.255.250:1900
321 NT:urn:schemas-upnp-org:device:MediaServer:1
322 NTS:ssdp:alive
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
329 error_code ec;
330 if (!in_local_network(m_io_service, from.address(), ec))
332 #ifdef TORRENT_UPNP_LOGGING
333 if (ec)
335 m_log << time_now_string() << " <== (" << from << ") error: "
336 << ec.message() << std::endl;
338 else
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 << ") ";
348 m_log << std::endl;
350 #endif
351 return;
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
361 if (ec)
363 m_log << time_now_string() << " <== (" << from << ") error: "
364 << ec.message() << std::endl;
366 else
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 << ") ";
375 m_log << std::endl;
377 #endif
378 return;
381 http_parser p;
382 bool error = false;
383 p.incoming(buffer::const_interval(buffer
384 , buffer + bytes_transferred), error);
385 if (error)
387 #ifdef TORRENT_UPNP_LOGGING
388 m_log << time_now_string() << " <== (" << from << ") Rootdevice "
389 "responded with incorrect HTTP packet. Ignoring device" << std::endl;
390 #endif
391 return;
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;
401 else
402 m_log << time_now_string()
403 << " <== (" << from << ") Device with HTTP method: " << p.method()
404 << ". Ignoring device" << std::endl;
405 #endif
406 return;
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;
415 #endif
416 return;
419 std::string url = p.header("location");
420 if (url.empty())
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;
426 #endif
427 return;
430 rootdevice d;
431 d.url = url;
433 std::set<rootdevice>::iterator i = m_devices.find(d);
435 if (i == m_devices.end())
438 std::string protocol;
439 std::string auth;
440 char const* error;
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);
445 if (error)
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;
451 #endif
452 return;
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;
464 #endif
465 return;
468 if (d.port == 0)
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;
474 #endif
475 return;
477 #ifdef TORRENT_UPNP_LOGGING
478 m_log << time_now_string()
479 << " <== (" << from << ") Found rootdevice: " << d.url
480 << " total: " << m_devices.size() << std::endl;
481 #endif
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;
489 #endif
490 return;
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)
497 mapping_t m;
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())
512 error_code ec;
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,
521 // ask for it
522 rootdevice& d = const_cast<rootdevice&>(*i);
523 TORRENT_ASSERT(d.magic == 1337);
524 #ifndef BOOST_NO_EXCEPTIONS
527 #endif
528 #ifdef TORRENT_UPNP_LOGGING
529 m_log << time_now_string()
530 << " ==> connecting to " << d.url << std::endl;
531 #endif
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)
541 (void)e;
542 #ifdef TORRENT_UPNP_LOGGING
543 m_log << time_now_string()
544 << " *** Connection failed to: " << d.url
545 << " " << e.what() << std::endl;
546 #endif
547 d.disabled = true;
549 #endif
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;
574 #endif
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;
590 #endif
591 return;
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 << "\">";
603 error_code ec;
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);
623 else
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;
651 #endif
652 next(d, i);
653 return;
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;
662 #endif
663 if (m.action == mapping_t::action_add)
665 if (m.failcount > 5)
667 // giving up
668 next(d, i);
669 return;
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)
679 , seconds(10), 1);
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)
689 , seconds(10), 1);
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;
707 #endif
708 return;
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);
728 namespace
730 struct parse_state
732 parse_state(): found_service(false), exit(false) {}
733 void reset(char const* st)
735 found_service = false;
736 exit = false;
737 service_type = st;
739 bool found_service;
740 bool exit;
741 std::string top_tag;
742 std::string control_url;
743 char const* service_type;
744 std::string model;
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;
813 #endif
814 d.disabled = true;
815 return;
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;
823 #endif
824 d.disabled = true;
825 return;
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;
833 #endif
834 d.disabled = true;
835 return;
838 parse_state s;
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)));
842 if (s.found_service)
844 d.service_namespace = s.service_type;
845 if (!s.model.empty()) m_model = s.model;
847 else
849 // we didn't find the WAN IP connection, look for
850 // a PPP connection
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)));
854 if (s.found_service)
856 d.service_namespace = s.service_type;
857 if (!s.model.empty()) m_model = s.model;
859 else
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;
865 #endif
866 d.disabled = true;
867 return;
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;
875 #endif
877 d.control_url = s.control_url;
879 if (num_mappings() > 0) update_map(d, 0);
882 void upnp::disable(char const* msg)
884 m_disabled = true;
886 // kill all mappings
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;
891 i->protocol = none;
892 m_callback(i - m_mappings.begin(), 0, msg);
895 m_devices.clear();
896 error_code ec;
897 m_broadcast_timer.cancel(ec);
898 m_refresh_timer.cancel(ec);
899 m_socket.close();
902 namespace
904 struct error_code_parse_state
906 error_code_parse_state(): in_error_code(false), exit(false), error_code(-1) {}
907 bool in_error_code;
908 bool exit;
909 int error_code;
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);
922 state.exit = true;
927 namespace
929 struct error_code_t
931 int code;
932 char const* msg;
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;
972 #endif
973 d.disabled = true;
974 return;
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/">
982 // <s:Body>
983 // <s:Fault>
984 // <faultcode>s:Client</faultcode>
985 // <faultstring>UPnPError</faultstring>
986 // <detail>
987 // <UPnPErrorxmlns="urn:schemas-upnp-org:control-1-0">
988 // <errorCode>402</errorCode>
989 // <errorDescription>Invalid Args</errorDescription>
990 // </UPnPError>
991 // </detail>
992 // </s:Fault>
993 // </s:Body>
994 // </s:Envelope>
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;
1001 #endif
1002 next(d, mapping);
1003 return;
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;
1019 #endif
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;
1028 ++m.failcount;
1029 update_map(d, mapping);
1030 return;
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;
1040 ++m.failcount;
1041 update_map(d, mapping);
1042 return;
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;
1052 ++m.failcount;
1053 update_map(d, mapping);
1054 return;
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)
1064 << std::endl;
1065 #endif
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)
1078 error_code ec;
1079 m_refresh_timer.expires_at(m.expires, ec);
1080 m_refresh_timer.async_wait(bind(&upnp::on_expire, self(), _1));
1083 else
1085 m.expires = max_time();
1087 m.failcount = 0;
1090 next(d, mapping);
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;
1128 #endif
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;
1134 #endif
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;
1141 #endif
1143 else
1146 #ifdef TORRENT_UPNP_LOGGING
1147 m_log << time_now_string()
1148 << " <== unmap response: " << std::string(p.get_body().begin, p.get_body().end)
1149 << std::endl;
1150 #endif
1153 d.mapping[mapping].protocol = none;
1155 next(d, mapping);
1158 void upnp::on_expire(error_code const& e)
1160 if (e) return;
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())
1175 continue;
1177 if (d.mapping[m].expires < now)
1179 d.mapping[m].expires = max_time();
1180 update_map(d, m);
1182 else if (d.mapping[m].expires < next_expire)
1184 next_expire = d.mapping[m].expires;
1188 if (next_expire != max_time())
1190 error_code ec;
1191 m_refresh_timer.expires_at(next_expire, ec);
1192 m_refresh_timer.async_wait(bind(&upnp::on_expire, self(), _1));
1196 void upnp::close()
1198 mutex_t::scoped_lock l(m_mutex);
1200 error_code ec;
1201 m_refresh_timer.cancel(ec);
1202 m_broadcast_timer.cancel(ec);
1203 m_closing = true;
1204 m_socket.close();
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;
1219 continue;
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);