support separate external ip for each interface (#1383)

support separate external ip for each interface
This commit is contained in:
Steven Siloti 2016-12-04 12:58:51 -08:00 committed by Arvid Norberg
parent 69eff36a52
commit b9169609df
14 changed files with 122 additions and 72 deletions

View File

@ -131,9 +131,9 @@ namespace libtorrent
udp_port_mapping[1] = -1;
}
// this is typically empty but can be set
// to the WAN IP address of NAT-PMP or UPnP router
address external_address;
// this may be empty but can be set
// to the WAN IP address of a NAT router
ip_voter external_address;
// this is a cached local endpoint for the listen TCP socket
tcp::endpoint local_endpoint;
@ -658,7 +658,7 @@ namespace libtorrent
void set_external_address(address const& ip
, int source_type, address const& source) override;
virtual external_ip const& external_address() const override;
virtual external_ip external_address() const override;
// used when posting synchronous function
// calls to session_impl and torrent objects
@ -1223,9 +1223,6 @@ namespace libtorrent
std::list<std::shared_ptr<tracker_logger>> m_tracker_loggers;
#endif
// state for keeping track of external IPs
external_ip m_external_ip;
#ifndef TORRENT_DISABLE_EXTENSIONS
// this is a list to allow extensions to potentially remove themselves.
std::array<std::vector<std::shared_ptr<plugin>>, 4> m_ses_extensions;

View File

@ -139,7 +139,7 @@ namespace libtorrent { namespace aux
virtual void set_external_address(address const& ip
, int source_type, address const& source) = 0;
virtual external_ip const& external_address() const = 0;
virtual external_ip external_address() const = 0;
virtual disk_interface& disk_thread() = 0;

View File

@ -53,6 +53,7 @@ namespace libtorrent
// determines if the operating system supports IPv6
TORRENT_EXTRA_EXPORT bool supports_ipv6();
address ensure_v6(address const& a);
typedef std::function<void(udp::endpoint const& from
, char* buffer, int size)> receive_handler_t;

View File

@ -106,23 +106,30 @@ namespace libtorrent
time_point m_last_rotate;
};
// this keeps track of multiple external IPs (for now, just IPv6 and IPv4, but
// it could be extended to deal with loopback and local network addresses as well)
// stores one address for each combination of local/global and ipv4/ipv6
// use of this class should be avoided, get the IP from the appropriate
// listen interface wherever possible
struct TORRENT_EXTRA_EXPORT external_ip
{
// returns true if a different IP is the top vote now
// i.e. we changed our idea of what our external IP is
bool cast_vote(address const& ip, int source_type, address const& source);
external_ip()
#if TORRENT_USE_IPV6
: m_addresses{{address_v4(), address_v6()}, {address_v4(), address_v6()}}
#endif
{}
external_ip(address const& local4, address const& global4
, address const& local6, address const& global6);
// the external IP as it would be observed from `ip`
address external_address(address const& ip) const;
private:
// for now, assume one external IPv4 and one external IPv6 address
// 0 = IPv4 1 = IPv6
// TODO: 1 instead, have one instance per possible subnet, global IPv4, global IPv6, loopback, 192.168.x.x, 10.x.x.x, etc.
ip_voter m_vote_group[2];
// support one local and one global address per address family
// [0][n] = global [1][n] = local
// [n][0] = IPv4 [n][1] = IPv6
// TODO: 1 have one instance per possible subnet, 192.168.x.x, 10.x.x.x, etc.
address m_addresses[2][2];
};
}

View File

@ -43,14 +43,13 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/socket.hpp"
#include "libtorrent/address.hpp"
#include "libtorrent/invariant_check.hpp"
#include "libtorrent/ip_voter.hpp"
#include "libtorrent/config.hpp"
#include "libtorrent/debug.hpp"
#include "libtorrent/peer_connection_interface.hpp"
namespace libtorrent
{
struct external_ip;
struct ip_filter;
class port_filter;
struct torrent_peer_allocator_interface;
@ -68,7 +67,7 @@ namespace libtorrent
, max_peerlist_size(1000)
, min_reconnect_time(60)
, loop_counter(0)
, ip(nullptr), port(0)
, port(0)
, max_failcount(3)
, peer_allocator(nullptr)
{}
@ -90,7 +89,7 @@ namespace libtorrent
// these are used only by find_connect_candidates in order
// to implement peer ranking. See:
// http://blog.libtorrent.org/2012/12/swarm-connectivity/
external_ip const* ip;
external_ip ip;
int port;
// the number of times a peer must fail before it's no longer considered

View File

@ -143,7 +143,7 @@ TORRENT_TEST(dht_bootstrap)
ses.post_session_stats();
std::printf("depth: %d nodes: %d\n", routing_table_depth, num_nodes);
TEST_CHECK(routing_table_depth >= 8);
TEST_CHECK(num_nodes >= 110);
TEST_CHECK(num_nodes >= 50);
dht.stop();
return true;
}

