diff --git a/include/libtorrent/aux_/session_impl.hpp b/include/libtorrent/aux_/session_impl.hpp index 1220507ed..1e8960657 100644 --- a/include/libtorrent/aux_/session_impl.hpp +++ b/include/libtorrent/aux_/session_impl.hpp @@ -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> 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>, 4> m_ses_extensions; diff --git a/include/libtorrent/aux_/session_interface.hpp b/include/libtorrent/aux_/session_interface.hpp index ff97800fc..19e6f91de 100644 --- a/include/libtorrent/aux_/session_interface.hpp +++ b/include/libtorrent/aux_/session_interface.hpp @@ -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; diff --git a/include/libtorrent/broadcast_socket.hpp b/include/libtorrent/broadcast_socket.hpp index 914a11325..82f82d058 100644 --- a/include/libtorrent/broadcast_socket.hpp +++ b/include/libtorrent/broadcast_socket.hpp @@ -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 receive_handler_t; diff --git a/include/libtorrent/ip_voter.hpp b/include/libtorrent/ip_voter.hpp index 3823ed9a5..c0d05fa0c 100644 --- a/include/libtorrent/ip_voter.hpp +++ b/include/libtorrent/ip_voter.hpp @@ -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]; }; } diff --git a/include/libtorrent/peer_list.hpp b/include/libtorrent/peer_list.hpp index b1cd4b7b9..2f0d60281 100644 --- a/include/libtorrent/peer_list.hpp +++ b/include/libtorrent/peer_list.hpp @@ -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 diff --git a/simulation/test_dht.cpp b/simulation/test_dht.cpp index 95c592704..18c72fea2 100644 --- a/simulation/test_dht.cpp +++ b/simulation/test_dht.cpp @@ -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; } diff --git a/src/broadcast_socket.cpp b/src/broadcast_socket.cpp index 26f9a3756..c6145dc70 100644 --- a/src/broadcast_socket.cpp +++ b/src/broadcast_socket.cpp @@ -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) diff --git a/src/ip_voter.cpp b/src/ip_voter.cpp index 29738daae..b28318dd0 100644 --- a/src/ip_voter.cpp +++ b/src/ip_voter.cpp @@ -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 diff --git a/src/peer_connection.cpp b/src/peer_connection.cpp index 8ee4a0d30..7b8d407c5 100644 --- a/src/peer_connection.cpp +++ b/src/peer_connection.cpp @@ -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" diff --git a/src/peer_list.cpp b/src/peer_list.cpp index 04aabc0ea..833372540 100644 --- a/src/peer_list.cpp +++ b/src/peer_list.cpp @@ -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; diff --git a/src/session_impl.cpp b/src/session_impl.cpp index 1cd8e652f..07d138fd3 100644 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -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"); diff --git a/src/torrent.cpp b/src/torrent.cpp index 34744c6b6..8f41ed596 100644 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -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; diff --git a/test/test_ip_voter.cpp b/test/test_ip_voter.cpp index 0efc20762..92d2ad7d7 100644 --- a/test/test_ip_voter.cpp +++ b/test/test_ip_voter.cpp @@ -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 } diff --git a/test/test_peer_list.cpp b/test/test_peer_list.cpp index a38d279dd..8ef3d6f45 100644 --- a/test/test_peer_list.cpp +++ b/test/test_peer_list.cpp @@ -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 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
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
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
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
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;