From c8f7d896527be37d9a99fb1197c75cc3d3f5e66e Mon Sep 17 00:00:00 2001 From: Alden Torres Date: Tue, 7 Jun 2016 18:44:39 -0400 Subject: [PATCH 1/3] early resume data reset when fatal_disk_error (#798) --- src/torrent.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/torrent.cpp b/src/torrent.cpp index 1856d2370..83a2e9f0a 100644 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -1837,7 +1837,7 @@ namespace libtorrent #endif } - m_block_size_shift = root2((std::min)(int(block_size()), m_torrent_file->piece_length())); + m_block_size_shift = root2((std::min)(block_size(), m_torrent_file->piece_length())); if (m_torrent_file->num_pieces() > piece_picker::max_pieces) { @@ -2317,12 +2317,12 @@ namespace libtorrent if (j->ret == piece_manager::fatal_disk_error) { + m_resume_data.reset(); handle_disk_error(j); auto_managed(false); pause(); set_state(torrent_status::checking_files); if (should_check_files()) start_checking(); - m_resume_data.reset(); return; } @@ -2505,8 +2505,7 @@ namespace libtorrent } // parse unfinished pieces - int num_blocks_per_piece = - static_cast(torrent_file().piece_length()) / block_size(); + int num_blocks_per_piece = torrent_file().piece_length() / block_size(); if (bdecode_node unfinished_ent = m_resume_data->node.dict_find_list("unfinished")) From 5eaf713d1f2a893f8d4c9fac34e5c6b1bebe460d Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Tue, 7 Jun 2016 21:45:48 -0400 Subject: [PATCH 2/3] revert handshake round-trip optimization (#797) revert handshake round-trip optimization. it prevents taking advantage of FAST extensions since the bitfield is sent before receiving the other peer's handshake --- ChangeLog | 1 + include/libtorrent/bt_peer_connection.hpp | 4 +- src/bt_peer_connection.cpp | 76 +++++++----------- src/peer_connection.cpp | 4 +- test/test_fast_extension.cpp | 98 ++++++++++++++++++++--- 5 files changed, 122 insertions(+), 61 deletions(-) diff --git a/ChangeLog b/ChangeLog index 7872ad1ed..37143e68d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,6 @@ 1.1.1 release + * fix issue where FAST extension messages were not used during handshake * fixed crash on invalid input in http_parser * upgraded to libtommath 1.0 * fixed parsing of IPv6 endpoint with invalid port character separator diff --git a/include/libtorrent/bt_peer_connection.hpp b/include/libtorrent/bt_peer_connection.hpp index 97b9d1077..b37db0ca0 100644 --- a/include/libtorrent/bt_peer_connection.hpp +++ b/include/libtorrent/bt_peer_connection.hpp @@ -223,7 +223,7 @@ namespace libtorrent void write_dont_have(int index) TORRENT_OVERRIDE; void write_piece(peer_request const& r, disk_buffer_holder& buffer) TORRENT_OVERRIDE; void write_keepalive() TORRENT_OVERRIDE; - void write_handshake(bool plain_handshake = false); + void write_handshake(); #ifndef TORRENT_DISABLE_EXTENSIONS void write_extensions(); void write_upload_only(); @@ -242,7 +242,7 @@ namespace libtorrent void write_reject_request(peer_request const&) TORRENT_OVERRIDE; void write_allow_fast(int piece) TORRENT_OVERRIDE; void write_suggest(int piece) TORRENT_OVERRIDE; - + void on_connected() TORRENT_OVERRIDE; void on_metadata() TORRENT_OVERRIDE; diff --git a/src/bt_peer_connection.cpp b/src/bt_peer_connection.cpp index 1826e7de8..9d7825f78 100644 --- a/src/bt_peer_connection.cpp +++ b/src/bt_peer_connection.cpp @@ -765,7 +765,7 @@ namespace libtorrent } } - void bt_peer_connection::write_handshake(bool plain_handshake) + void bt_peer_connection::write_handshake() { INVARIANT_CHECK; @@ -853,33 +853,6 @@ namespace libtorrent , "ih: %s", to_hex(ih.to_string()).c_str()); #endif send_buffer(handshake, sizeof(handshake)); - - // for encrypted peers, just send a plain handshake. We - // don't know at this point if the rest should be - // obfuscated or not, we have to wait for the other end's - // response first. - if (plain_handshake) return; - - // we don't know how many pieces there are until we - // have the metadata - if (t->ready_for_connections()) - { - write_bitfield(); -#ifndef TORRENT_DISABLE_DHT - if (m_supports_dht_port && m_ses.has_dht()) - write_dht_port(m_ses.external_udp_port()); -#endif - - // if we don't have any pieces, don't do any preemptive - // unchoking at all. - if (t->num_have() > 0) - { - // if the peer is ignoring unchoke slots, or if we have enough - // unused slots, unchoke this peer right away, to save a round-trip - // in case it's interested. - maybe_unchoke_this_peer(); - } - } } boost::optional bt_peer_connection::downloading_piece_progress() const @@ -2113,6 +2086,10 @@ namespace libtorrent { INVARIANT_CHECK; + // if we have not received the other peer's extension bits yet, how do we + // know whether to send a have-all or have-none? + TORRENT_ASSERT(m_state >= read_peer_id); + boost::shared_ptr t = associated_torrent().lock(); TORRENT_ASSERT(t); TORRENT_ASSERT(m_sent_handshake); @@ -2698,7 +2675,7 @@ namespace libtorrent // always rc4 if sent here. m_rc4_encrypted is flagged // again according to peer selection. switch_send_crypto(m_rc4); - write_handshake(true); + write_handshake(); switch_send_crypto(boost::shared_ptr()); // vc,crypto_select,len(pad),pad, encrypt(handshake) @@ -3134,25 +3111,6 @@ namespace libtorrent bytes_transferred = 0; TORRENT_ASSERT(m_encrypted); - if (is_outgoing() && t->ready_for_connections()) - { - write_bitfield(); -#ifndef TORRENT_DISABLE_DHT - if (m_supports_dht_port && m_ses.has_dht()) - write_dht_port(m_ses.external_udp_port()); -#endif - - // if we don't have any pieces, don't do any preemptive - // unchoking at all. - if (t->num_have() > 0) - { - // if the peer is ignoring unchoke slots, or if we have enough - // unused slots, unchoke this peer right away, to save a round-trip - // in case it's interested. - maybe_unchoke_this_peer(); - } - } - // decrypt remaining received bytes if (m_rc4_encrypted) { @@ -3480,6 +3438,28 @@ namespace libtorrent } #endif + // complete the handshake + // we don't know how many pieces there are until we + // have the metadata + if (t->ready_for_connections()) + { + write_bitfield(); +#ifndef TORRENT_DISABLE_DHT + if (m_supports_dht_port && m_ses.has_dht()) + write_dht_port(m_ses.external_udp_port()); +#endif + + // if we don't have any pieces, don't do any preemptive + // unchoking at all. + if (t->num_have() > 0) + { + // if the peer is ignoring unchoke slots, or if we have enough + // unused slots, unchoke this peer right away, to save a round-trip + // in case it's interested. + maybe_unchoke_this_peer(); + } + } + m_state = read_packet_size; m_recv_buffer.reset(5); diff --git a/src/peer_connection.cpp b/src/peer_connection.cpp index 50eac4020..8fcc428d9 100644 --- a/src/peer_connection.cpp +++ b/src/peer_connection.cpp @@ -580,10 +580,10 @@ namespace libtorrent return; } - int num_allowed_pieces = m_settings.get_int(settings_pack::allowed_fast_set_size); + int const num_allowed_pieces = m_settings.get_int(settings_pack::allowed_fast_set_size); if (num_allowed_pieces == 0) return; - int num_pieces = t->torrent_file().num_pieces(); + int const num_pieces = t->torrent_file().num_pieces(); if (num_allowed_pieces >= num_pieces) { diff --git a/test/test_fast_extension.cpp b/test/test_fast_extension.cpp index 249eb78f7..bd99207dd 100644 --- a/test/test_fast_extension.cpp +++ b/test/test_fast_extension.cpp @@ -273,14 +273,20 @@ void do_handshake(tcp::socket& s, sha1_hash const& ih, char* buffer) // check for fast extension support TEST_CHECK(extensions[7] & 0x4); -#ifndef TORRENT_DISABLE_EXTENSIONS // check for extension protocol support - TEST_CHECK(extensions[5] & 0x10); + bool const lt_extension_protocol = (extensions[5] & 0x10) != 0; +#ifndef TORRENT_DISABLE_EXTENSIONS + TEST_CHECK(lt_extension_protocol == true); +#else + TEST_CHECK(lt_extension_protocol == false); #endif -#ifndef TORRENT_DISABLE_DHT // check for DHT support - TEST_CHECK(extensions[7] & 0x1); + bool const dht_support = (extensions[7] & 0x1) != 0; +#ifndef TORRENT_DISABLE_DHT + TEST_CHECK(dht_support == true); +#else + TEST_CHECK(dht_support == false); #endif TEST_CHECK(std::memcmp(buffer + 28, ih.begin(), 20) == 0); @@ -402,7 +408,8 @@ entry read_ut_metadata_msg(tcp::socket& s, char* recv_buffer, int size) } boost::shared_ptr setup_peer(tcp::socket& s, sha1_hash& ih - , boost::shared_ptr& ses, torrent_handle* th = NULL) + , boost::shared_ptr& ses, bool incoming = true + , int flags = 0, torrent_handle* th = NULL) { boost::shared_ptr t = ::create_torrent(); ih = t->info_hash(); @@ -413,12 +420,17 @@ boost::shared_ptr setup_peer(tcp::socket& s, sha1_hash& ih sett.set_bool(settings_pack::enable_natpmp, false); sett.set_bool(settings_pack::enable_lsd, false); sett.set_bool(settings_pack::enable_dht, false); + sett.set_int(settings_pack::in_enc_policy, settings_pack::pe_disabled); + sett.set_int(settings_pack::out_enc_policy, settings_pack::pe_disabled); + sett.set_bool(settings_pack::enable_outgoing_utp, false); + sett.set_bool(settings_pack::enable_incoming_utp, false); ses.reset(new lt::session(sett, lt::session::add_default_plugins)); error_code ec; add_torrent_params p; p.flags &= ~add_torrent_params::flag_paused; p.flags &= ~add_torrent_params::flag_auto_managed; + p.flags |= flags; p.ti = t; p.save_path = "./tmp1_fast"; @@ -429,10 +441,29 @@ boost::shared_ptr setup_peer(tcp::socket& s, sha1_hash& ih if (th) *th = ret; // wait for the torrent to be ready - wait_for_downloading(*ses, "ses"); + if ((flags & add_torrent_params::flag_seed_mode) == 0) + { + wait_for_downloading(*ses, "ses"); + } - s.connect(tcp::endpoint(address::from_string("127.0.0.1", ec), ses->listen_port()), ec); - if (ec) TEST_ERROR(ec.message()); + if (incoming) + { + s.connect(tcp::endpoint(address::from_string("127.0.0.1", ec), ses->listen_port()), ec); + if (ec) TEST_ERROR(ec.message()); + } + else + { + tcp::acceptor l(s.get_io_service()); + l.open(tcp::v4()); + l.bind(tcp::endpoint(address_v4::from_string("127.0.0.1") + , 3000 + rand() % 60000)); + l.listen(); + tcp::endpoint addr = l.local_endpoint(); + + ret.connect_peer(addr); + print_session_log(*ses); + l.accept(s); + } print_session_log(*ses); @@ -706,7 +737,7 @@ TORRENT_TEST(dont_have) boost::shared_ptr ses; io_service ios; tcp::socket s(ios); - boost::shared_ptr ti = setup_peer(s, ih, ses, &th); + boost::shared_ptr ti = setup_peer(s, ih, ses, true, 0, &th); char recv_buffer[1000]; do_handshake(s, ih, recv_buffer); @@ -875,6 +906,55 @@ TORRENT_TEST(invalid_request) send_request(s, req); } +void have_all_test(bool const incoming) +{ + sha1_hash ih; + boost::shared_ptr ses; + io_service ios; + tcp::socket s(ios); + setup_peer(s, ih, ses, incoming, add_torrent_params::flag_seed_mode); + + char recv_buffer[1000]; + do_handshake(s, ih, recv_buffer); + print_session_log(*ses); + + // expect to receive a have-all (not a bitfield) + // since we advertised support for FAST extensions + for (;;) + { + int const len = read_message(s, recv_buffer, sizeof(recv_buffer)); + if (len == -1) + { + TEST_ERROR("failed to receive have-all despite advertising support for FAST"); + break; + } + print_message(recv_buffer, len); + int const msg = recv_buffer[0]; + if (msg == 0xe) // have-all + { + // success! + break; + } + if (msg == 5) // bitfield + { + TEST_ERROR("received bitfield from seed despite advertising support for FAST"); + break; + } + } +} + +TORRENT_TEST(outgoing_have_all) +{ + std::cerr << "\n === test outgoing have-all ===\n" << std::endl; + have_all_test(true); +} + +TORRENT_TEST(incoming_have_all) +{ + std::cerr << "\n === test outgoing have-all ===\n" << std::endl; + have_all_test(false); +} + #endif // TORRENT_DISABLE_EXTENSIONS // TODO: test sending invalid requests (out of bound piece index, offsets and From 830a79dbcfdba9d56c1956a437032e1c213ba1e5 Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Thu, 9 Jun 2016 08:02:41 -0400 Subject: [PATCH 3/3] optimize allow-fast logic (#800) optimize allow-fast logic --- ChangeLog | 1 + include/libtorrent/bt_peer_connection.hpp | 4 + simulation/Jamfile | 1 + simulation/create_torrent.cpp | 5 +- simulation/create_torrent.hpp | 3 +- simulation/fake_peer.hpp | 69 +++++++- simulation/test_fast_extensions.cpp | 206 ++++++++++++++++++++++ simulation/test_swarm.cpp | 1 - src/bt_peer_connection.cpp | 19 +- src/peer_connection.cpp | 28 +-- 10 files changed, 311 insertions(+), 26 deletions(-) create mode 100644 simulation/test_fast_extensions.cpp diff --git a/ChangeLog b/ChangeLog index 37143e68d..14a6db264 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,6 @@ 1.1.1 release + * optimize allow-fast logic * fix issue where FAST extension messages were not used during handshake * fixed crash on invalid input in http_parser * upgraded to libtommath 1.0 diff --git a/include/libtorrent/bt_peer_connection.hpp b/include/libtorrent/bt_peer_connection.hpp index b37db0ca0..e22f97623 100644 --- a/include/libtorrent/bt_peer_connection.hpp +++ b/include/libtorrent/bt_peer_connection.hpp @@ -356,6 +356,10 @@ private: // and can send bittorrent messages bool m_sent_handshake:1; + // set to true once we send the allowed-fast messages. This is + // only done once per connection + bool m_sent_allowed_fast:1; + #if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) // this is set to true after the encryption method has been // successfully negotiated (either plaintext or rc4), to signal diff --git a/simulation/Jamfile b/simulation/Jamfile index 6bc68914e..21922a16c 100644 --- a/simulation/Jamfile +++ b/simulation/Jamfile @@ -41,5 +41,6 @@ alias libtorrent-sims : [ run test_trackers_extension.cpp ] [ run test_tracker.cpp ] [ run test_ip_filter.cpp ] + [ run test_fast_extensions.cpp ] ; diff --git a/simulation/create_torrent.cpp b/simulation/create_torrent.cpp index b8a5a126c..d25eed3ae 100644 --- a/simulation/create_torrent.cpp +++ b/simulation/create_torrent.cpp @@ -46,7 +46,8 @@ std::string save_path(int idx) return path; } -lt::add_torrent_params create_torrent(int idx, bool seed) +lt::add_torrent_params create_torrent(int const idx, bool const seed + , int const num_pieces) { // TODO: if we want non-seeding torrents, that could be a bit cheaper to // create @@ -60,7 +61,7 @@ lt::add_torrent_params create_torrent(int idx, bool seed) if (ec) fprintf(stderr, "failed to create directory: \"%s\": %s\n" , path.c_str(), ec.message().c_str()); std::ofstream file(lt::combine_path(path, name).c_str()); - params.ti = ::create_torrent(&file, name, 0x4000, 9 + idx, false); + params.ti = ::create_torrent(&file, name, 0x4000, num_pieces + idx, false); file.close(); // by setting the save path to a dummy path, it won't be seeding diff --git a/simulation/create_torrent.hpp b/simulation/create_torrent.hpp index ebb9bb870..e8785f0db 100644 --- a/simulation/create_torrent.hpp +++ b/simulation/create_torrent.hpp @@ -37,7 +37,8 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/add_torrent_params.hpp" std::string save_path(int idx); -libtorrent::add_torrent_params create_torrent(int idx, bool seed = true); +libtorrent::add_torrent_params create_torrent(int idx, bool seed = true + , int num_pieces = 9); #endif diff --git a/simulation/fake_peer.hpp b/simulation/fake_peer.hpp index 55b2408e8..ae34faf79 100644 --- a/simulation/fake_peer.hpp +++ b/simulation/fake_peer.hpp @@ -34,6 +34,7 @@ POSSIBILITY OF SUCH DAMAGE. #define SIMULATION_FAKE_PEER_HPP #include +#include #include "test.hpp" #include "simulator/simulator.hpp" #include "libtorrent/session.hpp" @@ -41,6 +42,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/torrent_handle.hpp" #include "libtorrent/sha1_hash.hpp" #include "libtorrent/torrent_info.hpp" +#include "libtorrent/io.hpp" using namespace sim; @@ -89,19 +91,56 @@ struct fake_peer bool tripped() const { return m_tripped; } + void send_interested() + { + m_send_buffer.resize(m_send_buffer.size() + 5); + char* ptr = m_send_buffer.data() + m_send_buffer.size() - 5; + + lt::detail::write_uint32(1, ptr); + lt::detail::write_uint8(2, ptr); + } + + void send_bitfield(std::vector const& pieces) + { + int const bytes = (pieces.size() + 7) / 8; + m_send_buffer.resize(m_send_buffer.size() + 5 + bytes); + char* ptr = m_send_buffer.data() + m_send_buffer.size() - 5 - bytes; + + lt::detail::write_uint32(1 + bytes, ptr); + lt::detail::write_uint8(5, ptr); + + boost::uint8_t b = 0; + int cnt = 7; + for (std::vector::const_iterator i = pieces.begin() + , end(pieces.end()); i != end; ++i) + { + if (*i) b |= 1 << cnt; + --cnt; + if (cnt < 0) + { + lt::detail::write_uint8(b, ptr); + b = 0; + cnt = 7; + } + } + lt::detail::write_uint8(b, ptr); + } + private: - void write_handshake(boost::system::error_code const& ec, lt::sha1_hash ih) + void write_handshake(boost::system::error_code const& ec + , lt::sha1_hash ih) { - asio::ip::tcp::endpoint ep = m_out_socket.remote_endpoint(); + using namespace std::placeholders; + + asio::ip::tcp::endpoint const ep = m_out_socket.remote_endpoint(); printf("fake_peer::connect (%s) -> (%d) %s\n" , lt::print_endpoint(ep).c_str(), ec.value() , ec.message().c_str()); static char const handshake[] = "\x13" "BitTorrent protocol\0\0\0\0\0\0\0\x04" " " // space for info-hash - "aaaaaaaaaaaaaaaaaaaa" // peer-id - "\0\0\0\x01\x02"; // interested + "aaaaaaaaaaaaaaaaaaaa"; // peer-id int const len = sizeof(handshake) - 1; memcpy(m_out_buffer, handshake, len); memcpy(&m_out_buffer[28], ih.data(), 20); @@ -112,10 +151,28 @@ private: printf("fake_peer::write_handshake(%s) -> (%d) %s\n" , lt::print_endpoint(ep).c_str(), ec.value() , ec.message().c_str()); - this->m_out_socket.close(); + if (m_send_buffer.empty()) + { + this->m_out_socket.close(); + } + else + { + asio::async_write(m_out_socket, asio::const_buffers_1( + m_send_buffer.data(), m_send_buffer.size()) + , std::bind(&fake_peer::write_send_buffer, this, _1, _2)); + } }); } + void write_send_buffer(boost::system::error_code const& ec + , size_t bytes_transferred) + { + printf("fake_peer::write_send_buffer() -> (%d) %s\n" + , ec.value(), ec.message().c_str()); + + m_out_socket.close(); + } + char m_out_buffer[300]; asio::io_service m_ios; @@ -123,6 +180,8 @@ private: asio::ip::tcp::socket m_in_socket; asio::ip::tcp::socket m_out_socket; bool m_tripped; + + std::vector m_send_buffer; }; inline void add_fake_peers(lt::torrent_handle h) diff --git a/simulation/test_fast_extensions.cpp b/simulation/test_fast_extensions.cpp new file mode 100644 index 000000000..ae3350e22 --- /dev/null +++ b/simulation/test_fast_extensions.cpp @@ -0,0 +1,206 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#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" + +namespace lt = libtorrent; + +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_service ios(sim, lt::address_v4::from_string("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::add_torrent_params::flag_auto_managed; + params.flags &= ~lt::add_torrent_params::flag_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& ec) + { + ses->set_alert_notify([]{}); + // shut down + zombie = ses->abort(); + + p1.close(); + + ses.reset(); + }); + + sim.run(); +} + +// 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& ses, 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_hash()); + p1.send_bitfield(bitfield); + p1.send_interested(); + } + else if (auto l = lt::alert_cast(a)) + { + if (strcmp(l->event_type, "ALLOWED_FAST") != 0) return; + + int const piece = atoi(l->msg()); + // 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& ses, 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_hash()); + p1.send_interested(); + } + else if (auto l = lt::alert_cast(a)) + { + if (strcmp(l->event_type, "ALLOWED_FAST") != 0) return; + + int const piece = atoi(l->msg()); + + // 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); +} diff --git a/simulation/test_swarm.cpp b/simulation/test_swarm.cpp index b337def1f..bf9f217a8 100644 --- a/simulation/test_swarm.cpp +++ b/simulation/test_swarm.cpp @@ -418,7 +418,6 @@ TORRENT_TEST(delete_partfile) // outgoing connections // TODO: add test that makes sure a torrent in graceful pause mode won't accept // incoming connections -// TODO: test allow-fast // TODO: test the different storage allocation modes // TODO: test contiguous buffers diff --git a/src/bt_peer_connection.cpp b/src/bt_peer_connection.cpp index 9d7825f78..7d37f4704 100644 --- a/src/bt_peer_connection.cpp +++ b/src/bt_peer_connection.cpp @@ -110,6 +110,7 @@ namespace libtorrent , m_supports_fast(false) , m_sent_bitfield(false) , m_sent_handshake(false) + , m_sent_allowed_fast(false) #if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) , m_encrypted(false) , m_rc4_encrypted(false) @@ -366,6 +367,10 @@ namespace libtorrent if (!m_supports_fast) return; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "ALLOWED_FAST", "%d", piece); +#endif + TORRENT_ASSERT(m_sent_handshake); TORRENT_ASSERT(m_sent_bitfield); TORRENT_ASSERT(associated_torrent().lock()->valid_metadata()); @@ -991,6 +996,15 @@ namespace libtorrent } if (!m_recv_buffer.packet_finished()) return; + // we defer sending the allowed set until the peer says it's interested in + // us. This saves some bandwidth and allows us to omit messages for pieces + // that the peer already has + if (!m_sent_allowed_fast && m_supports_fast) + { + m_sent_allowed_fast = true; + send_allowed_set(); + } + incoming_interested(); } @@ -2116,13 +2130,11 @@ namespace libtorrent else if (m_supports_fast && t->is_seed() && !m_settings.get_bool(settings_pack::lazy_bitfields)) { write_have_all(); - send_allowed_set(); return; } else if (m_supports_fast && t->num_have() == 0) { write_have_none(); - send_allowed_set(); return; } else if (t->num_have() == 0) @@ -2237,9 +2249,6 @@ namespace libtorrent } // TODO: if we're finished, send upload_only message } - - if (m_supports_fast) - send_allowed_set(); } #ifndef TORRENT_DISABLE_EXTENSIONS diff --git a/src/peer_connection.cpp b/src/peer_connection.cpp index 8fcc428d9..a80e0ebee 100644 --- a/src/peer_connection.cpp +++ b/src/peer_connection.cpp @@ -596,9 +596,6 @@ namespace libtorrent // that the peer already has if (has_piece(i)) continue; -#ifndef TORRENT_DISABLE_LOGGING - peer_log(peer_log_alert::outgoing_message, "ALLOWED_FAST", "%d", i); -#endif write_allow_fast(i); TORRENT_ASSERT(std::find(m_accept_fast.begin() , m_accept_fast.end(), i) @@ -631,18 +628,26 @@ namespace libtorrent x.append(t->torrent_file().info_hash().data(), 20); sha1_hash hash = hasher(x.c_str(), x.size()).final(); + int attempts = 0; + int loops = 0; for (;;) { - char* p = hash.data(); - for (int i = 0; i < 5; ++i) + char const* p = hash.data(); + for (int i = 0; i < hash.size / sizeof(boost::uint32_t); ++i) { - int piece = detail::read_uint32(p) % num_pieces; + ++loops; + int const piece = detail::read_uint32(p) % num_pieces; if (std::find(m_accept_fast.begin(), m_accept_fast.end(), piece) - == m_accept_fast.end()) + != m_accept_fast.end()) + { + // this is our safety-net to make sure this loop terminates, even + // under the worst conditions + if (++loops > 500) return; + continue; + } + + if (!has_piece(piece)) { -#ifndef TORRENT_DISABLE_LOGGING - peer_log(peer_log_alert::outgoing_message, "ALLOWED_FAST", "%d", piece); -#endif write_allow_fast(piece); if (m_accept_fast.empty()) { @@ -651,9 +656,8 @@ namespace libtorrent } m_accept_fast.push_back(piece); m_accept_fast_piece_cnt.push_back(0); - if (int(m_accept_fast.size()) >= num_allowed_pieces - || int(m_accept_fast.size()) == num_pieces) return; } + if (++attempts >= num_allowed_pieces) return; } hash = hasher(hash.data(), 20).final(); }