added http_connection and a simple xml parser, used by a upnp implementation that still isn't finished

This commit is contained in:
Arvid Norberg 2007-03-27 07:04:31 +00:00
parent 985e457e91
commit 29e43d7f62
11 changed files with 1205 additions and 3 deletions

View File

@ -37,6 +37,7 @@ SOURCES =
entry.cpp
escape_string.cpp
file.cpp
http_connection.cpp
identify_client.cpp
ip_filter.cpp
peer_connection.cpp
@ -57,6 +58,7 @@ SOURCES =
udp_tracker_connection.cpp
sha1.cpp
metadata_transfer.cpp
upnp.cpp
ut_pex.cpp
logger.cpp
file_pool.cpp

View File

@ -13,6 +13,7 @@ libtorrent/file.hpp \
libtorrent/file_pool.hpp \
libtorrent/fingerprint.hpp \
libtorrent/hasher.hpp \
libtorrent/http_connection.hpp \
libtorrent/http_tracker_connection.hpp \
libtorrent/identify_client.hpp \
libtorrent/invariant_check.hpp \
@ -45,6 +46,7 @@ libtorrent/torrent_info.hpp \
libtorrent/tracker_manager.hpp \
libtorrent/udp_tracker_connection.hpp \
libtorrent/utf8.hpp \
libtorrent/xml_parse.hpp \
libtorrent/version.hpp \
libtorrent/aux_/allocate_resources_impl.hpp \
libtorrent/aux_/session_impl.hpp \

View File

@ -0,0 +1,109 @@
/*
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.
*/
#ifndef TORRENT_HTTP_CONNECTION
#define TORRENT_HTTP_CONNECTION
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/noncopyable.hpp>
#include <boost/date_time/time_duration.hpp>
#include <vector>
#include <string>
#include "libtorrent/socket.hpp"
#include "libtorrent/http_tracker_connection.hpp"
namespace libtorrent
{
typedef boost::function<void(asio::error_code const&
, http_parser const&, char const* data, int size)> http_handler;
// TODO: add bind interface
// when bottled, the last two arguments to the handler
// will always be 0
struct http_connection : boost::enable_shared_from_this<http_connection>, boost::noncopyable
{
http_connection(asio::io_service& ios, http_handler handler, bool bottled = true)
: m_sock(ios)
, m_read_pos(0)
, m_resolver(ios)
, m_handler(handler)
, m_timer(ios)
, m_bottled(bottled)
{
assert(!m_handler.empty());
}
std::string sendbuffer;
void get(std::string const& url, boost::posix_time::time_duration timeout
= boost::posix_time::seconds(30));
void start(std::string const& hostname, std::string const& port
, boost::posix_time::time_duration timeout);
void close();
private:
void on_resolve(asio::error_code const& e
, tcp::resolver::iterator i);
void on_connect(asio::error_code const& e
/* , tcp::resolver::iterator i*/);
void on_write(asio::error_code const& e);
void on_read(asio::error_code const& e, std::size_t bytes_transferred);
void on_timeout(asio::error_code const& e);
std::vector<char> m_recvbuffer;
tcp::socket m_sock;
int m_read_pos;
tcp::resolver m_resolver;
http_parser m_parser;
http_handler m_handler;
deadline_timer m_timer;
boost::posix_time::time_duration m_timeout;
// bottled means that the handler is called once, when
// everything is received (and buffered in memory).
// non bottled means that once the headers have been
// received, data is streamed to the handler
bool m_bottled;
std::string m_hostname;
std::string m_port;
};
}
#endif

201
include/libtorrent/upnp.hpp Normal file
View File

