revamped part of the port mapping code (UPnP and NAT-PMP). Added documentation for start_{lsd,natpmp,upnp} and stop_{lsd,natpmp,upnp}

This commit is contained in:
Arvid Norberg 2008-04-06 19:17:58 +00:00
parent 35fd9aec61
commit 2e6b9c2dce
13 changed files with 720 additions and 333 deletions

View File

@ -161,6 +161,15 @@ The ``session`` class has the following synopsis::
, int> const& node); , int> const& node);
void add_dht_router(std::pair<std::string void add_dht_router(std::pair<std::string
, int> const& node); , int> const& node);
void start_lsd();
void stop_lsd();
boost::intrusive_ptr<upnp> start_upnp();
void stop_upnp();
boost::intrusvice_ptr<natpmp> start_natpmp();
void stop_natpmp();
}; };
Once it's created, the session object will spawn the main thread that will do all the work. Once it's created, the session object will spawn the main thread that will do all the work.
@ -855,6 +864,47 @@ An example routing node that you could typically add is
``router.bittorrent.com``. ``router.bittorrent.com``.
start_lsd() stop_lsd()
----------------------
::
void start_lsd();
void stop_lsd();
Starts and stops Local Service Discovery. This service will broadcast
the infohashes of all the non-private torrents on the local network to
look for peers on the same swarm within multicast reach.
It is turned off by default.
start_upnp() stop_upnp()
------------------------
::
boost::intrusive_ptr<upnp> start_upnp();
void stop_upnp();
Starts and stops the UPnP service. When started, the listen port and the DHT
port are attempted to be forwarded on local UPnP router devices.
It is off by default.
start_natpmp() stop_natpmp()
----------------------------
::
boost::intrusvice_ptr<natpmp> start_natpmp();
void stop_natpmp();
Starts and stops the NAT-PMP service. When started, the listen port and the DHT
port are attempted to be forwarded on the router through NAT-PMP.
It is off by default.
entry entry
===== =====

View File

@ -426,20 +426,25 @@ namespace libtorrent
struct TORRENT_EXPORT portmap_error_alert: alert struct TORRENT_EXPORT portmap_error_alert: alert
{ {
portmap_error_alert(const std::string& msg) portmap_error_alert(int i, const std::string& msg)
: alert(alert::warning, msg) : mapping(i), alert(alert::warning, msg)
{} {}
int mapping;
virtual std::auto_ptr<alert> clone() const virtual std::auto_ptr<alert> clone() const
{ return std::auto_ptr<alert>(new portmap_error_alert(*this)); } { return std::auto_ptr<alert>(new portmap_error_alert(*this)); }
}; };
struct TORRENT_EXPORT portmap_alert: alert struct TORRENT_EXPORT portmap_alert: alert
{ {
portmap_alert(const std::string& msg) portmap_alert(int i, int port, const std::string& msg)
: alert(alert::info, msg) : mapping(i), external_port(port), alert(alert::info, msg)
{} {}
int mapping;
int external_port;
virtual std::auto_ptr<alert> clone() const virtual std::auto_ptr<alert> clone() const
{ return std::auto_ptr<alert>(new portmap_alert(*this)); } { return std::auto_ptr<alert>(new portmap_alert(*this)); }
}; };

View File

@ -185,7 +185,8 @@ namespace libtorrent
// called when a port mapping is successful, or a router returns // called when a port mapping is successful, or a router returns
// a failure to map a port // a failure to map a port
void on_port_mapping(int tcp_port, int udp_port, std::string const& errmsg); void on_port_mapping(int mapping, int port, std::string const& errmsg
, int nat_transport);
bool is_aborted() const { return m_abort; } bool is_aborted() const { return m_abort; }
@ -319,8 +320,8 @@ namespace libtorrent
} }
#endif #endif
void start_lsd(); void start_lsd();
void start_natpmp(); boost::intrusive_ptr<natpmp> start_natpmp();
void start_upnp(); boost::intrusive_ptr<upnp> start_upnp();
void stop_lsd(); void stop_lsd();
void stop_natpmp(); void stop_natpmp();
@ -537,6 +538,10 @@ namespace libtorrent
boost::intrusive_ptr<upnp> m_upnp; boost::intrusive_ptr<upnp> m_upnp;
boost::intrusive_ptr<lsd> m_lsd; boost::intrusive_ptr<lsd> m_lsd;
// 0 is natpmp 1 is upnp
int m_tcp_mapping[2];
int m_udp_mapping[2];
// the timer used to fire the second_tick // the timer used to fire the second_tick
deadline_timer m_timer; deadline_timer m_timer;

View File

