From 9e1123eb9eaa5083557d4606b9beaa490987cdf9 Mon Sep 17 00:00:00 2001
From: Arvid Norberg
Date: Sat, 31 Mar 2007 23:23:30 +0000
Subject: [PATCH] more UPnP work. updated ChangeLog and features documentation
---
ChangeLog | 1 +
docs/features.html | 2 +-
docs/features.rst | 2 +-
include/libtorrent/aux_/session_impl.hpp | 2 +
include/libtorrent/upnp.hpp | 46 ++++---
src/session_impl.cpp | 11 ++
src/upnp.cpp | 162 +++++++++++++++++++++--
7 files changed, 191 insertions(+), 35 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index 807cfc425..2ace30ac4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,4 @@
+ * added UPnP support
* fixed problem where peer interested flags were not updated correctly
when pieces were filtered
* improvements to ut_pex messages, including support for seed flag
diff --git a/docs/features.html b/docs/features.html
index c866c14dd..1e1dd486c 100644
--- a/docs/features.html
+++ b/docs/features.html
@@ -50,7 +50,7 @@ following features:
trackerless torrents (using the Mainline kademlia DHT protocol) with
some DHT extensions.
support for IPv6
-NAT-PMP support (automatic port mapping on routers that supports it)
+NAT-PMP and UPnP support (automatic port mapping on routers that supports it)
piece-wise, unordered, incremental file allocation
uses separate threads for checking files and for main downloader, with a
fool-proof thread-safe library interface. (i.e. There's no way for the
diff --git a/docs/features.rst b/docs/features.rst
index 04a21d6a9..2f4f83a32 100644
--- a/docs/features.rst
+++ b/docs/features.rst
@@ -32,7 +32,7 @@ following features:
* trackerless torrents (using the Mainline kademlia DHT protocol) with
some `DHT extensions`_.
* support for IPv6
-* NAT-PMP support (automatic port mapping on routers that supports it)
+* NAT-PMP and UPnP support (automatic port mapping on routers that supports it)
* piece-wise, unordered, incremental file allocation
* uses separate threads for checking files and for main downloader, with a
fool-proof thread-safe library interface. (i.e. There's no way for the
diff --git a/include/libtorrent/aux_/session_impl.hpp b/include/libtorrent/aux_/session_impl.hpp
index 5f2f9b208..d1a4d9702 100644
--- a/include/libtorrent/aux_/session_impl.hpp
+++ b/include/libtorrent/aux_/session_impl.hpp
@@ -78,6 +78,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/file_pool.hpp"
#include "libtorrent/bandwidth_manager.hpp"
#include "libtorrent/natpmp.hpp"
+#include "libtorrent/upnp.hpp"
namespace libtorrent
{
@@ -393,6 +394,7 @@ namespace libtorrent
int m_external_udp_port;
#endif
natpmp m_natpmp;
+ upnp m_upnp;
// the timer used to fire the second_tick
deadline_timer m_timer;
diff --git a/include/libtorrent/upnp.hpp b/include/libtorrent/upnp.hpp
index 05e963219..84e46646a 100644
--- a/include/libtorrent/upnp.hpp
+++ b/include/libtorrent/upnp.hpp
@@ -39,6 +39,8 @@ POSSIBILITY OF SUCH DAMAGE.
#include
#include
#include
+#include
+#include
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
#include
@@ -57,6 +59,7 @@ class upnp : boost::noncopyable
public:
upnp(io_service& ios, address const& listen_interface
, std::string const& user_agent, portmap_callback_t const& cb);
+ ~upnp();
void rebind(address const& listen_interface);
@@ -67,6 +70,9 @@ public:
void close();
private:
+
+ enum { num_mappings = 2 };
+ enum { default_lease_time = 3600 };
void update_mapping(int i, int port);
void resend_request(asio::error_code const& e);
@@ -81,32 +87,24 @@ private:
void on_upnp_map_response(asio::error_code const& e
, libtorrent::http_parser const& p, rootdevice& d
, int mapping);
-
-/*
- 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 on_upnp_unmap_response(asio::error_code const& e
+ , libtorrent::http_parser const& p, rootdevice& d
+ , int mapping);
+ void on_expire(asio::error_code const& e);
void post(rootdevice& d, std::stringstream const& s
, std::string const& soap_action);
void map_port(rootdevice& d, int i);
-
+ void unmap_port(rootdevice& d, int i);
+
struct mapping_t
{
mapping_t()
- : need_update(false)
- , local_port(0)
+ : 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;
@@ -125,8 +123,12 @@ private:
struct rootdevice
{
- rootdevice(): ports_mapped(false), lease_duration(3600)
- , supports_specific_external(true) {}
+ rootdevice(): lease_duration(default_lease_time)
+ , supports_specific_external(true)
+ {
+ mapping[0].protocol = 0;
+ mapping[1].protocol = 1;
+ }
// the interface url, through which the list of
// supported interfaces are fetched
std::string url;
@@ -136,10 +138,7 @@ private:
// 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;
+ mapping_t mapping[num_mappings];
std::string hostname;
int port;
@@ -188,7 +187,12 @@ private:
// timer used to refresh mappings
deadline_timer m_refresh_timer;
+ // locks m_closing and m_devices
+ boost::mutex m_mutex;
+ boost::condition m_condvar;
+
bool m_disabled;
+ bool m_closing;
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
std::ofstream m_log;
diff --git a/src/session_impl.cpp b/src/session_impl.cpp
index e4f10b30f..b975384a8 100755
--- a/src/session_impl.cpp
+++ b/src/session_impl.cpp
@@ -496,6 +496,9 @@ namespace libtorrent { namespace detail
#endif
, m_natpmp(m_io_service, m_listen_interface.address()
, bind(&session_impl::on_port_mapping, this, _1, _2, _3))
+ , m_upnp(m_io_service, m_listen_interface.address()
+ , m_settings.user_agent
+ , bind(&session_impl::on_port_mapping, this, _1, _2, _3))
, m_timer(m_io_service)
, m_checker_impl(*this)
{
@@ -687,6 +690,7 @@ namespace libtorrent { namespace detail
}
#endif
m_natpmp.set_mappings(m_listen_interface.port(), 0);
+ m_upnp.set_mappings(m_listen_interface.port(), 0);
if (m_listen_socket) async_accept();
}
@@ -1078,6 +1082,7 @@ namespace libtorrent { namespace detail
deadline_timer tracker_timer(m_io_service);
// this will remove the port mappings
m_natpmp.close();
+ m_upnp.close();
session_impl::mutex_t::scoped_lock l(m_mutex);
@@ -1471,8 +1476,12 @@ namespace libtorrent { namespace detail
m_dht->rebind(new_interface.address()
, m_dht_settings.service_port);
if (new_listen_address)
+ {
m_natpmp.rebind(new_interface.address());
+ m_upnp.rebind(new_interface.address());
+ }
m_natpmp.set_mappings(0, m_dht_settings.service_port);
+ m_upnp.set_mappings(0, m_dht_settings.service_port);
}
#endif
@@ -1595,6 +1604,7 @@ namespace libtorrent { namespace detail
}
m_external_udp_port = m_dht_settings.service_port;
m_natpmp.set_mappings(0, m_dht_settings.service_port);
+ m_upnp.set_mappings(0, m_dht_settings.service_port);
m_dht = new dht::dht_tracker(m_io_service
, m_dht_settings, m_listen_interface.address()
, startup_state);
@@ -1625,6 +1635,7 @@ namespace libtorrent { namespace detail
m_dht->rebind(m_listen_interface.address()
, settings.service_port);
m_natpmp.set_mappings(0, m_dht_settings.service_port);
+ m_upnp.set_mappings(0, m_dht_settings.service_port);
m_external_udp_port = settings.service_port;
}
m_dht_settings = settings;
diff --git a/src/upnp.cpp b/src/upnp.cpp
index 8c9d69e1a..712d5213c 100644
--- a/src/upnp.cpp
+++ b/src/upnp.cpp
@@ -41,6 +41,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include
#include
#include
+#include
#include
using boost::bind;
@@ -48,6 +49,8 @@ using namespace libtorrent;
using boost::posix_time::microsec_clock;
using boost::posix_time::milliseconds;
using boost::posix_time::seconds;
+using boost::posix_time::second_clock;
+using boost::posix_time::ptime;
enum { num_mappings = 2 };
@@ -64,6 +67,7 @@ upnp::upnp(io_service& ios, address const& listen_interface
, m_broadcast_timer(ios)
, m_refresh_timer(ios)
, m_disabled(false)
+ , m_closing(false)
{
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log.open("upnp.log", std::ios::in | std::ios::out | std::ios::trunc);
@@ -71,6 +75,13 @@ upnp::upnp(io_service& ios, address const& listen_interface
rebind(listen_interface);
}
+upnp::~upnp()
+{
+ boost::mutex::scoped_lock l(m_mutex);
+ while (!m_devices.empty())
+ m_condvar.wait(l);
+}
+
void upnp::rebind(address const& listen_interface)
{
if (listen_interface.is_v4() && listen_interface != address_v4::from_string("0.0.0.0"))
@@ -269,7 +280,9 @@ void upnp::on_reply(asio::error_code const& e
rootdevice d;
d.url = url;
-
+
+ boost::mutex::scoped_lock l(m_mutex);
+
std::set::iterator i = m_devices.find(d);
if (i == m_devices.end())
@@ -316,11 +329,6 @@ void upnp::on_reply(asio::error_code const& e
, 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
@@ -372,6 +380,33 @@ void upnp::map_port(rootdevice& d, int i)
}
+void upnp::unmap_port(rootdevice& d, int i)
+{
+ d.upnp_connection.reset(new http_connection(m_socket.io_service()
+ , boost::bind(&upnp::on_upnp_unmap_response, this, _1, _2
+ , boost::ref(d), i)));
+
+ std::string soap_action = "DeletePortMapping";
+
+ std::stringstream soap;
+
+ soap << "\n"
+ ""
+ "";
+
+ soap << ""
+ "" << d.mapping[i].external_port << ""
+ "" << (d.mapping[i].protocol ? "UDP" : "TCP") << "";
+ soap << "";
+
+ post(d, soap, soap_action);
+#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
+ m_log << to_simple_string(microsec_clock::universal_time())
+ << " ==> DeletePortMapping: " << soap.str() << std::endl;
+#endif
+}
+
namespace
{
struct parse_state
@@ -507,6 +542,10 @@ void upnp::on_upnp_map_response(asio::error_code const& e
return;
}
+ boost::mutex::scoped_lock l(m_mutex);
+ if (m_closing) return;
+ l.unlock();
+
// error code response may look like this:
//
@@ -568,9 +607,13 @@ void upnp::on_upnp_map_response(asio::error_code const& e
m_callback(0, 0, "UPnP mapping error " + boost::lexical_cast(s.error_code)
+ ": " + error_codes[s.error_code]);
}
-
- // TODO: update d.mapping[mapping].expires
- std::cerr << std::string(p.get_body().begin, p.get_body().end) << std::endl;
+
+#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
+ m_log << to_simple_string(microsec_clock::universal_time())
+ << " <== map response: " << std::string(p.get_body().begin, p.get_body().end)
+ << std::endl;
+#endif
+
if (s.error_code == -1)
{
int tcp = 0;
@@ -582,16 +625,111 @@ void upnp::on_upnp_map_response(asio::error_code const& e
udp = d.mapping[mapping].external_port;
m_callback(tcp, udp, "");
+ if (d.lease_duration > 0)
+ {
+ d.mapping[mapping].expires = second_clock::universal_time() + seconds(d.lease_duration * 0.75);
+ ptime next_expire = m_refresh_timer.expires_at();
+ if (next_expire == ptime(boost::date_time::not_a_date_time)
+ || next_expire < second_clock::universal_time()
+ || next_expire > d.mapping[mapping].expires)
+ {
+ m_refresh_timer.expires_at(d.mapping[mapping].expires);
+ m_refresh_timer.async_wait(bind(&upnp::on_expire, this, _1));
+ }
+ }
+ else
+ {
+ d.mapping[mapping].expires = ptime(boost::date_time::not_a_date_time);
+ }
}
- if (mapping < 1) map_port(d, mapping + 1);
+ if (mapping < num_mappings - 1)
+ map_port(d, mapping + 1);
+}
+
+void upnp::on_upnp_unmap_response(asio::error_code const& e
+ , libtorrent::http_parser const& p, rootdevice& d, int mapping)
+{
+ d.upnp_connection.reset();
+
+ if (e)
+ {
+#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
+ m_log << to_simple_string(microsec_clock::universal_time())
+ << " <== error while deleting portmap: " << e << std::endl;
+#endif
+ }
+
+#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
+ m_log << to_simple_string(microsec_clock::universal_time())
+ << " <== 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);
+ }
+ else
+ {
+ boost::mutex::scoped_lock l(m_mutex);
+ // the main thread is likely to be waiting for
+ // all the unmap operations to complete
+ m_devices.erase(d);
+ m_condvar.notify_all();
+ }
+}
+
+void upnp::on_expire(asio::error_code const& e)
+{
+ if (e) return;
+
+ ptime now = second_clock::universal_time();
+ ptime next_expire = ptime(boost::date_time::not_a_date_time);
+
+ boost::mutex::scoped_lock l(m_mutex);
+ for (std::set::iterator i = m_devices.begin()
+ , end(m_devices.end()); i != end; ++i)
+ {
+ rootdevice& d = const_cast(*i);
+ for (int m = 0; m < num_mappings; ++m)
+ {
+ if (d.mapping[m].expires != ptime(boost::date_time::not_a_date_time))
+ continue;
+
+ if (d.mapping[m].expires < now)
+ {
+ d.mapping[m].expires = ptime(boost::date_time::not_a_date_time);
+ map_port(d, m);
+ }
+ else if (next_expire == ptime(boost::date_time::not_a_date_time)
+ || d.mapping[m].expires < next_expire)
+ {
+ next_expire = d.mapping[m].expires;
+ }
+ }
+ }
+ if (next_expire != ptime(boost::date_time::not_a_date_time))
+ {
+ m_refresh_timer.expires_at(next_expire);
+ m_refresh_timer.async_wait(bind(&upnp::on_expire, this, _1));
+ }
}
void upnp::close()
{
if (m_disabled) return;
m_socket.close();
- std::for_each(m_devices.begin(), m_devices.end()
- , bind(&rootdevice::close, _1));
+
+ boost::mutex::scoped_lock l(m_mutex);
+ m_closing = true;
+
+ for (std::set::iterator i = m_devices.begin()
+ , end(m_devices.end()); i != end; ++i)
+ {
+ rootdevice& d = const_cast(*i);
+ unmap_port(d, 0);
+ }
}