@ -0,0 +1,201 @@
/*
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.
*/
#ifndef TORRENT_UPNP_HPP
#define TORRENT_UPNP_HPP
#include "libtorrent/socket.hpp"
#include "libtorrent/http_connection.hpp"
#include <boost/date_time/posix_time/ptime.hpp>
#include <boost/function.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
#include <fstream>
#endif
namespace libtorrent
{
// int: external tcp port
// int: external udp port
// std::string: error message
typedef boost::function<void(int, int, std::string const&)> portmap_callback_t;
class upnp : boost::noncopyable
{
public:
upnp(io_service& ios, address const& listen_interface
, std::string const& user_agent, portmap_callback_t const& cb);
void rebind(address const& listen_interface);
// maps the ports, if a port is set to 0
// it will not be mapped
void set_mappings(int tcp, int udp);
void close();
private:
void update_mapping(int i, int port);
void resend_request(asio::error_code const& e);
void on_reply(asio::error_code const& e
, std::size_t bytes_transferred);
void discover_device();
struct rootdevice;
void on_upnp_xml(asio::error_code const& e
, libtorrent::http_parser const& p, rootdevice& d);
void on_upnp_map_response(asio::error_code const& e
, libtorrent::http_parser const& p, rootdevice& d);
/*
void send_map_request(int i);
void try_next_mapping(int i);
void update_expiration_timer();
void refresh_mapping(int i);
void mapping_expired(asio::error_code const& e, int i);
*/
void post(rootdevice& d, std::stringstream const& s
, std::string const& soap_action);
void map_port(rootdevice& d, int i);
struct mapping_t
{
mapping_t()
: need_update(false)
, local_port(0)
, external_port(0)
, protocol(1)
{}
// indicates that the mapping has changed
// and needs an update
bool need_update;
// the time the port mapping will expire
boost::posix_time::ptime expires;
// the local port for this mapping. If this is set
// to 0, the mapping is not in use
int local_port;
// the external (on the NAT router) port
// for the mapping. This is the port we
// should announce to others
int external_port;
// 1 = udp, 0 = tcp
int protocol;
};
struct rootdevice
{
rootdevice(): ports_mapped(false), lease_duration(3600)
, supports_specific_external(true) {}
// the interface url, through which the list of
// supported interfaces are fetched
std::string url;
// the url to the WANIP or WANPPP interface
std::string control_url;
// either the WANIP namespace or the WANPPP namespace
std::string service_namespace;
mapping_t mapping[2];
// true if we already mapped the ports on this device
bool ports_mapped;
std::string hostname;
int port;
std::string path;
int lease_duration;
// true if the device supports specifying a
// specific external port, false if it doesn't
bool supports_specific_external;
boost::shared_ptr<http_connection> upnp_connection;
void close() const { if (upnp_connection) upnp_connection->close(); }
bool operator<(rootdevice const& rhs) const
{ return url < rhs.url; }
};
std::string const& m_user_agent;
// the set of devices we've found
std::set<rootdevice> m_devices;
portmap_callback_t m_callback;
// current retry count
int m_retry_count;
// used to receive responses in
char m_receive_buffer[1024];
// the endpoint we received the message from
udp::endpoint m_remote;
// the local address we're listening on
address_v4 m_local_ip;
// the udp socket used to send and receive
// multicast messages on the network
datagram_socket m_socket;
// used to resend udp packets in case
// they time out
deadline_timer m_broadcast_timer;
// timer used to refresh mappings
deadline_timer m_refresh_timer;
bool m_disabled;
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
std::ofstream m_log;
#endif
};
}
#endif

View File

@ -0,0 +1,99 @@
/*
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.
*/
#ifndef TORRENT_XML_PARSE_HPP
#define TORRENT_XML_PARSE_HPP
namespace libtorrent
{
const int xml_start_tag = 0;
const int xml_end_tag = 1;
const int xml_string = 2;
template <class CallbackType>
void xml_parse(char* p, char* end, CallbackType callback)
{
for(;p != end; ++p)
{
char const* start = p;
// look for tag start
for(; *p != '<' && p != end; ++p);
if (p != start)
{
if (p != end)
{
assert(*p == '<');
*p = 0;
}
callback(xml_string, start);
if (p != end) *p = '<';
}
if (p == end) break;
// skip '<'
++p;
// parse the name of the tag. Ignore attributes
for (start = p; p != end && *p != '>'; ++p)
{
// terminate the string at the first space
// to ignore tag attributes
if (*p == ' ') *p = 0;
}
// parse error
if (p == end) break;
assert(*p == '>');
*p = 0;
if (*start == '/')
{
++start;
callback(xml_end_tag, start);
}
else
{
callback(xml_start_tag, start);
}
*p = '>';
}
}
}
#endif

