support requesting UPnP and NAT-PMP routers for our external IP address

This commit is contained in:
Arvid Norberg
2010-12-05 20:40:28 +00:00
parent 2f115bc1aa
commit 779014ddac
10 changed files with 276 additions and 35 deletions

View File

@@ -3992,6 +3992,8 @@ session_settings
bool rate_limit_utp; bool rate_limit_utp;
int listen_queue_size; int listen_queue_size;
bool announce_double_nat;
}; };
``version`` is automatically set to the libtorrent version you're using ``version`` is automatically set to the libtorrent version you're using
@@ -4790,6 +4792,10 @@ expects to receive a lot of connections, or used in a simulator or test, it
might make sense to raise this number. It will not take affect until listen_on() might make sense to raise this number. It will not take affect until listen_on()
is called again (or for the first time). is called again (or for the first time).
if ``announce_double_nat`` is true, the ``&ip=`` argument in tracker requests
(unless otherwise specified) will be set to the intermediate IP address, if the
user is double NATed. If ther user is not double NATed, this option has no affect.
pe_settings pe_settings
=========== ===========

View File

@@ -235,8 +235,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 mapping, int port, error_code const& ec void on_port_mapping(int mapping, address const& ip, int port
, int nat_transport); , error_code const& ec, int nat_transport);
bool is_aborted() const { return m_abort; } bool is_aborted() const { return m_abort; }
bool is_paused() const { return m_paused; } bool is_paused() const { return m_paused; }
@@ -301,6 +301,7 @@ namespace libtorrent
session_status status() const; session_status status() const;
void set_peer_id(peer_id const& id); void set_peer_id(peer_id const& id);
void set_key(int key); void set_key(int key);
address listen_address() const;
unsigned short listen_port() const; unsigned short listen_port() const;
void abort(); void abort();
@@ -581,6 +582,11 @@ namespace libtorrent
struct listen_socket_t struct listen_socket_t
{ {
listen_socket_t(): external_port(0) {} listen_socket_t(): external_port(0) {}
// this is typically empty but can be set
// to the WAN IP address of NAT-PMP or UPnP router
address external_address;
// this is typically set to the same as the local // this is typically set to the same as the local
// listen port. In case a NAT port forward was // listen port. In case a NAT port forward was
// successfully opened, this will be set to the // successfully opened, this will be set to the

View File

@@ -42,7 +42,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/deadline_timer.hpp" #include "libtorrent/deadline_timer.hpp"
#include <boost/function/function1.hpp> #include <boost/function/function1.hpp>
#include <boost/function/function3.hpp> #include <boost/function/function4.hpp>
namespace libtorrent namespace libtorrent
{ {
@@ -50,7 +50,7 @@ namespace libtorrent
// int: port mapping index // int: port mapping index
// int: external port // int: external port
// std::string: error message // std::string: error message
typedef boost::function<void(int, int, error_code const&)> portmap_callback_t; typedef boost::function<void(int, address, int, error_code const&)> portmap_callback_t;
typedef boost::function<void(char const*)> log_callback_t; typedef boost::function<void(char const*)> log_callback_t;
class TORRENT_EXPORT natpmp : public intrusive_ptr_base<natpmp> class TORRENT_EXPORT natpmp : public intrusive_ptr_base<natpmp>
@@ -75,6 +75,7 @@ private:
void update_mapping(int i, mutex::scoped_lock& l); void update_mapping(int i, mutex::scoped_lock& l);
void send_map_request(int i, mutex::scoped_lock& l); void send_map_request(int i, mutex::scoped_lock& l);
void send_get_ip_address_request(mutex::scoped_lock& l);
void resend_request(int i, error_code const& e); void resend_request(int i, error_code const& e);
void on_reply(error_code const& e void on_reply(error_code const& e
, std::size_t bytes_transferred); , std::size_t bytes_transferred);
@@ -142,6 +143,9 @@ private:
// used to receive responses in // used to receive responses in
char m_response_buffer[16]; char m_response_buffer[16];
// router external IP address
address m_external_ip;
// the endpoint we received the message from // the endpoint we received the message from
udp::endpoint m_remote; udp::endpoint m_remote;

View File

@@ -251,6 +251,7 @@ namespace libtorrent
, mixed_mode_algorithm(peer_proportional) , mixed_mode_algorithm(peer_proportional)
, rate_limit_utp(false) , rate_limit_utp(false)
, listen_queue_size(5) , listen_queue_size(5)
, announce_double_nat(false)
{} {}
// libtorrent version. Used for forward binary compatibility // libtorrent version. Used for forward binary compatibility
@@ -992,6 +993,12 @@ namespace libtorrent
// the number of connections to accept while we're // the number of connections to accept while we're
// not waiting in an accept() call. // not waiting in an accept() call.
int listen_queue_size; int listen_queue_size;
// if this is true, the &ip= argument in tracker requests
// (unless otherwise specified) will be set to the intermediate
// IP address if the user is double NATed. If ther user is not
// double NATed, this option does not have an affect
bool announce_double_nat;
}; };
#ifndef TORRENT_DISABLE_DHT #ifndef TORRENT_DISABLE_DHT

View File

@@ -90,12 +90,13 @@ namespace libtorrent
#endif #endif
// int: port-mapping index // int: port-mapping index
// address: external address as queried from router
// int: external port // int: external port
// std::string: error message // std::string: error message
// an empty string as error means success // an empty string as error means success
// a port-mapping index of -1 means it's // a port-mapping index of -1 means it's
// an informational log message // an informational log message
typedef boost::function<void(int, int, error_code const&)> portmap_callback_t; typedef boost::function<void(int, address, int, error_code const&)> portmap_callback_t;
typedef boost::function<void(char const*)> log_callback_t; typedef boost::function<void(char const*)> log_callback_t;
class TORRENT_EXPORT upnp : public intrusive_ptr_base<upnp> class TORRENT_EXPORT upnp : public intrusive_ptr_base<upnp>
@@ -147,6 +148,9 @@ private:
void on_upnp_xml(error_code const& e void on_upnp_xml(error_code const& e
, libtorrent::http_parser const& p, rootdevice& d , libtorrent::http_parser const& p, rootdevice& d
, http_connection& c); , http_connection& c);
void on_upnp_get_ip_address_response(error_code const& e
, libtorrent::http_parser const& p, rootdevice& d
, http_connection& c);
void on_upnp_map_response(error_code const& e void on_upnp_map_response(error_code const& e
, libtorrent::http_parser const& p, rootdevice& d , libtorrent::http_parser const& p, rootdevice& d
, int mapping, http_connection& c); , int mapping, http_connection& c);
@@ -159,6 +163,7 @@ private:
void return_error(int mapping, int code, mutex::scoped_lock& l); void return_error(int mapping, int code, mutex::scoped_lock& l);
void log(char const* msg, mutex::scoped_lock& l); void log(char const* msg, mutex::scoped_lock& l);
void get_ip_address(rootdevice& d);
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, char const* soap void post(upnp::rootdevice const& d, char const* soap
@@ -247,6 +252,7 @@ private:
std::string hostname; std::string hostname;
int port; int port;
std::string path; std::string path;
address external_ip;
int lease_duration; int lease_duration;
// true if the device supports specifying a // true if the device supports specifying a

View File

@@ -59,6 +59,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/io.hpp" #include "libtorrent/io.hpp"
#include "libtorrent/socket.hpp" #include "libtorrent/socket.hpp"
#include "libtorrent/aux_/session_impl.hpp" #include "libtorrent/aux_/session_impl.hpp"
#include "libtorrent/broadcast_socket.hpp" // for is_local
using namespace libtorrent; using namespace libtorrent;
@@ -180,23 +181,34 @@ namespace libtorrent
} }
else else
#endif #endif
if (!settings.announce_ip.empty()) if (!m_ses.settings().anonymous_mode)
{ {
error_code ec; if (!settings.announce_ip.empty())
if (!ec) url += "&ip=" + escape_string( {
settings.announce_ip.c_str(), settings.announce_ip.size()); url += "&ip=" + escape_string(
} settings.announce_ip.c_str(), settings.announce_ip.size());
}
if (!tracker_req().ipv6.empty() && !i2p) else if (m_ses.settings().announce_double_nat
{ && is_local(m_ses.listen_address()))
url += "&ipv6="; {
url += tracker_req().ipv6; // only use the global external listen address here
} // if it turned out to be on a local network
// since otherwise the tracker should use our
if (!tracker_req().ipv4.empty() && !i2p) // source IP to determine our origin
{ url += "&ip=" + print_address(m_ses.listen_address());
url += "&ipv4="; }
url += tracker_req().ipv4;
if (!tracker_req().ipv6.empty() && !i2p)
{
url += "&ipv6=";
url += tracker_req().ipv6;
}
if (!tracker_req().ipv4.empty() && !i2p)
{
url += "&ipv4=";
url += tracker_req().ipv4;
}
} }
} }

