/* Copyright (c) 2016, 2021, Alden Torres Copyright (c) 2016-2017, 2019-2021, Arvid Norberg All rights reserved. You may use, distribute and modify this code under the terms of the BSD license, see LICENSE file. */ #include "test.hpp" #include "utils.hpp" #include "libtorrent/alert.hpp" #include "libtorrent/alert_types.hpp" #include "libtorrent/session.hpp" #include "libtorrent/add_torrent_params.hpp" #include "create_torrent.hpp" #include "settings.hpp" #include "fake_peer.hpp" #include "setup_transfer.hpp" // for ep() #include "simulator/utils.hpp" #include "libtorrent/string_view.hpp" #include "libtorrent/aux_/random.hpp" using namespace lt::literals; template void run_fake_peer_test( lt::add_torrent_params params , Sett const& sett , Alert const& alert) { sim::default_config cfg; sim::simulation sim{cfg}; sim::asio::io_context ios(sim, lt::make_address_v4("50.0.0.1")); lt::session_proxy zombie; // setup settings pack to use for the session (customization point) lt::settings_pack pack = settings(); pack.set_str(lt::settings_pack::listen_interfaces, "0.0.0.0:6881"); sett(pack); // create session std::shared_ptr ses = std::make_shared(pack, ios); fake_peer p1(sim, "60.0.0.0"); params.flags &= ~lt::torrent_flags::auto_managed; params.flags &= ~lt::torrent_flags::paused; ses->async_add_torrent(params); // the alert notification function is called from within libtorrent's // context. It's not OK to talk to libtorrent in there, post it back out and // then ask for alerts. print_alerts(*ses, [&](lt::session& ses, lt::alert const* a) { alert(ses, a, p1); }); sim::timer t(sim, lt::seconds(1) , [&](boost::system::error_code const&) { // shut down zombie = ses->abort(); p1.close(); ses.reset(); }); sim.run(); } struct idle_peer { idle_peer(simulation& sim, char const* ip) : m_ios(sim, lt::make_address(ip)) { boost::system::error_code ec; m_acceptor.open(asio::ip::tcp::v4(), ec); TEST_CHECK(!ec); m_acceptor.bind(asio::ip::tcp::endpoint(asio::ip::address_v4::any(), 6881), ec); TEST_CHECK(!ec); m_acceptor.listen(10, ec); TEST_CHECK(!ec); m_acceptor.async_accept(m_socket, [&] (boost::system::error_code const& ec) { m_accepted = true; if (!m_handshake) return; static char handshake_buffer[68]; asio::async_read(m_socket, asio::buffer(handshake_buffer, 68) , [&](boost::system::error_code const& ec, std::size_t) { if (memcmp(handshake_buffer, "\x13" "BitTorrent protocol", 20) != 0) { std::printf(" invalid protocol specifier\n"); m_socket.close(); return; } // change the peer ID and echo back the handshake lt::aux::random_bytes({handshake_buffer + 48, 20}); asio::async_write(m_socket, asio::buffer(handshake_buffer, 68) , [](boost::system::error_code const& ec, size_t) { }); }); }); } void enable_handshake() { m_handshake = true; } void close() { m_acceptor.close(); m_socket.close(); } bool accepted() const { return m_accepted; } asio::io_context m_ios; asio::ip::tcp::acceptor m_acceptor{m_ios}; asio::ip::tcp::socket m_socket{m_ios}; bool m_accepted = false; bool m_handshake = false; }; lt::time_duration run_timeout_sim(sim::simulation& sim) { sim::asio::io_context ios(sim, lt::make_address_v4("50.0.0.1")); lt::session_proxy zombie; // setup settings pack to use for the session (customization point) lt::settings_pack pack = settings(); pack.set_str(lt::settings_pack::listen_interfaces, "0.0.0.0:6881"); pack.set_bool(lt::settings_pack::enable_outgoing_utp, false); pack.set_bool(lt::settings_pack::enable_incoming_utp, false); pack.set_int(lt::settings_pack::alert_mask, lt::alert_category::error | lt::alert_category::connect | lt::alert_category::peer_log); // create session std::shared_ptr ses = std::make_shared(pack, ios); int const num_pieces = 5; lt::add_torrent_params params = create_torrent(0, false, num_pieces); params.flags &= ~lt::torrent_flags::auto_managed; params.flags &= ~lt::torrent_flags::paused; ses->async_add_torrent(params); lt::time_point peer_timeout_timestamp{}; lt::time_point const start = lt::clock_type::now(); // the alert notification function is called from within libtorrent's // context. It's not OK to talk to libtorrent in there, post it back out and // then ask for alerts. print_alerts(*ses, [&](lt::session& ses, lt::alert const* a) { if (auto at = lt::alert_cast(a)) { lt::torrent_handle h = at->handle; h.connect_peer(ep("60.0.0.0", 6881)); } else if (auto pe = lt::alert_cast(a)) { if (peer_timeout_timestamp == lt::time_point{}) peer_timeout_timestamp = pe->timestamp(); } }); sim::timer t(sim, lt::seconds(300) , [&](boost::system::error_code const&) { // shut down zombie = ses->abort(); ses.reset(); }); sim.run(); TEST_CHECK(peer_timeout_timestamp != lt::time_point{}); return peer_timeout_timestamp - start; } #ifndef TORRENT_DISABLE_LOGGING // make sure we consistently send the same allow-fast pieces, regardless // of which pieces the peer has. TORRENT_TEST(allow_fast) { std::set allowed_fast; int const num_pieces = 50; lt::add_torrent_params params = create_torrent(0, false, num_pieces); std::vector bitfield(num_pieces, false); for (int i = 0; i < num_pieces + 1; ++i) { // just for this one session, to check for duplicates std::set local_allowed_fast; run_fake_peer_test(params, [] (lt::settings_pack& pack) { pack.set_int(lt::settings_pack::allowed_fast_set_size, 13); } , [&] (lt::session&, lt::alert const* a, fake_peer& p1) { if (auto at = lt::alert_cast(a)) { lt::torrent_handle h = at->handle; p1.connect_to(ep("50.0.0.1", 6881) , h.torrent_file()->info_hashes().v1); p1.send_bitfield(bitfield); p1.send_interested(); } else if (auto l = lt::alert_cast(a)) { if (l->event_type != lt::peer_log_alert::allowed_fast) return; int const piece = atoi(l->log_message()); // make sure we don't get the same allowed piece more than once TEST_EQUAL(local_allowed_fast.count(piece), 0); // build the union of all allow-fast pieces we've received, across // simulations. allowed_fast.insert(piece); local_allowed_fast.insert(piece); // make sure this is a valid piece TEST_CHECK(piece < num_pieces); TEST_CHECK(piece >= 0); // and make sure it's not one of the pieces we have // because that would be redundant TEST_EQUAL(bitfield[piece], false); } }); // i goes from [0, mum_pieces + 1) to cover the have-none and have-all // cases. After the last iteration, we can't add another piece. if (i < int(bitfield.size())) bitfield[i] = true; } // we should never have sent any other pieces than the 13 designated for this // peer's IP. TEST_EQUAL(int(allowed_fast.size()), 13); } // This tests a worst case scenario of allow-fast configuration where we must // verify that libtorrent correctly aborts before satisfying the settings // (because doing so would be too expensive) // // we have a torrent with a lot of pieces, and we want to send that many minus // one allow-fast pieces. The way allow-fast pieces are computed is by hashing // the peer's IP modulus the number of pieces. To actually compute which pieces // to send (or which one piece _not_ to send) we would have to work hard through // a lot of duplicates. This test makes sure we don't, and abort well before // then TORRENT_TEST(allow_fast_stress) { std::set allowed_fast; int const num_pieces = 50000; lt::add_torrent_params params = create_torrent(0, false, num_pieces); run_fake_peer_test(params, [&] (lt::settings_pack& pack) { pack.set_int(lt::settings_pack::allowed_fast_set_size, num_pieces - 1); } , [&] (lt::session&, lt::alert const* a, fake_peer& p1) { if (auto at = lt::alert_cast(a)) { lt::torrent_handle h = at->handle; p1.connect_to(ep("50.0.0.1", 6881) , h.torrent_file()->info_hashes().v1); p1.send_interested(); } else if (auto l = lt::alert_cast(a)) { if (l->event_type != lt::peer_log_alert::allowed_fast) return; int const piece = atoi(l->log_message()); // make sure we don't get the same allowed piece more than once TEST_EQUAL(allowed_fast.count(piece), 0); // build the union of all allow-fast pieces we've received, across // simulations. allowed_fast.insert(piece); // make sure this is a valid piece TEST_CHECK(piece < num_pieces); TEST_CHECK(piece >= 0); } }); std::printf("received %d allowed fast, out of %d configured ones\n" , int(allowed_fast.size()), num_pieces - 1); TEST_CHECK(int(allowed_fast.size()) < num_pieces / 80); } #endif TORRENT_TEST(peer_idle_timeout) { sim::default_config cfg; sim::simulation sim{cfg}; // just a listen socket that accepts connections, and just respond with a // bittorrent handshake, but nothing more idle_peer peer(sim, "60.0.0.0"); peer.enable_handshake(); auto peer_timeout_timestamp = run_timeout_sim(sim); // the peer timeout defaults to 120 seconds // settings_pack::peer_timeout TEST_CHECK(peer_timeout_timestamp < lt::seconds(122)); TEST_CHECK(peer_timeout_timestamp > lt::seconds(120)); } TORRENT_TEST(handshake_timeout) { sim::default_config cfg; sim::simulation sim{cfg}; // just a listen socket that accepts connections, but never responds idle_peer peer(sim, "60.0.0.0"); auto peer_timeout_timestamp = run_timeout_sim(sim); // the handshake timeout defaults to 10 seconds // settings_pack::handshake_timeout TEST_CHECK(peer_timeout_timestamp < lt::seconds(15)); TEST_CHECK(peer_timeout_timestamp > lt::seconds(9)); }