View File

@ -5,7 +5,7 @@ bandwidth_manager.cpp entry.cpp escape_string.cpp \
peer_connection.cpp bt_peer_connection.cpp web_peer_connection.cpp \
natpmp.cpp piece_picker.cpp policy.cpp session.cpp session_impl.cpp sha1.cpp \
stat.cpp storage.cpp torrent.cpp torrent_handle.cpp \
torrent_info.cpp tracker_manager.cpp \
torrent_info.cpp tracker_manager.cpp http_connection.cpp \
http_tracker_connection.cpp udp_tracker_connection.cpp \
alert.cpp identify_client.cpp ip_filter.cpp file.cpp metadata_transfer.cpp \
logger.cpp file_pool.cpp ut_pex.cpp \
@ -40,6 +40,7 @@ $(top_srcdir)/include/libtorrent/file.hpp \
$(top_srcdir)/include/libtorrent/file_pool.hpp \
$(top_srcdir)/include/libtorrent/fingerprint.hpp \
$(top_srcdir)/include/libtorrent/hasher.hpp \
$(top_srcdir)/include/libtorrent/http_connection.hpp \
$(top_srcdir)/include/libtorrent/session_settings.hpp \
$(top_srcdir)/include/libtorrent/http_tracker_connection.hpp \
$(top_srcdir)/include/libtorrent/identify_client.hpp \
@ -70,6 +71,7 @@ $(top_srcdir)/include/libtorrent/torrent_info.hpp \
$(top_srcdir)/include/libtorrent/tracker_manager.hpp \
$(top_srcdir)/include/libtorrent/udp_tracker_connection.hpp \
$(top_srcdir)/include/libtorrent/utf8.hpp \
$(top_srcdir)/include/libtorrent/xml_parse.hpp \
$(top_srcdir)/include/libtorrent/version.hpp

206
src/http_connection.cpp Normal file
View File