View File

@@ -120,6 +120,7 @@ void natpmp::rebind(address const& listen_interface)
#endif #endif
m_socket.async_receive_from(asio::buffer(&m_response_buffer, 16) m_socket.async_receive_from(asio::buffer(&m_response_buffer, 16)
, m_remote, boost::bind(&natpmp::on_reply, self(), _1, _2)); , m_remote, boost::bind(&natpmp::on_reply, self(), _1, _2));
send_get_ip_address_request(l);
for (std::vector<mapping_t>::iterator i = m_mappings.begin() for (std::vector<mapping_t>::iterator i = m_mappings.begin()
, end(m_mappings.end()); i != end; ++i) , end(m_mappings.end()); i != end; ++i)
@@ -132,6 +133,20 @@ void natpmp::rebind(address const& listen_interface)
} }
} }
void natpmp::send_get_ip_address_request(mutex::scoped_lock& l)
{
using namespace libtorrent::detail;
char buf[2];
char* out = buf;
write_uint8(0, out); // NAT-PMP version
write_uint8(0, out); // public IP address request opcode
log("==> get public IP address", l);
error_code ec;
m_socket.send_to(asio::buffer(buf, sizeof(buf)), m_nat_endpoint, 0, ec);
}
bool natpmp::get_mapping(int index, int& local_port, int& external_port, int& protocol) const bool natpmp::get_mapping(int index, int& local_port, int& external_port, int& protocol) const
{ {
mutex::scoped_lock l(m_mutex); mutex::scoped_lock l(m_mutex);
@@ -164,7 +179,7 @@ void natpmp::disable(error_code const& ec, mutex::scoped_lock& l)
i->protocol = none; i->protocol = none;
int index = i - m_mappings.begin(); int index = i - m_mappings.begin();
l.unlock(); l.unlock();
m_callback(index, 0, ec); m_callback(index, address(), 0, ec);
l.lock(); l.lock();
} }
close_impl(l); close_impl(l);
@@ -337,7 +352,7 @@ void natpmp::send_map_request(int i, mutex::scoped_lock& l)
log(msg, l); log(msg, l);
error_code ec; error_code ec;
m_socket.send_to(asio::buffer(buf, 12), m_nat_endpoint, 0, ec); m_socket.send_to(asio::buffer(buf, sizeof(buf)), m_nat_endpoint, 0, ec);
m.map_sent = true; m.map_sent = true;
m.outstanding_request = true; m.outstanding_request = true;
if (m_abort) if (m_abort)
@@ -428,11 +443,45 @@ void natpmp::on_reply(error_code const& e
error_code ec; error_code ec;
m_send_timer.cancel(ec); m_send_timer.cancel(ec);
if (bytes_transferred < 12)
{
char msg[200];
snprintf(msg, sizeof(msg), "received packet of invalid size: %d", bytes_transferred);
log(msg, l);
return;
}
char* in = m_response_buffer; char* in = m_response_buffer;
int version = read_uint8(in); int version = read_uint8(in);
int cmd = read_uint8(in); int cmd = read_uint8(in);
int result = read_uint16(in); int result = read_uint16(in);
int time = read_uint32(in); int time = read_uint32(in);
// for some reason the Airport extreme responds with
// a cmd of 130 for the public IP request. However, the
// response is still identifiable by its size
// this might be a bug triggered by libtorrent not serializing
// its port mapping requests and the external IP request
if (cmd == 128 || bytes_transferred == 12)
{
// public IP request response
m_external_ip = address_v4(read_uint32(in));
char msg[200];
snprintf(msg, sizeof(msg), "<== public IP address [ %s ]", print_address(m_external_ip).c_str());
log(msg, l);
return;
}
if (bytes_transferred < 16)
{
char msg[200];
snprintf(msg, sizeof(msg), "received packet of invalid size: %d", bytes_transferred);
log(msg, l);
return;
}
int private_port = read_uint16(in); int private_port = read_uint16(in);
int public_port = read_uint16(in); int public_port = read_uint16(in);
int lifetime = read_uint32(in); int lifetime = read_uint32(in);
@@ -505,13 +554,14 @@ void natpmp::on_reply(error_code const& e
m->expires = time_now() + hours(2); m->expires = time_now() + hours(2);
l.unlock(); l.unlock();
m_callback(index, 0, error_code(ev, get_libtorrent_category())); m_callback(index, address(), 0, error_code(ev, get_libtorrent_category()));
l.lock(); l.lock();
} }
else if (m->action == mapping_t::action_add) else if (m->action == mapping_t::action_add)
{ {
l.unlock(); l.unlock();
m_callback(index, m->external_port, error_code(errors::no_error, get_libtorrent_category())); m_callback(index, m_external_ip, m->external_port,
error_code(errors::no_error, get_libtorrent_category()));
l.lock(); l.lock();
} }