@ -37,6 +37,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/intrusive_ptr_base.hpp" #include "libtorrent/intrusive_ptr_base.hpp"
#include <boost/function.hpp> #include <boost/function.hpp>
#include <boost/thread/mutex.hpp>
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
#include <fstream> #include <fstream>
@ -45,8 +46,8 @@ POSSIBILITY OF SUCH DAMAGE.
namespace libtorrent namespace libtorrent
{ {
// int: external tcp port // int: port mapping index
// int: external udp port // int: external port
// std::string: error message // std::string: error message
typedef boost::function<void(int, int, std::string const&)> portmap_callback_t; typedef boost::function<void(int, int, std::string const&)> portmap_callback_t;
@ -59,36 +60,38 @@ public:
// maps the ports, if a port is set to 0 // maps the ports, if a port is set to 0
// it will not be mapped // it will not be mapped
void set_mappings(int tcp, int udp); enum protocol_type { none = 0, udp = 1, tcp = 2 };
int add_mapping(protocol_type p, int external_port, int local_port);
void delete_mapping(int mapping_index);
void close(); void close();
private: private:
void update_mapping(int i, int port); void update_mapping(int i);
void send_map_request(int i); void send_map_request(int i);
void resend_request(int i, asio::error_code const& e); void resend_request(int i, asio::error_code const& e);
void on_reply(asio::error_code const& e void on_reply(asio::error_code const& e
, std::size_t bytes_transferred); , std::size_t bytes_transferred);
void try_next_mapping(int i); void try_next_mapping(int i);
void update_expiration_timer(); void update_expiration_timer();
void refresh_mapping(int i);
void mapping_expired(asio::error_code const& e, int i); void mapping_expired(asio::error_code const& e, int i);
void disable(char const* message); void disable(char const* message);
struct mapping struct mapping_t
{ {
mapping() enum action_t { action_none, action_add, action_delete };
: need_update(false) mapping_t()
: action(action_none)
, local_port(0) , local_port(0)
, external_port(0) , external_port(0)
, protocol(1) , protocol(none)
{} {}
// indicates that the mapping has changed // indicates that the mapping has changed
// and needs an update // and needs an update
bool need_update; int action;
// the time the port mapping will expire // the time the port mapping will expire
ptime expires; ptime expires;
@ -102,14 +105,12 @@ private:
// should announce to others // should announce to others
int external_port; int external_port;
// 1 = udp, 2 = tcp
int protocol; int protocol;
}; };
portmap_callback_t m_callback; portmap_callback_t m_callback;
// 0 is tcp and 1 is udp std::vector<mapping_t> m_mappings;
mapping m_mappings[2];
// the endpoint to the nat router // the endpoint to the nat router
udp::endpoint m_nat_endpoint; udp::endpoint m_nat_endpoint;
@ -138,9 +139,17 @@ private:
// timer used to refresh mappings // timer used to refresh mappings
deadline_timer m_refresh_timer; deadline_timer m_refresh_timer;
// the mapping index that will expire next
int m_next_refresh;
bool m_disabled; bool m_disabled;
bool m_abort;
typedef boost::mutex mutex_t;
mutex_t m_mutex;
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
std::ofstream m_log; std::ofstream m_log;
#endif #endif

View File

@ -284,8 +284,8 @@ namespace libtorrent
// starts/stops UPnP, NATPMP or LSD port mappers // starts/stops UPnP, NATPMP or LSD port mappers
// they are stopped by default // they are stopped by default
void start_lsd(); void start_lsd();
void start_natpmp(); boost::intrusive_ptr<natpmp> start_natpmp();
void start_upnp(); boost::intrusive_ptr<upnp> start_upnp();
void stop_lsd(); void stop_lsd();
void stop_natpmp(); void stop_natpmp();

View File

@ -58,9 +58,10 @@ POSSIBILITY OF SUCH DAMAGE.
namespace libtorrent namespace libtorrent
{ {
// int: external tcp port // int: port-mapping index
// int: external udp port // int: external port
// std::string: error message // std::string: error message
// an empty string as error means success
typedef boost::function<void(int, int, std::string const&)> portmap_callback_t; typedef boost::function<void(int, int, std::string const&)> portmap_callback_t;
class upnp : public intrusive_ptr_base<upnp> class upnp : public intrusive_ptr_base<upnp>
@ -71,29 +72,35 @@ public:
, portmap_callback_t const& cb, bool ignore_nonrouters); , portmap_callback_t const& cb, bool ignore_nonrouters);
~upnp(); ~upnp();
// maps the ports, if a port is set to 0 enum protocol_type { none = 0, tcp = 1, udp = 2 };
// it will not be mapped int add_mapping(protocol_type p, int external_port, int local_port);
void set_mappings(int tcp, int udp); void delete_mapping(int index);
void discover_device(); void discover_device();
void close(); void close();
std::string router_model() { return m_model; } std::string router_model()
{
mutex_t::scoped_lock l(m_mutex);
return m_model;
}
private: private:
void discover_device_impl();
static address_v4 upnp_multicast_address; static address_v4 upnp_multicast_address;
static udp::endpoint upnp_multicast_endpoint; static udp::endpoint upnp_multicast_endpoint;
enum { num_mappings = 2 };
enum { default_lease_time = 3600 }; enum { default_lease_time = 3600 };
void update_mapping(int i, int port);
void resend_request(asio::error_code const& e); void resend_request(asio::error_code const& e);
void on_reply(udp::endpoint const& from, char* buffer void on_reply(udp::endpoint const& from, char* buffer
, std::size_t bytes_transferred); , std::size_t bytes_transferred);
struct rootdevice; struct rootdevice;
void next(rootdevice& d, int i);
void update_map(rootdevice& d, int i);
void on_upnp_xml(asio::error_code const& e void on_upnp_xml(asio::error_code const& e
, libtorrent::http_parser const& p, rootdevice& d); , libtorrent::http_parser const& p, rootdevice& d);
@ -105,30 +112,43 @@ private:
, int mapping); , int mapping);
void on_expire(asio::error_code const& e); void on_expire(asio::error_code const& e);
void map_port(rootdevice& d, int i); void disable(char const* msg);
void unmap_port(rootdevice& d, int i); void return_error(int mapping, int code);
void disable();
void return_error(int code);
void delete_port_mapping(rootdevice& d, int i); void delete_port_mapping(rootdevice& d, int i);
void create_port_mapping(http_connection& c, rootdevice& d, int i); void create_port_mapping(http_connection& c, rootdevice& d, int i);
void post(upnp::rootdevice const& d, std::string const& soap void post(upnp::rootdevice const& d, std::string const& soap
, std::string const& soap_action); , std::string const& soap_action);
int num_mappings() const { return int(m_mappings.size()); }
struct global_mapping_t
{
global_mapping_t()
: protocol(none)
, external_port(0)
, local_port(0)
{}
int protocol;
int external_port;
int local_port;
};
struct mapping_t struct mapping_t
{ {
enum action_t { action_none, action_add, action_delete };
mapping_t() mapping_t()
: need_update(false) : action(action_none)
, local_port(0) , local_port(0)
, external_port(0) , external_port(0)
, protocol(1) , protocol(none)
, failcount(0) , failcount(0)
{} {}
// the time the port mapping will expire // the time the port mapping will expire
ptime expires; ptime expires;
bool need_update; int action;
// the local port for this mapping. If this is set // the local port for this mapping. If this is set
// to 0, the mapping is not in use // to 0, the mapping is not in use
@ -139,7 +159,7 @@ private:
// should announce to others // should announce to others
int external_port; int external_port;
// 1 = udp, 0 = tcp // 2 = udp, 1 = tcp
int protocol; int protocol;
// the number of times this mapping has failed // the number of times this mapping has failed
@ -153,8 +173,6 @@ private:
, supports_specific_external(true) , supports_specific_external(true)
, disabled(false) , disabled(false)
{ {
mapping[0].protocol = 0;
mapping[1].protocol = 1;
#ifndef NDEBUG #ifndef NDEBUG
magic = 1337; magic = 1337;
#endif #endif
@ -167,7 +185,7 @@ private:
magic = 0; magic = 0;
} }
#endif #endif
// the interface url, through which the list of // the interface url, through which the list of
// supported interfaces are fetched // supported interfaces are fetched
std::string url; std::string url;
@ -177,7 +195,7 @@ private:
// either the WANIP namespace or the WANPPP namespace // either the WANIP namespace or the WANPPP namespace
char const* service_namespace; char const* service_namespace;
mapping_t mapping[num_mappings]; std::vector<mapping_t> mapping;
std::string hostname; std::string hostname;
int port; int port;
@ -207,8 +225,7 @@ private:
{ return url < rhs.url; } { return url < rhs.url; }
}; };
int m_udp_local_port; std::vector<global_mapping_t> m_mappings;
int m_tcp_local_port;
std::string const& m_user_agent; std::string const& m_user_agent;
@ -239,6 +256,9 @@ private:
connection_queue& m_cc; connection_queue& m_cc;
typedef boost::mutex mutex_t;
mutex_t m_mutex;
std::string m_model; std::string m_model;
#ifdef TORRENT_UPNP_LOGGING #ifdef TORRENT_UPNP_LOGGING

View File

