fixed bug in latest upnp change
[libtorrent-kjk.git] / src / upnp.cpp
blobf8402e1c440bc2d6a4c9f300ceddf89058220023
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/http_tracker_connection.hpp"
39 #include "libtorrent/xml_parse.hpp"
40 #include <boost/bind.hpp>
41 #include <boost/ref.hpp>
42 #include <asio/ip/host_name.hpp>
43 #include <asio/ip/multicast.hpp>
44 #include <boost/thread/mutex.hpp>
45 #include <cstdlib>
47 using boost::bind;
48 using namespace libtorrent;
50 address_v4 upnp::upnp_multicast_address;
51 udp::endpoint upnp::upnp_multicast_endpoint;
53 upnp::upnp(io_service& ios, address const& listen_interface
54 , std::string const& user_agent, portmap_callback_t const& cb)
55 : m_udp_local_port(0)
56 , m_tcp_local_port(0)
57 , m_user_agent(user_agent)
58 , m_callback(cb)
59 , m_retry_count(0)
60 , m_socket(ios)
61 , m_broadcast_timer(ios)
62 , m_refresh_timer(ios)
63 , m_strand(ios)
64 , m_disabled(false)
65 , m_closing(false)
67 // UPnP multicast address and port
68 upnp_multicast_address = address_v4::from_string("239.255.255.250");
69 upnp_multicast_endpoint = udp::endpoint(upnp_multicast_address, 1900);
71 #ifdef TORRENT_UPNP_LOGGING
72 m_log.open("upnp.log", std::ios::in | std::ios::out | std::ios::trunc);
73 #endif
74 rebind(listen_interface);
77 upnp::~upnp()
81 void upnp::rebind(address const& listen_interface) try
83 if (listen_interface.is_v4() && listen_interface != address_v4::from_string("0.0.0.0"))
85 m_local_ip = listen_interface.to_v4();
87 else
89 // make a best guess of the interface we're using and its IP
90 udp::resolver r(m_socket.io_service());
91 udp::resolver::iterator i = r.resolve(udp::resolver::query(asio::ip::host_name(), "0"));
92 for (;i != udp::resolver_iterator(); ++i)
94 if (i->endpoint().address().is_v4()) break;
97 if (i == udp::resolver_iterator())
99 throw std::runtime_error("local host name did not resolve to an "
100 "IPv4 address. disabling NAT-PMP");
103 m_local_ip = i->endpoint().address().to_v4();
106 #ifdef TORRENT_UPNP_LOGGING
107 m_log << time_now_string()
108 << " local ip: " << m_local_ip.to_string() << std::endl;
109 #endif
111 if ((m_local_ip.to_ulong() & 0xff000000) != 0x0a000000
112 && (m_local_ip.to_ulong() & 0xfff00000) != 0xac100000
113 && (m_local_ip.to_ulong() & 0xffff0000) != 0xc0a80000)
115 // the local address seems to be an external
116 // internet address. Assume it is not behind a NAT
117 throw std::runtime_error("local IP is not on a local network");
120 // the local interface hasn't changed
121 if (m_socket.is_open()
122 && m_socket.local_endpoint().address() == m_local_ip)
123 return;
125 m_socket.close();
127 using namespace asio::ip::multicast;
129 m_socket.open(udp::v4());
130 m_socket.set_option(datagram_socket::reuse_address(true));
131 m_socket.bind(udp::endpoint(m_local_ip, 0));
133 m_socket.set_option(join_group(upnp_multicast_address));
134 m_socket.set_option(outbound_interface(m_local_ip));
135 m_socket.set_option(hops(255));
136 m_disabled = false;
138 m_retry_count = 0;
139 discover_device();
141 catch (std::exception& e)
143 m_disabled = true;
144 std::stringstream msg;
145 msg << "UPnP portmapping disabled: " << e.what();
146 m_callback(0, 0, msg.str());
149 void upnp::discover_device()
151 const char msearch[] =
152 "M-SEARCH * HTTP/1.1\r\n"
153 "HOST: 239.255.255.250:1900\r\n"
154 "ST:upnp:rootdevice\r\n"
155 "MAN:\"ssdp:discover\"\r\n"
156 "MX:3\r\n"
157 "\r\n\r\n";
159 m_socket.async_receive_from(asio::buffer(m_receive_buffer
160 , sizeof(m_receive_buffer)), m_remote, m_strand.wrap(bind(
161 &upnp::on_reply, this, _1, _2)));
162 m_socket.send_to(asio::buffer(msearch, sizeof(msearch) - 1)
163 , upnp_multicast_endpoint);
165 ++m_retry_count;
166 m_broadcast_timer.expires_from_now(milliseconds(250 * m_retry_count));
167 m_broadcast_timer.async_wait(m_strand.wrap(bind(&upnp::resend_request
168 , this, _1)));
170 #ifdef TORRENT_UPNP_LOGGING
171 m_log << time_now_string()
172 << " ==> Broadcasting search for rootdevice" << std::endl;
173 #endif
176 void upnp::set_mappings(int tcp, int udp)
178 if (m_disabled) return;
179 if (udp != 0) m_udp_local_port = udp;
180 if (tcp != 0) m_tcp_local_port = tcp;
182 for (std::set<rootdevice>::iterator i = m_devices.begin()
183 , end(m_devices.end()); i != end; ++i)
185 rootdevice& d = const_cast<rootdevice&>(*i);
186 if (d.mapping[0].local_port != m_tcp_local_port)
188 if (d.mapping[0].external_port == 0)
189 d.mapping[0].external_port = m_tcp_local_port;
190 d.mapping[0].local_port = m_tcp_local_port;
191 d.mapping[0].need_update = true;
193 if (d.mapping[1].local_port != m_udp_local_port)
195 if (d.mapping[1].external_port == 0)
196 d.mapping[1].external_port = m_udp_local_port;
197 d.mapping[1].local_port = m_udp_local_port;
198 d.mapping[1].need_update = true;
200 if (d.mapping[0].need_update || d.mapping[1].need_update)
201 map_port(d, 0);
205 void upnp::resend_request(asio::error_code const& e)
207 if (e) return;
208 if (m_retry_count < 9
209 && (m_devices.empty() || m_retry_count < 4))
211 discover_device();
212 return;
215 if (m_devices.empty())
217 #ifdef TORRENT_UPNP_LOGGING
218 m_log << time_now_string()
219 << " *** Got no response in 9 retries. Giving up, "
220 "disabling UPnP." << std::endl;
221 #endif
222 m_disabled = true;
223 return;
226 for (std::set<rootdevice>::iterator i = m_devices.begin()
227 , end(m_devices.end()); i != end; ++i)
229 if (i->control_url.empty() && !i->upnp_connection)
231 // we don't have a WANIP or WANPPP url for this device,
232 // ask for it
233 rootdevice& d = const_cast<rootdevice&>(*i);
234 d.upnp_connection.reset(new http_connection(m_socket.io_service()
235 , m_strand.wrap(bind(&upnp::on_upnp_xml, this, _1, _2, boost::ref(d)))));
236 d.upnp_connection->get(d.url);
241 void upnp::on_reply(asio::error_code const& e
242 , std::size_t bytes_transferred)
244 using namespace libtorrent::detail;
245 if (e) return;
247 // since we're using udp, send the query 4 times
248 // just to make sure we find all devices
249 if (m_retry_count >= 4)
250 m_broadcast_timer.cancel();
252 // parse out the url for the device
255 the response looks like this:
257 HTTP/1.1 200 OK
258 ST:upnp:rootdevice
259 USN:uuid:000f-66d6-7296000099dc::upnp:rootdevice
260 Location: http://192.168.1.1:5431/dyndev/uuid:000f-66d6-7296000099dc
261 Server: Custom/1.0 UPnP/1.0 Proc/Ver
262 EXT:
263 Cache-Control:max-age=180
264 DATE: Fri, 02 Jan 1970 08:10:38 GMT
266 http_parser p;
269 p.incoming(buffer::const_interval(m_receive_buffer
270 , m_receive_buffer + bytes_transferred));
272 catch (std::exception& e)
274 #ifdef TORRENT_UPNP_LOGGING
275 m_log << time_now_string()
276 << " <== Rootdevice responded with incorrect HTTP packet: "
277 << e.what() << ". Ignoring device" << std::endl;
278 #endif
279 return;
282 if (p.status_code() != 200)
284 #ifdef TORRENT_UPNP_LOGGING
285 m_log << time_now_string()
286 << " <== Rootdevice responded with HTTP status: " << p.status_code()
287 << ". Ignoring device" << std::endl;
288 #endif
289 return;
292 if (!p.header_finished())
294 #ifdef TORRENT_UPNP_LOGGING
295 m_log << time_now_string()
296 << " <== Rootdevice responded with incomplete HTTP "
297 "packet. Ignoring device" << std::endl;
298 #endif
299 return;
302 std::string url = p.header<std::string>("location");
303 if (url.empty())
305 #ifdef TORRENT_UPNP_LOGGING
306 m_log << time_now_string()
307 << " <== Rootdevice response is missing a location header. "
308 "Ignoring device" << std::endl;
309 #endif
310 return;
313 rootdevice d;
314 d.url = url;
316 std::set<rootdevice>::iterator i = m_devices.find(d);
318 if (i == m_devices.end())
321 std::string protocol;
322 // we don't have this device in our list. Add it
323 boost::tie(protocol, d.hostname, d.port, d.path)
324 = parse_url_components(d.url);
326 if (protocol != "http")
328 #ifdef TORRENT_UPNP_LOGGING
329 m_log << time_now_string()
330 << " <== Rootdevice uses unsupported protocol: '" << protocol
331 << "'. Ignoring device" << std::endl;
332 #endif
333 return;
336 if (d.port == 0)
338 #ifdef TORRENT_UPNP_LOGGING
339 m_log << time_now_string()
340 << " <== Rootdevice responded with a url with port 0. "
341 "Ignoring device" << std::endl;
342 #endif
343 return;
345 #ifdef TORRENT_UPNP_LOGGING
346 m_log << time_now_string()
347 << " <== Found rootdevice: " << d.url << std::endl;
348 #endif
350 if (m_tcp_local_port != 0)
352 d.mapping[0].need_update = true;
353 d.mapping[0].local_port = m_tcp_local_port;
355 if (m_udp_local_port != 0)
357 d.mapping[1].need_update = true;
358 d.mapping[1].local_port = m_udp_local_port;
360 boost::tie(i, boost::tuples::ignore) = m_devices.insert(d);
364 void upnp::post(rootdevice& d, std::stringstream const& soap
365 , std::string const& soap_action)
367 std::stringstream header;
369 header << "POST " << d.control_url << " HTTP/1.1\r\n"
370 "Host: " << d.hostname << ":" << d.port << "\r\n"
371 "Content-Type: text/xml; charset=\"utf-8\"\r\n"
372 "Content-Length: " << soap.str().size() << "\r\n"
373 "Soapaction: \"" << d.service_namespace << "#" << soap_action << "\"\r\n\r\n" << soap.str();
375 d.upnp_connection->sendbuffer = header.str();
376 d.upnp_connection->start(d.hostname, boost::lexical_cast<std::string>(d.port)
377 , seconds(10));
380 void upnp::map_port(rootdevice& d, int i)
382 if (d.upnp_connection) return;
384 if (!d.mapping[i].need_update)
386 if (i < num_mappings - 1)
387 map_port(d, i + 1);
388 return;
390 d.mapping[i].need_update = false;
391 assert(!d.upnp_connection);
392 d.upnp_connection.reset(new http_connection(m_socket.io_service()
393 , m_strand.wrap(bind(&upnp::on_upnp_map_response, this, _1, _2
394 , boost::ref(d), i))));
396 std::string soap_action = "AddPortMapping";
398 std::stringstream soap;
400 soap << "<?xml version=\"1.0\"?>\n"
401 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
402 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
403 "<s:Body><u:" << soap_action << " xmlns:u=\"" << d.service_namespace << "\">";
405 soap << "<NewRemoteHost></NewRemoteHost>"
406 "<NewExternalPort>" << d.mapping[i].external_port << "</NewExternalPort>"
407 "<NewProtocol>" << (d.mapping[i].protocol ? "UDP" : "TCP") << "</NewProtocol>"
408 "<NewInternalPort>" << d.mapping[i].local_port << "</NewInternalPort>"
409 "<NewInternalClient>" << m_local_ip.to_string() << "</NewInternalClient>"
410 "<NewEnabled>1</NewEnabled>"
411 "<NewPortMappingDescription>" << m_user_agent << "</NewPortMappingDescription>"
412 "<NewLeaseDuration>" << d.lease_duration << "</NewLeaseDuration>";
413 soap << "</u:" << soap_action << "></s:Body></s:Envelope>";
415 post(d, soap, soap_action);
416 #ifdef TORRENT_UPNP_LOGGING
417 m_log << time_now_string()
418 << " ==> AddPortMapping: " << soap.str() << std::endl;
419 #endif
423 // requires the mutex to be locked
424 void upnp::unmap_port(rootdevice& d, int i)
426 if (d.mapping[i].external_port == 0)
428 if (i < num_mappings - 1)
430 unmap_port(d, i + 1);
432 else
434 m_devices.erase(d);
436 return;
438 d.upnp_connection.reset(new http_connection(m_socket.io_service()
439 , m_strand.wrap(bind(&upnp::on_upnp_unmap_response, this, _1, _2
440 , boost::ref(d), i))));
442 std::string soap_action = "DeletePortMapping";
444 std::stringstream soap;
446 soap << "<?xml version=\"1.0\"?>\n"
447 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
448 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
449 "<s:Body><u:" << soap_action << " xmlns:u=\"" << d.service_namespace << "\">";
451 soap << "<NewRemoteHost></NewRemoteHost>"
452 "<NewExternalPort>" << d.mapping[i].external_port << "</NewExternalPort>"
453 "<NewProtocol>" << (d.mapping[i].protocol ? "UDP" : "TCP") << "</NewProtocol>";
454 soap << "</u:" << soap_action << "></s:Body></s:Envelope>";
456 post(d, soap, soap_action);
457 #ifdef TORRENT_UPNP_LOGGING
458 m_log << time_now_string()
459 << " ==> DeletePortMapping: " << soap.str() << std::endl;
460 #endif
463 namespace
465 struct parse_state
467 parse_state(): found_service(false), exit(false) {}
468 void reset(char const* st)
470 found_service = false;
471 exit = false;
472 service_type = st;
474 bool found_service;
475 bool exit;
476 std::string top_tag;
477 std::string control_url;
478 char const* service_type;
481 void find_control_url(int type, char const* string, parse_state& state)
483 if (state.exit) return;
485 if (type == xml_start_tag)
487 if ((!state.top_tag.empty() && state.top_tag == "service")
488 || !strcmp(string, "service"))
490 state.top_tag = string;
493 else if (type == xml_end_tag)
495 if (!strcmp(string, "service"))
497 state.top_tag.clear();
498 if (state.found_service) state.exit = true;
500 else if (!state.top_tag.empty() && state.top_tag != "service")
501 state.top_tag = "service";
503 else if (type == xml_string)
505 if (state.top_tag == "serviceType")
507 if (!strcmp(string, state.service_type))
508 state.found_service = true;
510 else if (state.top_tag == "controlURL")
512 state.control_url = string;
513 if (state.found_service) state.exit = true;
520 void upnp::on_upnp_xml(asio::error_code const& e
521 , libtorrent::http_parser const& p, rootdevice& d)
523 if (d.upnp_connection)
525 d.upnp_connection->close();
526 d.upnp_connection.reset();
529 if (e)
531 #ifdef TORRENT_UPNP_LOGGING
532 m_log << time_now_string()
533 << " <== error while fetching control url: " << e.message() << std::endl;
534 #endif
535 return;
538 if (!p.header_finished())
540 #ifdef TORRENT_UPNP_LOGGING
541 m_log << time_now_string()
542 << " <== incomplete http message" << std::endl;
543 #endif
544 return;
547 parse_state s;
548 s.reset("urn:schemas-upnp-org:service:WANIPConnection:1");
549 xml_parse((char*)p.get_body().begin, (char*)p.get_body().end
550 , m_strand.wrap(bind(&find_control_url, _1, _2, boost::ref(s))));
551 d.service_namespace = "urn:schemas-upnp-org:service:WANIPConnection:1";
552 if (!s.found_service)
554 // we didn't find the WAN IP connection, look for
555 // a PPP IP connection
556 s.reset("urn:schemas-upnp-org:service:PPPIPConnection:1");
557 xml_parse((char*)p.get_body().begin, (char*)p.get_body().end
558 , m_strand.wrap(bind(&find_control_url, _1, _2, boost::ref(s))));
559 d.service_namespace = "urn:schemas-upnp-org:service:WANPPPConnection:1";
562 #ifdef TORRENT_UPNP_LOGGING
563 m_log << time_now_string()
564 << " <== Rootdevice response, found control URL: " << s.control_url << std::endl;
565 #endif
567 d.control_url = s.control_url;
569 map_port(d, 0);
572 namespace
574 struct error_code_parse_state
576 error_code_parse_state(): in_error_code(false), exit(false), error_code(-1) {}
577 bool in_error_code;
578 bool exit;
579 int error_code;
582 void find_error_code(int type, char const* string, error_code_parse_state& state)
584 if (state.exit) return;
585 if (type == xml_start_tag && !strcmp("errorCode", string))
587 state.in_error_code = true;
589 else if (type == xml_string && state.in_error_code)
591 state.error_code = std::atoi(string);
592 state.exit = true;
597 void upnp::on_upnp_map_response(asio::error_code const& e
598 , libtorrent::http_parser const& p, rootdevice& d, int mapping)
600 if (d.upnp_connection)
602 d.upnp_connection->close();
603 d.upnp_connection.reset();
606 if (e)
608 #ifdef TORRENT_UPNP_LOGGING
609 m_log << time_now_string()
610 << " <== error while adding portmap: " << e.message() << std::endl;
611 #endif
612 m_devices.erase(d);
613 return;
616 if (m_closing) return;
618 // error code response may look like this:
619 // <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
620 // s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
621 // <s:Body>
622 // <s:Fault>
623 // <faultcode>s:Client</faultcode>
624 // <faultstring>UPnPError</faultstring>
625 // <detail>
626 // <UPnPErrorxmlns="urn:schemas-upnp-org:control-1-0">
627 // <errorCode>402</errorCode>
628 // <errorDescription>Invalid Args</errorDescription>
629 // </UPnPError>
630 // </detail>
631 // </s:Fault>
632 // </s:Body>
633 // </s:Envelope>
635 if (!p.header_finished())
637 #ifdef TORRENT_UPNP_LOGGING
638 m_log << time_now_string()
639 << " <== incomplete http message" << std::endl;
640 #endif
641 m_devices.erase(d);
642 return;
645 error_code_parse_state s;
646 xml_parse((char*)p.get_body().begin, (char*)p.get_body().end
647 , m_strand.wrap(bind(&find_error_code, _1, _2, boost::ref(s))));
649 #ifdef TORRENT_UPNP_LOGGING
650 if (s.error_code != -1)
652 m_log << time_now_string()
653 << " <== got error message: " << s.error_code << std::endl;
655 #endif
657 if (s.error_code == 725)
659 // only permanent leases supported
660 d.lease_duration = 0;
661 d.mapping[mapping].need_update = true;
662 map_port(d, mapping);
663 return;
665 else if (s.error_code == 718)
667 // conflict in mapping, try next external port
668 ++d.mapping[mapping].external_port;
669 d.mapping[mapping].need_update = true;
670 map_port(d, mapping);
671 return;
673 else if (s.error_code != -1)
675 std::map<int, std::string> error_codes;
676 error_codes[402] = "Invalid Arguments";
677 error_codes[501] = "Action Failed";
678 error_codes[714] = "The specified value does not exist in the array";
679 error_codes[715] = "The source IP address cannot be wild-carded";
680 error_codes[716] = "The external port cannot be wild-carded";
681 error_codes[718] = "The port mapping entry specified conflicts with "
682 "a mapping assigned previously to another client";
683 error_codes[724] = "Internal and External port values must be the same";
684 error_codes[725] = "The NAT implementation only supports permanent "
685 "lease times on port mappings";
686 error_codes[726] = "RemoteHost must be a wildcard and cannot be a "
687 "specific IP address or DNS name";
688 error_codes[727] = "ExternalPort must be a wildcard and cannot be a specific port ";
689 m_callback(0, 0, "UPnP mapping error " + boost::lexical_cast<std::string>(s.error_code)
690 + ": " + error_codes[s.error_code]);
693 #ifdef TORRENT_UPNP_LOGGING
694 m_log << time_now_string()
695 << " <== map response: " << std::string(p.get_body().begin, p.get_body().end)
696 << std::endl;
697 #endif
699 if (s.error_code == -1)
701 int tcp = 0;
702 int udp = 0;
704 if (mapping == 0)
705 tcp = d.mapping[mapping].external_port;
706 else
707 udp = d.mapping[mapping].external_port;
709 m_callback(tcp, udp, "");
710 if (d.lease_duration > 0)
712 d.mapping[mapping].expires = time_now()
713 + seconds(int(d.lease_duration * 0.75f));
714 ptime next_expire = m_refresh_timer.expires_at();
715 if (next_expire < time_now()
716 || next_expire > d.mapping[mapping].expires)
718 m_refresh_timer.expires_at(d.mapping[mapping].expires);
719 m_refresh_timer.async_wait(m_strand.wrap(bind(&upnp::on_expire, this, _1)));
722 else
724 d.mapping[mapping].expires = max_time();
728 for (int i = 0; i < num_mappings; ++i)
730 if (d.mapping[i].need_update)
732 map_port(d, i);
733 return;
738 void upnp::on_upnp_unmap_response(asio::error_code const& e
739 , libtorrent::http_parser const& p, rootdevice& d, int mapping)
741 if (d.upnp_connection)
743 d.upnp_connection->close();
744 d.upnp_connection.reset();
747 if (e)
749 #ifdef TORRENT_UPNP_LOGGING
750 m_log << time_now_string()
751 << " <== error while deleting portmap: " << e.message() << std::endl;
752 #endif
755 if (!p.header_finished())
757 #ifdef TORRENT_UPNP_LOGGING
758 m_log << time_now_string()
759 << " <== incomplete http message" << std::endl;
760 #endif
761 return;
764 #ifdef TORRENT_UPNP_LOGGING
765 m_log << time_now_string()
766 << " <== unmap response: " << std::string(p.get_body().begin, p.get_body().end)
767 << std::endl;
768 #endif
770 // ignore errors and continue with the next mapping for this device
771 if (mapping < num_mappings - 1)
773 unmap_port(d, mapping + 1);
774 return;
777 // the main thread is likely to be waiting for
778 // all the unmap operations to complete
779 m_devices.erase(d);
782 void upnp::on_expire(asio::error_code const& e)
784 if (e) return;
786 ptime now = time_now();
787 ptime next_expire = max_time();
789 for (std::set<rootdevice>::iterator i = m_devices.begin()
790 , end(m_devices.end()); i != end; ++i)
792 rootdevice& d = const_cast<rootdevice&>(*i);
793 for (int m = 0; m < num_mappings; ++m)
795 if (d.mapping[m].expires != max_time())
796 continue;
798 if (d.mapping[m].expires < now)
800 d.mapping[m].expires = max_time();
801 map_port(d, m);
803 else if (d.mapping[m].expires < next_expire)
805 next_expire = d.mapping[m].expires;
809 if (next_expire != max_time())
811 m_refresh_timer.expires_at(next_expire);
812 m_refresh_timer.async_wait(m_strand.wrap(bind(&upnp::on_expire, this, _1)));
816 void upnp::close()
818 m_refresh_timer.cancel();
819 m_broadcast_timer.cancel();
820 m_closing = true;
821 m_socket.close();
823 if (m_disabled)
825 m_devices.clear();
826 return;
829 for (std::set<rootdevice>::iterator i = m_devices.begin()
830 , end(m_devices.end()); i != end;)
832 rootdevice& d = const_cast<rootdevice&>(*i);
833 if (d.control_url.empty())
835 m_devices.erase(i++);
836 continue;
838 ++i;
839 unmap_port(d, 0);