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;
int listen_queue_size;
bool announce_double_nat;
};
``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()
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
===========

View File

@ -235,8 +235,8 @@ namespace libtorrent
// called when a port mapping is successful, or a router returns
// a failure to map a port
void on_port_mapping(int mapping, int port, error_code const& ec
, int nat_transport);
void on_port_mapping(int mapping, address const& ip, int port
, error_code const& ec, int nat_transport);
bool is_aborted() const { return m_abort; }
bool is_paused() const { return m_paused; }
@ -301,6 +301,7 @@ namespace libtorrent
session_status status() const;
void set_peer_id(peer_id const& id);
void set_key(int key);
address listen_address() const;
unsigned short listen_port() const;
void abort();
@ -581,6 +582,11 @@ namespace libtorrent
struct listen_socket_t
{
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
// listen port. In case a NAT port forward was
// successfully opened, this will be set to the

View File

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

View File

@ -251,6 +251,7 @@ namespace libtorrent
, mixed_mode_algorithm(peer_proportional)
, rate_limit_utp(false)
, listen_queue_size(5)
, announce_double_nat(false)
{}
// libtorrent version. Used for forward binary compatibility
@ -992,6 +993,12 @@ namespace libtorrent
// the number of connections to accept while we're
// not waiting in an accept() call.
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

View File

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

View File

@ -59,6 +59,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/io.hpp"
#include "libtorrent/socket.hpp"
#include "libtorrent/aux_/session_impl.hpp"
#include "libtorrent/broadcast_socket.hpp" // for is_local
using namespace libtorrent;
@ -180,23 +181,34 @@ namespace libtorrent
}
else
#endif
if (!settings.announce_ip.empty())
if (!m_ses.settings().anonymous_mode)
{
error_code ec;
if (!ec) url += "&ip=" + escape_string(
settings.announce_ip.c_str(), settings.announce_ip.size());
}
if (!tracker_req().ipv6.empty() && !i2p)
{
url += "&ipv6=";
url += tracker_req().ipv6;
}
if (!tracker_req().ipv4.empty() && !i2p)
{
url += "&ipv4=";
url += tracker_req().ipv4;
if (!settings.announce_ip.empty())
{
url += "&ip=" + escape_string(
settings.announce_ip.c_str(), settings.announce_ip.size());
}
else if (m_ses.settings().announce_double_nat
&& is_local(m_ses.listen_address()))
{
// 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
// source IP to determine our origin
url += "&ip=" + print_address(m_ses.listen_address());
}
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
m_socket.async_receive_from(asio::buffer(&m_response_buffer, 16)
, 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()
, 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
{
mutex::scoped_lock l(m_mutex);
@ -164,7 +179,7 @@ void natpmp::disable(error_code const& ec, mutex::scoped_lock& l)
i->protocol = none;
int index = i - m_mappings.begin();
l.unlock();
m_callback(index, 0, ec);
m_callback(index, address(), 0, ec);
l.lock();
}
close_impl(l);
@ -337,7 +352,7 @@ void natpmp::send_map_request(int i, mutex::scoped_lock& l)
log(msg, l);
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.outstanding_request = true;
if (m_abort)
@ -428,11 +443,45 @@ void natpmp::on_reply(error_code const& e
error_code 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;
int version = read_uint8(in);
int cmd = read_uint8(in);
int result = read_uint16(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 public_port = read_uint16(in);
int lifetime = read_uint32(in);
@ -505,13 +554,14 @@ void natpmp::on_reply(error_code const& e
m->expires = time_now() + hours(2);
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();
}
else if (m->action == mapping_t::action_add)
{
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();
}

View File

@ -3608,6 +3608,16 @@ namespace aux {
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
{
// 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));
}
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)
{
TORRENT_ASSERT(is_network_thread());
@ -3685,8 +3695,12 @@ namespace aux {
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;
}
if (m_alerts.should_post<portmap_alert>())
m_alerts.post_alert(portmap_alert(mapping, port
, map_transport));
@ -4248,7 +4262,7 @@ namespace aux {
natpmp* n = new (std::nothrow) natpmp(m_io_service
, m_listen_interface.address()
, 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
, this, _1, 0));
if (n == 0) return 0;
@ -4280,7 +4294,7 @@ namespace aux {
, m_listen_interface.address()
, m_settings.user_agent
, 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
, this, _1, 1)
, m_settings.upnp_ignore_nonrouters);

View File

@ -960,7 +960,42 @@ void upnp::on_upnp_xml(error_code const& e
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)
@ -974,7 +1009,7 @@ void upnp::disable(error_code const& ec, mutex::scoped_lock& l)
if (i->protocol == none) continue;
i->protocol = none;
l.unlock();
m_callback(i - m_mappings.begin(), 0, ec);
m_callback(i - m_mappings.begin(), address(), 0, ec);
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
{
int code;
@ -1074,6 +1132,84 @@ namespace libtorrent
#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
, libtorrent::http_parser const& p, rootdevice& d, int mapping
, http_connection& c)
@ -1188,7 +1324,7 @@ void upnp::on_upnp_map_response(error_code const& e
if (s.error_code == -1)
{
l.unlock();
m_callback(mapping, m.external_port, error_code());
m_callback(mapping, d.external_ip, m.external_port, error_code());
l.lock();
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;
}
l.unlock();
m_callback(mapping, 0, error_code(code, upnp_category));
m_callback(mapping, address(), 0, error_code(code, upnp_category));
l.lock();
}

View File

@ -193,11 +193,11 @@ struct callback_info
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};
callbacks.push_back(info);
std::cerr << "mapping: " << mapping << ", port: " << port
std::cerr << "mapping: " << mapping << ", port: " << port << ", IP: " << ip
<< ", error: \"" << err.message() << "\"\n";
//TODO: store the callbacks and verify that the ports were successful
}