/* Copyright (c) 2014-2018, 2020-2021, Arvid Norberg Copyright (c) 2016, 2018, 2020-2021, Alden Torres All rights reserved. You may use, distribute and modify this code under the terms of the BSD license, see LICENSE file. */ #include "libtorrent/aux_/peer_list.hpp" #include "libtorrent/torrent_handle.hpp" #include "libtorrent/aux_/torrent_peer_allocator.hpp" #include "libtorrent/peer_connection_interface.hpp" #include "libtorrent/aux_/stat.hpp" #include "libtorrent/aux_/ip_voter.hpp" #include "libtorrent/ip_filter.hpp" #include "libtorrent/peer_info.hpp" #include "libtorrent/aux_/random.hpp" #include "libtorrent/aux_/socket_io.hpp" #include "test.hpp" #include "setup_transfer.hpp" #include #include // for shared_ptr #include using namespace lt; using namespace lt::aux; namespace { struct mock_torrent; struct mock_peer_connection : peer_connection_interface , std::enable_shared_from_this { mock_peer_connection(mock_torrent* tor, bool out) : m_choked(false) , m_outgoing(out) , m_tp(nullptr) , m_our_id(nullptr) , m_disconnect_called(false) , m_torrent(*tor) { aux::random_bytes(m_id); } mock_peer_connection(mock_torrent* tor, bool out, tcp::endpoint const& remote) : mock_peer_connection(tor, out) { m_remote = remote; m_local = ep("127.0.0.1", 8080); } #if TORRENT_USE_I2P mock_peer_connection(mock_torrent* tor, bool out, std::string remote) : mock_peer_connection(tor, out) { m_i2p_destination = std::move(remote); } #endif virtual ~mock_peer_connection() = default; #if !defined TORRENT_DISABLE_LOGGING bool should_log(peer_log_alert::direction_t) const noexcept override { return true; } void peer_log(peer_log_alert::direction_t, peer_log_alert::event_t , char const* fmt, ...) const noexcept override { va_list v; va_start(v, fmt); #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-nonliteral" #endif std::vprintf(fmt, v); #ifdef __clang__ #pragma clang diagnostic pop #endif va_end(v); } #endif bool was_disconnected() const { return m_disconnect_called; } void set_local_ep(tcp::endpoint const& ep) { m_local = ep; } aux::stat m_stat; bool m_choked; bool m_outgoing; torrent_peer* m_tp; tcp::endpoint m_remote; tcp::endpoint m_local; #if TORRENT_USE_I2P std::string m_i2p_destination; std::string m_local_i2p_endpoint; #endif peer_id m_id; peer_id m_our_id; bool m_disconnect_called; mock_torrent& m_torrent; #if TORRENT_USE_I2P std::string const& destination() const override { return m_i2p_destination; } std::string const& local_i2p_endpoint() const override { return m_local_i2p_endpoint; } #endif void get_peer_info(peer_info&) const override {} tcp::endpoint const& remote() const override { return m_remote; } tcp::endpoint local_endpoint() const override { return m_local; } void disconnect(error_code const& ec , operation_t op, disconnect_severity_t error = peer_connection_interface::normal) override; peer_id const& pid() const override { return m_id; } peer_id our_pid() const override { return m_our_id; } void set_holepunch_mode() override {} torrent_peer* peer_info_struct() const override { return m_tp; } void set_peer_info(torrent_peer* pi) override { m_tp = pi; } bool is_outgoing() const override { return m_outgoing; } void add_stat(std::int64_t downloaded, std::int64_t uploaded) override { m_stat.add_stat(downloaded, uploaded); } bool fast_reconnect() const override { return true; } bool is_choked() const override { return m_choked; } bool failed() const override { return false; } aux::stat const& statistics() const override { return m_stat; } }; struct mock_torrent { explicit mock_torrent(torrent_state* st) : m_p(nullptr), m_state(st) {} virtual ~mock_torrent() = default; bool connect_to_peer(torrent_peer* peerinfo) { TORRENT_ASSERT(peerinfo->connection == nullptr); if (peerinfo->connection) return false; auto c = std::make_shared(this, true, peerinfo->ip()); c->set_peer_info(peerinfo); m_connections.push_back(c); m_p->set_connection(peerinfo, c.get()); return true; } peer_list* m_p; torrent_state* m_state; std::vector> m_connections; }; void mock_peer_connection::disconnect(error_code const& , operation_t, disconnect_severity_t) { m_torrent.m_p->connection_closed(*this, 0, m_torrent.m_state); auto const i = std::find(m_torrent.m_connections.begin(), m_torrent.m_connections.end() , std::static_pointer_cast(shared_from_this())); if (i != m_torrent.m_connections.end()) m_torrent.m_connections.erase(i); m_tp = nullptr; m_disconnect_called = true; } bool has_peer(peer_list const& p, tcp::endpoint const& ep) { auto const its = p.find_peers(ep.address()); return its.first != its.second; } torrent_state init_state() { torrent_state st; st.is_finished = false; st.max_peerlist_size = 1000; st.allow_multiple_connections_per_ip = false; st.port = 9999; return st; } torrent_peer* add_peer(peer_list& p, torrent_state& st, tcp::endpoint const& ep) { int cc = p.num_connect_candidates(); torrent_peer* peer = p.add_peer(ep, {}, {}, &st); if (peer) { TEST_EQUAL(p.num_connect_candidates(), cc + 1); TEST_EQUAL(peer->port, ep.port()); } st.erased.clear(); return peer; } #if TORRENT_USE_I2P torrent_peer* add_i2p_peer(peer_list& p, torrent_state& st, std::string const& destination) { int cc = p.num_connect_candidates(); torrent_peer* peer = p.add_i2p_peer(destination, {}, {}, &st); if (peer) { TEST_EQUAL(p.num_connect_candidates(), cc + 1); TEST_EQUAL(peer->dest(), destination); } st.erased.clear(); return peer; } #endif void connect_peer(peer_list& p, mock_torrent& t, torrent_state& st) { torrent_peer* tp = p.connect_one_peer(0, &st); TEST_CHECK(tp); if (!tp) return; t.connect_to_peer(tp); st.erased.clear(); TEST_CHECK(tp->connection); } static torrent_peer_allocator allocator; } // anonymous namespace // test multiple peers with the same IP // when disallowing it TORRENT_TEST(multiple_ips_disallowed) { torrent_state st = init_state(); mock_torrent t(&st); peer_list p(allocator); t.m_p = &p; TEST_EQUAL(p.num_connect_candidates(), 0); torrent_peer* peer1 = p.add_peer(ep("10.0.0.2", 3000), {}, {}, &st); TEST_EQUAL(p.num_peers(), 1); TEST_EQUAL(p.num_connect_candidates(), 1); st.erased.clear(); torrent_peer* peer2 = p.add_peer(ep("10.0.0.2", 9020), {}, {}, &st); TEST_EQUAL(p.num_peers(), 1); TEST_EQUAL(peer1, peer2); TEST_EQUAL(p.num_connect_candidates(), 1); st.erased.clear(); } // test multiple peers with the same IP // when allowing it TORRENT_TEST(multiple_ips_allowed) { torrent_state st = init_state(); mock_torrent t(&st); st.allow_multiple_connections_per_ip = true; peer_list p(allocator); t.m_p = &p; torrent_peer* peer1 = p.add_peer(ep("10.0.0.2", 3000), {}, {}, &st); TEST_EQUAL(p.num_connect_candidates(), 1); TEST_EQUAL(p.num_peers(), 1); st.erased.clear(); torrent_peer* peer2 = p.add_peer(ep("10.0.0.2", 9020), {}, {}, &st); TEST_EQUAL(p.num_peers(), 2); TEST_CHECK(peer1 != peer2); TEST_EQUAL(p.num_connect_candidates(), 2); st.erased.clear(); } // test adding two peers with the same IP, but different ports, to // make sure they can be connected at the same time // with allow_multiple_connections_per_ip enabled TORRENT_TEST(multiple_ips_allowed2) { torrent_state st = init_state(); mock_torrent t(&st); st.allow_multiple_connections_per_ip = true; peer_list p(allocator); t.m_p = &p; torrent_peer* peer1 = p.add_peer(ep("10.0.0.2", 3000), {}, {}, &st); TEST_EQUAL(p.num_connect_candidates(), 1); st.erased.clear(); TEST_EQUAL(p.num_peers(), 1); torrent_peer* tp = p.connect_one_peer(0, &st); TEST_CHECK(tp); t.connect_to_peer(tp); st.erased.clear(); // we only have one peer, we can't // connect another one tp = p.connect_one_peer(0, &st); TEST_CHECK(tp == nullptr); st.erased.clear(); torrent_peer* peer2 = p.add_peer(ep("10.0.0.2", 9020), {}, {}, &st); TEST_EQUAL(p.num_peers(), 2); TEST_CHECK(peer1 != peer2); TEST_EQUAL(p.num_connect_candidates(), 1); st.erased.clear(); tp = p.connect_one_peer(0, &st); TEST_CHECK(tp); t.connect_to_peer(tp); TEST_EQUAL(p.num_connect_candidates(), 0); st.erased.clear(); } // test adding two peers with the same IP, but different ports, to // make sure they can not be connected at the same time // with allow_multiple_connections_per_ip disabled TORRENT_TEST(multiple_ips_disallowed2) { torrent_state st = init_state(); mock_torrent t(&st); st.allow_multiple_connections_per_ip = false; peer_list p(allocator); t.m_p = &p; torrent_peer* peer1 = p.add_peer(ep("10.0.0.2", 3000), {}, {}, &st); TEST_EQUAL(p.num_connect_candidates(), 1); TEST_EQUAL(peer1->port, 3000); st.erased.clear(); TEST_EQUAL(p.num_peers(), 1); torrent_peer* tp = p.connect_one_peer(0, &st); TEST_CHECK(tp); t.connect_to_peer(tp); st.erased.clear(); // we only have one peer, we can't // connect another one tp = p.connect_one_peer(0, &st); TEST_CHECK(tp == nullptr); st.erased.clear(); torrent_peer* peer2 = p.add_peer(ep("10.0.0.2", 9020), {}, {}, &st); TEST_EQUAL(p.num_peers(), 1); TEST_EQUAL(peer2->port, 9020); TEST_CHECK(peer1 == peer2); TEST_EQUAL(p.num_connect_candidates(), 0); st.erased.clear(); } // test incoming connection // and update_peer_port TORRENT_TEST(update_peer_port) { torrent_state st = init_state(); mock_torrent t(&st); st.allow_multiple_connections_per_ip = false; peer_list p(allocator); t.m_p = &p; TEST_EQUAL(p.num_connect_candidates(), 0); auto c = std::make_shared(&t, true, ep("10.0.0.1", 8080)); p.new_connection(*c, 0, &st); TEST_EQUAL(p.num_connect_candidates(), 0); TEST_EQUAL(p.num_peers(), 1); st.erased.clear(); p.update_peer_port(4000, c->peer_info_struct(), peer_info::incoming, &st); TEST_EQUAL(p.num_connect_candidates(), 0); TEST_EQUAL(p.num_peers(), 1); TEST_EQUAL(c->peer_info_struct()->port, 4000); st.erased.clear(); } // test incoming connection // and update_peer_port, causing collision TORRENT_TEST(update_peer_port_collide) { torrent_state st = init_state(); mock_torrent t(&st); st.allow_multiple_connections_per_ip = true; peer_list p(allocator); t.m_p = &p; torrent_peer* peer2 = p.add_peer(ep("10.0.0.1", 4000), {}, {}, &st); TEST_CHECK(peer2); TEST_EQUAL(p.num_connect_candidates(), 1); auto c = std::make_shared(&t, true, ep("10.0.0.1", 8080)); p.new_connection(*c, 0, &st); TEST_EQUAL(p.num_connect_candidates(), 1); // at this point we have two peers, because we think they have different // ports TEST_EQUAL(p.num_peers(), 2); st.erased.clear(); // this peer will end up having the same port as the existing peer in the list p.update_peer_port(4000, c->peer_info_struct(), peer_info::incoming, &st); TEST_EQUAL(p.num_connect_candidates(), 0); // the expected behavior is to replace that one TEST_EQUAL(p.num_peers(), 1); TEST_EQUAL(c->peer_info_struct()->port, 4000); st.erased.clear(); } namespace { std::shared_ptr shared_from_this(lt::peer_connection_interface* p) { return std::static_pointer_cast( static_cast(p)->shared_from_this()); } } // anonymous namespace // test ip filter TORRENT_TEST(ip_filter) { torrent_state st = init_state(); mock_torrent t(&st); st.allow_multiple_connections_per_ip = false; peer_list p(allocator); t.m_p = &p; // add peer 1 torrent_peer* peer1 = add_peer(p, st, ep("10.0.0.2", 3000)); torrent_peer* peer2 = add_peer(p, st, ep("11.0.0.2", 9020)); TEST_CHECK(peer1 != peer2); connect_peer(p, t, st); connect_peer(p, t, st); auto con1 = shared_from_this(peer1->connection); TEST_EQUAL(con1->was_disconnected(), false); auto con2 = shared_from_this(peer2->connection); TEST_EQUAL(con2->was_disconnected(), false); // now, filter one of the IPs and make sure the peer is removed ip_filter filter; filter.add_rule(addr4("11.0.0.0"), addr4("255.255.255.255"), 1); std::vector
banned; p.apply_ip_filter(filter, &st, banned); // we just erased a peer, because it was filtered by the ip filter TEST_EQUAL(st.erased.size(), 1); TEST_EQUAL(p.num_connect_candidates(), 0); TEST_EQUAL(p.num_peers(), 1); TEST_EQUAL(banned.size(), 1); TEST_EQUAL(banned[0], addr4("11.0.0.2")); TEST_EQUAL(con2->was_disconnected(), true); TEST_EQUAL(con1->was_disconnected(), false); } // test port filter TORRENT_TEST(port_filter) { torrent_state st = init_state(); mock_torrent t(&st); st.allow_multiple_connections_per_ip = false; peer_list p(allocator); t.m_p = &p; // add peer 1 torrent_peer* peer1 = add_peer(p, st, ep("10.0.0.2", 3000)); torrent_peer* peer2 = add_peer(p, st, ep("11.0.0.2", 9020)); TEST_CHECK(peer1 != peer2); connect_peer(p, t, st); connect_peer(p, t, st); auto con1 = shared_from_this(peer1->connection); TEST_EQUAL(con1->was_disconnected(), false); auto con2 = shared_from_this(peer2->connection); TEST_EQUAL(con2->was_disconnected(), false); // now, filter one of the IPs and make sure the peer is removed port_filter filter; filter.add_rule(9000, 10000, 1); std::vector banned; p.apply_port_filter(filter, &st, banned); // we just erased a peer, because it was filtered by the ip filter TEST_EQUAL(st.erased.size(), 1); TEST_EQUAL(p.num_connect_candidates(), 0); TEST_EQUAL(p.num_peers(), 1); TEST_EQUAL(banned.size(), 1); TEST_EQUAL(banned[0], ep("11.0.0.2", 9020)); TEST_EQUAL(con2->was_disconnected(), true); TEST_EQUAL(con1->was_disconnected(), false); } // test banning peers TORRENT_TEST(ban_peers) { torrent_state st = init_state(); mock_torrent t(&st); st.allow_multiple_connections_per_ip = false; peer_list p(allocator); t.m_p = &p; torrent_peer* peer1 = add_peer(p, st, ep("10.0.0.1", 4000)); TEST_EQUAL(p.num_connect_candidates(), 1); auto c = std::make_shared(&t, true, ep("10.0.0.1", 8080)); p.new_connection(*c, 0, &st); TEST_EQUAL(p.num_connect_candidates(), 0); TEST_EQUAL(p.num_peers(), 1); st.erased.clear(); // now, ban the peer bool ok = p.ban_peer(c->peer_info_struct()); TEST_EQUAL(ok, true); TEST_EQUAL(bool(peer1->banned), true); // we still have it in the list TEST_EQUAL(p.num_peers(), 1); // it's just not a connect candidate, nor allowed to receive incoming connections TEST_EQUAL(p.num_connect_candidates(), 0); p.connection_closed(*c, 0, &st); TEST_EQUAL(p.num_peers(), 1); TEST_EQUAL(p.num_connect_candidates(), 0); st.erased.clear(); c = std::make_shared(&t, true, ep("10.0.0.1", 8080)); ok = p.new_connection(*c, 0, &st); // since it's banned, we should not allow this incoming connection TEST_EQUAL(ok, false); TEST_EQUAL(p.num_connect_candidates(), 0); st.erased.clear(); } // test erase_peers when we fill up the peer list TORRENT_TEST(erase_peers) { torrent_state st = init_state(); mock_torrent t(&st); st.max_peerlist_size = 100; st.allow_multiple_connections_per_ip = true; peer_list p(allocator); t.m_p = &p; for (int i = 0; i < 100; ++i) { TEST_EQUAL(st.erased.size(), 0); tcp::endpoint ep = rand_tcp_ep(); torrent_peer* peer = add_peer(p, st, ep); TEST_CHECK(peer); if (peer == nullptr || st.erased.size() > 0) { std::printf("unexpected rejection of peer: %s | %d in list. " "added peer %p, erased %d peers\n" , print_endpoint(ep).c_str(), p.num_peers(), static_cast(peer) , int(st.erased.size())); } } TEST_EQUAL(p.num_peers(), 100); // trigger the eviction of one peer torrent_peer* peer = p.add_peer(rand_tcp_ep(), {}, {}, &st); // we either removed an existing peer, or rejected this one // either is valid behavior when the list is full TEST_CHECK(st.erased.size() == 1 || peer == nullptr); } // test set_ip_filter TORRENT_TEST(set_ip_filter) { torrent_state st = init_state(); std::vector
banned; mock_torrent t(&st); peer_list p(allocator); t.m_p = &p; for (int i = 0; i < 100; ++i) { p.add_peer(tcp::endpoint( address_v4(std::uint32_t((10 << 24) + ((i + 10) << 16))), 353), {}, {}, &st); TEST_EQUAL(st.erased.size(), 0); st.erased.clear(); } TEST_EQUAL(p.num_peers(), 100); TEST_EQUAL(p.num_connect_candidates(), 100); // trigger the removal of one peer ip_filter filter; filter.add_rule(addr4("10.13.0.0"), addr4("10.13.255.255"), ip_filter::blocked); p.apply_ip_filter(filter, &st, banned); TEST_EQUAL(st.erased.size(), 1); TEST_EQUAL(st.erased[0]->address(), addr4("10.13.0.0")); TEST_EQUAL(p.num_peers(), 99); TEST_EQUAL(p.num_connect_candidates(), 99); } // test set_port_filter TORRENT_TEST(set_port_filter) { torrent_state st = init_state(); std::vector banned; mock_torrent t(&st); peer_list p(allocator); t.m_p = &p; for (int i = 0; i < 100; ++i) { p.add_peer(tcp::endpoint( address_v4(std::uint32_t((10 << 24) + ((i + 10) << 16))), std::uint16_t(i + 10)), {}, {}, &st); TEST_EQUAL(st.erased.size(), 0); st.erased.clear(); } TEST_EQUAL(p.num_peers(), 100); TEST_EQUAL(p.num_connect_candidates(), 100); // trigger the removal of one peer port_filter filter; filter.add_rule(13, 13, port_filter::blocked); p.apply_port_filter(filter, &st, banned); TEST_EQUAL(st.erased.size(), 1); TEST_EQUAL(st.erased[0]->address(), addr4("10.13.0.0")); TEST_EQUAL(st.erased[0]->port, 13); TEST_EQUAL(p.num_peers(), 99); TEST_EQUAL(p.num_connect_candidates(), 99); } // test set_max_failcount TORRENT_TEST(set_max_failcount) { torrent_state st = init_state(); mock_torrent t(&st); peer_list p(allocator); t.m_p = &p; for (int i = 0; i < 100; ++i) { torrent_peer* peer = p.add_peer(tcp::endpoint( address_v4(std::uint32_t((10 << 24) + ((i + 10) << 16))), std::uint16_t(i + 10)), {}, {}, &st); TEST_EQUAL(st.erased.size(), 0); st.erased.clear(); // every other peer has a failcount of 1 if (i % 2) p.inc_failcount(peer); } TEST_EQUAL(p.num_peers(), 100); TEST_EQUAL(p.num_connect_candidates(), 100); // set the max failcount to 1 and observe how half the peers no longer // are connect candidates st.max_failcount = 1; p.set_max_failcount(&st); TEST_EQUAL(p.num_connect_candidates(), 50); TEST_EQUAL(p.num_peers(), 100); } // test set_seed TORRENT_TEST(set_seed) { torrent_state st = init_state(); mock_torrent t(&st); peer_list p(allocator); t.m_p = &p; for (int i = 0; i < 100; ++i) { torrent_peer* peer = p.add_peer(tcp::endpoint( address_v4(std::uint32_t((10 << 24) + ((i + 10) << 16))), std::uint16_t(i + 10)), {}, {}, &st); TEST_EQUAL(st.erased.size(), 0); st.erased.clear(); // make every other peer a seed if (i % 2) p.set_seed(peer, true); } TEST_EQUAL(p.num_peers(), 100); TEST_EQUAL(p.num_connect_candidates(), 100); // now, the torrent completes and we're no longer interested in // connecting to seeds. Make sure half the peers are no longer // considered connect candidates st.is_finished = true; // this will make the peer_list recalculate the connect candidates std::vector peers; p.connect_one_peer(1, &st); TEST_EQUAL(p.num_connect_candidates(), 50); TEST_EQUAL(p.num_peers(), 100); } // test has_peer TORRENT_TEST(has_peer) { torrent_state st = init_state(); std::vector
banned; mock_torrent t(&st); peer_list p(allocator); t.m_p = &p; torrent_peer* peer1 = add_peer(p, st, ep("10.10.0.1", 10)); torrent_peer* peer2 = add_peer(p, st, ep("10.10.0.2", 11)); TEST_EQUAL(p.num_peers(), 2); TEST_EQUAL(p.num_connect_candidates(), 2); TEST_EQUAL(p.has_peer(peer1), true); TEST_EQUAL(p.has_peer(peer2), true); ip_filter filter; filter.add_rule(addr4("10.10.0.1"), addr4("10.10.0.1"), ip_filter::blocked); p.apply_ip_filter(filter, &st, banned); TEST_EQUAL(st.erased.size(), 1); st.erased.clear(); TEST_EQUAL(p.num_peers(), 1); TEST_EQUAL(p.num_connect_candidates(), 1); TEST_EQUAL(p.has_peer(peer1), false); TEST_EQUAL(p.has_peer(peer2), true); } // test connect_candidates torrent_finish TORRENT_TEST(connect_candidates_finish) { torrent_state st = init_state(); std::vector
banned; mock_torrent t(&st); peer_list p(allocator); t.m_p = &p; torrent_peer* peer1 = add_peer(p, st, ep("10.10.0.1", 10)); TEST_CHECK(peer1); p.set_seed(peer1, true); torrent_peer* peer2 = add_peer(p, st, ep("10.10.0.2", 11)); TEST_CHECK(peer2); p.set_seed(peer2, true); torrent_peer* peer3 = add_peer(p, st, ep("10.10.0.3", 11)); TEST_CHECK(peer3); p.set_seed(peer3, true); torrent_peer* peer4 = add_peer(p, st, ep("10.10.0.4", 11)); TEST_CHECK(peer4); torrent_peer* peer5 = add_peer(p, st, ep("10.10.0.5", 11)); TEST_CHECK(peer5); TEST_EQUAL(p.num_peers(), 5); TEST_EQUAL(p.num_connect_candidates(), 5); st.is_finished = true; // we're finished downloading now, only the non-seeds are // connect candidates // connect to one of them connect_peer(p, t, st); TEST_EQUAL(p.num_peers(), 5); // and there should be one left TEST_EQUAL(p.num_connect_candidates(), 1); } // test self-connection TORRENT_TEST(self_connection) { torrent_state st = init_state(); mock_torrent t(&st); st.allow_multiple_connections_per_ip = false; peer_list p(allocator); t.m_p = &p; // add and connect peer torrent_peer* peer = add_peer(p, st, ep("10.0.0.2", 3000)); connect_peer(p, t, st); auto con_out = shared_from_this(peer->connection); con_out->set_local_ep(ep("10.0.0.2", 8080)); auto con_in = std::make_shared(&t, false, ep("10.0.0.2", 8080)); con_in->set_local_ep(ep("10.0.0.2", 3000)); p.new_connection(*con_in, 0, &st); // from the peer_list's point of view, this looks like we made one // outgoing connection and received an incoming one. Since they share // the exact same endpoints (IP ports) but just swapped source and // destination, the peer list is supposed to figure out that we connected // to ourself and disconnect it TEST_EQUAL(con_out->was_disconnected(), true); TEST_EQUAL(con_in->was_disconnected(), true); } // test double connection (both incoming) TORRENT_TEST(double_connection) { torrent_state st = init_state(); mock_torrent t(&st); st.allow_multiple_connections_per_ip = false; peer_list p(allocator); t.m_p = &p; // we are 10.0.0.1 and the other peer is 10.0.0.2 // first incoming connection auto con1 = std::make_shared(&t, false, ep("10.0.0.2", 7528)); con1->set_local_ep(ep("10.0.0.1", 8080)); p.new_connection(*con1, 0, &st); // the seconds incoming connection auto con2 = std::make_shared(&t, false, ep("10.0.0.2", 3561)); con2->set_local_ep(ep("10.0.0.1", 8080)); p.new_connection(*con2, 0, &st); // the second incoming connection should be closed TEST_EQUAL(con1->was_disconnected(), false); TEST_EQUAL(con2->was_disconnected(), true); } // test double connection (we loose) TORRENT_TEST(double_connection_loose) { torrent_state st = init_state(); mock_torrent t(&st); st.allow_multiple_connections_per_ip = false; peer_list p(allocator); t.m_p = &p; // we are 10.0.0.1 and the other peer is 10.0.0.2 // our outgoing connection torrent_peer* peer = add_peer(p, st, ep("10.0.0.2", 3000)); connect_peer(p, t, st); auto con_out = shared_from_this(peer->connection); con_out->set_local_ep(ep("10.0.0.1", 3163)); // and the incoming connection auto con_in = std::make_shared(&t, false, ep("10.0.0.2", 3561)); con_in->set_local_ep(ep("10.0.0.1", 8080)); p.new_connection(*con_in, 0, &st); // the rules are documented in peer_list.cpp TEST_EQUAL(con_out->was_disconnected(), true); TEST_EQUAL(con_in->was_disconnected(), false); } // test double connection with identical ports (random) TORRENT_TEST(double_connection_random) { int in = 0; int out = 0; for (int i = 0; i < 30; ++i) { torrent_state st = init_state(); mock_torrent t(&st); st.allow_multiple_connections_per_ip = false; peer_list p(allocator); t.m_p = &p; // we are 10.0.0.1 and the other peer is 10.0.0.2 // our outgoing connection torrent_peer* peer = add_peer(p, st, ep("10.0.0.2", 3000)); connect_peer(p, t, st); auto con_out = static_cast(peer->connection)->shared_from_this(); con_out->set_local_ep(ep("10.0.0.1", 3000)); // and the incoming connection auto con_in = std::make_shared(&t, false, ep("10.0.0.2", 3000)); con_in->set_local_ep(ep("10.0.0.1", 3000)); p.new_connection(*con_in, 0, &st); // the rules are documented in peer_list.cpp out += con_out->was_disconnected(); in += con_in->was_disconnected(); } // we should have gone different ways randomly TEST_CHECK(out > 0); TEST_CHECK(in > 0); } // test double connection (we win) TORRENT_TEST(double_connection_win) { torrent_state st = init_state(); mock_torrent t(&st); st.allow_multiple_connections_per_ip = false; peer_list p(allocator); t.m_p = &p; // we are 10.0.0.1 and the other peer is 10.0.0.2 // our outgoing connection torrent_peer* peer = add_peer(p, st, ep("10.0.0.2", 8080)); connect_peer(p, t, st); auto con_out = shared_from_this(peer->connection); con_out->set_local_ep(ep("10.0.0.1", 3163)); //and the incoming connection auto con_in = std::make_shared(&t, false, ep("10.0.0.2", 3561)); con_in->set_local_ep(ep("10.0.0.1", 3000)); p.new_connection(*con_in, 0, &st); // the rules are documented in peer_list.cpp TEST_EQUAL(con_out->was_disconnected(), false); TEST_EQUAL(con_in->was_disconnected(), true); } #if TORRENT_USE_I2P // test i2p self-connection TORRENT_TEST(self_connection_i2p) { torrent_state st = init_state(); mock_torrent t(&st); st.allow_multiple_connections_per_ip = false; peer_list p(allocator); t.m_p = &p; // add and connect peer torrent_peer* peer = add_i2p_peer(p, st, "foobar"); connect_peer(p, t, st); auto con_out = shared_from_this(peer->connection); con_out->m_i2p_destination = "foobar"; con_out->m_local_i2p_endpoint = "foobar"; auto con_in = std::make_shared(&t, false, "foobar"); con_in->m_local_i2p_endpoint = "foobar"; p.new_connection(*con_in, 0, &st); // from the peer_list's point of view, this looks like we made one // outgoing connection and received an incoming one. Since they share // the exact same endpoints (IP ports) but just swapped source and // destination, the peer list is supposed to figure out that we connected // to ourself and disconnect it TEST_EQUAL(con_out->was_disconnected(), true); TEST_EQUAL(con_in->was_disconnected(), true); } // test double i2p connection (both incoming) TORRENT_TEST(double_connection_i2p) { torrent_state st = init_state(); mock_torrent t(&st); st.allow_multiple_connections_per_ip = false; peer_list p(allocator); t.m_p = &p; // we are "foo" and the other peer is "bar" // first incoming connection auto con1 = std::make_shared(&t, false, "bar"); con1->m_local_i2p_endpoint = "foo"; p.new_connection(*con1, 0, &st); // the second incoming connection auto con2 = std::make_shared(&t, false, "bar"); con2->m_local_i2p_endpoint = "foo"; p.new_connection(*con2, 0, &st); // the second incoming connection should be closed TEST_EQUAL(con1->was_disconnected(), false); TEST_EQUAL(con2->was_disconnected(), true); } // test double connection (we loose) TORRENT_TEST(double_connection_loose_i2p) { torrent_state st = init_state(); mock_torrent t(&st); st.allow_multiple_connections_per_ip = false; peer_list p(allocator); t.m_p = &p; // we are "foo" and the other peer is "bar" // our outgoing connection torrent_peer* peer = add_i2p_peer(p, st, "bar"); connect_peer(p, t, st); auto con_out = shared_from_this(peer->connection); con_out->m_local_i2p_endpoint = "foo"; // and the incoming connection auto con_in = std::make_shared(&t, false, "bar"); con_in->m_local_i2p_endpoint = "foo"; p.new_connection(*con_in, 0, &st); // the rules are documented in peer_list.cpp TEST_EQUAL(con_out->was_disconnected(), true); TEST_EQUAL(con_in->was_disconnected(), false); } // test double connection (we win) TORRENT_TEST(double_connection_win_i2p) { torrent_state st = init_state(); mock_torrent t(&st); st.allow_multiple_connections_per_ip = false; peer_list p(allocator); t.m_p = &p; // we are "bar" and the other peer is "foo" // "bar" < "foo", so we gets to make the outgoing connection // our outgoing connection torrent_peer* peer = add_i2p_peer(p, st, "foo"); connect_peer(p, t, st); auto con_out = shared_from_this(peer->connection); con_out->m_local_i2p_endpoint = "bar"; //and the incoming connection auto con_in = std::make_shared(&t, false, "foo"); con_in->m_local_i2p_endpoint = "bar"; p.new_connection(*con_in, 0, &st); // the rules are documented in peer_list.cpp TEST_EQUAL(con_out->was_disconnected(), true); TEST_EQUAL(con_in->was_disconnected(), false); } #endif // test incoming connection when we are at the list size limit TORRENT_TEST(incoming_size_limit) { torrent_state st = init_state(); st.max_peerlist_size = 5; mock_torrent t(&st); st.allow_multiple_connections_per_ip = false; peer_list p(allocator); t.m_p = &p; torrent_peer* peer1 = add_peer(p, st, ep("10.0.0.1", 8080)); TEST_CHECK(peer1); TEST_EQUAL(p.num_peers(), 1); torrent_peer* peer2 = add_peer(p, st, ep("10.0.0.2", 8080)); TEST_CHECK(peer2); TEST_EQUAL(p.num_peers(), 2); torrent_peer* peer3 = add_peer(p, st, ep("10.0.0.3", 8080)); TEST_CHECK(peer3); TEST_EQUAL(p.num_peers(), 3); torrent_peer* peer4 = add_peer(p, st, ep("10.0.0.4", 8080)); TEST_CHECK(peer4); TEST_EQUAL(p.num_peers(), 4); torrent_peer* peer5 = add_peer(p, st, ep("10.0.0.5", 8080)); TEST_CHECK(peer5); TEST_EQUAL(p.num_peers(), 5); auto con_in = std::make_shared(&t, false, ep("10.0.1.2", 3561)); con_in->set_local_ep(ep("10.0.2.1", 3000)); // since we're already at 5 peers in the peer list, this call should // erase one of the existing ones. p.new_connection(*con_in, 0, &st); TEST_EQUAL(con_in->was_disconnected(), false); TEST_EQUAL(p.num_peers(), 5); // one of the previous ones should have been removed TEST_EQUAL(has_peer(p, ep("10.0.0.1", 8080)) + has_peer(p, ep("10.0.0.2", 8080)) + has_peer(p, ep("10.0.0.3", 8080)) + has_peer(p, ep("10.0.0.4", 8080)) + has_peer(p, ep("10.0.0.5", 8080)) , 4); } // test new peer when we are at the list size limit TORRENT_TEST(new_peer_size_limit) { torrent_state st = init_state(); st.max_peerlist_size = 5; mock_torrent t(&st); st.allow_multiple_connections_per_ip = false; peer_list p(allocator); t.m_p = &p; torrent_peer* peer1 = add_peer(p, st, ep("10.0.0.1", 8080)); TEST_CHECK(peer1); TEST_EQUAL(p.num_peers(), 1); torrent_peer* peer2 = add_peer(p, st, ep("10.0.0.2", 8080)); TEST_CHECK(peer2); TEST_EQUAL(p.num_peers(), 2); torrent_peer* peer3 = add_peer(p, st, ep("10.0.0.3", 8080)); TEST_CHECK(peer3); TEST_EQUAL(p.num_peers(), 3); torrent_peer* peer4 = add_peer(p, st, ep("10.0.0.4", 8080)); TEST_CHECK(peer4); TEST_EQUAL(p.num_peers(), 4); torrent_peer* peer5 = add_peer(p, st, ep("10.0.0.5", 8080)); TEST_CHECK(peer5); TEST_EQUAL(p.num_peers(), 5); torrent_peer* peer6 = p.add_peer(ep("10.0.0.6", 8080), {}, {}, &st); TEST_CHECK(peer6 == nullptr); TEST_EQUAL(p.num_peers(), 5); // one of the connection should have been removed TEST_EQUAL(has_peer(p, ep("10.0.0.1", 8080)) + has_peer(p, ep("10.0.0.2", 8080)) + has_peer(p, ep("10.0.0.3", 8080)) + has_peer(p, ep("10.0.0.4", 8080)) + has_peer(p, ep("10.0.0.5", 8080)) + has_peer(p, ep("10.0.0.6", 8080)) , 5); } TORRENT_TEST(peer_info_comparison) { peer_address_compare cmp; ipv4_peer const ip_low(ep("1.1.1.1", 6888), true, {}); ipv4_peer const ip_high(ep("100.1.1.1", 6888), true, {}); TEST_CHECK(cmp(&ip_low, &ip_high)); TEST_CHECK(!cmp(&ip_high, &ip_low)); TEST_CHECK(!cmp(&ip_high, &ip_high)); TEST_CHECK(!cmp(&ip_low, &ip_low)); TEST_CHECK(!cmp(&ip_low, addr("1.1.1.1"))); TEST_CHECK(cmp(&ip_low, addr("1.1.1.2"))); TEST_CHECK(cmp(&ip_low, addr("100.1.1.1"))); TEST_CHECK(!cmp(addr("1.1.1.1"), &ip_low)); TEST_CHECK(cmp(addr("1.1.1.0"), &ip_low)); TEST_CHECK(!cmp( addr("100.1.1.1"), &ip_low)); #if TORRENT_USE_I2P i2p_peer const i2p_low("aaaaaa", true, {}); i2p_peer const i2p_high("zzzzzz", true, {}); // noremal IPs always sort before i2p addresses TEST_CHECK(cmp(&ip_low, &i2p_high)); TEST_CHECK(cmp(&ip_low, &i2p_low)); TEST_CHECK(cmp(&ip_high, &i2p_high)); TEST_CHECK(cmp(&ip_high, &i2p_low)); // i2p addresses always sort after noremal IPs TEST_CHECK(!cmp(&i2p_high, &ip_low)); TEST_CHECK(!cmp(&i2p_low, &ip_low)); TEST_CHECK(!cmp(&i2p_high, &ip_high)); TEST_CHECK(!cmp(&i2p_low, &ip_high)); // noremal IPs always sort before i2p addresses TEST_CHECK(cmp(addr4("1.1.1.1"), &i2p_high)); TEST_CHECK(cmp(addr4("1.1.1.1"), &i2p_low)); TEST_CHECK(cmp(addr4("100.1.1.1"), &i2p_high)); TEST_CHECK(cmp(addr4("100.1.1.1"), &i2p_low)); // i2p addresses always sort after noremal IPs TEST_CHECK(!cmp(&i2p_high, addr4("1.1.1.1"))); TEST_CHECK(!cmp(&i2p_low, addr4("1.1.1.1"))); TEST_CHECK(!cmp(&i2p_high, addr4("100.1.1.1"))); TEST_CHECK(!cmp(&i2p_low, addr4("100.1.1.1"))); // internal i2p sorting TEST_CHECK(cmp(&i2p_low, &i2p_high)); TEST_CHECK(!cmp(&i2p_high, &i2p_low)); TEST_CHECK(!cmp(&i2p_high, &i2p_high)); TEST_CHECK(!cmp(&i2p_low, &i2p_low)); TEST_CHECK(cmp(&i2p_low, "zzzzzz")); TEST_CHECK(!cmp(&i2p_high, "aaaaaa")); TEST_CHECK(!cmp(&i2p_high, "zzzzzz")); TEST_CHECK(!cmp(&i2p_low, "aaaaaa")); TEST_CHECK(cmp(&i2p_low, "aaaaab")); TEST_CHECK(cmp("aaaaaa", &i2p_high)); TEST_CHECK(!cmp("zzzzzz", &i2p_low)); TEST_CHECK(!cmp("zzzzzz", &i2p_high)); TEST_CHECK(cmp("zzzzzy", &i2p_high)); TEST_CHECK(!cmp("aaaaaa", &i2p_low)); #endif } #if TORRENT_USE_I2P TORRENT_TEST(peer_info_set_i2p_destination) { peer_info p; TORRENT_ASSERT(!(p.flags & peer_info::i2p_socket)); p.set_i2p_destination(sha256_hash("................................")); TORRENT_ASSERT(p.i2p_destination() == sha256_hash("................................")); TORRENT_ASSERT(p.flags & peer_info::i2p_socket); } #endif TORRENT_TEST(clear_peers) { torrent_state st = init_state(); st.max_peerlist_size = 5; mock_torrent t(&st); peer_list p(allocator); t.m_p = &p; torrent_peer* peer1 = add_peer(p, st, ep("10.0.0.1", 8080)); TEST_CHECK(peer1); p.set_seed(peer1, true); torrent_peer* peer2 = add_peer(p, st, ep("10.0.0.2", 8080)); TEST_CHECK(peer2); torrent_peer* peer3 = add_peer(p, st, ep("10.0.0.3", 8080)); TEST_CHECK(peer3); p.connect_one_peer(1, &st); TEST_EQUAL(p.num_peers(), 3); TEST_EQUAL(p.num_candidate_cache(), 2); TEST_EQUAL(p.num_connect_candidates(), 3); TEST_EQUAL(p.num_seeds(), 1); p.clear(); TEST_EQUAL(p.num_peers(), 0); TEST_EQUAL(p.num_candidate_cache(), 0); TEST_EQUAL(p.num_connect_candidates(), 0); TEST_EQUAL(p.num_seeds(), 0); } // TODO: test erasing peers // TODO: test update_peer_port with allow_multiple_connections_per_ip and without // TODO: test add i2p peers // TODO: test allow_i2p_mixed // TODO: test insert_peer failing with all error conditions // TODO: test IPv6 // TODO: test connect_to_peer() failing // TODO: test connection_closed // TODO: connect candidates recalculation when incrementing failcount