@ -0,0 +1,206 @@
/*
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 "http_connection.hpp"
#include <asio/ip/tcp.hpp>
namespace libtorrent
{
void http_connection::get(std::string const& url, boost::posix_time::time_duration timeout)
{
std::string protocol;
std::string hostname;
std::string path;
int port;
boost::tie(protocol, hostname, port, path) = parse_url_components(url);
std::stringstream headers;
headers << "GET " << path << " HTTP/1.0\r\n"
"Host:" << hostname <<
"Connection: close\r\n"
"\r\n\r\n";
sendbuffer = headers.str();
start(hostname, boost::lexical_cast<std::string>(port), timeout);
}
void http_connection::start(std::string const& hostname, std::string const& port
, boost::posix_time::time_duration timeout)
{
m_timeout = timeout;
m_timer.expires_from_now(m_timeout);
m_timer.async_wait(bind(&http_connection::on_timeout, shared_from_this(), _1));
if (m_hostname == hostname && m_port == port)
{
m_parser.reset();
asio::async_write(m_sock, asio::buffer(sendbuffer)
, bind(&http_connection::on_write, shared_from_this(), _1));
}
else
{
m_sock.close();
tcp::resolver::query query(hostname, port);
m_resolver.async_resolve(query, bind(&http_connection::on_resolve
, shared_from_this(), _1, _2));
m_hostname = hostname;
m_port = port;
}
}
void http_connection::on_timeout(asio::error_code const& e)
{
if (e == asio::error::operation_aborted) return;
m_handler(asio::error::timed_out, m_parser, 0, 0);
}
void http_connection::close()
{
m_timer.cancel();
m_sock.close();
m_hostname.clear();
m_port.clear();
}
void http_connection::on_resolve(asio::error_code const& e
, tcp::resolver::iterator i)
{
if (e)
{
close();
m_handler(e, m_parser, 0, 0);
return;
}
assert(i != tcp::resolver::iterator());
m_sock.async_connect(*i, boost::bind(&http_connection::on_connect
, shared_from_this(), _1/*, ++i*/));
}
void http_connection::on_connect(asio::error_code const& e
/*, tcp::resolver::iterator i*/)
{
if (!e)
{
m_timer.expires_from_now(m_timeout);
m_timer.async_wait(bind(&http_connection::on_timeout, shared_from_this(), _1));
asio::async_write(m_sock, asio::buffer(sendbuffer)
, bind(&http_connection::on_write, shared_from_this(), _1));
}
/* else if (i != tcp::resolver::iterator())
{
// The connection failed. Try the next endpoint in the list.
m_sock.close();
m_sock.async_connect(*i, bind(&http_connection::on_connect
, shared_from_this(), _1, ++i));
}
*/ else
{
close();
m_handler(e, m_parser, 0, 0);
}
}
void http_connection::on_write(asio::error_code const& e)
{
if (e)
{
close();
m_handler(e, m_parser, 0, 0);
return;
}
std::string().swap(sendbuffer);
m_recvbuffer.resize(4096);
m_sock.async_read_some(asio::buffer(&m_recvbuffer[0] + m_read_pos
, m_recvbuffer.size() - m_read_pos)
, bind(&http_connection::on_read, shared_from_this(), _1, _2));
}
void http_connection::on_read(asio::error_code const& e
, std::size_t bytes_transferred)
{
if (e == asio::error::eof)
{
close();
m_handler(e, m_parser, 0, 0);
return;
}
if (e)
{
close();
m_handler(e, m_parser, 0, 0);
return;
}
m_read_pos += bytes_transferred;
assert(m_read_pos <= int(m_recvbuffer.size()));
if (m_bottled || !m_parser.header_finished())
{
libtorrent::buffer::const_interval rcv_buf(&m_recvbuffer[0]
, &m_recvbuffer[0] + m_read_pos);
m_parser.incoming(rcv_buf);
if (!m_bottled && m_parser.header_finished())
{
if (m_read_pos > m_parser.body_start())
m_handler(e, m_parser, &m_recvbuffer[0] + m_parser.body_start()
, m_read_pos - m_parser.body_start());
m_read_pos = 0;
m_timer.expires_from_now(m_timeout);
m_timer.async_wait(bind(&http_connection::on_timeout, shared_from_this(), _1));
}
else if (m_bottled && m_parser.finished())
{
m_handler(e, m_parser, 0, 0);
}
}
else
{
m_handler(e, m_parser, &m_recvbuffer[0], m_read_pos);
m_read_pos = 0;
}
if (int(m_recvbuffer.size()) == m_read_pos)
m_recvbuffer.resize(std::min(m_read_pos + 2048, 1024*500));
if (m_read_pos == 1024 * 500)
{
close();
m_handler(asio::error::eof, m_parser, 0, 0);
return;
}
m_sock.async_read_some(asio::buffer(&m_recvbuffer[0] + m_read_pos
, m_recvbuffer.size() - m_read_pos)
, bind(&http_connection::on_read
, shared_from_this(), _1, _2));
}
}

View File