@ -43,8 +43,6 @@ POSSIBILITY OF SUCH DAMAGE.
using boost::bind; using boost::bind;
using namespace libtorrent; using namespace libtorrent;
enum { num_mappings = 2 };
natpmp::natpmp(io_service& ios, address const& listen_interface, portmap_callback_t const& cb) natpmp::natpmp(io_service& ios, address const& listen_interface, portmap_callback_t const& cb)
: m_callback(cb) : m_callback(cb)
, m_currently_mapping(-1) , m_currently_mapping(-1)
@ -52,11 +50,9 @@ natpmp::natpmp(io_service& ios, address const& listen_interface, portmap_callbac
, m_socket(ios) , m_socket(ios)
, m_send_timer(ios) , m_send_timer(ios)
, m_refresh_timer(ios) , m_refresh_timer(ios)
, m_next_refresh(-1)
, m_disabled(false) , m_disabled(false)
{ {
m_mappings[0].protocol = 2; // tcp
m_mappings[1].protocol = 1; // udp
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log.open("natpmp.log", std::ios::in | std::ios::out | std::ios::trunc); m_log.open("natpmp.log", std::ios::in | std::ios::out | std::ios::trunc);
#endif #endif
@ -65,6 +61,8 @@ natpmp::natpmp(io_service& ios, address const& listen_interface, portmap_callbac
void natpmp::rebind(address const& listen_interface) void natpmp::rebind(address const& listen_interface)
{ {
mutex_t::scoped_lock l(m_mutex);
asio::error_code ec; asio::error_code ec;
address gateway = get_default_gateway(m_socket.get_io_service(), listen_interface, ec); address gateway = get_default_gateway(m_socket.get_io_service(), listen_interface, ec);
if (ec) if (ec)
@ -101,42 +99,131 @@ void natpmp::rebind(address const& listen_interface)
return; return;
} }
for (int i = 0; i < num_mappings; ++i) for (std::vector<mapping_t>::iterator i = m_mappings.begin()
, end(m_mappings.end()); i != end; ++i)
{ {
if (m_mappings[i].local_port == 0) if (i->protocol != none
|| i->action != mapping_t::action_none)
continue; continue;
refresh_mapping(i); i->action = mapping_t::action_add;
update_mapping(i - m_mappings.begin());
} }
} }
void natpmp::disable(char const* message) void natpmp::disable(char const* message)
{ {
m_disabled = true; m_disabled = true;
std::stringstream msg;
msg << "NAT-PMP disabled: " << message; for (std::vector<mapping_t>::iterator i = m_mappings.begin()
, end(m_mappings.end()); i != end; ++i)
{
if (i->protocol == none) continue;
i->protocol = none;
m_callback(i - m_mappings.begin(), 0, message);
}
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << msg.str() << std::endl; m_log << time_now_string() << " NAT-PMP disabled: " << message << std::endl;
#endif #endif
m_callback(0, 0, msg.str()); close();
}
void natpmp::delete_mapping(int index)
{
TORRENT_ASSERT(index < int(m_mappings.size()) && index >= 0);
if (index >= int(m_mappings.size()) || index < 0) return;
mapping_t& m = m_mappings[index];
if (m.protocol == none) return;
m.action = mapping_t::action_delete;
update_mapping(index);
} }
void natpmp::set_mappings(int tcp, int udp) int natpmp::add_mapping(protocol_type p, int external_port, int local_port)
{ {
if (m_disabled) return; mutex_t::scoped_lock l(m_mutex);
update_mapping(0, tcp);
update_mapping(1, udp); if (m_disabled) return -1;
std::vector<mapping_t>::iterator i = std::find_if(m_mappings.begin()
, m_mappings.end(), boost::bind(&mapping_t::protocol, _1) == int(none));
if (i == m_mappings.end())
{
m_mappings.push_back(mapping_t());
i = m_mappings.end() - 1;
}
i->protocol = p;
i->external_port = external_port;
i->local_port = local_port;
i->action = mapping_t::action_add;
int mapping_index = i - m_mappings.begin();
update_mapping(mapping_index);
return mapping_index;
} }
void natpmp::update_mapping(int i, int port) void natpmp::try_next_mapping(int i)
{ {
natpmp::mapping& m = m_mappings[i]; #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
if (port <= 0) return; m_log << time_now_string() << " try_next_mapping [ " << i << " ]" << std::endl;
if (m.local_port != port) #endif
m.need_update = true;
m.local_port = port; #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
// prefer the same external port as the local port ptime now = time_now();
if (m.external_port == 0) m.external_port = port; for (std::vector<mapping_t>::iterator i = m_mappings.begin()
, end(m_mappings.end()); i != end; ++i)
{
m_log << " " << (i - m_mappings.begin()) << " [ "
"proto: " << (i->protocol == none ? "none" : i->protocol == tcp ? "tcp" : "udp")
<< " port: " << i->external_port
<< " local-port: " << i->local_port
<< " action: " << (i->action == mapping_t::action_none ? "none" : i->action == mapping_t::action_add ? "add" : "delete")
<< " ttl: " << total_seconds(i->expires - now)
<< " ]" << std::endl;
}
#endif
if (i < int(m_mappings.size()) - 1)
{
update_mapping(i + 1);
return;
}
std::vector<mapping_t>::iterator m = std::find_if(
m_mappings.begin(), m_mappings.end()
, boost::bind(&mapping_t::action, _1) != int(mapping_t::action_none));
if (m == m_mappings.end())
{
if (m_abort)
{
asio::error_code ec;
m_send_timer.cancel(ec);
m_socket.close(ec);
}
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << " done" << (m_abort?" shutting down":"") << std::endl;
#endif
return;
}
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << " updating " << (m - m_mappings.begin()) << std::endl;
#endif
update_mapping(m - m_mappings.begin());
}
void natpmp::update_mapping(int i)
{
natpmp::mapping_t& m = m_mappings[i];
if (m.action == mapping_t::action_none
|| m.protocol == none)
{
try_next_mapping(i);
return;
}
if (m_currently_mapping == -1) if (m_currently_mapping == -1)
{ {
@ -156,7 +243,8 @@ void natpmp::send_map_request(int i)
TORRENT_ASSERT(m_currently_mapping == -1 TORRENT_ASSERT(m_currently_mapping == -1
|| m_currently_mapping == i); || m_currently_mapping == i);
m_currently_mapping = i; m_currently_mapping = i;
mapping& m = m_mappings[i]; mapping_t& m = m_mappings[i];
TORRENT_ASSERT(m.action != mapping_t::action_none);
char buf[12]; char buf[12];
char* out = buf; char* out = buf;
write_uint8(0, out); // NAT-PMP version write_uint8(0, out); // NAT-PMP version
@ -164,14 +252,16 @@ void natpmp::send_map_request(int i)
write_uint16(0, out); // reserved write_uint16(0, out); // reserved
write_uint16(m.local_port, out); // private port write_uint16(m.local_port, out); // private port
write_uint16(m.external_port, out); // requested public port write_uint16(m.external_port, out); // requested public port
int ttl = m.external_port == 0 ? 0 : 3600; int ttl = m.action == mapping_t::action_add ? 3600 : 0;
write_uint32(ttl, out); // port mapping lifetime write_uint32(ttl, out); // port mapping lifetime
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << time_now_string() m_log << time_now_string()
<< " ==> port map request: " << (m.protocol == 1 ? "udp" : "tcp") << " ==> port map ["
<< " action: " << (m.action == mapping_t::action_add ? "add" : "delete") << " "
<< " proto: " << (m.protocol == udp ? "udp" : "tcp")
<< " local: " << m.local_port << " external: " << m.external_port << " local: " << m.local_port << " external: " << m.external_port
<< " ttl: " << ttl << std::endl; << " ttl: " << ttl << " ]" << std::endl;
#endif #endif
asio::error_code ec; asio::error_code ec;
@ -185,12 +275,16 @@ void natpmp::send_map_request(int i)
void natpmp::resend_request(int i, asio::error_code const& e) void natpmp::resend_request(int i, asio::error_code const& e)
{ {
if (e) return; if (e) return;
mutex_t::scoped_lock l(m_mutex);
if (m_currently_mapping != i) return; if (m_currently_mapping != i) return;
if (m_retry_count >= 9) if (m_retry_count >= 9)
{ {
m_mappings[i].need_update = false; m_currently_mapping = -1;
m_mappings[i].action = mapping_t::action_none;
// try again in two hours // try again in two hours
m_mappings[i].expires = time_now() + hours(2); m_mappings[i].expires = time_now() + hours(2);
try_next_mapping(i);
return; return;
} }
send_map_request(i); send_map_request(i);
@ -209,12 +303,14 @@ void natpmp::on_reply(asio::error_code const& e
return; return;
} }
mutex_t::scoped_lock l(m_mutex);
asio::error_code ec; asio::error_code ec;
m_send_timer.cancel(ec); m_send_timer.cancel(ec);
TORRENT_ASSERT(m_currently_mapping >= 0); TORRENT_ASSERT(m_currently_mapping >= 0);
int i = m_currently_mapping; int i = m_currently_mapping;
mapping& m = m_mappings[i]; mapping_t& m = m_mappings[i];
char* in = m_response_buffer; char* in = m_response_buffer;
int version = read_uint8(in); int version = read_uint8(in);
@ -229,9 +325,10 @@ void natpmp::on_reply(asio::error_code const& e
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << time_now_string() m_log << time_now_string()
<< " <== port map response: " << (cmd - 128 == 1 ? "udp" : "tcp") << " <== port map ["
<< " protocol: " << (cmd - 128 == 1 ? "udp" : "tcp")
<< " local: " << private_port << " external: " << public_port << " local: " << private_port << " external: " << public_port
<< " ttl: " << lifetime << std::endl; << " ttl: " << lifetime << " ]" << std::endl;
#endif #endif
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
@ -259,7 +356,7 @@ void natpmp::on_reply(asio::error_code const& e
{ {
// this means the mapping was // this means the mapping was
// successfully closed // successfully closed
m.local_port = 0; m.protocol = none;
} }
else else
{ {
@ -282,21 +379,16 @@ void natpmp::on_reply(asio::error_code const& e
case 4: errmsg << "Out of resources"; break; case 4: errmsg << "Out of resources"; break;
case 5: errmsg << "Unsupported opcode"; break; case 5: errmsg << "Unsupported opcode"; break;
} }
m_mappings[i].expires = time_now() + hours(2); m.expires = time_now() + hours(2);
m_callback(0, 0, errmsg.str()); m_callback(i, 0, errmsg.str());
} }
else if (m.local_port != 0) else if (m.action == mapping_t::action_add)
{ {
// don't report when we remove mappings m_callback(i, m.external_port, "");
int tcp_port = 0;
int udp_port = 0;
if (m.protocol == 1) udp_port = m.external_port;
else tcp_port = public_port;
m_callback(tcp_port, udp_port, "");
} }
m_currently_mapping = -1; m_currently_mapping = -1;
m_mappings[i].need_update = false; m.action = mapping_t::action_none;
m_send_timer.cancel(ec); m_send_timer.cancel(ec);
update_expiration_timer(); update_expiration_timer();
try_next_mapping(i); try_next_mapping(i);
@ -304,69 +396,95 @@ void natpmp::on_reply(asio::error_code const& e
void natpmp::update_expiration_timer() void natpmp::update_expiration_timer()
{ {
if (m_abort) return;
ptime now = time_now(); ptime now = time_now();
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << time_now_string() << " update_expiration_timer " << std::endl;
for (std::vector<mapping_t>::iterator i = m_mappings.begin()
, end(m_mappings.end()); i != end; ++i)
{
m_log << " " << (i - m_mappings.begin()) << " [ "
"proto: " << (i->protocol == none ? "none" : i->protocol == tcp ? "tcp" : "udp")
<< " port: " << i->external_port
<< " local-port: " << i->local_port
<< " action: " << (i->action == mapping_t::action_none ? "none" : i->action == mapping_t::action_add ? "add" : "delete")
<< " ttl: " << total_seconds(i->expires - now)
<< " ]" << std::endl;
}
#endif
ptime min_expire = now + seconds(3600); ptime min_expire = now + seconds(3600);
int min_index = -1; int min_index = -1;
for (int i = 0; i < num_mappings; ++i) for (std::vector<mapping_t>::iterator i = m_mappings.begin()
if (m_mappings[i].expires < min_expire , end(m_mappings.end()); i != end; ++i)
&& m_mappings[i].local_port != 0) {
if (i->protocol == none
|| i->action != mapping_t::action_none) continue;
if (i->expires < min_expire)
{ {
min_expire = m_mappings[i].expires; min_expire = i->expires;
min_index = i; min_index = i - m_mappings.begin();
} }
}
// this is already the mapping we're waiting for
if (m_next_refresh == min_index) return;
if (min_index >= 0) if (min_index >= 0)
{ {
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << time_now_string() << " next expiration ["
" i: " << min_index
<< " ttl: " << total_seconds(min_expire - time_now())
<< " ]" << std::endl;
#endif
asio::error_code ec; asio::error_code ec;
if (m_next_refresh >= 0) m_refresh_timer.cancel(ec);
m_refresh_timer.expires_from_now(min_expire - now, ec); m_refresh_timer.expires_from_now(min_expire - now, ec);
m_refresh_timer.async_wait(bind(&natpmp::mapping_expired, self(), _1, min_index)); m_refresh_timer.async_wait(bind(&natpmp::mapping_expired, self(), _1, min_index));
m_next_refresh = min_index;
} }
} }
void natpmp::mapping_expired(asio::error_code const& e, int i) void natpmp::mapping_expired(asio::error_code const& e, int i)
{ {
if (e) return; if (e) return;
mutex_t::scoped_lock l(m_mutex);
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << "*** mapping " << i << " expired, updating" << std::endl; m_log << time_now_string() << " mapping expired [ i: " << i << " ]" << std::endl;
#endif #endif
refresh_mapping(i); m_mappings[i].action = mapping_t::action_add;
} if (m_next_refresh == i) m_next_refresh = -1;
update_mapping(i);
void natpmp::refresh_mapping(int i)
{
m_mappings[i].need_update = true;
if (m_currently_mapping == -1)
{
// the socket is not currently in use
// send out a mapping request
m_retry_count = 0;
send_map_request(i);
m_socket.async_receive_from(asio::buffer(&m_response_buffer, 16)
, m_remote, bind(&natpmp::on_reply, self(), _1, _2));
}
}
void natpmp::try_next_mapping(int i)
{
++i;
if (i >= num_mappings) i = 0;
if (m_mappings[i].need_update)
refresh_mapping(i);
} }
void natpmp::close() void natpmp::close()
{ {
mutex_t::scoped_lock l(m_mutex);
m_abort = true;
asio::error_code ec; asio::error_code ec;
m_socket.close(ec); #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << time_now_string() << " close" << std::endl;
#endif
if (m_disabled) return; if (m_disabled) return;
for (int i = 0; i < num_mappings; ++i) ptime now = time_now();
for (std::vector<mapping_t>::iterator i = m_mappings.begin()
, end(m_mappings.end()); i != end; ++i)
{ {
if (m_mappings[i].local_port == 0) #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
continue; m_log << " " << (i - m_mappings.begin()) << " [ "
m_mappings[i].external_port = 0; "proto: " << (i->protocol == none ? "none" : i->protocol == tcp ? "tcp" : "udp")
refresh_mapping(i); << " port: " << i->external_port
<< " local-port: " << i->local_port
<< " action: " << (i->action == mapping_t::action_none ? "none" : i->action == mapping_t::action_add ? "add" : "delete")
<< " ttl: " << total_seconds(i->expires - now)
<< " ]" << std::endl;
#endif
if (i->protocol == none) continue;
i->action = mapping_t::action_delete;
} }
m_refresh_timer.cancel(ec); m_refresh_timer.cancel(ec);
m_send_timer.cancel(ec); update_mapping(0);
} }

View File

@ -479,14 +479,14 @@ namespace libtorrent
m_impl->start_lsd(); m_impl->start_lsd();
} }
void session::start_natpmp() boost::intrusive_ptr<natpmp> session::start_natpmp()
{ {
m_impl->start_natpmp(); return m_impl->start_natpmp();
} }
void session::start_upnp() boost::intrusive_ptr<upnp> session::start_upnp()
{ {
m_impl->start_upnp(); return m_impl->start_upnp();
} }
void session::stop_lsd() void session::stop_lsd()

View File

@ -174,6 +174,10 @@ namespace aux {
, m_geoip_db(0) , m_geoip_db(0)
#endif #endif
{ {
m_tcp_mapping[0] = -1;
m_tcp_mapping[1] = -1;
m_udp_mapping[0] = -1;
m_udp_mapping[1] = -1;
#ifdef WIN32 #ifdef WIN32
// windows XP has a limit on the number of // windows XP has a limit on the number of
// simultaneous half-open TCP connections // simultaneous half-open TCP connections
@ -668,8 +672,18 @@ namespace aux {
tcp::endpoint local = m_listen_sockets.front().sock->local_endpoint(ec); tcp::endpoint local = m_listen_sockets.front().sock->local_endpoint(ec);
if (!ec) if (!ec)
{ {
if (m_natpmp.get()) m_natpmp->set_mappings(local.port(), 0); if (m_natpmp.get())
if (m_upnp.get()) m_upnp->set_mappings(local.port(), 0); {
if (m_tcp_mapping[0] != -1) m_natpmp->delete_mapping(m_tcp_mapping[0]);
m_tcp_mapping[0] = m_natpmp->add_mapping(natpmp::tcp
, local.port(), local.port());
}
if (m_upnp.get())
{
if (m_tcp_mapping[1] != -1) m_upnp->delete_mapping(m_tcp_mapping[1]);
m_tcp_mapping[1] = m_upnp->add_mapping(upnp::tcp
, local.port(), local.port());
}
} }
} }
} }
@ -1667,9 +1681,19 @@ namespace aux {
// the listen interface changed, rebind the dht listen socket as well // the listen interface changed, rebind the dht listen socket as well
m_dht_socket.bind(m_dht_settings.service_port); m_dht_socket.bind(m_dht_settings.service_port);
if (m_natpmp.get()) if (m_natpmp.get())
m_natpmp->set_mappings(0, m_dht_settings.service_port); {
if (m_udp_mapping[0] != -1) m_natpmp->delete_mapping(m_udp_mapping[0]);
m_udp_mapping[0] = m_natpmp->add_mapping(natpmp::tcp
, m_dht_settings.service_port
, m_dht_settings.service_port);
}
if (m_upnp.get()) if (m_upnp.get())
m_upnp->set_mappings(0, m_dht_settings.service_port); {
if (m_udp_mapping[1] != -1) m_upnp->delete_mapping(m_udp_mapping[1]);
m_udp_mapping[1] = m_upnp->add_mapping(upnp::tcp
, m_dht_settings.service_port
, m_dht_settings.service_port);
}
} }
#endif #endif
@ -1714,43 +1738,40 @@ namespace aux {
t->get_policy().peer_from_tracker(peer, peer_id(0), peer_info::lsd, 0); t->get_policy().peer_from_tracker(peer, peer_id(0), peer_info::lsd, 0);
} }
void session_impl::on_port_mapping(int tcp_port, int udp_port void session_impl::on_port_mapping(int mapping, int port
, std::string const& errmsg) , std::string const& errmsg, int map_transport)
{ {
#ifndef TORRENT_DISABLE_DHT #ifndef TORRENT_DISABLE_DHT
if (udp_port != 0) if (mapping == m_udp_mapping[map_transport] && port != 0)
{ {
m_external_udp_port = udp_port; m_external_udp_port = port;
m_dht_settings.service_port = udp_port; m_dht_settings.service_port = port;
if (m_alerts.should_post(alert::info)) if (m_alerts.should_post(alert::info))
{ m_alerts.post_alert(portmap_alert(mapping, port
std::stringstream msg; , "successfully mapped UDP port"));
msg << "successfully mapped UDP port " << udp_port; return;
m_alerts.post_alert(portmap_alert(msg.str()));
}
} }
#endif #endif
if (tcp_port != 0) if (mapping == m_tcp_mapping[map_transport] && port != 0)
{ {
if (!m_listen_sockets.empty()) if (!m_listen_sockets.empty())
m_listen_sockets.front().external_port = tcp_port; m_listen_sockets.front().external_port = port;
if (m_alerts.should_post(alert::info)) if (m_alerts.should_post(alert::info))
{ m_alerts.post_alert(portmap_alert(mapping, port
std::stringstream msg; , "successfully mapped TCP port"));
msg << "successfully mapped TCP port " << tcp_port; return;
m_alerts.post_alert(portmap_alert(msg.str()));
}
} }
if (!errmsg.empty()) if (!errmsg.empty())
{ {
if (m_alerts.should_post(alert::warning)) if (m_alerts.should_post(alert::warning))
{ m_alerts.post_alert(portmap_error_alert(mapping, errmsg));
std::stringstream msg; }
msg << "Error while mapping ports on NAT router: " << errmsg; else
m_alerts.post_alert(portmap_error_alert(msg.str())); {
} if (m_alerts.should_post(alert::warning))
m_alerts.post_alert(portmap_alert(mapping, port, "successfully mapped port"));
} }
} }
@ -1831,10 +1852,18 @@ namespace aux {
m_dht_settings.service_port = m_listen_interface.port(); m_dht_settings.service_port = m_listen_interface.port();
} }
m_external_udp_port = m_dht_settings.service_port; m_external_udp_port = m_dht_settings.service_port;
if (m_natpmp.get()) if (m_natpmp.get() && m_udp_mapping[0] == -1)
m_natpmp->set_mappings(0, m_dht_settings.service_port); {
if (m_upnp.get()) m_udp_mapping[0] = m_natpmp->add_mapping(natpmp::udp
m_upnp->set_mappings(0, m_dht_settings.service_port); , m_dht_settings.service_port
, m_dht_settings.service_port);
}
if (m_upnp.get() && m_udp_mapping[1] == -1)
{
m_udp_mapping[1] = m_upnp->add_mapping(upnp::udp
, m_dht_settings.service_port
, m_dht_settings.service_port);
}
m_dht = new dht::dht_tracker(m_dht_socket, m_dht_settings, startup_state); m_dht = new dht::dht_tracker(m_dht_socket, m_dht_settings, startup_state);
if (!m_dht_socket.is_open() || m_dht_socket.local_port() != m_dht_settings.service_port) if (!m_dht_socket.is_open() || m_dht_socket.local_port() != m_dht_settings.service_port)
{ {
@ -1867,9 +1896,19 @@ namespace aux {
m_dht_socket.bind(settings.service_port); m_dht_socket.bind(settings.service_port);
if (m_natpmp.get()) if (m_natpmp.get())
m_natpmp->set_mappings(0, m_dht_settings.service_port); {
if (m_udp_mapping[0] != -1) m_upnp->delete_mapping(m_udp_mapping[0]);
m_udp_mapping[0] = m_natpmp->add_mapping(natpmp::udp
, m_dht_settings.service_port
, m_dht_settings.service_port);
}
if (m_upnp.get()) if (m_upnp.get())
m_upnp->set_mappings(0, m_dht_settings.service_port); {
if (m_udp_mapping[1] != -1) m_upnp->delete_mapping(m_udp_mapping[1]);
m_udp_mapping[1] = m_upnp->add_mapping(upnp::udp
, m_dht_settings.service_port
, m_dht_settings.service_port);
}
m_external_udp_port = settings.service_port; m_external_udp_port = settings.service_port;
} }
m_dht_settings = settings; m_dht_settings = settings;
@ -2053,47 +2092,55 @@ namespace aux {
, bind(&session_impl::on_lsd_peer, this, _1, _2)); , bind(&session_impl::on_lsd_peer, this, _1, _2));
} }
void session_impl::start_natpmp() boost::intrusive_ptr<natpmp> session_impl::start_natpmp()
{ {
mutex_t::scoped_lock l(m_mutex); mutex_t::scoped_lock l(m_mutex);
INVARIANT_CHECK; INVARIANT_CHECK;
if (m_natpmp) return; if (m_natpmp) return m_natpmp;
m_natpmp = new natpmp(m_io_service m_natpmp = new natpmp(m_io_service
, m_listen_interface.address() , m_listen_interface.address()
, bind(&session_impl::on_port_mapping , bind(&session_impl::on_port_mapping
, this, _1, _2, _3)); , this, _1, _2, _3, 0));
m_natpmp->set_mappings(m_listen_interface.port(), m_tcp_mapping[0] = m_natpmp->add_mapping(natpmp::tcp
, m_listen_interface.port(), m_listen_interface.port());
#ifndef TORRENT_DISABLE_DHT #ifndef TORRENT_DISABLE_DHT
m_dht ? m_dht_settings.service_port : if (m_dht)
m_udp_mapping[0] = m_natpmp->add_mapping(natpmp::udp
, m_dht_settings.service_port
, m_dht_settings.service_port);
#endif #endif
0); return m_natpmp;
} }
void session_impl::start_upnp() boost::intrusive_ptr<upnp> session_impl::start_upnp()
{ {
mutex_t::scoped_lock l(m_mutex); mutex_t::scoped_lock l(m_mutex);
INVARIANT_CHECK; INVARIANT_CHECK;
if (m_upnp) return; if (m_upnp) return m_upnp;
m_upnp = new upnp(m_io_service, m_half_open m_upnp = new upnp(m_io_service, m_half_open
, m_listen_interface.address() , m_listen_interface.address()
, m_settings.user_agent , m_settings.user_agent
, bind(&session_impl::on_port_mapping , bind(&session_impl::on_port_mapping
, this, _1, _2, _3) , this, _1, _2, _3, 1)
, m_settings.upnp_ignore_nonrouters); , m_settings.upnp_ignore_nonrouters);
m_upnp->discover_device(); m_upnp->discover_device();
m_upnp->set_mappings(m_listen_interface.port(), m_tcp_mapping[1] = m_upnp->add_mapping(upnp::tcp
, m_listen_interface.port(), m_listen_interface.port());
#ifndef TORRENT_DISABLE_DHT #ifndef TORRENT_DISABLE_DHT
m_dht ? m_dht_settings.service_port : if (m_dht)
m_udp_mapping[1] = m_upnp->add_mapping(upnp::udp
, m_dht_settings.service_port
, m_dht_settings.service_port);
#endif #endif
0); return m_upnp;
} }
void session_impl::stop_lsd() void session_impl::stop_lsd()
@ -2116,7 +2163,11 @@ namespace aux {
{ {
mutex_t::scoped_lock l(m_mutex); mutex_t::scoped_lock l(m_mutex);
if (m_upnp.get()) if (m_upnp.get())
{
m_upnp->close(); m_upnp->close();
m_udp_mapping[1] = -1;
m_tcp_mapping[1] = -1;
}
m_upnp = 0; m_upnp = 0;
} }

View File

@ -63,9 +63,7 @@ namespace libtorrent
upnp::upnp(io_service& ios, connection_queue& cc upnp::upnp(io_service& ios, connection_queue& cc
, address const& listen_interface, std::string const& user_agent , address const& listen_interface, std::string const& user_agent
, portmap_callback_t const& cb, bool ignore_nonrouters) , portmap_callback_t const& cb, bool ignore_nonrouters)
: m_udp_local_port(0) : m_user_agent(user_agent)
, m_tcp_local_port(0)
, m_user_agent(user_agent)
, m_callback(cb) , m_callback(cb)
, m_retry_count(0) , m_retry_count(0)
, m_io_service(ios) , m_io_service(ios)
@ -89,6 +87,13 @@ upnp::~upnp()
} }
void upnp::discover_device() void upnp::discover_device()
{
mutex_t::scoped_lock l(m_mutex);
discover_device_impl();
}
void upnp::discover_device_impl()
{ {
const char msearch[] = const char msearch[] =
"M-SEARCH * HTTP/1.1\r\n" "M-SEARCH * HTTP/1.1\r\n"
@ -112,7 +117,7 @@ void upnp::discover_device()
<< " ==> Broadcast FAILED: " << ec.message() << std::endl << " ==> Broadcast FAILED: " << ec.message() << std::endl
<< "aborting" << std::endl; << "aborting" << std::endl;
#endif #endif
disable(); disable(ec.message().c_str());
return; return;
} }
@ -127,51 +132,99 @@ void upnp::discover_device()
#endif #endif
} }
void upnp::set_mappings(int tcp, int udp) // returns a reference to a mapping or -1 on failure
int upnp::add_mapping(upnp::protocol_type p, int external_port, int local_port)
{ {
mutex_t::scoped_lock l(m_mutex);
#ifdef TORRENT_UPNP_LOGGING #ifdef TORRENT_UPNP_LOGGING
m_log << time_now_string() m_log << time_now_string()
<< " *** set mappings " << tcp << " " << udp; << " *** add mapping [ proto: " << (p == tcp?"tcp":"udp")
<< " ext_port: " << external_port
<< " local_port :" << local_port << " ]";
if (m_disabled) m_log << " DISABLED"; if (m_disabled) m_log << " DISABLED";
m_log << std::endl; m_log << std::endl;
#endif #endif
if (m_disabled) return -1;
if (m_disabled) return; std::vector<global_mapping_t>::iterator i = std::find_if(
if (udp != 0) m_udp_local_port = udp; m_mappings.begin(), m_mappings.end()
if (tcp != 0) m_tcp_local_port = tcp; , boost::bind(&global_mapping_t::protocol, _1) == int(none));
if (i == m_mappings.end())
{
m_mappings.push_back(global_mapping_t());
i = m_mappings.end() - 1;
}
i->protocol = p;
i->external_port = external_port;
i->local_port = local_port;
int mapping_index = i - m_mappings.begin();
for (std::set<rootdevice>::iterator i = m_devices.begin() for (std::set<rootdevice>::iterator i = m_devices.begin()
, end(m_devices.end()); i != end; ++i) , end(m_devices.end()); i != end; ++i)
{ {
rootdevice& d = const_cast<rootdevice&>(*i); rootdevice& d = const_cast<rootdevice&>(*i);
TORRENT_ASSERT(d.magic == 1337); TORRENT_ASSERT(d.magic == 1337);
if (d.mapping[0].local_port != m_tcp_local_port)
{ if (int(d.mapping.size()) <= mapping_index)
if (d.mapping[0].external_port == 0) d.mapping.resize(mapping_index + 1);
d.mapping[0].external_port = m_tcp_local_port; mapping_t& m = d.mapping[mapping_index];
d.mapping[0].local_port = m_tcp_local_port;
d.mapping[0].need_update = true; m.action = mapping_t::action_add;
} m.protocol = p;
if (d.mapping[1].local_port != m_udp_local_port) m.external_port = external_port;
{ m.local_port = local_port;
if (d.mapping[1].external_port == 0)
d.mapping[1].external_port = m_udp_local_port; if (d.service_namespace) update_map(d, mapping_index);
d.mapping[1].local_port = m_udp_local_port; }
d.mapping[1].need_update = true;
} return mapping_index;
if (d.service_namespace }
&& (d.mapping[0].need_update || d.mapping[1].need_update))
map_port(d, 0); void upnp::delete_mapping(int mapping)
{
mutex_t::scoped_lock l(m_mutex);
if (mapping <= int(m_mappings.size())) return;
global_mapping_t& m = m_mappings[mapping];
#ifdef TORRENT_UPNP_LOGGING
m_log << time_now_string()
<< " *** delete mapping [ proto: " << (m.protocol == tcp?"tcp":"udp")
<< " ext_port:" << m.external_port
<< " local_port:" << m.local_port << " ]";
m_log << std::endl;
#endif
if (m.protocol == none) return;
for (std::set<rootdevice>::iterator i = m_devices.begin()
, end(m_devices.end()); i != end; ++i)
{
rootdevice& d = const_cast<rootdevice&>(*i);
TORRENT_ASSERT(d.magic == 1337);
TORRENT_ASSERT(mapping < int(d.mapping.size()));
d.mapping[mapping].action = mapping_t::action_delete;
if (d.service_namespace) update_map(d, mapping);
} }
} }
void upnp::resend_request(asio::error_code const& e) void upnp::resend_request(asio::error_code const& e)
{ {
if (e) return; if (e) return;
mutex_t::scoped_lock l(m_mutex);
if (m_retry_count < 9 if (m_retry_count < 9
&& (m_devices.empty() || m_retry_count < 4)) && (m_devices.empty() || m_retry_count < 4))
{ {
discover_device(); discover_device_impl();
return; return;
} }
@ -182,7 +235,7 @@ void upnp::resend_request(asio::error_code const& e)
<< " *** Got no response in 9 retries. Giving up, " << " *** Got no response in 9 retries. Giving up, "
"disabling UPnP." << std::endl; "disabling UPnP." << std::endl;
#endif #endif
disable(); disable("no UPnP router found");
return; return;
} }
@ -223,6 +276,8 @@ void upnp::resend_request(asio::error_code const& e)
void upnp::on_reply(udp::endpoint const& from, char* buffer void upnp::on_reply(udp::endpoint const& from, char* buffer
, std::size_t bytes_transferred) , std::size_t bytes_transferred)
{ {
mutex_t::scoped_lock l(m_mutex);
using namespace libtorrent::detail; using namespace libtorrent::detail;
// parse out the url for the device // parse out the url for the device
@ -379,25 +434,16 @@ void upnp::on_reply(udp::endpoint const& from, char* buffer
return; return;
} }
if (m_tcp_local_port != 0) TORRENT_ASSERT(d.mapping.empty());
for (std::vector<global_mapping_t>::iterator j = m_mappings.begin()
, end(m_mappings.end()); j != end; ++j)
{ {
d.mapping[0].need_update = true; mapping_t m;
d.mapping[0].local_port = m_tcp_local_port; m.action = mapping_t::action_add;
if (d.mapping[0].external_port == 0) m.local_port = j->local_port;
d.mapping[0].external_port = d.mapping[0].local_port; m.external_port = j->external_port;
#ifdef TORRENT_UPNP_LOGGING m.protocol = j->protocol;
m_log << time_now_string() << " *** Mapping 0 will be updated" << std::endl; d.mapping.push_back(m);
#endif
}
if (m_udp_local_port != 0)
{
d.mapping[1].need_update = true;
d.mapping[1].local_port = m_udp_local_port;
if (d.mapping[1].external_port == 0)
d.mapping[1].external_port = d.mapping[1].local_port;
#ifdef TORRENT_UPNP_LOGGING
m_log << time_now_string() << " *** Mapping 1 will be updated" << std::endl;
#endif
} }
boost::tie(i, boost::tuples::ignore) = m_devices.insert(d); boost::tie(i, boost::tuples::ignore) = m_devices.insert(d);
} }
@ -474,6 +520,8 @@ void upnp::post(upnp::rootdevice const& d, std::string const& soap
void upnp::create_port_mapping(http_connection& c, rootdevice& d, int i) void upnp::create_port_mapping(http_connection& c, rootdevice& d, int i)
{ {
mutex_t::scoped_lock l(m_mutex);
TORRENT_ASSERT(d.magic == 1337); TORRENT_ASSERT(d.magic == 1337);
if (!d.upnp_connection) if (!d.upnp_connection)
@ -509,29 +557,45 @@ void upnp::create_port_mapping(http_connection& c, rootdevice& d, int i)
post(d, soap.str(), soap_action); post(d, soap.str(), soap_action);
} }
void upnp::map_port(rootdevice& d, int i) void upnp::next(rootdevice& d, int i)
{
if (i < num_mappings() - 1)
{
update_map(d, i + 1);
}
else
{
std::vector<mapping_t>::iterator i
= std::find_if(d.mapping.begin(), d.mapping.end()
, boost::bind(&mapping_t::action, _1) != int(mapping_t::action_none));
if (i == d.mapping.end()) return;
update_map(d, i - d.mapping.begin());
}
}
void upnp::update_map(rootdevice& d, int i)
{ {
TORRENT_ASSERT(d.magic == 1337); TORRENT_ASSERT(d.magic == 1337);
TORRENT_ASSERT(i < int(d.mapping.size()));
TORRENT_ASSERT(d.mapping.size() == m_mappings.size());
if (d.upnp_connection) return; if (d.upnp_connection) return;
if (d.mapping[i].failcount > 5) mapping_t& m = d.mapping[i];
{
// giving up if (m.action == mapping_t::action_none
if (i < num_mappings - 1) || m.protocol == none)
map_port(d, i + 1);
return;
}
if (!d.mapping[i].need_update)
{ {
#ifdef TORRENT_UPNP_LOGGING #ifdef TORRENT_UPNP_LOGGING
m_log << time_now_string() << " *** mapping (" << i if (m.protocol != none)
<< ") does not need update, skipping" << std::endl; m_log << time_now_string() << " *** mapping (" << i
<< ") does not need update, skipping" << std::endl;
#endif #endif
if (i < num_mappings - 1) next(d, i);
map_port(d, i + 1);
return; return;
} }
d.mapping[i].need_update = false;
TORRENT_ASSERT(!d.upnp_connection); TORRENT_ASSERT(!d.upnp_connection);
TORRENT_ASSERT(d.service_namespace); TORRENT_ASSERT(d.service_namespace);
@ -539,17 +603,40 @@ void upnp::map_port(rootdevice& d, int i)
m_log << time_now_string() m_log << time_now_string()
<< " ==> connecting to " << d.hostname << std::endl; << " ==> connecting to " << d.hostname << std::endl;
#endif #endif
d.upnp_connection.reset(new http_connection(m_io_service if (m.action == mapping_t::action_add)
, m_cc, bind(&upnp::on_upnp_map_response, self(), _1, _2 {
, boost::ref(d), i), true if (m.failcount > 5)
, bind(&upnp::create_port_mapping, self(), _1, boost::ref(d), i))); {
// giving up
next(d, i);
return;
}
d.upnp_connection->start(d.hostname, boost::lexical_cast<std::string>(d.port) d.upnp_connection.reset(new http_connection(m_io_service
, seconds(10), 1); , m_cc, bind(&upnp::on_upnp_map_response, self(), _1, _2
, boost::ref(d), i), true
, bind(&upnp::create_port_mapping, self(), _1, boost::ref(d), i)));
d.upnp_connection->start(d.hostname, boost::lexical_cast<std::string>(d.port)
, seconds(10), 1);
}
else if (m.action == mapping_t::action_delete)
{
d.upnp_connection.reset(new http_connection(m_io_service
, m_cc, bind(&upnp::on_upnp_unmap_response, self(), _1, _2
, boost::ref(d), i), true
, bind(&upnp::delete_port_mapping, self(), boost::ref(d), i)));
d.upnp_connection->start(d.hostname, boost::lexical_cast<std::string>(d.port)
, seconds(10), 1);
}
m.action = mapping_t::action_none;
} }
void upnp::delete_port_mapping(rootdevice& d, int i) void upnp::delete_port_mapping(rootdevice& d, int i)
{ {
mutex_t::scoped_lock l(m_mutex);
TORRENT_ASSERT(d.magic == 1337); TORRENT_ASSERT(d.magic == 1337);
if (!d.upnp_connection) if (!d.upnp_connection)
@ -579,31 +666,6 @@ void upnp::delete_port_mapping(rootdevice& d, int i)
post(d, soap.str(), soap_action); post(d, soap.str(), soap_action);
} }
// requires the mutex to be locked
void upnp::unmap_port(rootdevice& d, int i)
{
TORRENT_ASSERT(d.magic == 1337);
if (d.mapping[i].external_port == 0
|| d.disabled)
{
if (i < num_mappings - 1)
{
unmap_port(d, i + 1);
}
return;
}
#ifdef TORRENT_UPNP_LOGGING
m_log << time_now_string()
<< " ==> connecting to " << d.hostname << std::endl;
#endif
d.upnp_connection.reset(new http_connection(m_io_service
, m_cc, bind(&upnp::on_upnp_unmap_response, self(), _1, _2
, boost::ref(d), i), true
, bind(&upnp::delete_port_mapping, self(), boost::ref(d), i)));
d.upnp_connection->start(d.hostname, boost::lexical_cast<std::string>(d.port)
, seconds(10), 1);
}
namespace namespace
{ {
struct parse_state struct parse_state
@ -673,6 +735,8 @@ namespace
void upnp::on_upnp_xml(asio::error_code const& e void upnp::on_upnp_xml(asio::error_code const& e
, libtorrent::http_parser const& p, rootdevice& d) , libtorrent::http_parser const& p, rootdevice& d)
{ {
mutex_t::scoped_lock l(m_mutex);
TORRENT_ASSERT(d.magic == 1337); TORRENT_ASSERT(d.magic == 1337);
if (d.upnp_connection) if (d.upnp_connection)
{ {
@ -752,12 +816,22 @@ void upnp::on_upnp_xml(asio::error_code const& e
d.control_url = s.control_url; d.control_url = s.control_url;
map_port(d, 0); update_map(d, 0);
} }
void upnp::disable() void upnp::disable(char const* msg)
{ {
m_disabled = true; m_disabled = true;
// kill all mappings
for (std::vector<global_mapping_t>::iterator i = m_mappings.begin()
, end(m_mappings.end()); i != end; ++i)
{
if (i->protocol == none) continue;
i->protocol = none;
m_callback(i - m_mappings.begin(), 0, msg);
}
m_devices.clear(); m_devices.clear();
asio::error_code ec; asio::error_code ec;
m_broadcast_timer.cancel(ec); m_broadcast_timer.cancel(ec);
@ -820,6 +894,8 @@ namespace
void upnp::on_upnp_map_response(asio::error_code const& e void upnp::on_upnp_map_response(asio::error_code const& e
, libtorrent::http_parser const& p, rootdevice& d, int mapping) , libtorrent::http_parser const& p, rootdevice& d, int mapping)
{ {
mutex_t::scoped_lock l(m_mutex);
TORRENT_ASSERT(d.magic == 1337); TORRENT_ASSERT(d.magic == 1337);
if (d.upnp_connection) if (d.upnp_connection)
{ {
@ -862,7 +938,7 @@ void upnp::on_upnp_map_response(asio::error_code const& e
m_log << time_now_string() m_log << time_now_string()
<< " <== error while adding portmap: incomplete http message" << std::endl; << " <== error while adding portmap: incomplete http message" << std::endl;
#endif #endif
d.disabled = true; next(d, mapping);
return; return;
} }
@ -881,42 +957,44 @@ void upnp::on_upnp_map_response(asio::error_code const& e
} }
#endif #endif
mapping_t& m = d.mapping[mapping];
if (s.error_code == 725) if (s.error_code == 725)
{ {
// only permanent leases supported // only permanent leases supported
d.lease_duration = 0; d.lease_duration = 0;
d.mapping[mapping].need_update = true; m.action = mapping_t::action_add;
++d.mapping[mapping].failcount; ++m.failcount;
map_port(d, mapping); update_map(d, mapping);
return; return;
} }
else if (s.error_code == 718 || s.error_code == 727) else if (s.error_code == 718 || s.error_code == 727)
{ {
if (d.mapping[mapping].external_port != 0) if (m.external_port != 0)
{ {
// conflict in mapping, set port to wildcard // conflict in mapping, set port to wildcard
// and let the router decide // and let the router decide
d.mapping[mapping].external_port = 0; m.external_port = 0;
d.mapping[mapping].need_update = true; m.action = mapping_t::action_add;
++d.mapping[mapping].failcount; ++m.failcount;
map_port(d, mapping); update_map(d, mapping);
return; return;
} }
return_error(s.error_code); return_error(mapping, s.error_code);
} }
else if (s.error_code == 716) else if (s.error_code == 716)
{ {
// The external port cannot be wildcarder // The external port cannot be wildcarder
// pick a random port // pick a random port
d.mapping[mapping].external_port = 40000 + (rand() % 10000); m.external_port = 40000 + (rand() % 10000);
d.mapping[mapping].need_update = true; m.action = mapping_t::action_add;
++d.mapping[mapping].failcount; ++m.failcount;
map_port(d, mapping); update_map(d, mapping);
return; return;
} }
else if (s.error_code != -1) else if (s.error_code != -1)
{ {
return_error(s.error_code); return_error(mapping, s.error_code);
} }
#ifdef TORRENT_UPNP_LOGGING #ifdef TORRENT_UPNP_LOGGING
@ -927,46 +1005,31 @@ void upnp::on_upnp_map_response(asio::error_code const& e
if (s.error_code == -1) if (s.error_code == -1)
{ {
int tcp = 0; m_callback(mapping, m.external_port, "");
int udp = 0;
if (mapping == 0)
tcp = d.mapping[mapping].external_port;
else
udp = d.mapping[mapping].external_port;
m_callback(tcp, udp, "");
if (d.lease_duration > 0) if (d.lease_duration > 0)
{ {
d.mapping[mapping].expires = time_now() m.expires = time_now()
+ seconds(int(d.lease_duration * 0.75f)); + seconds(int(d.lease_duration * 0.75f));
ptime next_expire = m_refresh_timer.expires_at(); ptime next_expire = m_refresh_timer.expires_at();
if (next_expire < time_now() if (next_expire < time_now()
|| next_expire > d.mapping[mapping].expires) || next_expire > m.expires)
{ {
asio::error_code ec; asio::error_code ec;
m_refresh_timer.expires_at(d.mapping[mapping].expires, ec); m_refresh_timer.expires_at(m.expires, ec);
m_refresh_timer.async_wait(bind(&upnp::on_expire, self(), _1)); m_refresh_timer.async_wait(bind(&upnp::on_expire, self(), _1));
} }
} }
else else
{ {
d.mapping[mapping].expires = max_time(); m.expires = max_time();
} }
d.mapping[mapping].failcount = 0; m.failcount = 0;
} }
for (int i = 0; i < num_mappings; ++i) next(d, mapping);
{
if (d.mapping[i].need_update)
{
map_port(d, i);
return;
}
}
} }
void upnp::return_error(int code) void upnp::return_error(int mapping, int code)
{ {
int num_errors = sizeof(error_codes) / sizeof(error_codes[0]); int num_errors = sizeof(error_codes) / sizeof(error_codes[0]);
error_code_t* end = error_codes + num_errors; error_code_t* end = error_codes + num_errors;
@ -980,12 +1043,14 @@ void upnp::return_error(int code)
error_string += ": "; error_string += ": ";
error_string += e->msg; error_string += e->msg;
} }
m_callback(0, 0, error_string); m_callback(mapping, 0, error_string);
} }
void upnp::on_upnp_unmap_response(asio::error_code const& e void upnp::on_upnp_unmap_response(asio::error_code const& e
, libtorrent::http_parser const& p, rootdevice& d, int mapping) , libtorrent::http_parser const& p, rootdevice& d, int mapping)
{ {
mutex_t::scoped_lock l(m_mutex);
TORRENT_ASSERT(d.magic == 1337); TORRENT_ASSERT(d.magic == 1337);
if (d.upnp_connection) if (d.upnp_connection)
{ {
@ -999,39 +1064,33 @@ void upnp::on_upnp_unmap_response(asio::error_code const& e
m_log << time_now_string() m_log << time_now_string()
<< " <== error while deleting portmap: " << e.message() << std::endl; << " <== error while deleting portmap: " << e.message() << std::endl;
#endif #endif
} } else if (!p.header_finished())
if (!p.header_finished())
{ {
#ifdef TORRENT_UPNP_LOGGING #ifdef TORRENT_UPNP_LOGGING
m_log << time_now_string() m_log << time_now_string()
<< " <== error while deleting portmap: incomplete http message" << std::endl; << " <== error while deleting portmap: incomplete http message" << std::endl;
#endif #endif
return;
} }
else if (p.status_code() != 200)
if (p.status_code() != 200)
{ {
#ifdef TORRENT_UPNP_LOGGING #ifdef TORRENT_UPNP_LOGGING
m_log << time_now_string() m_log << time_now_string()
<< " <== error while deleting portmap: " << p.message() << std::endl; << " <== error while deleting portmap: " << p.message() << std::endl;
#endif #endif
d.disabled = true;
return;
} }
else
{
#ifdef TORRENT_UPNP_LOGGING #ifdef TORRENT_UPNP_LOGGING
m_log << time_now_string() m_log << time_now_string()
<< " <== unmap response: " << std::string(p.get_body().begin, p.get_body().end) << " <== unmap response: " << std::string(p.get_body().begin, p.get_body().end)
<< std::endl; << std::endl;
#endif #endif
// ignore errors and continue with the next mapping for this device
if (mapping < num_mappings - 1)
{
unmap_port(d, mapping + 1);
return;
} }
d.mapping[mapping].protocol = none;
next(d, mapping);
} }
void upnp::on_expire(asio::error_code const& e) void upnp::on_expire(asio::error_code const& e)
@ -1041,12 +1100,14 @@ void upnp::on_expire(asio::error_code const& e)
ptime now = time_now(); ptime now = time_now();
ptime next_expire = max_time(); ptime next_expire = max_time();
mutex_t::scoped_lock l(m_mutex);
for (std::set<rootdevice>::iterator i = m_devices.begin() for (std::set<rootdevice>::iterator i = m_devices.begin()
, end(m_devices.end()); i != end; ++i) , end(m_devices.end()); i != end; ++i)
{ {
rootdevice& d = const_cast<rootdevice&>(*i); rootdevice& d = const_cast<rootdevice&>(*i);
TORRENT_ASSERT(d.magic == 1337); TORRENT_ASSERT(d.magic == 1337);
for (int m = 0; m < num_mappings; ++m) for (int m = 0; m < num_mappings(); ++m)
{ {
if (d.mapping[m].expires != max_time()) if (d.mapping[m].expires != max_time())
continue; continue;
@ -1054,7 +1115,7 @@ void upnp::on_expire(asio::error_code const& e)
if (d.mapping[m].expires < now) if (d.mapping[m].expires < now)
{ {
d.mapping[m].expires = max_time(); d.mapping[m].expires = max_time();
map_port(d, m); update_map(d, m);
} }
else if (d.mapping[m].expires < next_expire) else if (d.mapping[m].expires < next_expire)
{ {
@ -1072,25 +1133,33 @@ void upnp::on_expire(asio::error_code const& e)
void upnp::close() void upnp::close()
{ {
mutex_t::scoped_lock l(m_mutex);
asio::error_code ec; asio::error_code ec;
m_refresh_timer.cancel(ec); m_refresh_timer.cancel(ec);
m_broadcast_timer.cancel(ec); m_broadcast_timer.cancel(ec);
m_closing = true; m_closing = true;
m_socket.close(); m_socket.close();
if (m_disabled)
{
m_devices.clear();
return;
}
for (std::set<rootdevice>::iterator i = m_devices.begin() for (std::set<rootdevice>::iterator i = m_devices.begin()
, end(m_devices.end()); i != end; ++i) , end(m_devices.end()); i != end; ++i)
{ {
rootdevice& d = const_cast<rootdevice&>(*i); rootdevice& d = const_cast<rootdevice&>(*i);
TORRENT_ASSERT(d.magic == 1337); TORRENT_ASSERT(d.magic == 1337);
if (d.control_url.empty()) continue; if (d.control_url.empty()) continue;
unmap_port(d, 0); for (std::vector<mapping_t>::iterator j = d.mapping.begin()
, end(d.mapping.end()); j != end; ++j)
{
if (j->protocol == none) continue;
if (j->action == mapping_t::action_add)
{
j->action = mapping_t::action_none;
continue;
}
j->action = mapping_t::action_delete;
m_mappings[j - d.mapping.begin()].protocol = none;
}
update_map(d, 0);
} }
} }

View File

@ -3,6 +3,9 @@ use-project /torrent : .. ;
exe test_upnp : test_upnp.cpp /torrent//torrent exe test_upnp : test_upnp.cpp /torrent//torrent
: <link>static <threading>multi <logging>verbose <upnp-logging>on ; : <link>static <threading>multi <logging>verbose <upnp-logging>on ;
exe test_natpmp : test_natpmp.cpp /torrent//torrent
: <link>static <threading>multi <logging>verbose <upnp-logging>on ;
lib test_common lib test_common
: :
main.cpp main.cpp

56
test/test_natpmp.cpp Normal file
View File

@ -0,0 +1,56 @@
#include "libtorrent/natpmp.hpp"
#include "libtorrent/socket.hpp"
#include "libtorrent/connection_queue.hpp"
#include <boost/bind.hpp>
#include <boost/ref.hpp>
#include <boost/intrusive_ptr.hpp>
using namespace libtorrent;
void callback(int mapping, int port, std::string const& err)
{
std::cerr << "mapping: " << mapping << ", port: " << port << ", error: \"" << err << "\"\n";
}
int main(int argc, char* argv[])
{
io_service ios;
std::string user_agent = "test agent";
if (argc != 3)
{
std::cerr << "usage: " << argv[0] << " tcp-port udp-port" << std::endl;
return 1;
}
connection_queue cc(ios);
boost::intrusive_ptr<natpmp> natpmp_handler = new natpmp(ios, address_v4(), &callback);
deadline_timer timer(ios);
int tcp_map = natpmp_handler->add_mapping(natpmp::tcp, atoi(argv[1]), atoi(argv[1]));
int udp_map = natpmp_handler->add_mapping(natpmp::udp, atoi(argv[2]), atoi(argv[2]));
timer.expires_from_now(seconds(2));
timer.async_wait(boost::bind(&io_service::stop, boost::ref(ios)));
std::cerr << "mapping ports TCP: " << argv[1]
<< " UDP: " << argv[2] << std::endl;
ios.reset();
ios.run();
timer.expires_from_now(seconds(2));
timer.async_wait(boost::bind(&io_service::stop, boost::ref(ios)));
std::cerr << "removing mapping " << tcp_map << std::endl;
natpmp_handler->delete_mapping(tcp_map);
ios.reset();
ios.run();
std::cerr << "removing mappings" << std::endl;
natpmp_handler->close();
ios.reset();
ios.run();
std::cerr << "closing" << std::endl;
}

View File

@ -7,9 +7,9 @@
using namespace libtorrent; using namespace libtorrent;
void callback(int tcp, int udp, std::string const& err) void callback(int mapping, int port, std::string const& err)
{ {
std::cerr << "tcp: " << tcp << ", udp: " << udp << ", error: \"" << err << "\"\n"; std::cerr << "mapping: " << mapping << ", port: " << port << ", error: \"" << err << "\"\n";
} }
int main(int argc, char* argv[]) int main(int argc, char* argv[])
@ -36,8 +36,9 @@ int main(int argc, char* argv[])
ios.reset(); ios.reset();
ios.run(); ios.run();
upnp_handler->set_mappings(atoi(argv[1]), atoi(argv[2])); upnp_handler->add_mapping(upnp::tcp, atoi(argv[1]), atoi(argv[1]));
timer.expires_from_now(seconds(5)); upnp_handler->add_mapping(upnp::udp, atoi(argv[2]), atoi(argv[2]));
timer.expires_from_now(seconds(10));
timer.async_wait(boost::bind(&io_service::stop, boost::ref(ios))); timer.async_wait(boost::bind(&io_service::stop, boost::ref(ios)));
std::cerr << "mapping ports TCP: " << argv[1] std::cerr << "mapping ports TCP: " << argv[1]
<< " UDP: " << argv[2] << std::endl; << " UDP: " << argv[2] << std::endl;