View File

@@ -3608,6 +3608,16 @@ namespace aux {
return !m_listen_sockets.empty(); return !m_listen_sockets.empty();
} }
address session_impl::listen_address() const
{
for (std::list<listen_socket_t>::const_iterator i = m_listen_sockets.begin()
, end(m_listen_sockets.end()); i != end; ++i)
{
if (i->external_address != address()) return i->external_address;
}
return address();
}
unsigned short session_impl::listen_port() const unsigned short session_impl::listen_port() const
{ {
// if peer connections are set up to be received over a socks // if peer connections are set up to be received over a socks
@@ -3667,7 +3677,7 @@ namespace aux {
m_alerts.post_alert(portmap_log_alert(map_transport, msg)); m_alerts.post_alert(portmap_log_alert(map_transport, msg));
} }
void session_impl::on_port_mapping(int mapping, int port void session_impl::on_port_mapping(int mapping, address const& ip, int port
, error_code const& ec, int map_transport) , error_code const& ec, int map_transport)
{ {
TORRENT_ASSERT(is_network_thread()); TORRENT_ASSERT(is_network_thread());
@@ -3685,8 +3695,12 @@ namespace aux {
if (mapping == m_tcp_mapping[map_transport] && port != 0) if (mapping == m_tcp_mapping[map_transport] && port != 0)
{ {
if (!m_listen_sockets.empty()) if (ip != address()) set_external_address(ip);
if (!m_listen_sockets.empty()) {
m_listen_sockets.front().external_address = ip;
m_listen_sockets.front().external_port = port; m_listen_sockets.front().external_port = port;
}
if (m_alerts.should_post<portmap_alert>()) if (m_alerts.should_post<portmap_alert>())
m_alerts.post_alert(portmap_alert(mapping, port m_alerts.post_alert(portmap_alert(mapping, port
, map_transport)); , map_transport));
@@ -4248,7 +4262,7 @@ namespace aux {
natpmp* n = new (std::nothrow) natpmp(m_io_service natpmp* n = new (std::nothrow) natpmp(m_io_service
, m_listen_interface.address() , m_listen_interface.address()
, boost::bind(&session_impl::on_port_mapping , boost::bind(&session_impl::on_port_mapping
, this, _1, _2, _3, 0) , this, _1, _2, _3, _4, 0)
, boost::bind(&session_impl::on_port_map_log , boost::bind(&session_impl::on_port_map_log
, this, _1, 0)); , this, _1, 0));
if (n == 0) return 0; if (n == 0) return 0;
@@ -4280,7 +4294,7 @@ namespace aux {
, m_listen_interface.address() , m_listen_interface.address()
, m_settings.user_agent , m_settings.user_agent
, boost::bind(&session_impl::on_port_mapping , boost::bind(&session_impl::on_port_mapping
, this, _1, _2, _3, 1) , this, _1, _2, _3, _4, 1)
, boost::bind(&session_impl::on_port_map_log , boost::bind(&session_impl::on_port_map_log
, this, _1, 1) , this, _1, 1)
, m_settings.upnp_ignore_nonrouters); , m_settings.upnp_ignore_nonrouters);

View File

@@ -960,7 +960,42 @@ void upnp::on_upnp_xml(error_code const& e
return; return;
} }
if (num_mappings() > 0) update_map(d, 0, l); d.upnp_connection.reset(new http_connection(m_io_service
, m_cc, boost::bind(&upnp::on_upnp_get_ip_address_response, self(), _1, _2
, boost::ref(d), _5), true
, boost::bind(&upnp::get_ip_address, self(), boost::ref(d))));
d.upnp_connection->start(d.hostname, to_string(d.port).elems
, seconds(10), 1);
}
void upnp::get_ip_address(rootdevice& d)
{
mutex::scoped_lock l(m_mutex);
TORRENT_ASSERT(d.magic == 1337);
if (!d.upnp_connection)
{
TORRENT_ASSERT(d.disabled);
char msg[200];
snprintf(msg, sizeof(msg), "getting external IP address");
log(msg, l);
return;
}
char const* soap_action = "GetExternalIPAddress";
char soap[2048];
error_code ec;
snprintf(soap, sizeof(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:%s xmlns:u=\"%s\">"
"</u:%s></s:Body></s:Envelope>"
, soap_action, d.service_namespace
, soap_action);
post(d, soap, soap_action, l);
} }
void upnp::disable(error_code const& ec, mutex::scoped_lock& l) void upnp::disable(error_code const& ec, mutex::scoped_lock& l)
@@ -974,7 +1009,7 @@ void upnp::disable(error_code const& ec, mutex::scoped_lock& l)
if (i->protocol == none) continue; if (i->protocol == none) continue;
i->protocol = none; i->protocol = none;
l.unlock(); l.unlock();
m_callback(i - m_mappings.begin(), 0, ec); m_callback(i - m_mappings.begin(), address(), 0, ec);
l.lock(); l.lock();
} }
@@ -1012,6 +1047,29 @@ namespace
} }
} }
struct ip_address_parse_state: public error_code_parse_state
{
ip_address_parse_state(): in_ip_address(false) {}
bool in_ip_address;
std::string ip_address;
};
void find_ip_address(int type, char const* string, ip_address_parse_state& state)
{
find_error_code(type, string, state);
if (state.exit) return;
if (type == xml_start_tag && !std::strcmp("NewExternalIPAddress", string))
{
state.in_ip_address = true;
}
else if (type == xml_string && state.in_ip_address)
{
state.ip_address = string;
state.exit = true;
}
}
struct error_code_t struct error_code_t
{ {
int code; int code;
@@ -1074,6 +1132,84 @@ namespace libtorrent
#endif #endif
void upnp::on_upnp_get_ip_address_response(error_code const& e
, libtorrent::http_parser const& p, rootdevice& d
, http_connection& c)
{
boost::intrusive_ptr<upnp> me(self());
mutex::scoped_lock l(m_mutex);
TORRENT_ASSERT(d.magic == 1337);
if (d.upnp_connection && d.upnp_connection.get() == &c)
{
d.upnp_connection->close();
d.upnp_connection.reset();
}
if (m_closing) return;
if (e && e != asio::error::eof)
{
char msg[200];
snprintf(msg, sizeof(msg), "error while getting external IP address: %s", e.message().c_str());
log(msg, l);
if (num_mappings() > 0) update_map(d, 0, l);
return;
}
if (!p.header_finished())
{
log("error while getting external IP address: incomplete http message", l);
if (num_mappings() > 0) update_map(d, 0, l);
return;
}
if (p.status_code() != 200)
{
char msg[200];
snprintf(msg, sizeof(msg), "error while getting external IP address: %s", p.message().c_str());
log(msg, l);
if (num_mappings() > 0) update_map(d, 0, l);
return;
}
// response may look like
// <?xml version="1.0"?>
// <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
// <s:Body><u:GetExternalIPAddressResponse xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">
// <NewExternalIPAddress>192.168.160.19</NewExternalIPAddress>
// </u:GetExternalIPAddressResponse>
// </s:Body>
// </s:Envelope>
char msg[500];
snprintf(msg, sizeof(msg), "get external IP address response: %s"
, std::string(p.get_body().begin, p.get_body().end).c_str());
log(msg, l);
ip_address_parse_state s;
xml_parse((char*)p.get_body().begin, (char*)p.get_body().end
, boost::bind(&find_ip_address, _1, _2, boost::ref(s)));
if (s.error_code != -1)
{
char msg[200];
snprintf(msg, sizeof(msg), "error while getting external IP address, code: %u"
, s.error_code);
log(msg, l);
}
if (!s.ip_address.empty()) {
snprintf(msg, sizeof(msg), "got router external IP address %s", s.ip_address.c_str());
log(msg, l);
d.external_ip = address::from_string(s.ip_address.c_str(), ec);
} else {
log("failed to find external IP address in response", l);
}
if (num_mappings() > 0) update_map(d, 0, l);
}
void upnp::on_upnp_map_response(error_code const& e void upnp::on_upnp_map_response(error_code const& e
, libtorrent::http_parser const& p, rootdevice& d, int mapping , libtorrent::http_parser const& p, rootdevice& d, int mapping
, http_connection& c) , http_connection& c)
@@ -1188,7 +1324,7 @@ void upnp::on_upnp_map_response(error_code const& e
if (s.error_code == -1) if (s.error_code == -1)
{ {
l.unlock(); l.unlock();
m_callback(mapping, m.external_port, error_code()); m_callback(mapping, d.external_ip, m.external_port, error_code());
l.lock(); l.lock();
if (d.lease_duration > 0) if (d.lease_duration > 0)
{ {
@@ -1231,7 +1367,7 @@ void upnp::return_error(int mapping, int code, mutex::scoped_lock& l)
error_string += e->msg; error_string += e->msg;
} }
l.unlock(); l.unlock();
m_callback(mapping, 0, error_code(code, upnp_category)); m_callback(mapping, address(), 0, error_code(code, upnp_category));
l.lock(); l.lock();
} }

View File

@@ -193,11 +193,11 @@ struct callback_info
std::list<callback_info> callbacks; std::list<callback_info> callbacks;
void callback(int mapping, int port, error_code const& err) void callback(int mapping, address const& ip, int port, error_code const& err)
{ {
callback_info info = {mapping, port, err}; callback_info info = {mapping, port, err};
callbacks.push_back(info); callbacks.push_back(info);
std::cerr << "mapping: " << mapping << ", port: " << port std::cerr << "mapping: " << mapping << ", port: " << port << ", IP: " << ip
<< ", error: \"" << err.message() << "\"\n"; << ", error: \"" << err.message() << "\"\n";
//TODO: store the callbacks and verify that the ports were successful //TODO: store the callbacks and verify that the ports were successful
} }