View File

@ -150,6 +150,15 @@ namespace libtorrent
#endif
}
address ensure_v6(address const& a)
{
#if TORRENT_USE_IPV6
return a == address_v4() ? address_v6() : a;
#else
return a;
#endif
}
broadcast_socket::broadcast_socket(
udp::endpoint const& multicast_endpoint)
: m_multicast_endpoint(multicast_endpoint)

View File

@ -173,14 +173,21 @@ namespace libtorrent
return true;
}
bool external_ip::cast_vote(address const& ip, int source_type, address const& source)
external_ip::external_ip(address const& local4, address const& global4
, address const& local6, address const& global6)
: m_addresses{{global4, ensure_v6(global6)}, {local4, ensure_v6(local6)}}
{
return m_vote_group[ip.is_v6()].cast_vote(ip, source_type, source);
#if TORRENT_USE_IPV6
TORRENT_ASSERT(m_addresses[0][1].is_v6());
TORRENT_ASSERT(m_addresses[1][1].is_v6());
#endif
TORRENT_ASSERT(m_addresses[0][0].is_v4());
TORRENT_ASSERT(m_addresses[1][0].is_v4());
}
address external_ip::external_address(address const& ip) const
{
address ext = m_vote_group[ip.is_v6()].external_address();
address ext = m_addresses[is_local(ip)][ip.is_v6()];
#if TORRENT_USE_IPV6
if (ip.is_v6() && ext == address_v4()) return address_v6();
#endif

View File

@ -59,6 +59,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/performance_counters.hpp" // for counters
#include "libtorrent/alert_manager.hpp" // for alert_manageralert_manager
#include "libtorrent/ip_filter.hpp"
#include "libtorrent/ip_voter.hpp"
#include "libtorrent/kademlia/node_id.hpp"
#include "libtorrent/close_reason.hpp"
#include "libtorrent/aux_/has_block.hpp"

View File

@ -479,7 +479,7 @@ namespace libtorrent
if (m_finished != state->is_finished)
recalculate_connect_candidates(state);
external_ip const& external = *state->ip;
external_ip const& external = state->ip;
int external_port = state->port;
if (m_round_robin >= int(m_peers.size())) m_round_robin = 0;

View File

@ -5334,10 +5334,9 @@ namespace aux {
{
// TODO: 1 report the proper address of the router as the source IP of
// this vote of our external address, instead of the empty address
set_external_address(ip, source_router, address());
ls->external_address.cast_vote(ip, source_router, address());
}
ls->external_address = ip;
if (tcp) ls->tcp_external_port = port;
else ls->udp_external_port = port;
}
@ -6463,9 +6462,22 @@ namespace aux {
m_upnp.reset();
}
external_ip const& session_impl::external_address() const
external_ip session_impl::external_address() const
{
return m_external_ip;
address ips[2][2];
// take the first IP we find which matches each category
for (auto const& i : m_listen_sockets)
{
address external_addr = i.external_address.external_address();
if (ips[0][external_addr.is_v6()] == address())
ips[0][external_addr.is_v6()] = external_addr;
address local_addr = i.local_endpoint.address();
if (ips[is_local(local_addr)][local_addr.is_v6()] == address())
ips[is_local(local_addr)][local_addr.is_v6()] = local_addr;
}
return external_ip(ips[1][0], ips[0][0], ips[1][1], ips[0][1]);
}
// this is the DHT observer version. DHT is the implied source
@ -6475,6 +6487,7 @@ namespace aux {
set_external_address(ip, source_dht, source);
}
// TODO 3 pass in a specific listen socket rather than an address family
address session_impl::external_address(udp proto)
{
#if !TORRENT_USE_IPV6
@ -6488,7 +6501,8 @@ namespace aux {
else
#endif
addr = address_v4();
return m_external_ip.external_address(addr);
addr = external_address().external_address(addr);
return addr;
}
void session_impl::get_peers(sha1_hash const& ih)
@ -6596,7 +6610,16 @@ namespace aux {
}
#endif
if (!m_external_ip.cast_vote(ip, source_type, source)) return;
// for now, just pick the first socket with a matching address family
// TODO: 3 allow the caller to select which listen socket to update
for (auto& i : m_listen_sockets)
{
if (i.local_endpoint.address().is_v4() != ip.is_v4())
continue;
if (!i.external_address.cast_vote(ip, source_type, source)) return;
break;
}
#ifndef TORRENT_DISABLE_LOGGING
session_log(" external IP updated");

View File

@ -10028,7 +10028,7 @@ namespace libtorrent
ret.min_reconnect_time = settings().get_int(settings_pack::min_reconnect_time);
ret.peer_allocator = m_ses.get_peer_allocator();
ret.ip = &m_ses.external_address();
ret.ip = m_ses.external_address();
ret.port = m_ses.listen_port();
ret.max_failcount = settings().get_int(settings_pack::max_failcount);
return ret;

View File

@ -148,12 +148,12 @@ TORRENT_TEST(one_ip)
TEST_CHECK(ipv.external_address() == addr1);
}
TORRENT_TEST(externa_ip_1)
TORRENT_TEST(ip_voter_1)
{
init_rand_address();
// test external ip voting
external_ip ipv1;
ip_voter ipv1;
// test a single malicious node
// adds 50 legitimate responses from different peers
@ -168,14 +168,14 @@ TORRENT_TEST(externa_ip_1)
ipv1.cast_vote(real_external, aux::session_impl::source_dht, rand_v4());
ipv1.cast_vote(rand_v4(), aux::session_impl::source_dht, malicious);
}
TEST_CHECK(ipv1.external_address(rand_v4()) == real_external);
TEST_CHECK(ipv1.external_address() == real_external);
}
TORRENT_TEST(externa_ip_2)
TORRENT_TEST(ip_voter_2)
{
init_rand_address();
external_ip ipv2;
ip_voter ipv2,ipv6;
// test a single malicious node
// adds 50 legitimate responses from different peers
@ -185,31 +185,40 @@ TORRENT_TEST(externa_ip_2)
TEST_CHECK(!ec);
address real_external1 = address_v4::from_string("5.5.5.5", ec);
TEST_CHECK(!ec);
address malicious_external = address_v4::from_string("3.3.3.3", ec);
TEST_CHECK(!ec);
address malicious2;
address real_external2;
address malicious_external2;
#if TORRENT_USE_IPV6
if (supports_ipv6())
{
malicious2 = address_v6::from_string("2f90::", ec);
TEST_CHECK(!ec);
real_external2 = address_v6::from_string("2f80::", ec);
TEST_CHECK(!ec);
malicious_external2 = address_v6::from_string("2f70::", ec);
TEST_CHECK(!ec);
}
#endif
malicious = address_v4::from_string("4.4.4.4", ec);
TEST_CHECK(!ec);
address malicious_external = address_v4::from_string("3.3.3.3", ec);
TEST_CHECK(!ec);
for (int i = 0; i < 50; ++i)
{
ipv2.cast_vote(real_external1, aux::session_impl::source_dht, rand_v4());
ipv2.cast_vote(malicious_external, aux::session_impl::source_dht, malicious);
#if TORRENT_USE_IPV6
if (supports_ipv6())
ipv2.cast_vote(real_external2, aux::session_impl::source_dht, rand_v6());
{
ipv6.cast_vote(real_external2, aux::session_impl::source_dht, rand_v6());
ipv6.cast_vote(malicious_external2, aux::session_impl::source_dht, malicious2);
}
#endif
ipv2.cast_vote(malicious_external, aux::session_impl::source_dht, malicious);
}
TEST_CHECK(ipv2.external_address(rand_v4()) == real_external1);
TEST_CHECK(ipv2.external_address() == real_external1);
#if TORRENT_USE_IPV6
if (supports_ipv6())
TEST_CHECK(ipv2.external_address(rand_v6()) == real_external2);
TEST_CHECK(ipv6.external_address() == real_external2);
#endif
}

