2007-03-27 09:04:31 +02:00
|
|
|
/*
|
|
|
|
|
|
|
|
Copyright (c) 2007, Arvid Norberg
|
|
|
|
All rights reserved.
|
|
|
|
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
|
|
modification, are permitted provided that the following conditions
|
|
|
|
are met:
|
|
|
|
|
|
|
|
* Redistributions of source code must retain the above copyright
|
|
|
|
notice, this list of conditions and the following disclaimer.
|
|
|
|
* Redistributions in binary form must reproduce the above copyright
|
|
|
|
notice, this list of conditions and the following disclaimer in
|
|
|
|
the documentation and/or other materials provided with the distribution.
|
|
|
|
* Neither the name of the author nor the names of its
|
|
|
|
contributors may be used to endorse or promote products derived
|
|
|
|
from this software without specific prior written permission.
|
|
|
|
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
|
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
|
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
|
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
|
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
|
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
|
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
|
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
|
POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "libtorrent/pch.hpp"
|
|
|
|
|
2007-04-05 00:27:36 +02:00
|
|
|
#include "libtorrent/socket.hpp"
|
2007-03-27 09:04:31 +02:00
|
|
|
#include "libtorrent/upnp.hpp"
|
|
|
|
#include "libtorrent/io.hpp"
|
|
|
|
#include "libtorrent/http_tracker_connection.hpp"
|
|
|
|
#include "libtorrent/xml_parse.hpp"
|
2007-05-05 02:29:33 +02:00
|
|
|
#include "libtorrent/connection_queue.hpp"
|
2008-01-07 06:48:28 +01:00
|
|
|
#include "libtorrent/enum_net.hpp"
|
2007-05-05 02:29:33 +02:00
|
|
|
|
2007-03-27 09:04:31 +02:00
|
|
|
#include <boost/bind.hpp>
|
|
|
|
#include <boost/ref.hpp>
|
|
|
|
#include <asio/ip/host_name.hpp>
|
|
|
|
#include <asio/ip/multicast.hpp>
|
2007-04-01 01:23:30 +02:00
|
|
|
#include <boost/thread/mutex.hpp>
|
2007-03-27 09:04:31 +02:00
|
|
|
#include <cstdlib>
|
|
|
|
|
2007-05-23 08:23:22 +02:00
|
|
|
#if (defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)) && !defined(TORRENT_UPNP_LOGGING)
|
2007-05-17 04:54:13 +02:00
|
|
|
#define TORRENT_UPNP_LOGGING
|
|
|
|
#endif
|
|
|
|
|
2007-03-27 09:04:31 +02:00
|
|
|
using boost::bind;
|
|
|
|
using namespace libtorrent;
|
|
|
|
|
2007-05-16 23:36:27 +02:00
|
|
|
namespace libtorrent
|
|
|
|
{
|
2007-09-10 01:52:34 +02:00
|
|
|
bool is_local(address const& a);
|
2007-10-01 19:21:19 +02:00
|
|
|
address guess_local_address(asio::io_service&);
|
2007-05-16 23:36:27 +02:00
|
|
|
}
|
|
|
|
|
2007-05-05 02:29:33 +02:00
|
|
|
upnp::upnp(io_service& ios, connection_queue& cc
|
|
|
|
, address const& listen_interface, std::string const& user_agent
|
2008-01-07 06:48:28 +01:00
|
|
|
, portmap_callback_t const& cb, bool ignore_nonrouters)
|
2007-04-01 03:18:10 +02:00
|
|
|
: m_udp_local_port(0)
|
|
|
|
, m_tcp_local_port(0)
|
|
|
|
, m_user_agent(user_agent)
|
2007-03-27 09:04:31 +02:00
|
|
|
, m_callback(cb)
|
|
|
|
, m_retry_count(0)
|
2007-09-10 01:52:34 +02:00
|
|
|
, m_io_service(ios)
|
|
|
|
, m_socket(ios, udp::endpoint(address_v4::from_string("239.255.255.250"), 1900)
|
2008-01-08 06:47:43 +01:00
|
|
|
, bind(&upnp::on_reply, self(), _1, _2, _3), false)
|
2007-03-27 09:04:31 +02:00
|
|
|
, m_broadcast_timer(ios)
|
|
|
|
, m_refresh_timer(ios)
|
|
|
|
, m_disabled(false)
|
2007-04-01 01:23:30 +02:00
|
|
|
, m_closing(false)
|
2008-01-11 07:49:37 +01:00
|
|
|
, m_ignore_outside_network(ignore_nonrouters)
|
2007-05-05 02:29:33 +02:00
|
|
|
, m_cc(cc)
|
2007-03-27 09:04:31 +02:00
|
|
|
{
|
2007-05-04 07:34:03 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
2007-03-27 09:04:31 +02:00
|
|
|
m_log.open("upnp.log", std::ios::in | std::ios::out | std::ios::trunc);
|
|
|
|
#endif
|
2007-09-10 01:52:34 +02:00
|
|
|
m_retry_count = 0;
|
2007-03-27 09:04:31 +02:00
|
|
|
}
|
|
|
|
|
2007-04-01 01:23:30 +02:00
|
|
|
upnp::~upnp()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2007-12-30 00:14:59 +01:00
|
|
|
void upnp::discover_device()
|
2007-03-27 09:04:31 +02:00
|
|
|
{
|
|
|
|
const char msearch[] =
|
|
|
|
"M-SEARCH * HTTP/1.1\r\n"
|
|
|
|
"HOST: 239.255.255.250:1900\r\n"
|
|
|
|
"ST:upnp:rootdevice\r\n"
|
|
|
|
"MAN:\"ssdp:discover\"\r\n"
|
|
|
|
"MX:3\r\n"
|
|
|
|
"\r\n\r\n";
|
|
|
|
|
2007-05-14 09:31:01 +02:00
|
|
|
asio::error_code ec;
|
|
|
|
#ifdef TORRENT_DEBUG_UPNP
|
|
|
|
// simulate packet loss
|
|
|
|
if (m_retry_count & 1)
|
|
|
|
#endif
|
2007-09-10 01:52:34 +02:00
|
|
|
m_socket.send(msearch, sizeof(msearch) - 1, ec);
|
2007-05-14 09:31:01 +02:00
|
|
|
|
|
|
|
if (ec)
|
|
|
|
{
|
2007-09-10 01:52:34 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
|
|
|
m_log << time_now_string()
|
|
|
|
<< " ==> Broadcast FAILED: " << ec.message() << std::endl
|
|
|
|
<< "aborting" << std::endl;
|
|
|
|
#endif
|
2007-05-14 09:31:01 +02:00
|
|
|
disable();
|
|
|
|
return;
|
|
|
|
}
|
2007-03-27 09:04:31 +02:00
|
|
|
|
|
|
|
++m_retry_count;
|
2007-12-30 00:14:59 +01:00
|
|
|
m_broadcast_timer.expires_from_now(milliseconds(250 * m_retry_count), ec);
|
2008-01-08 06:47:43 +01:00
|
|
|
m_broadcast_timer.async_wait(bind(&upnp::resend_request
|
|
|
|
, self(), _1));
|
2007-03-27 09:04:31 +02:00
|
|
|
|
2007-05-04 07:34:03 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
2007-04-10 09:52:58 +02:00
|
|
|
m_log << time_now_string()
|
2007-04-01 03:18:10 +02:00
|
|
|
<< " ==> Broadcasting search for rootdevice" << std::endl;
|
2007-03-27 09:04:31 +02:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void upnp::set_mappings(int tcp, int udp)
|
|
|
|
{
|
2007-05-14 10:56:46 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
|
|
|
m_log << time_now_string()
|
|
|
|
<< " *** set mappings " << tcp << " " << udp;
|
|
|
|
if (m_disabled) m_log << " DISABLED";
|
|
|
|
m_log << std::endl;
|
|
|
|
#endif
|
|
|
|
|
2007-03-27 09:04:31 +02:00
|
|
|
if (m_disabled) return;
|
2007-04-02 21:17:08 +02:00
|
|
|
if (udp != 0) m_udp_local_port = udp;
|
|
|
|
if (tcp != 0) m_tcp_local_port = tcp;
|
2007-04-01 03:18:10 +02:00
|
|
|
|
|
|
|
for (std::set<rootdevice>::iterator i = m_devices.begin()
|
|
|
|
, end(m_devices.end()); i != end; ++i)
|
|
|
|
{
|
|
|
|
rootdevice& d = const_cast<rootdevice&>(*i);
|
2007-10-26 02:47:30 +02:00
|
|
|
TORRENT_ASSERT(d.magic == 1337);
|
2007-04-01 03:18:10 +02:00
|
|
|
if (d.mapping[0].local_port != m_tcp_local_port)
|
|
|
|
{
|
|
|
|
if (d.mapping[0].external_port == 0)
|
|
|
|
d.mapping[0].external_port = m_tcp_local_port;
|
|
|
|
d.mapping[0].local_port = m_tcp_local_port;
|
|
|
|
d.mapping[0].need_update = true;
|
|
|
|
}
|
|
|
|
if (d.mapping[1].local_port != m_udp_local_port)
|
|
|
|
{
|
|
|
|
if (d.mapping[1].external_port == 0)
|
2007-04-01 17:39:08 +02:00
|
|
|
d.mapping[1].external_port = m_udp_local_port;
|
|
|
|
d.mapping[1].local_port = m_udp_local_port;
|
2007-04-01 03:18:10 +02:00
|
|
|
d.mapping[1].need_update = true;
|
|
|
|
}
|
2007-05-08 13:10:05 +02:00
|
|
|
if (d.service_namespace
|
|
|
|
&& (d.mapping[0].need_update || d.mapping[1].need_update))
|
2007-04-01 03:18:10 +02:00
|
|
|
map_port(d, 0);
|
|
|
|
}
|
2007-03-27 09:04:31 +02:00
|
|
|
}
|
|
|
|
|
2007-05-14 09:31:01 +02:00
|
|
|
void upnp::resend_request(asio::error_code const& e)
|
2007-03-27 09:04:31 +02:00
|
|
|
{
|
|
|
|
if (e) return;
|
2007-05-04 07:34:03 +02:00
|
|
|
if (m_retry_count < 9
|
|
|
|
&& (m_devices.empty() || m_retry_count < 4))
|
2007-03-27 09:04:31 +02:00
|
|
|
{
|
2007-05-04 07:34:03 +02:00
|
|
|
discover_device();
|
2007-05-04 19:39:44 +02:00
|
|
|
return;
|
2007-05-04 07:34:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (m_devices.empty())
|
|
|
|
{
|
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
2007-04-10 09:52:58 +02:00
|
|
|
m_log << time_now_string()
|
2007-03-27 09:04:31 +02:00
|
|
|
<< " *** Got no response in 9 retries. Giving up, "
|
|
|
|
"disabling UPnP." << std::endl;
|
|
|
|
#endif
|
2007-05-14 09:31:01 +02:00
|
|
|
disable();
|
2007-03-27 09:04:31 +02:00
|
|
|
return;
|
|
|
|
}
|
2007-05-04 07:34:03 +02:00
|
|
|
|
|
|
|
for (std::set<rootdevice>::iterator i = m_devices.begin()
|
|
|
|
, end(m_devices.end()); i != end; ++i)
|
|
|
|
{
|
2007-05-14 09:31:01 +02:00
|
|
|
if (i->control_url.empty() && !i->upnp_connection && !i->disabled)
|
2007-05-04 07:34:03 +02:00
|
|
|
{
|
|
|
|
// we don't have a WANIP or WANPPP url for this device,
|
|
|
|
// ask for it
|
|
|
|
rootdevice& d = const_cast<rootdevice&>(*i);
|
2007-10-26 02:47:30 +02:00
|
|
|
TORRENT_ASSERT(d.magic == 1337);
|
2007-05-14 09:31:01 +02:00
|
|
|
try
|
|
|
|
{
|
2007-10-26 02:47:30 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
|
|
|
m_log << time_now_string()
|
|
|
|
<< " ==> connecting to " << d.url << std::endl;
|
|
|
|
#endif
|
2007-09-10 01:52:34 +02:00
|
|
|
d.upnp_connection.reset(new http_connection(m_io_service
|
2008-01-08 06:47:43 +01:00
|
|
|
, m_cc, bind(&upnp::on_upnp_xml, self(), _1, _2
|
|
|
|
, boost::ref(d))));
|
2008-03-12 08:44:27 +01:00
|
|
|
d.upnp_connection->get(d.url, seconds(30), 1);
|
2007-05-14 09:31:01 +02:00
|
|
|
}
|
|
|
|
catch (std::exception& e)
|
|
|
|
{
|
2008-04-05 23:18:27 +02:00
|
|
|
(void)e;
|
2007-05-14 09:31:01 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
|
|
|
m_log << time_now_string()
|
|
|
|
<< " *** Connection failed to: " << d.url
|
|
|
|
<< " " << e.what() << std::endl;
|
|
|
|
#endif
|
|
|
|
d.disabled = true;
|
|
|
|
}
|
2007-05-04 07:34:03 +02:00
|
|
|
}
|
|
|
|
}
|
2007-03-27 09:04:31 +02:00
|
|
|
}
|
|
|
|
|
2007-09-10 01:52:34 +02:00
|
|
|
void upnp::on_reply(udp::endpoint const& from, char* buffer
|
2007-05-14 09:31:01 +02:00
|
|
|
, std::size_t bytes_transferred)
|
2007-03-27 09:04:31 +02:00
|
|
|
{
|
|
|
|
using namespace libtorrent::detail;
|
|
|
|
|
|
|
|
// parse out the url for the device
|
|
|
|
|
|
|
|
/*
|
|
|
|
the response looks like this:
|
|
|
|
|
|
|
|
HTTP/1.1 200 OK
|
|
|
|
ST:upnp:rootdevice
|
|
|
|
USN:uuid:000f-66d6-7296000099dc::upnp:rootdevice
|
|
|
|
Location: http://192.168.1.1:5431/dyndev/uuid:000f-66d6-7296000099dc
|
|
|
|
Server: Custom/1.0 UPnP/1.0 Proc/Ver
|
|
|
|
EXT:
|
|
|
|
Cache-Control:max-age=180
|
|
|
|
DATE: Fri, 02 Jan 1970 08:10:38 GMT
|
2007-09-21 01:25:40 +02:00
|
|
|
|
|
|
|
a notification looks like this:
|
|
|
|
|
|
|
|
NOTIFY * HTTP/1.1
|
|
|
|
Host:239.255.255.250:1900
|
|
|
|
NT:urn:schemas-upnp-org:device:MediaServer:1
|
|
|
|
NTS:ssdp:alive
|
|
|
|
Location:http://10.0.3.169:2869/upnphost/udhisapi.dll?content=uuid:c17f0c32-d19b-4938-ae94-65f945c3a26e
|
|
|
|
USN:uuid:c17f0c32-d19b-4938-ae94-65f945c3a26e::urn:schemas-upnp-org:device:MediaServer:1
|
|
|
|
Cache-Control:max-age=900
|
|
|
|
Server:Microsoft-Windows-NT/5.1 UPnP/1.0 UPnP-Device-Host/1.0
|
|
|
|
|
2007-03-27 09:04:31 +02:00
|
|
|
*/
|
2008-01-11 07:49:37 +01:00
|
|
|
asio::error_code ec;
|
|
|
|
if (m_ignore_outside_network && !in_local_network(m_io_service, from.address(), ec))
|
2008-01-07 06:48:28 +01:00
|
|
|
{
|
|
|
|
// this upnp device is filtered because it's not in the
|
|
|
|
// list of configured routers
|
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
2008-02-19 19:50:04 +01:00
|
|
|
if (ec)
|
|
|
|
{
|
|
|
|
m_log << time_now_string() << " <== (" << from << ") error: "
|
|
|
|
<< ec.message() << std::endl;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
std::vector<ip_interface> const& net = enum_net_interfaces(m_io_service, ec);
|
|
|
|
m_log << time_now_string() << " <== (" << from << ") UPnP device "
|
|
|
|
"ignored because it's not on our network ";
|
|
|
|
for (std::vector<ip_interface>::const_iterator i = net.begin()
|
|
|
|
, end(net.end()); i != end; ++i)
|
|
|
|
{
|
|
|
|
m_log << "(" << i->interface_address << ", " << i->netmask << ") ";
|
|
|
|
}
|
|
|
|
m_log << std::endl;
|
|
|
|
}
|
2008-01-07 06:48:28 +01:00
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2007-03-27 09:04:31 +02:00
|
|
|
http_parser p;
|
2007-12-29 19:24:50 +01:00
|
|
|
bool error = false;
|
|
|
|
p.incoming(buffer::const_interval(buffer
|
|
|
|
, buffer + bytes_transferred), error);
|
|
|
|
if (error)
|
2007-03-27 09:04:31 +02:00
|
|
|
{
|
2007-05-04 07:34:03 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
2007-12-29 19:24:50 +01:00
|
|
|
m_log << time_now_string() << " <== (" << from << ") Rootdevice "
|
|
|
|
"responded with incorrect HTTP packet. Ignoring device" << std::endl;
|
2007-03-27 09:04:31 +02:00
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2007-09-21 01:25:40 +02:00
|
|
|
if (p.status_code() != 200 && p.method() != "notify")
|
2007-03-27 09:04:31 +02:00
|
|
|
{
|
2007-05-04 07:34:03 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
2007-09-21 01:25:40 +02:00
|
|
|
if (p.method().empty())
|
|
|
|
m_log << time_now_string()
|
2007-10-26 02:47:30 +02:00
|
|
|
<< " <== (" << from << ") Device responded with HTTP status: " << p.status_code()
|
2007-09-21 01:25:40 +02:00
|
|
|
<< ". Ignoring device" << std::endl;
|
|
|
|
else
|
|
|
|
m_log << time_now_string()
|
2007-10-26 02:47:30 +02:00
|
|
|
<< " <== (" << from << ") Device with HTTP method: " << p.method()
|
2007-09-21 01:25:40 +02:00
|
|
|
<< ". Ignoring device" << std::endl;
|
2007-03-27 09:04:31 +02:00
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!p.header_finished())
|
|
|
|
{
|
2007-05-04 07:34:03 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
2007-04-10 09:52:58 +02:00
|
|
|
m_log << time_now_string()
|
2007-10-26 02:47:30 +02:00
|
|
|
<< " <== (" << from << ") Rootdevice responded with incomplete HTTP "
|
2007-03-27 09:04:31 +02:00
|
|
|
"packet. Ignoring device" << std::endl;
|
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2007-09-25 05:14:05 +02:00
|
|
|
std::string url = p.header("location");
|
2007-03-27 09:04:31 +02:00
|
|
|
if (url.empty())
|
|
|
|
{
|
2007-05-04 07:34:03 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
2007-04-10 09:52:58 +02:00
|
|
|
m_log << time_now_string()
|
2007-10-26 02:47:30 +02:00
|
|
|
<< " <== (" << from << ") Rootdevice response is missing a location header. "
|
2007-03-27 09:04:31 +02:00
|
|
|
"Ignoring device" << std::endl;
|
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
rootdevice d;
|
|
|
|
d.url = url;
|
2007-04-01 01:23:30 +02:00
|
|
|
|
2007-03-27 09:04:31 +02:00
|
|
|
std::set<rootdevice>::iterator i = m_devices.find(d);
|
|
|
|
|
|
|
|
if (i == m_devices.end())
|
|
|
|
{
|
|
|
|
|
|
|
|
std::string protocol;
|
2007-05-22 22:44:18 +02:00
|
|
|
std::string auth;
|
2007-03-27 09:04:31 +02:00
|
|
|
// we don't have this device in our list. Add it
|
2007-05-22 22:44:18 +02:00
|
|
|
boost::tie(protocol, auth, d.hostname, d.port, d.path)
|
2007-03-27 09:04:31 +02:00
|
|
|
= parse_url_components(d.url);
|
|
|
|
|
2007-05-22 22:44:18 +02:00
|
|
|
// ignore the auth here. It will be re-parsed
|
|
|
|
// by the http connection later
|
|
|
|
|
2007-03-27 09:04:31 +02:00
|
|
|
if (protocol != "http")
|
|
|
|
{
|
2007-05-04 07:34:03 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
2007-04-10 09:52:58 +02:00
|
|
|
m_log << time_now_string()
|
2007-10-26 02:47:30 +02:00
|
|
|
<< " <== (" << from << ") Rootdevice uses unsupported protocol: '" << protocol
|
2007-03-27 09:04:31 +02:00
|
|
|
<< "'. Ignoring device" << std::endl;
|
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (d.port == 0)
|
|
|
|
{
|
2007-05-04 07:34:03 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
2007-04-10 09:52:58 +02:00
|
|
|
m_log << time_now_string()
|
2007-10-26 02:47:30 +02:00
|
|
|
<< " <== (" << from << ") Rootdevice responded with a url with port 0. "
|
2007-03-27 09:04:31 +02:00
|
|
|
"Ignoring device" << std::endl;
|
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
2007-05-04 07:34:03 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
2007-04-10 09:52:58 +02:00
|
|
|
m_log << time_now_string()
|
2007-10-26 02:47:30 +02:00
|
|
|
<< " <== (" << from << ") Found rootdevice: " << d.url
|
|
|
|
<< " total: " << m_devices.size() << std::endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (m_devices.size() >= 50)
|
|
|
|
{
|
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
|
|
|
m_log << time_now_string()
|
|
|
|
<< " <== (" << from << ") Too many devices (" << m_devices.size() << "), "
|
|
|
|
"ignoring: " << d.url << std::endl;
|
2007-03-27 09:04:31 +02:00
|
|
|
#endif
|
2007-10-26 02:47:30 +02:00
|
|
|
return;
|
|
|
|
}
|
2007-03-27 09:04:31 +02:00
|
|
|
|
2007-04-01 03:18:10 +02:00
|
|
|
if (m_tcp_local_port != 0)
|
|
|
|
{
|
|
|
|
d.mapping[0].need_update = true;
|
|
|
|
d.mapping[0].local_port = m_tcp_local_port;
|
2007-09-12 23:08:29 +02:00
|
|
|
if (d.mapping[0].external_port == 0)
|
|
|
|
d.mapping[0].external_port = d.mapping[0].local_port;
|
2007-05-14 10:56:46 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
|
|
|
m_log << time_now_string() << " *** Mapping 0 will be updated" << std::endl;
|
|
|
|
#endif
|
2007-04-01 03:18:10 +02:00
|
|
|
}
|
|
|
|
if (m_udp_local_port != 0)
|
|
|
|
{
|
|
|
|
d.mapping[1].need_update = true;
|
|
|
|
d.mapping[1].local_port = m_udp_local_port;
|
2007-09-12 23:08:29 +02:00
|
|
|
if (d.mapping[1].external_port == 0)
|
|
|
|
d.mapping[1].external_port = d.mapping[1].local_port;
|
2007-05-14 10:56:46 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
|
|
|
m_log << time_now_string() << " *** Mapping 1 will be updated" << std::endl;
|
|
|
|
#endif
|
2007-04-01 03:18:10 +02:00
|
|
|
}
|
2007-03-27 09:04:31 +02:00
|
|
|
boost::tie(i, boost::tuples::ignore) = m_devices.insert(d);
|
|
|
|
}
|
2007-05-08 20:02:10 +02:00
|
|
|
|
|
|
|
|
|
|
|
// since we're using udp, send the query 4 times
|
|
|
|
// just to make sure we find all devices
|
2007-05-09 01:22:57 +02:00
|
|
|
if (m_retry_count >= 4 && !m_devices.empty())
|
2007-05-08 20:02:10 +02:00
|
|
|
{
|
2007-12-30 00:14:59 +01:00
|
|
|
asio::error_code ec;
|
|
|
|
m_broadcast_timer.cancel(ec);
|
2007-05-08 20:02:10 +02:00
|
|
|
|
|
|
|
for (std::set<rootdevice>::iterator i = m_devices.begin()
|
|
|
|
, end(m_devices.end()); i != end; ++i)
|
|
|
|
{
|
2007-05-14 09:31:01 +02:00
|
|
|
if (i->control_url.empty() && !i->upnp_connection && !i->disabled)
|
2007-05-08 20:02:10 +02:00
|
|
|
{
|
|
|
|
// we don't have a WANIP or WANPPP url for this device,
|
|
|
|
// ask for it
|
|
|
|
rootdevice& d = const_cast<rootdevice&>(*i);
|
2007-10-26 02:47:30 +02:00
|
|
|
TORRENT_ASSERT(d.magic == 1337);
|
2007-12-30 00:14:59 +01:00
|
|
|
#ifndef BOOST_NO_EXCEPTIONS
|
2007-05-14 09:31:01 +02:00
|
|
|
try
|
|
|
|
{
|
2007-12-30 00:14:59 +01:00
|
|
|
#endif
|
2007-10-26 02:47:30 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
|
|
|
m_log << time_now_string()
|
|
|
|
<< " ==> connecting to " << d.url << std::endl;
|
|
|
|
#endif
|
2007-09-10 01:52:34 +02:00
|
|
|
d.upnp_connection.reset(new http_connection(m_io_service
|
2008-01-08 06:47:43 +01:00
|
|
|
, m_cc, bind(&upnp::on_upnp_xml, self(), _1, _2
|
|
|
|
, boost::ref(d))));
|
2008-03-15 17:07:49 +01:00
|
|
|
d.upnp_connection->get(d.url, seconds(30), 1);
|
2007-12-30 00:14:59 +01:00
|
|
|
#ifndef BOOST_NO_EXCEPTIONS
|
2007-05-14 09:31:01 +02:00
|
|
|
}
|
|
|
|
catch (std::exception& e)
|
|
|
|
{
|
2008-04-05 23:18:27 +02:00
|
|
|
(void)e;
|
2007-05-14 09:31:01 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
|
|
|
m_log << time_now_string()
|
|
|
|
<< " *** Connection failed to: " << d.url
|
|
|
|
<< " " << e.what() << std::endl;
|
|
|
|
#endif
|
|
|
|
d.disabled = true;
|
|
|
|
}
|
2007-12-30 00:14:59 +01:00
|
|
|
#endif
|
2007-05-08 20:02:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2007-03-27 09:04:31 +02:00
|
|
|
}
|
|
|
|
|
2007-09-10 01:52:34 +02:00
|
|
|
void upnp::post(upnp::rootdevice const& d, std::string const& soap
|
2007-03-27 09:04:31 +02:00
|
|
|
, std::string const& soap_action)
|
|
|
|
{
|
2007-10-26 02:47:30 +02:00
|
|
|
TORRENT_ASSERT(d.magic == 1337);
|
2008-03-15 17:07:49 +01:00
|
|
|
TORRENT_ASSERT(d.upnp_connection);
|
|
|
|
|
2007-03-27 09:04:31 +02:00
|
|
|
std::stringstream header;
|
|
|
|
|
|
|
|
header << "POST " << d.control_url << " HTTP/1.1\r\n"
|
|
|
|
"Host: " << d.hostname << ":" << d.port << "\r\n"
|
|
|
|
"Content-Type: text/xml; charset=\"utf-8\"\r\n"
|
2007-09-10 01:52:34 +02:00
|
|
|
"Content-Length: " << soap.size() << "\r\n"
|
|
|
|
"Soapaction: \"" << d.service_namespace << "#" << soap_action << "\"\r\n\r\n" << soap;
|
2007-03-27 09:04:31 +02:00
|
|
|
|
|
|
|
d.upnp_connection->sendbuffer = header.str();
|
2007-09-10 01:52:34 +02:00
|
|
|
|
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
|
|
|
m_log << time_now_string()
|
2007-09-12 21:38:52 +02:00
|
|
|
<< " ==> sending: " << header.str() << std::endl;
|
2007-09-10 01:52:34 +02:00
|
|
|
#endif
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void upnp::create_port_mapping(http_connection& c, rootdevice& d, int i)
|
|
|
|
{
|
2007-10-26 02:47:30 +02:00
|
|
|
TORRENT_ASSERT(d.magic == 1337);
|
2008-03-15 17:07:49 +01:00
|
|
|
|
|
|
|
if (!d.upnp_connection)
|
|
|
|
{
|
|
|
|
TORRENT_ASSERT(d.disabled);
|
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
|
|
|
m_log << time_now_string() << " *** mapping (" << i
|
|
|
|
<< ") aborted" << std::endl;
|
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2007-09-10 01:52:34 +02:00
|
|
|
std::string soap_action = "AddPortMapping";
|
|
|
|
|
|
|
|
std::stringstream soap;
|
|
|
|
|
|
|
|
soap << "<?xml version=\"1.0\"?>\n"
|
|
|
|
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
|
|
|
|
"s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
|
|
|
|
"<s:Body><u:" << soap_action << " xmlns:u=\"" << d.service_namespace << "\">";
|
|
|
|
|
2007-12-30 00:14:59 +01:00
|
|
|
asio::error_code ec;
|
2007-09-10 01:52:34 +02:00
|
|
|
soap << "<NewRemoteHost></NewRemoteHost>"
|
|
|
|
"<NewExternalPort>" << d.mapping[i].external_port << "</NewExternalPort>"
|
|
|
|
"<NewProtocol>" << (d.mapping[i].protocol ? "UDP" : "TCP") << "</NewProtocol>"
|
|
|
|
"<NewInternalPort>" << d.mapping[i].local_port << "</NewInternalPort>"
|
2007-12-30 00:14:59 +01:00
|
|
|
"<NewInternalClient>" << c.socket().local_endpoint(ec).address() << "</NewInternalClient>"
|
2007-09-10 01:52:34 +02:00
|
|
|
"<NewEnabled>1</NewEnabled>"
|
|
|
|
"<NewPortMappingDescription>" << m_user_agent << "</NewPortMappingDescription>"
|
|
|
|
"<NewLeaseDuration>" << d.lease_duration << "</NewLeaseDuration>";
|
|
|
|
soap << "</u:" << soap_action << "></s:Body></s:Envelope>";
|
|
|
|
|
|
|
|
post(d, soap.str(), soap_action);
|
2007-03-27 09:04:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void upnp::map_port(rootdevice& d, int i)
|
|
|
|
{
|
2007-10-26 02:47:30 +02:00
|
|
|
TORRENT_ASSERT(d.magic == 1337);
|
2007-04-04 02:08:00 +02:00
|
|
|
if (d.upnp_connection) return;
|
|
|
|
|
2008-01-07 03:35:29 +01:00
|
|
|
if (d.mapping[i].failcount > 5)
|
|
|
|
{
|
|
|
|
// giving up
|
|
|
|
if (i < num_mappings - 1)
|
|
|
|
map_port(d, i + 1);
|
|
|
|
return;
|
|
|
|
}
|
2007-04-01 03:18:10 +02:00
|
|
|
if (!d.mapping[i].need_update)
|
|
|
|
{
|
2007-05-08 20:02:10 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
|
|
|
m_log << time_now_string() << " *** mapping (" << i
|
|
|
|
<< ") does not need update, skipping" << std::endl;
|
|
|
|
#endif
|
2007-04-01 03:18:10 +02:00
|
|
|
if (i < num_mappings - 1)
|
|
|
|
map_port(d, i + 1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
d.mapping[i].need_update = false;
|
2007-10-05 02:30:00 +02:00
|
|
|
TORRENT_ASSERT(!d.upnp_connection);
|
|
|
|
TORRENT_ASSERT(d.service_namespace);
|
2007-05-06 00:06:26 +02:00
|
|
|
|
2007-10-26 02:47:30 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
|
|
|
m_log << time_now_string()
|
|
|
|
<< " ==> connecting to " << d.hostname << std::endl;
|
|
|
|
#endif
|
2007-09-10 01:52:34 +02:00
|
|
|
d.upnp_connection.reset(new http_connection(m_io_service
|
2008-01-08 06:47:43 +01:00
|
|
|
, m_cc, bind(&upnp::on_upnp_map_response, self(), _1, _2
|
|
|
|
, boost::ref(d), i), true
|
2007-09-29 23:31:51 +02:00
|
|
|
, bind(&upnp::create_port_mapping, self(), _1, boost::ref(d), i)));
|
2007-03-27 09:04:31 +02:00
|
|
|
|
2007-09-10 01:52:34 +02:00
|
|
|
d.upnp_connection->start(d.hostname, boost::lexical_cast<std::string>(d.port)
|
2008-03-15 17:07:49 +01:00
|
|
|
, seconds(10), 1);
|
2007-09-10 01:52:34 +02:00
|
|
|
}
|
2007-03-27 09:04:31 +02:00
|
|
|
|
2007-09-10 01:52:34 +02:00
|
|
|
void upnp::delete_port_mapping(rootdevice& d, int i)
|
|
|
|
{
|
2007-10-26 02:47:30 +02:00
|
|
|
TORRENT_ASSERT(d.magic == 1337);
|
2008-03-15 17:07:49 +01:00
|
|
|
|
|
|
|
if (!d.upnp_connection)
|
|
|
|
{
|
|
|
|
TORRENT_ASSERT(d.disabled);
|
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
|
|
|
m_log << time_now_string() << " *** unmapping (" << i
|
|
|
|
<< ") aborted" << std::endl;
|
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2007-03-27 09:04:31 +02:00
|
|
|
std::stringstream soap;
|
|
|
|
|
2007-09-10 01:52:34 +02:00
|
|
|
std::string soap_action = "DeletePortMapping";
|
|
|
|
|
2007-03-27 09:04:31 +02:00
|
|
|
soap << "<?xml version=\"1.0\"?>\n"
|
|
|
|
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
|
|
|
|
"s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
|
|
|
|
"<s:Body><u:" << soap_action << " xmlns:u=\"" << d.service_namespace << "\">";
|
|
|
|
|
|
|
|
soap << "<NewRemoteHost></NewRemoteHost>"
|
|
|
|
"<NewExternalPort>" << d.mapping[i].external_port << "</NewExternalPort>"
|
2007-09-10 01:52:34 +02:00
|
|
|
"<NewProtocol>" << (d.mapping[i].protocol ? "UDP" : "TCP") << "</NewProtocol>";
|
2007-03-27 09:04:31 +02:00
|
|
|
soap << "</u:" << soap_action << "></s:Body></s:Envelope>";
|
|
|
|
|
2007-09-10 01:52:34 +02:00
|
|
|
post(d, soap.str(), soap_action);
|
2007-03-27 09:04:31 +02:00
|
|
|
}
|
|
|
|
|
2007-04-01 17:39:08 +02:00
|
|
|
// requires the mutex to be locked
|
2007-04-01 01:23:30 +02:00
|
|
|
void upnp::unmap_port(rootdevice& d, int i)
|
|
|
|
{
|
2007-10-26 02:47:30 +02:00
|
|
|
TORRENT_ASSERT(d.magic == 1337);
|
|
|
|
if (d.mapping[i].external_port == 0
|
|
|
|
|| d.disabled)
|
2007-04-01 17:39:08 +02:00
|
|
|
{
|
|
|
|
if (i < num_mappings - 1)
|
|
|
|
{
|
|
|
|
unmap_port(d, i + 1);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2007-10-26 02:47:30 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
|
|
|
m_log << time_now_string()
|
|
|
|
<< " ==> connecting to " << d.hostname << std::endl;
|
|
|
|
#endif
|
2007-09-10 01:52:34 +02:00
|
|
|
d.upnp_connection.reset(new http_connection(m_io_service
|
2008-01-08 06:47:43 +01:00
|
|
|
, m_cc, bind(&upnp::on_upnp_unmap_response, self(), _1, _2
|
|
|
|
, boost::ref(d), i), true
|
2007-09-29 23:31:51 +02:00
|
|
|
, bind(&upnp::delete_port_mapping, self(), boost::ref(d), i)));
|
2007-09-10 01:52:34 +02:00
|
|
|
d.upnp_connection->start(d.hostname, boost::lexical_cast<std::string>(d.port)
|
2008-03-15 17:07:49 +01:00
|
|
|
, seconds(10), 1);
|
2007-04-01 01:23:30 +02:00
|
|
|
}
|
|
|
|
|
2007-03-27 09:04:31 +02:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
struct parse_state
|
|
|
|
{
|
|
|
|
parse_state(): found_service(false), exit(false) {}
|
|
|
|
void reset(char const* st)
|
|
|
|
{
|
|
|
|
found_service = false;
|
|
|
|
exit = false;
|
|
|
|
service_type = st;
|
|
|
|
}
|
|
|
|
bool found_service;
|
|
|
|
bool exit;
|
|
|
|
std::string top_tag;
|
|
|
|
std::string control_url;
|
|
|
|
char const* service_type;
|
2007-12-22 17:47:46 +01:00
|
|
|
std::string model;
|
2007-03-27 09:04:31 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
void find_control_url(int type, char const* string, parse_state& state)
|
|
|
|
{
|
|
|
|
if (state.exit) return;
|
|
|
|
|
|
|
|
if (type == xml_start_tag)
|
|
|
|
{
|
|
|
|
if ((!state.top_tag.empty() && state.top_tag == "service")
|
|
|
|
|| !strcmp(string, "service"))
|
|
|
|
{
|
|
|
|
state.top_tag = string;
|
|
|
|
}
|
2007-12-22 17:47:46 +01:00
|
|
|
else if (!strcmp(string, "modelName"))
|
|
|
|
{
|
|
|
|
state.top_tag = string;
|
|
|
|
}
|
2007-03-27 09:04:31 +02:00
|
|
|
}
|
|
|
|
else if (type == xml_end_tag)
|
|
|
|
{
|
|
|
|
if (!strcmp(string, "service"))
|
|
|
|
{
|
|
|
|
state.top_tag.clear();
|
|
|
|
if (state.found_service) state.exit = true;
|
|
|
|
}
|
|
|
|
else if (!state.top_tag.empty() && state.top_tag != "service")
|
|
|
|
state.top_tag = "service";
|
|
|
|
}
|
|
|
|
else if (type == xml_string)
|
|
|
|
{
|
|
|
|
if (state.top_tag == "serviceType")
|
|
|
|
{
|
|
|
|
if (!strcmp(string, state.service_type))
|
|
|
|
state.found_service = true;
|
|
|
|
}
|
|
|
|
else if (state.top_tag == "controlURL")
|
|
|
|
{
|
|
|
|
state.control_url = string;
|
|
|
|
if (state.found_service) state.exit = true;
|
|
|
|
}
|
2007-12-22 17:47:46 +01:00
|
|
|
else if (state.top_tag == "modelName")
|
|
|
|
{
|
|
|
|
state.model = string;
|
|
|
|
}
|
2007-03-27 09:04:31 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void upnp::on_upnp_xml(asio::error_code const& e
|
2007-12-30 00:14:59 +01:00
|
|
|
, libtorrent::http_parser const& p, rootdevice& d)
|
2007-03-27 09:04:31 +02:00
|
|
|
{
|
2007-10-26 02:47:30 +02:00
|
|
|
TORRENT_ASSERT(d.magic == 1337);
|
2007-04-01 17:39:08 +02:00
|
|
|
if (d.upnp_connection)
|
|
|
|
{
|
|
|
|
d.upnp_connection->close();
|
|
|
|
d.upnp_connection.reset();
|
|
|
|
}
|
2007-03-30 18:57:55 +02:00
|
|
|
|
2007-08-07 09:59:51 +02:00
|
|
|
if (e && e != asio::error::eof)
|
2007-03-30 18:57:55 +02:00
|
|
|
{
|
2007-05-04 07:34:03 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
2007-04-10 09:52:58 +02:00
|
|
|
m_log << time_now_string()
|
2007-10-26 02:47:30 +02:00
|
|
|
<< " <== (" << d.url << ") error while fetching control url: "
|
|
|
|
<< e.message() << std::endl;
|
2007-03-30 18:57:55 +02:00
|
|
|
#endif
|
2007-10-26 02:47:30 +02:00
|
|
|
d.disabled = true;
|
2007-03-30 18:57:55 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2007-04-01 17:39:08 +02:00
|
|
|
if (!p.header_finished())
|
|
|
|
{
|
2007-05-04 07:34:03 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
2007-04-10 09:52:58 +02:00
|
|
|
m_log << time_now_string()
|
2007-10-26 02:47:30 +02:00
|
|
|
<< " <== (" << d.url << ") error while fetching control url: incomplete http message" << std::endl;
|
2007-08-07 09:59:51 +02:00
|
|
|
#endif
|
2007-10-26 02:47:30 +02:00
|
|
|
d.disabled = true;
|
2007-08-07 09:59:51 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (p.status_code() != 200)
|
|
|
|
{
|
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
|
|
|
m_log << time_now_string()
|
2007-10-26 02:47:30 +02:00
|
|
|
<< " <== (" << d.url << ") error while fetching control url: " << p.message() << std::endl;
|
2007-04-01 17:39:08 +02:00
|
|
|
#endif
|
2007-10-26 02:47:30 +02:00
|
|
|
d.disabled = true;
|
2007-04-01 17:39:08 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2007-03-27 09:04:31 +02:00
|
|
|
parse_state s;
|
|
|
|
s.reset("urn:schemas-upnp-org:service:WANIPConnection:1");
|
|
|
|
xml_parse((char*)p.get_body().begin, (char*)p.get_body().end
|
2007-12-22 17:47:46 +01:00
|
|
|
, bind(&find_control_url, _1, _2, boost::ref(s)));
|
2007-05-06 00:06:26 +02:00
|
|
|
if (s.found_service)
|
|
|
|
{
|
|
|
|
d.service_namespace = s.service_type;
|
2007-12-22 17:47:46 +01:00
|
|
|
if (!s.model.empty()) m_model = s.model;
|
2007-05-06 00:06:26 +02:00
|
|
|
}
|
|
|
|
else
|
2007-03-27 09:04:31 +02:00
|
|
|
{
|
|
|
|
// we didn't find the WAN IP connection, look for
|
2007-05-06 00:06:26 +02:00
|
|
|
// a PPP connection
|
|
|
|
s.reset("urn:schemas-upnp-org:service:WANPPPConnection:1");
|
2007-03-27 09:04:31 +02:00
|
|
|
xml_parse((char*)p.get_body().begin, (char*)p.get_body().end
|
2007-12-22 17:47:46 +01:00
|
|
|
, bind(&find_control_url, _1, _2, boost::ref(s)));
|
2007-05-06 00:06:26 +02:00
|
|
|
if (s.found_service)
|
|
|
|
{
|
|
|
|
d.service_namespace = s.service_type;
|
2007-12-22 17:47:46 +01:00
|
|
|
if (!s.model.empty()) m_model = s.model;
|
2007-05-06 00:06:26 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
|
|
|
m_log << time_now_string()
|
2007-10-26 02:47:30 +02:00
|
|
|
<< " <== (" << d.url << ") Rootdevice response, did not find "
|
|
|
|
"a port mapping interface" << std::endl;
|
2007-05-06 00:06:26 +02:00
|
|
|
#endif
|
2007-10-26 02:47:30 +02:00
|
|
|
d.disabled = true;
|
2007-05-06 00:06:26 +02:00
|
|
|
return;
|
|
|
|
}
|
2007-03-27 09:04:31 +02:00
|
|
|
}
|
|
|
|
|
2007-05-04 07:34:03 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
2007-04-10 09:52:58 +02:00
|
|
|
m_log << time_now_string()
|
2007-10-26 02:47:30 +02:00
|
|
|
<< " <== (" << d.url << ") Rootdevice response, found control URL: " << s.control_url
|
2007-05-05 22:00:28 +02:00
|
|
|
<< " namespace: " << d.service_namespace << std::endl;
|
2007-03-27 09:04:31 +02:00
|
|
|
#endif
|
|
|
|
|
|
|
|
d.control_url = s.control_url;
|
|
|
|
|
|
|
|
map_port(d, 0);
|
|
|
|
}
|
2007-05-14 09:31:01 +02:00
|
|
|
|
|
|
|
void upnp::disable()
|
2007-05-10 20:00:10 +02:00
|
|
|
{
|
|
|
|
m_disabled = true;
|
2007-05-14 09:31:01 +02:00
|
|
|
m_devices.clear();
|
2007-12-30 00:14:59 +01:00
|
|
|
asio::error_code ec;
|
|
|
|
m_broadcast_timer.cancel(ec);
|
|
|
|
m_refresh_timer.cancel(ec);
|
2007-05-14 09:31:01 +02:00
|
|
|
m_socket.close();
|
2007-05-10 20:00:10 +02:00
|
|
|
}
|
2007-03-27 09:04:31 +02:00
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
struct error_code_parse_state
|
|
|
|
{
|
|
|
|
error_code_parse_state(): in_error_code(false), exit(false), error_code(-1) {}
|
|
|
|
bool in_error_code;
|
|
|
|
bool exit;
|
|
|
|
int error_code;
|
|
|
|
};
|
|
|
|
|
|
|
|
void find_error_code(int type, char const* string, error_code_parse_state& state)
|
|
|
|
{
|
|
|
|
if (state.exit) return;
|
|
|
|
if (type == xml_start_tag && !strcmp("errorCode", string))
|
|
|
|
{
|
|
|
|
state.in_error_code = true;
|
|
|
|
}
|
|
|
|
else if (type == xml_string && state.in_error_code)
|
|
|
|
{
|
|
|
|
state.error_code = std::atoi(string);
|
|
|
|
state.exit = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-05-07 02:12:33 +02:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
struct error_code_t
|
|
|
|
{
|
|
|
|
int code;
|
|
|
|
char const* msg;
|
|
|
|
};
|
|
|
|
|
|
|
|
error_code_t error_codes[] =
|
|
|
|
{
|
|
|
|
{402, "Invalid Arguments"}
|
|
|
|
, {501, "Action Failed"}
|
|
|
|
, {714, "The specified value does not exist in the array"}
|
|
|
|
, {715, "The source IP address cannot be wild-carded"}
|
|
|
|
, {716, "The external port cannot be wild-carded"}
|
|
|
|
, {718, "The port mapping entry specified conflicts with "
|
|
|
|
"a mapping assigned previously to another client"}
|
|
|
|
, {724, "Internal and External port values must be the same"}
|
|
|
|
, {725, "The NAT implementation only supports permanent "
|
|
|
|
"lease times on port mappings"}
|
|
|
|
, {726, "RemoteHost must be a wildcard and cannot be a "
|
|
|
|
"specific IP address or DNS name"}
|
|
|
|
, {727, "ExternalPort must be a wildcard and cannot be a specific port "}
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2007-03-27 09:04:31 +02:00
|
|
|
void upnp::on_upnp_map_response(asio::error_code const& e
|
2007-12-30 00:14:59 +01:00
|
|
|
, libtorrent::http_parser const& p, rootdevice& d, int mapping)
|
2007-03-27 09:04:31 +02:00
|
|
|
{
|
2007-10-26 02:47:30 +02:00
|
|
|
TORRENT_ASSERT(d.magic == 1337);
|
2007-04-01 17:39:08 +02:00
|
|
|
if (d.upnp_connection)
|
|
|
|
{
|
|
|
|
d.upnp_connection->close();
|
|
|
|
d.upnp_connection.reset();
|
|
|
|
}
|
2007-03-30 18:57:55 +02:00
|
|
|
|
2007-08-07 09:59:51 +02:00
|
|
|
if (e && e != asio::error::eof)
|
2007-03-27 09:04:31 +02:00
|
|
|
{
|
2007-05-04 07:34:03 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
2007-04-10 09:52:58 +02:00
|
|
|
m_log << time_now_string()
|
2007-04-01 03:18:10 +02:00
|
|
|
<< " <== error while adding portmap: " << e.message() << std::endl;
|
2007-03-27 09:04:31 +02:00
|
|
|
#endif
|
2007-10-26 02:47:30 +02:00
|
|
|
d.disabled = true;
|
2007-03-27 09:04:31 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2007-04-01 01:23:30 +02:00
|
|
|
if (m_closing) return;
|
|
|
|
|
2007-03-27 09:04:31 +02:00
|
|
|
// error code response may look like this:
|
|
|
|
// <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
|
|
|
|
// s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
|
|
|
// <s:Body>
|
|
|
|
// <s:Fault>
|
|
|
|
// <faultcode>s:Client</faultcode>
|
|
|
|
// <faultstring>UPnPError</faultstring>
|
|
|
|
// <detail>
|
|
|
|
// <UPnPErrorxmlns="urn:schemas-upnp-org:control-1-0">
|
|
|
|
// <errorCode>402</errorCode>
|
|
|
|
// <errorDescription>Invalid Args</errorDescription>
|
|
|
|
// </UPnPError>
|
|
|
|
// </detail>
|
|
|
|
// </s:Fault>
|
|
|
|
// </s:Body>
|
|
|
|
// </s:Envelope>
|
|
|
|
|
2007-04-01 17:39:08 +02:00
|
|
|
if (!p.header_finished())
|
|
|
|
{
|
2007-05-04 07:34:03 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
2007-04-10 09:52:58 +02:00
|
|
|
m_log << time_now_string()
|
2007-08-07 09:59:51 +02:00
|
|
|
<< " <== error while adding portmap: incomplete http message" << std::endl;
|
2007-04-01 17:39:08 +02:00
|
|
|
#endif
|
2007-10-26 02:47:30 +02:00
|
|
|
d.disabled = true;
|
2007-04-01 17:39:08 +02:00
|
|
|
return;
|
|
|
|
}
|
2007-09-17 11:55:48 +02:00
|
|
|
|
|
|
|
// We don't want to ignore responses with return codes other than 200
|
|
|
|
// since those might contain valid UPnP error codes
|
2007-08-07 09:59:51 +02:00
|
|
|
|
2007-03-27 09:04:31 +02:00
|
|
|
error_code_parse_state s;
|
|
|
|
xml_parse((char*)p.get_body().begin, (char*)p.get_body().end
|
2007-12-22 17:47:46 +01:00
|
|
|
, bind(&find_error_code, _1, _2, boost::ref(s)));
|
2007-03-27 09:04:31 +02:00
|
|
|
|
2007-05-04 07:34:03 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
2007-03-27 09:04:31 +02:00
|
|
|
if (s.error_code != -1)
|
|
|
|
{
|
2007-04-10 09:52:58 +02:00
|
|
|
m_log << time_now_string()
|
2007-03-27 09:04:31 +02:00
|
|
|
<< " <== got error message: " << s.error_code << std::endl;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (s.error_code == 725)
|
|
|
|
{
|
2007-03-30 18:57:55 +02:00
|
|
|
// only permanent leases supported
|
2007-03-27 09:04:31 +02:00
|
|
|
d.lease_duration = 0;
|
2007-04-01 17:39:08 +02:00
|
|
|
d.mapping[mapping].need_update = true;
|
2008-01-07 03:35:29 +01:00
|
|
|
++d.mapping[mapping].failcount;
|
2007-03-30 18:57:55 +02:00
|
|
|
map_port(d, mapping);
|
2007-04-01 17:39:08 +02:00
|
|
|
return;
|
2007-03-30 18:57:55 +02:00
|
|
|
}
|
2008-01-07 03:35:29 +01:00
|
|
|
else if (s.error_code == 718 || s.error_code == 727)
|
|
|
|
{
|
|
|
|
if (d.mapping[mapping].external_port != 0)
|
|
|
|
{
|
|
|
|
// conflict in mapping, set port to wildcard
|
|
|
|
// and let the router decide
|
|
|
|
d.mapping[mapping].external_port = 0;
|
|
|
|
d.mapping[mapping].need_update = true;
|
|
|
|
++d.mapping[mapping].failcount;
|
|
|
|
map_port(d, mapping);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
return_error(s.error_code);
|
|
|
|
}
|
|
|
|
else if (s.error_code == 716)
|
2007-03-30 18:57:55 +02:00
|
|
|
{
|
2008-01-07 03:35:29 +01:00
|
|
|
// The external port cannot be wildcarder
|
|
|
|
// pick a random port
|
|
|
|
d.mapping[mapping].external_port = 40000 + (rand() % 10000);
|
2007-04-01 17:39:08 +02:00
|
|
|
d.mapping[mapping].need_update = true;
|
2008-01-07 03:35:29 +01:00
|
|
|
++d.mapping[mapping].failcount;
|
2007-03-30 18:57:55 +02:00
|
|
|
map_port(d, mapping);
|
2007-04-01 17:39:08 +02:00
|
|
|
return;
|
2007-03-30 18:57:55 +02:00
|
|
|
}
|
|
|
|
else if (s.error_code != -1)
|
|
|
|
{
|
2008-01-07 03:35:29 +01:00
|
|
|
return_error(s.error_code);
|
2007-03-27 09:04:31 +02:00
|
|
|
}
|
2007-04-01 01:23:30 +02:00
|
|
|
|
2007-05-04 07:34:03 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
2007-04-10 09:52:58 +02:00
|
|
|
m_log << time_now_string()
|
2007-04-01 01:23:30 +02:00
|
|
|
<< " <== map response: " << std::string(p.get_body().begin, p.get_body().end)
|
|
|
|
<< std::endl;
|
|
|
|
#endif
|
|
|
|
|
2007-03-30 18:57:55 +02:00
|
|
|
if (s.error_code == -1)
|
|
|
|
{
|
|
|
|
int tcp = 0;
|
|
|
|
int udp = 0;
|
|
|
|
|
|
|
|
if (mapping == 0)
|
|
|
|
tcp = d.mapping[mapping].external_port;
|
|
|
|
else
|
|
|
|
udp = d.mapping[mapping].external_port;
|
|
|
|
|
|
|
|
m_callback(tcp, udp, "");
|
2007-04-01 01:23:30 +02:00
|
|
|
if (d.lease_duration > 0)
|
|
|
|
{
|
2007-04-05 00:27:36 +02:00
|
|
|
d.mapping[mapping].expires = time_now()
|
2007-04-01 17:39:08 +02:00
|
|
|
+ seconds(int(d.lease_duration * 0.75f));
|
2007-04-01 01:23:30 +02:00
|
|
|
ptime next_expire = m_refresh_timer.expires_at();
|
2007-04-10 09:52:58 +02:00
|
|
|
if (next_expire < time_now()
|
2007-04-01 01:23:30 +02:00
|
|
|
|| next_expire > d.mapping[mapping].expires)
|
|
|
|
{
|
2007-12-30 00:14:59 +01:00
|
|
|
asio::error_code ec;
|
|
|
|
m_refresh_timer.expires_at(d.mapping[mapping].expires, ec);
|
2008-01-08 06:47:43 +01:00
|
|
|
m_refresh_timer.async_wait(bind(&upnp::on_expire, self(), _1));
|
2007-04-01 01:23:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2007-04-05 00:27:36 +02:00
|
|
|
d.mapping[mapping].expires = max_time();
|
2007-04-01 01:23:30 +02:00
|
|
|
}
|
2008-01-07 03:35:29 +01:00
|
|
|
d.mapping[mapping].failcount = 0;
|
2007-03-30 18:57:55 +02:00
|
|
|
}
|
|
|
|
|
2007-04-04 02:08:00 +02:00
|
|
|
for (int i = 0; i < num_mappings; ++i)
|
|
|
|
{
|
|
|
|
if (d.mapping[i].need_update)
|
|
|
|
{
|
|
|
|
map_port(d, i);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2007-04-01 01:23:30 +02:00
|
|
|
}
|
|
|
|
|
2008-01-07 03:35:29 +01:00
|
|
|
void upnp::return_error(int code)
|
|
|
|
{
|
|
|
|
int num_errors = sizeof(error_codes) / sizeof(error_codes[0]);
|
|
|
|
error_code_t* end = error_codes + num_errors;
|
|
|
|
error_code_t tmp = {code, 0};
|
|
|
|
error_code_t* e = std::lower_bound(error_codes, end, tmp
|
|
|
|
, bind(&error_code_t::code, _1) < bind(&error_code_t::code, _2));
|
|
|
|
std::string error_string = "UPnP mapping error ";
|
|
|
|
error_string += boost::lexical_cast<std::string>(code);
|
|
|
|
if (e != end && e->code == code)
|
|
|
|
{
|
|
|
|
error_string += ": ";
|
|
|
|
error_string += e->msg;
|
|
|
|
}
|
|
|
|
m_callback(0, 0, error_string);
|
|
|
|
}
|
|
|
|
|
2007-04-01 01:23:30 +02:00
|
|
|
void upnp::on_upnp_unmap_response(asio::error_code const& e
|
2007-12-30 00:14:59 +01:00
|
|
|
, libtorrent::http_parser const& p, rootdevice& d, int mapping)
|
2007-04-01 01:23:30 +02:00
|
|
|
{
|
2007-10-26 02:47:30 +02:00
|
|
|
TORRENT_ASSERT(d.magic == 1337);
|
2007-04-01 17:39:08 +02:00
|
|
|
if (d.upnp_connection)
|
|
|
|
{
|
|
|
|
d.upnp_connection->close();
|
|
|
|
d.upnp_connection.reset();
|
|
|
|
}
|
2007-04-01 01:23:30 +02:00
|
|
|
|
2007-08-07 09:59:51 +02:00
|
|
|
if (e && e != asio::error::eof)
|
2007-04-01 01:23:30 +02:00
|
|
|
{
|
2007-05-04 07:34:03 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
2007-04-10 09:52:58 +02:00
|
|
|
m_log << time_now_string()
|
2007-04-01 03:18:10 +02:00
|
|
|
<< " <== error while deleting portmap: " << e.message() << std::endl;
|
2007-04-01 01:23:30 +02:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2007-04-01 17:39:08 +02:00
|
|
|
if (!p.header_finished())
|
|
|
|
{
|
2007-05-04 07:34:03 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
2007-04-10 09:52:58 +02:00
|
|
|
m_log << time_now_string()
|
2007-08-07 09:59:51 +02:00
|
|
|
<< " <== error while deleting portmap: incomplete http message" << std::endl;
|
2007-04-01 17:39:08 +02:00
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2007-08-07 09:59:51 +02:00
|
|
|
if (p.status_code() != 200)
|
|
|
|
{
|
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
|
|
|
m_log << time_now_string()
|
|
|
|
<< " <== error while deleting portmap: " << p.message() << std::endl;
|
|
|
|
#endif
|
2007-10-26 02:47:30 +02:00
|
|
|
d.disabled = true;
|
2007-08-07 09:59:51 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2007-05-04 07:34:03 +02:00
|
|
|
#ifdef TORRENT_UPNP_LOGGING
|
2007-04-10 09:52:58 +02:00
|
|
|
m_log << time_now_string()
|
2007-04-01 01:23:30 +02:00
|
|
|
<< " <== unmap response: " << std::string(p.get_body().begin, p.get_body().end)
|
|
|
|
<< std::endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// ignore errors and continue with the next mapping for this device
|
|
|
|
if (mapping < num_mappings - 1)
|
|
|
|
{
|
|
|
|
unmap_port(d, mapping + 1);
|
2007-04-01 17:39:08 +02:00
|
|
|
return;
|
2007-04-01 01:23:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-12-30 00:14:59 +01:00
|
|
|
void upnp::on_expire(asio::error_code const& e)
|
2007-04-01 01:23:30 +02:00
|
|
|
{
|
|
|
|
if (e) return;
|
|
|
|
|
2007-04-05 00:27:36 +02:00
|
|
|
ptime now = time_now();
|
|
|
|
ptime next_expire = max_time();
|
2007-04-01 01:23:30 +02:00
|
|
|
|
|
|
|
for (std::set<rootdevice>::iterator i = m_devices.begin()
|
|
|
|
, end(m_devices.end()); i != end; ++i)
|
|
|
|
{
|
|
|
|
rootdevice& d = const_cast<rootdevice&>(*i);
|
2007-10-26 02:47:30 +02:00
|
|
|
TORRENT_ASSERT(d.magic == 1337);
|
2007-04-01 01:23:30 +02:00
|
|
|
for (int m = 0; m < num_mappings; ++m)
|
|
|
|
{
|
2007-04-05 00:27:36 +02:00
|
|
|
if (d.mapping[m].expires != max_time())
|
2007-04-01 01:23:30 +02:00
|
|
|
continue;
|
|
|
|
|
|
|
|
if (d.mapping[m].expires < now)
|
|
|
|
{
|
2007-04-05 00:27:36 +02:00
|
|
|
d.mapping[m].expires = max_time();
|
2007-04-01 01:23:30 +02:00
|
|
|
map_port(d, m);
|
|
|
|
}
|
2007-04-05 00:27:36 +02:00
|
|
|
else if (d.mapping[m].expires < next_expire)
|
2007-04-01 01:23:30 +02:00
|
|
|
{
|
|
|
|
next_expire = d.mapping[m].expires;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2007-04-05 00:27:36 +02:00
|
|
|
if (next_expire != max_time())
|
2007-04-01 01:23:30 +02:00
|
|
|
{
|
2007-12-30 00:14:59 +01:00
|
|
|
asio::error_code ec;
|
|
|
|
m_refresh_timer.expires_at(next_expire, ec);
|
2008-01-08 06:47:43 +01:00
|
|
|
m_refresh_timer.async_wait(bind(&upnp::on_expire, self(), _1));
|
2007-04-01 01:23:30 +02:00
|
|
|
}
|
2007-03-27 09:04:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void upnp::close()
|
|
|
|
{
|
2007-12-30 00:14:59 +01:00
|
|
|
asio::error_code ec;
|
|
|
|
m_refresh_timer.cancel(ec);
|
|
|
|
m_broadcast_timer.cancel(ec);
|
2007-04-01 01:23:30 +02:00
|
|
|
m_closing = true;
|
2007-04-02 22:09:48 +02:00
|
|
|
m_socket.close();
|
|
|
|
|
|
|
|
if (m_disabled)
|
|
|
|
{
|
|
|
|
m_devices.clear();
|
|
|
|
return;
|
|
|
|
}
|
2007-04-01 01:23:30 +02:00
|
|
|
|
|
|
|
for (std::set<rootdevice>::iterator i = m_devices.begin()
|
2007-10-26 02:47:30 +02:00
|
|
|
, end(m_devices.end()); i != end; ++i)
|
2007-04-01 01:23:30 +02:00
|
|
|
{
|
|
|
|
rootdevice& d = const_cast<rootdevice&>(*i);
|
2007-10-26 02:47:30 +02:00
|
|
|
TORRENT_ASSERT(d.magic == 1337);
|
|
|
|
if (d.control_url.empty()) continue;
|
2007-04-05 19:53:42 +02:00
|
|
|
unmap_port(d, 0);
|
2007-04-01 01:23:30 +02:00
|
|
|
}
|
2007-03-27 09:04:31 +02:00
|
|
|
}
|
|
|
|
|