@ -176,7 +176,7 @@ namespace libtorrent
boost::get<1>(ret) += newline - pos;
pos = newline;
std::string::size_type separator = line.find(": ");
std::string::size_type separator = line.find(':');
if (separator == std::string::npos)
{
// this means we got a blank line,
@ -193,7 +193,12 @@ namespace libtorrent
std::string name = line.substr(0, separator);
std::transform(name.begin(), name.end(), name.begin(), &to_lower);
std::string value = line.substr(separator + 2, std::string::npos);
++separator;
// skip whitespace
while (separator < line.size()
&& (line[separator] == ' ' || line[separator] == '\t'))
++separator;
std::string value = line.substr(separator, std::string::npos);
m_header.insert(std::make_pair(name, value));
if (name == "content-length")

View File

@ -250,6 +250,8 @@ void natpmp::on_reply(asio::error_code const& e
int public_port = read_uint16(in);
int lifetime = read_uint32(in);
(void)time; // to remove warning
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << to_simple_string(microsec_clock::universal_time())
<< " <== port map response: " << (cmd - 128 == 1 ? "udp" : "tcp")

View File

@ -1105,13 +1105,42 @@ namespace libtorrent
t->piece_failed(p.piece);
}
#ifndef NDEBUG
try
{
#endif
pol.piece_finished(p.piece, verified);
#ifndef NDEBUG
}
catch (std::exception const& e)
{
std::string err = e.what();
assert(false);
}
#endif
#ifndef NDEBUG
try
{
#endif
if (!was_seed && t->is_seed())
{
assert(verified);
t->completed();
}
#ifndef NDEBUG
}
catch (std::exception const& e)
{
std::string err = e.what();
assert(false);
}
#endif
}
#ifndef NDEBUG

545
src/upnp.cpp Normal file
View File