View File

@ -164,8 +164,7 @@ bool has_peer(peer_list const& p, tcp::endpoint const& ep)
return its.first != its.second;
}
torrent_state init_state(torrent_peer_allocator& allocator
, external_ip& ext_ip)
torrent_state init_state(torrent_peer_allocator& allocator)
{
torrent_state st;
st.is_finished = false;
@ -173,7 +172,6 @@ torrent_state init_state(torrent_peer_allocator& allocator
st.max_peerlist_size = 1000;
st.allow_multiple_connections_per_ip = false;
st.peer_allocator = &allocator;
st.ip = &ext_ip;
st.port = 9999;
return st;
}
@ -202,13 +200,12 @@ void connect_peer(peer_list& p, mock_torrent& t, torrent_state& st)
}
static torrent_peer_allocator allocator;
static external_ip ext_ip;
// test multiple peers with the same IP
// when disallowing it
TORRENT_TEST(multiple_ips_disallowed)
{
torrent_state st = init_state(allocator, ext_ip);
torrent_state st = init_state(allocator);
mock_torrent t(&st);
peer_list p;
t.m_p = &p;
@ -230,7 +227,7 @@ TORRENT_TEST(multiple_ips_disallowed)
// when allowing it
TORRENT_TEST(multiple_ips_allowed)
{
torrent_state st = init_state(allocator, ext_ip);
torrent_state st = init_state(allocator);
mock_torrent t(&st);
st.allow_multiple_connections_per_ip = true;
peer_list p;
@ -252,7 +249,7 @@ TORRENT_TEST(multiple_ips_allowed)
// with allow_multiple_connections_per_ip enabled
TORRENT_TEST(multiple_ips_allowed2)
{
torrent_state st = init_state(allocator, ext_ip);
torrent_state st = init_state(allocator);
mock_torrent t(&st);
st.allow_multiple_connections_per_ip = true;
peer_list p;
@ -291,7 +288,7 @@ TORRENT_TEST(multiple_ips_allowed2)
// with allow_multiple_connections_per_ip disabled
TORRENT_TEST(multiple_ips_disallowed2)
{
torrent_state st = init_state(allocator, ext_ip);
torrent_state st = init_state(allocator);
mock_torrent t(&st);
st.allow_multiple_connections_per_ip = false;
peer_list p;
@ -325,7 +322,7 @@ TORRENT_TEST(multiple_ips_disallowed2)
// and update_peer_port
TORRENT_TEST(update_peer_port)
{
torrent_state st = init_state(allocator, ext_ip);
torrent_state st = init_state(allocator);
mock_torrent t(&st);
st.allow_multiple_connections_per_ip = false;
peer_list p;
@ -348,7 +345,7 @@ TORRENT_TEST(update_peer_port)
// and update_peer_port, causing collission
TORRENT_TEST(update_peer_port_collide)
{
torrent_state st = init_state(allocator, ext_ip);
torrent_state st = init_state(allocator);
mock_torrent t(&st);
st.allow_multiple_connections_per_ip = true;
peer_list p;
@ -384,7 +381,7 @@ std::shared_ptr<mock_peer_connection> shared_from_this(libtorrent::peer_connecti
// test ip filter
TORRENT_TEST(ip_filter)
{
torrent_state st = init_state(allocator, ext_ip);
torrent_state st = init_state(allocator);
mock_torrent t(&st);
st.allow_multiple_connections_per_ip = false;
peer_list p;
@ -422,7 +419,7 @@ TORRENT_TEST(ip_filter)
// test port filter
TORRENT_TEST(port_filter)
{
torrent_state st = init_state(allocator, ext_ip);
torrent_state st = init_state(allocator);
mock_torrent t(&st);
st.allow_multiple_connections_per_ip = false;
peer_list p;
@ -460,7 +457,7 @@ TORRENT_TEST(port_filter)
// test banning peers
TORRENT_TEST(ban_peers)
{
torrent_state st = init_state(allocator, ext_ip);
torrent_state st = init_state(allocator);
mock_torrent t(&st);
st.allow_multiple_connections_per_ip = false;
peer_list p;
@ -500,7 +497,7 @@ TORRENT_TEST(ban_peers)
// test erase_peers when we fill up the peer list
TORRENT_TEST(erase_peers)
{
torrent_state st = init_state(allocator, ext_ip);
torrent_state st = init_state(allocator);
mock_torrent t(&st);
st.max_peerlist_size = 100;
st.allow_multiple_connections_per_ip = true;
@ -533,7 +530,7 @@ TORRENT_TEST(erase_peers)
// test set_ip_filter
TORRENT_TEST(set_ip_filter)
{
torrent_state st = init_state(allocator, ext_ip);
torrent_state st = init_state(allocator);
std::vector<address> banned;
mock_torrent t(&st);
@ -563,7 +560,7 @@ TORRENT_TEST(set_ip_filter)
// test set_port_filter
TORRENT_TEST(set_port_filter)
{
torrent_state st = init_state(allocator, ext_ip);
torrent_state st = init_state(allocator);
std::vector<address> banned;
mock_torrent t(&st);
@ -594,7 +591,7 @@ TORRENT_TEST(set_port_filter)
// test set_max_failcount
TORRENT_TEST(set_max_failcount)
{
torrent_state st = init_state(allocator, ext_ip);
torrent_state st = init_state(allocator);
mock_torrent t(&st);
peer_list p;
@ -624,7 +621,7 @@ TORRENT_TEST(set_max_failcount)
// test set_seed
TORRENT_TEST(set_seed)
{
torrent_state st = init_state(allocator, ext_ip);
torrent_state st = init_state(allocator);
mock_torrent t(&st);
peer_list p;
@ -658,7 +655,7 @@ TORRENT_TEST(set_seed)
// test has_peer
TORRENT_TEST(has_peer)
{
torrent_state st = init_state(allocator, ext_ip);
torrent_state st = init_state(allocator);
std::vector<address> banned;
mock_torrent t(&st);
@ -690,7 +687,7 @@ TORRENT_TEST(has_peer)
// test connect_candidates torrent_finish
TORRENT_TEST(connect_candidates_finish)
{
torrent_state st = init_state(allocator, ext_ip);
torrent_state st = init_state(allocator);
std::vector<address> banned;
mock_torrent t(&st);
@ -729,7 +726,7 @@ TORRENT_TEST(connect_candidates_finish)
// test self-connection
TORRENT_TEST(self_connection)
{
torrent_state st = init_state(allocator, ext_ip);
torrent_state st = init_state(allocator);
mock_torrent t(&st);
st.allow_multiple_connections_per_ip = false;
peer_list p;
@ -759,7 +756,7 @@ TORRENT_TEST(self_connection)
// test double connection (both incoming)
TORRENT_TEST(double_connection)
{
torrent_state st = init_state(allocator, ext_ip);
torrent_state st = init_state(allocator);
mock_torrent t(&st);
st.allow_multiple_connections_per_ip = false;
peer_list p;
@ -787,7 +784,7 @@ TORRENT_TEST(double_connection)
// test double connection (we loose)
TORRENT_TEST(double_connection_loose)
{
torrent_state st = init_state(allocator, ext_ip);
torrent_state st = init_state(allocator);
mock_torrent t(&st);
st.allow_multiple_connections_per_ip = false;
peer_list p;
@ -816,7 +813,7 @@ TORRENT_TEST(double_connection_loose)
// test double connection (we win)
TORRENT_TEST(double_connection_win)
{
torrent_state st = init_state(allocator, ext_ip);
torrent_state st = init_state(allocator);
mock_torrent t(&st);
st.allow_multiple_connections_per_ip = false;
peer_list p;
@ -845,7 +842,7 @@ TORRENT_TEST(double_connection_win)
// test incoming connection when we are at the list size limit
TORRENT_TEST(incoming_size_limit)
{
torrent_state st = init_state(allocator, ext_ip);
torrent_state st = init_state(allocator);
st.max_peerlist_size = 5;
mock_torrent t(&st);
st.allow_multiple_connections_per_ip = false;
@ -890,7 +887,7 @@ TORRENT_TEST(incoming_size_limit)
// test new peer when we are at the list size limit
TORRENT_TEST(new_peer_size_limit)
{
torrent_state st = init_state(allocator, ext_ip);
torrent_state st = init_state(allocator);
st.max_peerlist_size = 5;
mock_torrent t(&st);
st.allow_multiple_connections_per_ip = false;