diff --git a/src/download/choke_manager.cc b/src/download/choke_manager.cc index 4915a96..3c422c0 100644 --- a/src/download/choke_manager.cc +++ b/src/download/choke_manager.cc @@ -129,8 +129,14 @@ ChokeManager::cycle(uint32_t quota) { if (m_unchoked.size() > quota) choke_range(m_unchoked.begin(), m_unchoked.end() - unchoked, m_unchoked.size() - quota); + // In case there were friends who are not to be choked, try again with twice + // as many as remain unchoked (friends who will not be choked again plus as + // many non-friends). if (m_unchoked.size() > quota) - throw internal_error("ChokeManager::cycle() m_unchoked.size() > quota."); + choke_range(m_unchoked.begin(), m_unchoked.end() - unchoked, std::min((m_unchoked.size() - quota) * 2, m_unchoked.size())); + + // If there were friends in the n+1..2n range, we still have too many unchoked here. + // FIXME: Change choke_range to take the number of peers to unchoke, instead of a fixed range. return m_unchoked.size() - oldSize; } @@ -145,10 +151,11 @@ ChokeManager::set_queued(PeerConnectionBase* pc, ChokeManagerNode* base) { if (base->snubbed()) return; - if (!is_full() && (m_flags & flag_unchoke_all_new || m_slotCanUnchoke()) && - base->time_last_choke() + rak::timer::from_seconds(10) < cachedTime) { + if (pc->peer_info()->is_friend() || + (!is_full() && (m_flags & flag_unchoke_all_new || m_slotCanUnchoke()) && + base->time_last_choke() + rak::timer::from_seconds(10) < cachedTime)) { m_unchoked.push_back(value_type(pc, 0)); - m_slotConnection(pc, false); + m_slotConnection(pc, 0); m_slotUnchoke(1); @@ -169,7 +176,7 @@ ChokeManager::set_not_queued(PeerConnectionBase* pc, ChokeManagerNode* base) { if (base->unchoked()) { choke_manager_erase(&m_unchoked, pc); - m_slotConnection(pc, true); + m_slotConnection(pc, flag_connection_choke); m_slotUnchoke(-1); } else { @@ -186,7 +193,7 @@ ChokeManager::set_snubbed(PeerConnectionBase* pc, ChokeManagerNode* base) { if (base->unchoked()) { choke_manager_erase(&m_unchoked, pc); - m_slotConnection(pc, true); + m_slotConnection(pc, flag_connection_choke); m_slotUnchoke(-1); } else if (base->queued()) { @@ -212,7 +219,7 @@ ChokeManager::set_not_snubbed(PeerConnectionBase* pc, ChokeManagerNode* base) { if (!is_full() && (m_flags & flag_unchoke_all_new || m_slotCanUnchoke()) && base->time_last_choke() + rak::timer::from_seconds(10) < cachedTime) { m_unchoked.push_back(value_type(pc, 0)); - m_slotConnection(pc, false); + m_slotConnection(pc, 0); m_slotUnchoke(1); @@ -346,20 +353,20 @@ ChokeManager::choke_range(iterator first, iterator last, uint32_t max) { (itr - 1)->second > m_unchoked.end()) throw internal_error("ChokeManager::choke_range(...) bad iterator range."); - count += (itr - 1)->first; - // We move the connections that return true, while the ones that - // return false get thrown out. The function called must update - // ChunkManager::m_queued if false is returned. + // return false stay unchoked. // // The C++ standard says std::partition will call the predicate // max 'last - first' times, so we can assume it gets called once // per element. iterator split = std::partition(itr->second - (itr - 1)->first, itr->second, - rak::on(rak::mem_ref(&value_type::first), std::bind2nd(m_slotConnection, true))); + rak::on(rak::mem_ref(&value_type::first), + std::bind2nd(m_slotConnection, flag_connection_choke | flag_connection_may_ignore))); m_queued.insert(m_queued.end(), itr->second - (itr - 1)->first, split); - m_unchoked.erase(itr->second - (itr - 1)->first, itr->second); + m_unchoked.erase(itr->second - (itr - 1)->first, split); + + count += split - (itr->second - (itr - 1)->first); } if (count > max) @@ -392,7 +399,7 @@ ChokeManager::unchoke_range(iterator first, iterator last, uint32_t max) { count += (itr - 1)->first; std::for_each(itr->second - (itr - 1)->first, itr->second, - rak::on(rak::mem_ref(&value_type::first), std::bind2nd(m_slotConnection, false))); + rak::on(rak::mem_ref(&value_type::first), std::bind2nd(m_slotConnection, 0))); m_unchoked.insert(m_unchoked.end(), itr->second - (itr - 1)->first, itr->second); m_queued.erase(itr->second - (itr - 1)->first, itr->second); diff --git a/src/download/choke_manager.h b/src/download/choke_manager.h index d9ab886..3a7ec67 100644 --- a/src/download/choke_manager.h +++ b/src/download/choke_manager.h @@ -53,7 +53,7 @@ class ChokeManager { public: typedef rak::mem_fun1 slot_unchoke; typedef rak::mem_fun0 slot_can_unchoke; - typedef std::mem_fun1_t slot_connection; + typedef std::mem_fun1_t slot_connection; typedef std::vector > container_type; typedef container_type::value_type value_type; @@ -65,6 +65,9 @@ public: static const int flag_unchoke_all_new = 0x1; + static const int flag_connection_choke = 0x1; + static const int flag_connection_may_ignore = 0x2; + static const uint32_t order_base = (1 << 30); static const uint32_t order_max_size = 4; static const uint32_t weight_size_bytes = order_max_size * sizeof(uint32_t); diff --git a/src/protocol/handshake.cc b/src/protocol/handshake.cc index 7fb389b..ea3eee8 100644 --- a/src/protocol/handshake.cc +++ b/src/protocol/handshake.cc @@ -840,7 +840,7 @@ Handshake::validate_download() { throw handshake_error(ConnectionManager::handshake_dropped, e_handshake_unknown_download); if (!m_download->info()->is_active()) throw handshake_error(ConnectionManager::handshake_dropped, e_handshake_inactive_download); - if (!m_download->info()->is_accepting_new_peers()) + if (!m_download->info()->is_accepting_new_peers() && m_peerInfo != NULL && !m_peerInfo->is_friend()) throw handshake_error(ConnectionManager::handshake_dropped, e_handshake_not_accepting_connections); } @@ -1024,6 +1024,9 @@ Handshake::prepare_peer_info() { if (m_peerInfo == NULL) throw handshake_error(ConnectionManager::handshake_failed, e_handshake_network_error); + if (!m_download->info()->is_accepting_new_peers() && !m_peerInfo->is_friend()) + throw handshake_error(ConnectionManager::handshake_dropped, e_handshake_not_accepting_connections); + if (m_peerInfo->failed_counter() > m_manager->max_failed) throw handshake_error(ConnectionManager::handshake_dropped, e_handshake_toomanyfailed); diff --git a/src/protocol/handshake_manager.cc b/src/protocol/handshake_manager.cc index e06f594..2dea2e5 100644 --- a/src/protocol/handshake_manager.cc +++ b/src/protocol/handshake_manager.cc @@ -108,7 +108,7 @@ HandshakeManager::erase_download(DownloadMain* info) { void HandshakeManager::add_incoming(SocketFd fd, const rak::socket_address& sa) { if (!manager->connection_manager()->can_connect() || - !manager->connection_manager()->filter(sa.c_sockaddr()) || + manager->connection_manager()->filter(sa.c_sockaddr()) == ConnectionManager::filter_blocked || !setup_socket(fd)) { fd.close(); return; @@ -126,7 +126,7 @@ HandshakeManager::add_incoming(SocketFd fd, const rak::socket_address& sa) { void HandshakeManager::add_outgoing(const rak::socket_address& sa, DownloadMain* download) { if (!manager->connection_manager()->can_connect() || - !manager->connection_manager()->filter(sa.c_sockaddr())) + manager->connection_manager()->filter(sa.c_sockaddr()) == ConnectionManager::filter_blocked) return; create_outgoing(sa, download, manager->connection_manager()->encryption_options()); diff --git a/src/protocol/peer_connection_base.cc b/src/protocol/peer_connection_base.cc index 815ea93..9eddbb8 100644 --- a/src/protocol/peer_connection_base.cc +++ b/src/protocol/peer_connection_base.cc @@ -203,10 +203,14 @@ PeerConnectionBase::set_upload_snubbed(bool v) { } bool -PeerConnectionBase::receive_upload_choke(bool choke) { +PeerConnectionBase::receive_upload_choke(int flags) { + bool choke = flags & ChokeManager::flag_connection_choke; if (choke == m_upChoke.choked()) throw internal_error("PeerConnectionBase::receive_upload_choke(...) already set to the same state."); + if (choke && m_peerInfo->is_friend() && (flags & ChokeManager::flag_connection_may_ignore)) + return false; + write_insert_poll_safe(); m_sendChoked = true; @@ -217,7 +221,8 @@ PeerConnectionBase::receive_upload_choke(bool choke) { } bool -PeerConnectionBase::receive_download_choke(bool choke) { +PeerConnectionBase::receive_download_choke(int flags) { + bool choke = flags & ChokeManager::flag_connection_choke; if (choke == m_downChoke.choked()) throw internal_error("PeerConnectionBase::receive_download_choke(...) already set to the same state."); @@ -427,7 +432,7 @@ PeerConnectionBase::down_chunk() { transfer->adjust_position(bytesTransfered); m_down->throttle()->node_used(m_peerChunks.download_throttle(), bytesTransfered); - m_download->info()->down_rate()->insert(bytesTransfered); + m_download->info()->down_rate()->insert(bytesTransfered, m_peerInfo->is_friend()); return transfer->is_finished(); } @@ -497,7 +502,7 @@ PeerConnectionBase::down_chunk_process(const void* buffer, uint32_t length) { transfer->adjust_position(length); m_down->throttle()->node_used(m_peerChunks.download_throttle(), length); - m_download->info()->down_rate()->insert(length); + m_download->info()->down_rate()->insert(length, m_peerInfo->is_friend()); return length; } @@ -516,8 +521,8 @@ PeerConnectionBase::down_chunk_skip_process(const void* buffer, uint32_t length) // Hmm, this might result in more bytes than nessesary being // counted. m_down->throttle()->node_used(m_peerChunks.download_throttle(), length); - m_download->info()->down_rate()->insert(length); - m_download->info()->skip_rate()->insert(length); + m_download->info()->down_rate()->insert(length, m_peerInfo->is_friend()); + m_download->info()->skip_rate()->insert(length, m_peerInfo->is_friend()); if (!transfer->is_valid()) { transfer->adjust_position(length); @@ -662,7 +667,7 @@ PeerConnectionBase::up_chunk() { } m_up->throttle()->node_used(m_peerChunks.upload_throttle(), bytesTransfered); - m_download->info()->up_rate()->insert(bytesTransfered); + m_download->info()->up_rate()->insert(bytesTransfered, m_peerInfo->is_friend()); // Just modifying the piece to cover the remaining data ends up // being much cleaner and we avoid an unnessesary position variable. diff --git a/src/protocol/peer_connection_base.h b/src/protocol/peer_connection_base.h index d131341..dc0dc41 100644 --- a/src/protocol/peer_connection_base.h +++ b/src/protocol/peer_connection_base.h @@ -127,8 +127,8 @@ public: virtual void update_interested() = 0; virtual bool receive_keepalive() = 0; - bool receive_upload_choke(bool choke); - bool receive_download_choke(bool choke); + bool receive_upload_choke(int flags); + bool receive_download_choke(int flags); virtual void event_error(); diff --git a/src/protocol/peer_connection_leech.cc b/src/protocol/peer_connection_leech.cc index 36c6d7a..7918dff 100644 --- a/src/protocol/peer_connection_leech.cc +++ b/src/protocol/peer_connection_leech.cc @@ -516,6 +516,7 @@ PeerConnection::fill_write_buffer() { if (type == Download::CONNECTION_LEECH && m_tryRequest) { if (!(m_tryRequest = !should_request()) && !(m_tryRequest = try_request_pieces()) && + !m_peerInfo->is_friend() && !download_queue()->is_interested_in_active()) { m_sendInterested = true; diff --git a/src/torrent/connection_manager.cc b/src/torrent/connection_manager.cc index 152a0a1..efc919b 100644 --- a/src/torrent/connection_manager.cc +++ b/src/torrent/connection_manager.cc @@ -163,7 +163,7 @@ ConnectionManager::set_proxy_address(const sockaddr* sa) { uint32_t ConnectionManager::filter(const sockaddr* sa) { if (m_slotFilter.empty()) - return 1; + return filter_permitted; else return m_slotFilter(sa); } diff --git a/src/torrent/connection_manager.h b/src/torrent/connection_manager.h index 07ac736..c5e81c0 100644 --- a/src/torrent/connection_manager.h +++ b/src/torrent/connection_manager.h @@ -84,6 +84,10 @@ public: static const uint32_t encryption_enable_retry = (1 << 4); static const uint32_t encryption_prefer_plaintext = (1 << 5); + static const uint32_t filter_blocked = 0; + static const uint32_t filter_permitted = (1 << 0); + static const uint32_t filter_friend = (1 << 1); + // Internal to libtorrent. static const uint32_t encryption_use_proxy = (1 << 6); diff --git a/src/torrent/peer/connection_list.cc b/src/torrent/peer/connection_list.cc index e50606e..fa9e912 100644 --- a/src/torrent/peer/connection_list.cc +++ b/src/torrent/peer/connection_list.cc @@ -70,7 +70,7 @@ ConnectionList::clear() { PeerConnectionBase* ConnectionList::insert(PeerInfo* peerInfo, const SocketFd& fd, Bitfield* bitfield, EncryptionInfo* encryptionInfo, ProtocolExtension* extensions) { - if (size() >= m_maxSize) + if (size() >= m_maxSize && !peerInfo->is_friend()) return NULL; PeerConnectionBase* peerConnection = m_slotNewConnection(encryptionInfo->is_encrypted()); diff --git a/src/torrent/peer/peer.h b/src/torrent/peer/peer.h index 70169ad..13e2880 100644 --- a/src/torrent/peer/peer.h +++ b/src/torrent/peer/peer.h @@ -71,6 +71,8 @@ public: void set_snubbed(bool v); void set_banned(); + bool is_friend() const { return peer_info()->is_friend(); } + const HashString& id() const { return peer_info()->id(); } const char* options() const { return peer_info()->options(); } const sockaddr* address() const { return peer_info()->socket_address(); } diff --git a/src/torrent/peer/peer_info.h b/src/torrent/peer/peer_info.h index fe80027..8f56f73 100644 --- a/src/torrent/peer/peer_info.h +++ b/src/torrent/peer/peer_info.h @@ -40,6 +40,10 @@ #include #include +// For conditional compilation depending on whether this patch was applied. +// Remove for release. +#define LIBTORRENT_FRIENDS 1 + namespace torrent { class LIBTORRENT_EXPORT PeerInfo { @@ -56,8 +60,11 @@ public: static const int flag_handshake = (1 << 2); static const int flag_blocked = (1 << 3); // For initial seeding. static const int flag_restart = (1 << 4); + static const int flag_filtered = (1 << 5); + static const int flag_friend = (1 << 6); - PeerInfo(const sockaddr* address); + // Constructor is private. Use PeerList::insert_address or + // PeerList::create_peer_info instead. ~PeerInfo(); bool is_connected() const { return m_flags & flag_connected; } @@ -65,6 +72,8 @@ public: bool is_handshake() const { return m_flags & flag_handshake; } bool is_blocked() const { return m_flags & flag_blocked; } bool is_restart() const { return m_flags & flag_restart; } + bool is_filtered() const { return m_flags & flag_filtered; } + bool is_friend() const { return m_flags & flag_friend; } int flags() const { return m_flags; } @@ -108,6 +117,7 @@ protected: void set_connection(PeerConnectionBase* c) { m_connection = c; } private: + PeerInfo(const sockaddr* address); PeerInfo(const PeerInfo&); void operator = (const PeerInfo&); diff --git a/src/torrent/peer/peer_list.cc b/src/torrent/peer/peer_list.cc index cef840b..7164b91 100644 --- a/src/torrent/peer/peer_list.cc +++ b/src/torrent/peer/peer_list.cc @@ -42,7 +42,9 @@ #include #include "download/available_list.h" +#include "torrent/connection_manager.h" #include "torrent/peer/client_list.h" +#include "torrent.h" #include "exceptions.h" #include "globals.h" @@ -102,6 +104,20 @@ PeerList::~PeerList() { } PeerInfo* +PeerList::create_peer_info(const sockaddr* sa) { + PeerInfo* peerInfo = new PeerInfo(sa); + uint32_t filter = connection_manager()->filter(sa); + + if (filter == ConnectionManager::filter_blocked) + peerInfo->set_flags(PeerInfo::flag_filtered); + + else if (filter & ConnectionManager::filter_friend) + peerInfo->set_flags(PeerInfo::flag_friend); + + return peerInfo; +} + +PeerInfo* PeerList::insert_address(const sockaddr* sa, int flags) { if (!socket_address_key::is_comparable(sa)) return NULL; @@ -118,7 +134,7 @@ PeerList::insert_address(const sockaddr* sa, int flags) { const rak::socket_address* address = rak::socket_address::cast_from(sa); - PeerInfo* peerInfo = new PeerInfo(sa); + PeerInfo* peerInfo = create_peer_info(sa); peerInfo->set_listen_port(address->port()); manager->client_list()->retrieve_unknown(&peerInfo->mutable_client_info()); @@ -216,7 +232,7 @@ PeerList::connected(const sockaddr* sa, int flags) { if (range.first == range.second) { // Create a new entry. - peerInfo = new PeerInfo(sa); + peerInfo = create_peer_info(sa); base_type::insert(range.second, value_type(socket_address_key(peerInfo->socket_address()), peerInfo)); diff --git a/src/torrent/peer/peer_list.h b/src/torrent/peer/peer_list.h index fece106..2ee5e1e 100644 --- a/src/torrent/peer/peer_list.h +++ b/src/torrent/peer/peer_list.h @@ -111,6 +111,8 @@ public: const_reverse_iterator rbegin() const { return base_type::rbegin(); } const_reverse_iterator rend() const { return base_type::rend(); } + static PeerInfo* create_peer_info(const sockaddr* sa); + protected: // Insert, or find a PeerInfo with socket address 'sa'. Returns end // if no more connections are allowed from that host. diff --git a/src/torrent/rate.cc b/src/torrent/rate.cc index c28d718..586350d 100644 --- a/src/torrent/rate.cc +++ b/src/torrent/rate.cc @@ -58,7 +58,7 @@ Rate::rate() const { } void -Rate::insert(rate_type bytes) { +Rate::insert(rate_type bytes, bool rate_only) { discard_old(); if (m_current > ((rate_type)1 << 40) || bytes > ((rate_type)1 << 28)) @@ -69,7 +69,7 @@ Rate::insert(rate_type bytes) { else m_container.front().second += bytes; - m_total += bytes; + m_total += rate_only ? 0 : bytes; m_current += bytes; } diff --git a/src/torrent/rate.h b/src/torrent/rate.h index 239e4b6..4c48aea 100644 --- a/src/torrent/rate.h +++ b/src/torrent/rate.h @@ -68,7 +68,7 @@ public: timer_type span() const { return m_span; } void set_span(timer_type s) { m_span = s; } - void insert(rate_type bytes); + void insert(rate_type bytes, bool rate_only = false); void reset_rate() { m_current = 0; m_container.clear(); }