# Automated handling of leechers and other uncooperative peers. # # Adds the following commands: # d.snub_leechers=snub_ratio,unsnub_ratio,min_transfer # Snub peers (i.e. stop uploading to them) who download far more than they # upload. They're snubbed when their ratio exceeds the snub_ratio, # and unsnubbed when they drop below the unsnub_ratio again. For example, # a snub_ratio of 10 means the peer gets snubbed if we send more than # ten times the amount that we've received from them. Don't set # snub_ratio too low, a value of 5-10 seems to work best. The first # min_transfer bytes is uploaded for "free" without snubbing. # d.unsnub_peers= # Unsnub all snubbed peers, e.g. for use when the download is finished # and we no longer have anything we want to download anyway. # d.ban_slow_peers=min_seeds,min_rate,amount1,time1[,amount2,time2[,...]] # Ban peers whose upload is too slow, for instance hacked peers, badly # throttled connections or ISPs with excessive bandwidth shaping. This # clears up the peer list and allows connecting to better peers, to # prevent staying connected forever to peers that never send data. # Always keeps at least the set min_peers for the download, and at least # as many seeds as min_seeds as well as all peers currently sending at a # rate of min_rate or above. A peer is too slow if after time1 has elapsed, # it has sent less than amount1, or amount2 after time2, etc. # d.unban_peers= # Unban all peers, including those banned manually with shift-B. # # Note that peers marked as friends (if you have applied that patch) will # never be snubbed or banned by these commands. # # There are also global "snub_leechers" and "ban_slow_peers" commands which # take the same arguments and apply them to all unfinished downloads (except # those set to ignore commands). # # Note that the commands usually don't work well for poorly seeded torrents # or those being seeded initially, then unreasonably many peers will be # snubbed/banned. You should set the ignore_commands flag for such downloads, # or avoid using the global commands and instead only apply the # download-specific commands to the particular downloads that need them (e.g. # using d.multicall on a custom view that only contains appropriate downloads). # # # Example: (this seems to work fine for a typical ADSL line) # schedule = snub_leechers,120,120,"snub_leechers=10,5,1M" # schedule = ban_slow_peers,120,120,"ban_slow_peers=5,2K,64K,5,128K,10,1M,30" # on_finished = unban,"d.unban_peers=" # on_finished = unsnub,"d.unsnub_peers=" # # Snub peers after sending 1MB to them if they don't upload at least 1/10th in return. # Unsnub them when they upload to a ratio of 1/5th, and when the download finishes. # # Ban peers after 5 minutes unless they've uploaded less than 64KB, after 10 min # and less than 128 K or after 30 min and under 1MB upload. Keep at least 5 seeds, # and all peers currently uploading at 2KB/s or faster. # Unban peers when the download finishes. # Index: rtorrent/src/command_helpers.h =================================================================== --- rtorrent/src/command_helpers.h (revision 1105) +++ rtorrent/src/command_helpers.h (working copy) @@ -54,7 +54,7 @@ #define COMMAND_DOWNLOAD_SLOTS_SIZE 150 #define COMMAND_FILE_SLOTS_SIZE 30 #define COMMAND_FILE_ITR_SLOTS_SIZE 10 -#define COMMAND_PEER_SLOTS_SIZE 20 +#define COMMAND_PEER_SLOTS_SIZE 30 #define COMMAND_TRACKER_SLOTS_SIZE 15 #define COMMAND_ANY_SLOTS_SIZE 50 Index: rtorrent/src/command_download.cc =================================================================== --- rtorrent/src/command_download.cc (revision 1105) +++ rtorrent/src/command_download.cc (working copy) @@ -51,12 +51,14 @@ #include #include #include +#include #include #include "core/download.h" #include "core/download_store.h" #include "core/manager.h" #include "rpc/command_variable.h" +#include "rpc/parse.h" #include "globals.h" #include "control.h" @@ -170,7 +172,121 @@ rpc::call_command("d.set_tied_to_file", std::string(), rpc::make_target(download)); } +torrent::Object +apply_d_snub_leechers(core::Download* download, const torrent::Object& rawArgs) { + if (!download->is_open() || download->is_done() || rpc::call_command_value("d.get_ignore_commands", rpc::make_target(download)) != 0) + return torrent::Object(); + + const torrent::Object::list_type& args = rawArgs.as_list(); + + if (args.size() < 3) + throw torrent::input_error("Too few arguments."); + + torrent::Object::list_type::const_iterator argItr = args.begin(); + uint64_t snub_ratio = rpc::convert_to_value(*argItr++); + uint64_t unsnub_ratio = rpc::convert_to_value(*argItr++); + uint64_t min_transfer = rpc::convert_to_value(*argItr++); + + for (torrent::ConnectionList::iterator itr = download->download()->connection_list()->begin(); itr != download->download()->connection_list()->end(); ++itr) { +#if LIBTORRENT_FRIENDS + if (((*itr)->bitfield() && (*itr)->bitfield()->is_all_set()) || (*itr)->peer_info()->is_friend()) +#else + if (((*itr)->bitfield() && (*itr)->bitfield()->is_all_set())) +#endif + continue; + + uint64_t up = (*itr)->up_rate()->total(); + uint64_t down = (*itr)->down_rate()->total(); + + if ((*itr)->is_snubbed()) { + if (down * unsnub_ratio >= std::max(up, min_transfer)) + (*itr)->set_snubbed(false); + + } else if (up > min_transfer && down * snub_ratio < up) { + (*itr)->set_snubbed(true); + } + } + + return torrent::Object(); +} + +torrent::Object +apply_d_ban_slow_peers(core::Download* download, const torrent::Object& rawArgs) { + if (!download->is_open() || download->is_done() || rpc::call_command_value("d.get_ignore_commands", rpc::make_target(download)) != 0) + return torrent::Object(); + + const torrent::Object::list_type& args = rawArgs.as_list(); + + if (args.size() < 4) + throw torrent::input_error("Too few arguments."); + + torrent::ConnectionList* clist = download->download()->connection_list(); + int extraPeers = clist->size() - clist->min_size(); + if (extraPeers <= 0) + return torrent::Object(); + + torrent::Object::list_type::const_iterator argItrStart = args.begin(); + int extraSeeds = download->download()->peers_complete() - rpc::convert_to_value(*argItrStart++); + uint32_t minRate = rpc::convert_to_value(*argItrStart++); + + for (torrent::ConnectionList::iterator itr = clist->begin(); extraPeers > 0 && itr != clist->end(); ++itr) { +#if LIBTORRENT_FRIENDS + if ((*itr)->peer_info()->is_friend()) + continue; +#endif + + bool isSeed = (*itr)->bitfield() && (*itr)->bitfield()->is_all_set(); + if (isSeed && extraSeeds <= 0) + continue; + + int64_t down = (*itr)->down_rate()->total(); + uint32_t rate = (*itr)->down_rate()->rate(); + + for (torrent::Object::list_type::const_iterator argItr = argItrStart; argItr != args.end(); ++argItr) { + if (rate >= minRate || down >= rpc::convert_to_value(*argItr++)) + break; + + if (cachedTime.seconds() - (*itr)->peer_info()->last_connection() < rpc::convert_to_value(*argItr) * 60) + continue; + + (*itr)->set_banned(); + + extraSeeds -= isSeed; + extraPeers--; + break; + } + } + + // Need to go by indices because erasing may invalidate iterators. + for (size_t pId = 0; pId < clist->size(); ) + if ((*(clist->begin() + pId))->is_banned()) + download->connection_list()->erase(*(clist->begin() + pId), 0); + else + pId++; + + return torrent::Object(); +} + void +apply_d_unban_peers(core::Download* download) { + torrent::PeerList* list = download->download()->peer_list(); + + for (torrent::PeerList::const_iterator itr = list->begin(); itr != list->end(); ++itr) + if (itr->second->is_banned()) + itr->second->set_unbanned(); +} + +void +apply_d_unsnub_peers(core::Download* download) { + if (!download->is_open()) + return; + + for (torrent::ConnectionList::iterator itr = download->download()->connection_list()->begin(); itr != download->download()->connection_list()->end(); ++itr) + if ((*itr)->is_snubbed()) + (*itr)->set_snubbed(false); +} + +void apply_d_connection_type(core::Download* download, const std::string& name) { torrent::Download::ConnectionType connType; @@ -580,6 +696,11 @@ ADD_CD_LIST("delete_link", rak::bind_ptr_fn(&apply_d_change_link, 1)); ADD_CD_V_VOID("delete_tied", &apply_d_delete_tied); + ADD_CD_LIST("ban_slow_peers", rak::ptr_fn(&apply_d_ban_slow_peers)); + ADD_CD_LIST("snub_leechers", rak::ptr_fn(&apply_d_snub_leechers)); + ADD_CD_V_VOID("unban_peers", &apply_d_unban_peers); + ADD_CD_V_VOID("unsnub_peers", &apply_d_unsnub_peers); + CMD_FUNC_SINGLE("d.start", "d.set_hashing_failed=0 ;view.set_visible=started"); CMD_FUNC_SINGLE("d.stop", "view.set_visible=stopped"); CMD_FUNC_SINGLE("d.try_start", "branch=\"or={d.get_hashing_failed=,d.get_ignore_commands=}\",{},{view.set_visible=started}"); Index: rtorrent/src/command_events.cc =================================================================== --- rtorrent/src/command_events.cc (revision 1105) +++ rtorrent/src/command_events.cc (working copy) @@ -42,6 +42,8 @@ #include #include #include +#include +#include #include #include @@ -276,7 +278,32 @@ control->core()->push_log("Closed torrents due to low diskspace."); } +// Should call the d.* commands via RPC, but there doesn't seem to be a way to +// pass variable-sized argument lists, so call the functions directly for now. +torrent::Object apply_d_snub_leechers(core::Download*, const torrent::Object&); +torrent::Object apply_d_ban_slow_peers(core::Download*, const torrent::Object&); + torrent::Object +apply_snub_leechers(const torrent::Object& rawArgs) { + for (core::Manager::DListItr ditr = control->core()->download_list()->begin(); ditr != control->core()->download_list()->end(); ditr++) { + if ((*ditr)->is_open() && !(*ditr)->is_done() && rpc::call_command_value("d.get_ignore_commands", rpc::make_target(*ditr)) == 0) + apply_d_snub_leechers(*ditr, rawArgs); + } + + return torrent::Object(); +} + +torrent::Object +apply_ban_slow_peers(const torrent::Object& rawArgs) { + for (core::Manager::DListItr ditr = control->core()->download_list()->begin(); ditr != control->core()->download_list()->end(); ditr++) { + if ((*ditr)->is_open() && !(*ditr)->is_done() && rpc::call_command_value("d.get_ignore_commands", rpc::make_target(*ditr)) == 0) + apply_d_ban_slow_peers(*ditr, rawArgs); + } + + return torrent::Object(); +} + +torrent::Object apply_download_list(const torrent::Object& rawArgs) { const torrent::Object::list_type& args = rawArgs.as_list(); torrent::Object::list_const_iterator argsItr = args.begin(); @@ -369,6 +396,8 @@ ADD_COMMAND_LIST("on_finished", rak::bind_ptr_fn(&apply_on_state_change, "event.download.finished")); ADD_COMMAND_STRING("on_ratio", rak::ptr_fn(&apply_on_ratio)); + ADD_COMMAND_LIST("snub_leechers", rak::ptr_fn(&apply_snub_leechers)); + ADD_COMMAND_LIST("ban_slow_peers", rak::ptr_fn(&apply_ban_slow_peers)); ADD_COMMAND_VOID("start_tied", &apply_start_tied); ADD_COMMAND_VOID("stop_untied", &apply_stop_untied); Index: libtorrent/src/torrent/peer/peer.cc =================================================================== --- libtorrent/src/torrent/peer/peer.cc (revision 1105) +++ libtorrent/src/torrent/peer/peer.cc (working copy) @@ -63,7 +63,8 @@ bool Peer::is_snubbed() const { return c_ptr()->is_up_snubbed(); } void Peer::set_snubbed(bool v) { m_ptr()->set_upload_snubbed(v); } -void Peer::set_banned() { m_peerInfo->set_failed_counter(64); } +void Peer::set_banned() { m_peerInfo->set_banned(); } +void Peer::set_unbanned() { m_peerInfo->set_unbanned(); } const Rate* Peer::down_rate() const { return c_ptr()->c_peer_chunks()->download_throttle()->rate(); } const Rate* Peer::up_rate() const { return c_ptr()->c_peer_chunks()->upload_throttle()->rate(); } Index: libtorrent/src/torrent/peer/peer.h =================================================================== --- libtorrent/src/torrent/peer/peer.h (revision 1105) +++ libtorrent/src/torrent/peer/peer.h (working copy) @@ -69,7 +69,10 @@ bool is_snubbed() const; void set_snubbed(bool v); + + bool is_banned() const { return peer_info()->is_banned(); } void set_banned(); + void set_unbanned(); const HashString& id() const { return peer_info()->id(); } const char* options() const { return peer_info()->options(); } Index: libtorrent/src/torrent/peer/peer_info.h =================================================================== --- libtorrent/src/torrent/peer/peer_info.h (revision 1105) +++ libtorrent/src/torrent/peer/peer_info.h (working copy) @@ -51,6 +51,8 @@ friend class PeerList; friend class ProtocolExtension; + static const unsigned int banned_mask = 4096; + static const int flag_connected = (1 << 0); static const int flag_incoming = (1 << 1); static const int flag_handshake = (1 << 2); @@ -79,6 +81,10 @@ uint32_t failed_counter() const { return m_failedCounter; } void set_failed_counter(uint32_t c) { m_failedCounter = c; } + bool is_banned() const { return m_failedCounter & banned_mask; } + void set_banned() { m_failedCounter |= banned_mask; } + void set_unbanned() { m_failedCounter &= ~banned_mask; } + uint32_t transfer_counter() const { return m_transferCounter; } void set_transfer_counter(uint32_t c) { m_transferCounter = c; } Index: libtorrent/src/download/choke_manager.cc =================================================================== --- libtorrent/src/download/choke_manager.cc (revision 1105) +++ libtorrent/src/download/choke_manager.cc (working copy) @@ -193,7 +193,8 @@ choke_manager_erase(&m_queued, pc); } - base->set_queued(false); + //breaks unsnubbing, ticket #989: + //base->set_queued(false); } void