@ -0,0 +1,545 @@
/*
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"
#include "libtorrent/upnp.hpp"
#include "libtorrent/io.hpp"
#include "libtorrent/http_tracker_connection.hpp"
#include "libtorrent/xml_parse.hpp"
#include <boost/bind.hpp>
#include <boost/ref.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <asio/ip/host_name.hpp>
#include <asio/ip/multicast.hpp>
#include <cstdlib>
using boost::bind;
using namespace libtorrent;
using boost::posix_time::microsec_clock;
using boost::posix_time::milliseconds;
using boost::posix_time::seconds;
enum { num_mappings = 2 };
// UPnP multicast address and port
address_v4 multicast_address = address_v4::from_string("239.255.255.250");
udp::endpoint multicast_endpoint(multicast_address, 1900);
upnp::upnp(io_service& ios, address const& listen_interface
, std::string const& user_agent, portmap_callback_t const& cb)
: m_user_agent(user_agent)
, m_callback(cb)
, m_retry_count(0)
, m_socket(ios)
, m_broadcast_timer(ios)
, m_refresh_timer(ios)
, m_disabled(false)
{
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log.open("upnp.log", std::ios::in | std::ios::out | std::ios::trunc);
#endif
rebind(listen_interface);
}
void upnp::rebind(address const& listen_interface)
{
if (listen_interface.is_v4() && listen_interface != address_v4::from_string("0.0.0.0"))
{
m_local_ip = listen_interface.to_v4();
}
else
{
// make a best guess of the interface we're using and its IP
udp::resolver r(m_socket.io_service());
udp::resolver::iterator i = r.resolve(udp::resolver::query(asio::ip::host_name(), "0"));
for (;i != udp::resolver_iterator(); ++i)
{
if (i->endpoint().address().is_v4()) break;
}
if (i == udp::resolver_iterator())
{
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << "local host name did not resolve to an IPv4 address. "
"disabling UPnP" << std::endl;
#endif
m_disabled = true;
return;
}
m_local_ip = i->endpoint().address().to_v4();
}
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << to_simple_string(microsec_clock::universal_time())
<< " local ip: " << m_local_ip.to_string() << std::endl;
#endif
if ((m_local_ip.to_ulong() & 0xff000000) != 0x0a000000
&& (m_local_ip.to_ulong() & 0xfff00000) != 0xac100000
&& (m_local_ip.to_ulong() & 0xffff0000) != 0xc0a80000)
{
// the local address seems to be an external
// internet address. Assume it is not behind a NAT
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << "not on a NAT. disabling UPnP" << std::endl;
#endif
m_disabled = true;
return;
}
try
{
using namespace asio::ip::multicast;
m_socket.open(udp::v4());
m_socket.set_option(datagram_socket::reuse_address(true));
m_socket.bind(udp::endpoint(m_local_ip, 0));
m_socket.set_option(join_group(multicast_address));
m_socket.set_option(outbound_interface(m_local_ip));
}
catch (std::exception&)
{
m_disabled = true;
return;
}
m_disabled = false;
m_retry_count = 0;
discover_device();
}
void upnp::discover_device()
{
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";
m_socket.async_receive_from(asio::buffer(m_receive_buffer
, sizeof(m_receive_buffer)), m_remote, bind(&upnp::on_reply, this, _1, _2));
m_socket.send_to(asio::buffer(msearch, sizeof(msearch) - 1)
, multicast_endpoint);
++m_retry_count;
m_broadcast_timer.expires_from_now(milliseconds(250 * m_retry_count));
m_broadcast_timer.async_wait(bind(&upnp::resend_request, this, _1));
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << to_simple_string(microsec_clock::universal_time())
<< " ==> Broadcasting search for rootdevice" << std::endl;
#endif
}
void upnp::set_mappings(int tcp, int udp)
{
if (m_disabled) return;
/*
update_mapping(0, tcp);
update_mapping(1, udp);
*/
}
void upnp::resend_request(asio::error_code const& e)
{
using boost::posix_time::hours;
if (e) return;
if (m_retry_count >= 9)
{
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << to_simple_string(microsec_clock::universal_time())
<< " *** Got no response in 9 retries. Giving up, "
"disabling UPnP." << std::endl;
#endif
// try again in two hours
m_disabled = true;
return;
}
discover_device();
}
void upnp::on_reply(asio::error_code const& e
, std::size_t bytes_transferred)
{
using namespace libtorrent::detail;
using boost::posix_time::seconds;
if (e) return;
// since we're using udp, send the query 4 times
// just to make sure we find all devices
if (m_retry_count >= 4)
m_broadcast_timer.cancel();
// 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
*/
http_parser p;
try
{
p.incoming(buffer::const_interval(m_receive_buffer
, m_receive_buffer + bytes_transferred));
}
catch (std::exception& e)
{
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << to_simple_string(microsec_clock::universal_time())
<< " <== Rootdevice responded with incorrect HTTP packet: "
<< e.what() << ". Ignoring device" << std::endl;
#endif
return;
}
if (p.status_code() != 200)
{
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << to_simple_string(microsec_clock::universal_time())
<< " <== Rootdevice responded with HTTP status: " << p.status_code()
<< ". Ignoring device" << std::endl;
#endif
return;
}
if (!p.header_finished())
{
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << to_simple_string(microsec_clock::universal_time())
<< " <== Rootdevice responded with incomplete HTTP "
"packet. Ignoring device" << std::endl;
#endif
return;
}
std::string url = p.header<std::string>("location");
if (url.empty())
{
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << to_simple_string(microsec_clock::universal_time())
<< " <== Rootdevice response is missing a location header. "
"Ignoring device" << std::endl;
#endif
return;
}
rootdevice d;
d.url = url;
std::set<rootdevice>::iterator i = m_devices.find(d);
if (i == m_devices.end())
{
std::string protocol;
// we don't have this device in our list. Add it
boost::tie(protocol, d.hostname, d.port, d.path)
= parse_url_components(d.url);
if (protocol != "http")
{
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << to_simple_string(microsec_clock::universal_time())
<< " <== Rootdevice uses unsupported protocol: '" << protocol
<< "'. Ignoring device" << std::endl;
#endif
return;
}
if (d.port == 0)
{
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << to_simple_string(microsec_clock::universal_time())
<< " <== Rootdevice responded with a url with port 0. "
"Ignoring device" << std::endl;
#endif
return;
}
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << to_simple_string(microsec_clock::universal_time())
<< " <== Found rootdevice: " << d.url << std::endl;
#endif
boost::tie(i, boost::tuples::ignore) = m_devices.insert(d);
}
if (i->control_url.empty())
{
// we don't have a WANIP or WANPPP url for this device,
// ask for it
rootdevice& d = const_cast<rootdevice&>(*i);
d.upnp_connection.reset(new http_connection(m_socket.io_service()
, boost::bind(&upnp::on_upnp_xml, this, _1, _2, boost::ref(d))));
d.upnp_connection->get(d.url);
}
else if (!i->ports_mapped)
{
// TODO: initiate a port map operation if any is pending
}
}
void upnp::post(rootdevice& d, std::stringstream const& soap
, std::string const& soap_action)
{
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"
"Content-Length: " << soap.str().size() << "\r\n"
"Soapaction: \"" << d.service_namespace << "#" << soap_action << "\"\r\n\r\n" << soap.str();
d.upnp_connection->sendbuffer = header.str();
d.upnp_connection->start(d.hostname, boost::lexical_cast<std::string>(d.port)
, seconds(10));
}
void upnp::map_port(rootdevice& d, int i)
{
d.upnp_connection.reset(new http_connection(m_socket.io_service()
, boost::bind(&upnp::on_upnp_map_response, this, _1, _2
, boost::ref(d))));
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 << "\">";
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>"
"<NewInternalClient>" << m_local_ip.to_string() << "</NewInternalClient>"
"<NewEnabled>1</NewEnabled>"
"<NewPortMappingDescription>" << m_user_agent << "</NewPortMappingDescription>"
"<NewLeaseDuration>" << d.lease_duration << "</NewLeaseDuration>";
soap << "</u:" << soap_action << "></s:Body></s:Envelope>";
post(d, soap, soap_action);
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << to_simple_string(microsec_clock::universal_time())
<< " ==> AddPortMapping: " << soap.str() << std::endl;
#endif
}
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;
};
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;
}
}
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;
}
}
}
}
void upnp::on_upnp_xml(asio::error_code const& e
, libtorrent::http_parser const& p, rootdevice& d)
{
parse_state s;
s.reset("urn:schemas-upnp-org:service:WANIPConnection:1");
xml_parse((char*)p.get_body().begin, (char*)p.get_body().end
, boost::bind(&find_control_url, _1, _2, boost::ref(s)));
d.service_namespace = "urn:schemas-upnp-org:service:WANIPConnection:1";
if (!s.found_service)
{
// we didn't find the WAN IP connection, look for
// a PPP IP connection
s.reset("urn:schemas-upnp-org:service:PPPIPConnection:1");
xml_parse((char*)p.get_body().begin, (char*)p.get_body().end
, boost::bind(&find_control_url, _1, _2, boost::ref(s)));
d.service_namespace = "urn:schemas-upnp-org:service:WANPPPConnection:1";
}
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << to_simple_string(microsec_clock::universal_time())
<< " <== Rootdevice response, found control URL: " << s.control_url << std::endl;
#endif
d.control_url = s.control_url;
d.upnp_connection.reset();
map_port(d, 0);
}
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;
}
}
}
void upnp::on_upnp_map_response(asio::error_code const& e
, libtorrent::http_parser const& p, rootdevice& d)
{
if (e)
{
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << to_simple_string(microsec_clock::universal_time())
<< " <== error while adding portmap: " << e << std::endl;
#endif
return;
}
// 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>
error_code_parse_state s;
xml_parse((char*)p.get_body().begin, (char*)p.get_body().end
, bind(&find_error_code, _1, _2, boost::ref(s)));
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
if (s.error_code != -1)
{
m_log << to_simple_string(microsec_clock::universal_time())
<< " <== got error message: " << s.error_code << std::endl;
}
#endif
if (s.error_code == 725)
{
d.lease_duration = 0;
map_port(d, 0);
return;
}
std::cerr << std::string(p.get_body().begin, p.get_body().end) << std::endl;
}
void upnp::close()
{
if (m_disabled) return;
m_socket.close();
std::for_each(m_devices.begin(), m_devices.end()
, bind(&rootdevice::close, _1));
}