# Add the notion of "friends", set by IP using "friend=host", "friend=network/prefix" # or "friend=first,last". # This is especially useful in combination with a NULL throttle for these IPs for quick data # distribution between peers on a LAN or other unmetered and fast network. # If friends regularly download the same torrents, having them autoconnect to each other # is also useful (but won't work for private torrents): # system.method.set_key=event.download.start,add_friend,"d.add_peer=10.0.1.4:6881" # system.method.set_key=event.download.hash_done,add_friend,"d.add_peer=10.0.1.4:6881" # (This is also the only way to use private IPs for friends.) Index: rtorrent/src/core/manager.h =================================================================== --- rtorrent/src/core/manager.h (revision 1105) +++ rtorrent/src/core/manager.h (working copy) @@ -95,6 +95,9 @@ void set_address_throttle(uint32_t begin, uint32_t end, torrent::ThrottlePair throttles); torrent::ThrottlePair get_address_throttle(const sockaddr* addr); + void set_peer_filter(uint32_t begin, uint32_t end, uint32_t flags); + uint32_t get_peer_filter(const sockaddr* addr); + // Really should find a more descriptive name. void initialize_second(); void cleanup(); @@ -131,6 +134,7 @@ private: typedef RangeMap AddressThrottleMap; + typedef RangeMap AddressFilterMap; void create_http(const std::string& uri); void create_final(std::istream* s); @@ -150,6 +154,7 @@ ThrottleMap m_throttles; AddressThrottleMap m_addressThrottles; + AddressFilterMap m_addressFilters; Log m_logImportant; Log m_logComplete; Index: rtorrent/src/core/manager.cc =================================================================== --- rtorrent/src/core/manager.cc (revision 1105) +++ rtorrent/src/core/manager.cc (working copy) @@ -215,6 +215,17 @@ return m_addressThrottles.get(rak::socket_address::cast_from(addr)->sa_inet()->address_h(), torrent::ThrottlePair(NULL, NULL)); } +void +Manager::set_peer_filter(uint32_t begin, uint32_t end, uint32_t filter) { + m_addressFilters.set_merge(begin, end, filter); + torrent::connection_manager()->set_filter(sigc::mem_fun(control->core(), &core::Manager::get_peer_filter)); +} + +uint32_t +Manager::get_peer_filter(const sockaddr* addr) { + return m_addressFilters.get(rak::socket_address::cast_from(addr)->sa_inet()->address_h(), torrent::ConnectionManager::filter_permitted); +} + // Most of this should be possible to move out. void Manager::initialize_second() { Index: rtorrent/src/command_network.cc =================================================================== --- rtorrent/src/command_network.cc (revision 1105) +++ rtorrent/src/command_network.cc (working copy) @@ -209,6 +209,17 @@ } torrent::Object +apply_friend(const torrent::Object& rawArgs) { + const torrent::Object::list_type& args = rawArgs.as_list(); + if (args.empty() || args.size() > 2) + throw torrent::input_error("Incorrect number of arguments."); + + std::pair range = parse_address_range(args, args.begin()); + control->core()->set_peer_filter(range.first, range.second, torrent::ConnectionManager::filter_friend); + return torrent::Object(); +} + +torrent::Object apply_tos(const torrent::Object& rawArg) { rpc::Command::value_type value; torrent::ConnectionManager* cm = torrent::connection_manager(); @@ -455,6 +466,8 @@ ADD_COMMAND_LIST("encryption", rak::ptr_fn(&apply_encryption)); + ADD_COMMAND_LIST("friend", rak::ptr_fn(&apply_friend)); + ADD_COMMAND_STRING("tos", rak::ptr_fn(&apply_tos)); ADD_COMMAND_STRING_TRI("bind", rak::make_mem_fun(control->core(), &core::Manager::set_bind_address), rak::make_mem_fun(control->core(), &core::Manager::bind_address)); Index: rtorrent/src/command_peer.cc =================================================================== --- rtorrent/src/command_peer.cc (revision 1105) +++ rtorrent/src/command_peer.cc (working copy) @@ -133,6 +133,7 @@ ADD_CP_VALUE("is_incoming", std::mem_fun(&torrent::Peer::is_incoming)); ADD_CP_VALUE("is_obfuscated", std::mem_fun(&torrent::Peer::is_obfuscated)); ADD_CP_VALUE("is_snubbed", std::mem_fun(&torrent::Peer::is_snubbed)); + ADD_CP_VALUE("is_friend", std::mem_fun(&torrent::Peer::is_friend)); ADD_CP_STRING_UNI("address", std::ptr_fun(&retrieve_p_address)); ADD_CP_VALUE_UNI("port", std::ptr_fun(&retrieve_p_port)); Index: rtorrent/src/ui/element_peer_list.cc =================================================================== --- rtorrent/src/ui/element_peer_list.cc (revision 1105) +++ rtorrent/src/ui/element_peer_list.cc (working copy) @@ -112,6 +112,7 @@ element->push_back(""); element->push_column("Snubbed:", te_command("if=$p.is_snubbed=,yes,no")); + element->push_column("Friend:", te_command("if=$p.is_friend=,yes,no")); element->push_column("Done:", te_command("p.get_completed_percent=")); element->push_column("Rate:", te_command("cat=$to_kb=$p.get_up_rate=,\\ KB\\ ,$to_kb=$p.get_down_rate=,\\ KB")); element->push_column("Total:", te_command("cat=$to_kb=$p.get_up_total=,\\ KB\\ ,$to_kb=$p.get_down_total=,\\ KB")); Index: libtorrent/src/torrent/rate.cc =================================================================== --- libtorrent/src/torrent/rate.cc (revision 1105) +++ libtorrent/src/torrent/rate.cc (working copy) @@ -58,7 +58,7 @@ } 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 @@ else m_container.front().second += bytes; - m_total += bytes; + m_total += rate_only ? 0 : bytes; m_current += bytes; } Index: libtorrent/src/torrent/rate.h =================================================================== --- libtorrent/src/torrent/rate.h (revision 1105) +++ libtorrent/src/torrent/rate.h (working copy) @@ -68,7 +68,7 @@ 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(); } Index: libtorrent/src/torrent/peer/peer.h =================================================================== --- libtorrent/src/torrent/peer/peer.h (revision 1105) +++ libtorrent/src/torrent/peer/peer.h (working copy) @@ -71,6 +71,8 @@ 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(); } Index: libtorrent/src/torrent/peer/peer_list.cc =================================================================== --- libtorrent/src/torrent/peer/peer_list.cc (revision 1105) +++ libtorrent/src/torrent/peer/peer_list.cc (working copy) @@ -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" @@ -94,6 +96,20 @@ } 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; @@ -110,7 +126,7 @@ 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()); @@ -208,7 +224,7 @@ 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)); 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) @@ -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 @@ 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 @@ 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 @@ void set_connection(PeerConnectionBase* c) { m_connection = c; } private: + PeerInfo(const sockaddr* address); PeerInfo(const PeerInfo&); void operator = (const PeerInfo&); Index: libtorrent/src/torrent/peer/peer_list.h =================================================================== --- libtorrent/src/torrent/peer/peer_list.h (revision 1105) +++ libtorrent/src/torrent/peer/peer_list.h (working copy) @@ -111,6 +111,8 @@ 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. Index: libtorrent/src/torrent/peer/connection_list.cc =================================================================== --- libtorrent/src/torrent/peer/connection_list.cc (revision 1105) +++ libtorrent/src/torrent/peer/connection_list.cc (working copy) @@ -70,7 +70,7 @@ 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()); Index: libtorrent/src/torrent/connection_manager.cc =================================================================== --- libtorrent/src/torrent/connection_manager.cc (revision 1105) +++ libtorrent/src/torrent/connection_manager.cc (working copy) @@ -152,7 +152,7 @@ uint32_t ConnectionManager::filter(const sockaddr* sa) { if (m_slotFilter.empty()) - return 1; + return filter_permitted; else return m_slotFilter(sa); } Index: libtorrent/src/torrent/connection_manager.h =================================================================== --- libtorrent/src/torrent/connection_manager.h (revision 1105) +++ libtorrent/src/torrent/connection_manager.h (working copy) @@ -84,6 +84,10 @@ 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); Index: libtorrent/src/protocol/peer_connection_base.cc =================================================================== --- libtorrent/src/protocol/peer_connection_base.cc (revision 1105) +++ libtorrent/src/protocol/peer_connection_base.cc (working copy) @@ -202,10 +202,14 @@ } 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; @@ -216,7 +220,8 @@ } 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."); @@ -426,7 +431,7 @@ 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(); } @@ -496,7 +501,7 @@ 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; } @@ -515,8 +520,8 @@ // 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); @@ -657,7 +662,7 @@ } 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. Index: libtorrent/src/protocol/peer_connection_base.h =================================================================== --- libtorrent/src/protocol/peer_connection_base.h (revision 1105) +++ libtorrent/src/protocol/peer_connection_base.h (working copy) @@ -127,8 +127,8 @@ 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(); Index: libtorrent/src/protocol/peer_connection_leech.cc =================================================================== --- libtorrent/src/protocol/peer_connection_leech.cc (revision 1105) +++ libtorrent/src/protocol/peer_connection_leech.cc (working copy) @@ -509,6 +509,7 @@ 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; Index: libtorrent/src/protocol/handshake_manager.cc =================================================================== --- libtorrent/src/protocol/handshake_manager.cc (revision 1105) +++ libtorrent/src/protocol/handshake_manager.cc (working copy) @@ -108,7 +108,7 @@ 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 @@ 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()); Index: libtorrent/src/protocol/handshake.cc =================================================================== --- libtorrent/src/protocol/handshake.cc (revision 1105) +++ libtorrent/src/protocol/handshake.cc (working copy) @@ -829,7 +829,7 @@ 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); } @@ -1013,6 +1013,9 @@ 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); Index: libtorrent/src/download/choke_manager.cc =================================================================== --- libtorrent/src/download/choke_manager.cc (revision 1105) +++ libtorrent/src/download/choke_manager.cc (working copy) @@ -129,9 +129,15 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ (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 @@ 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); Index: libtorrent/src/download/choke_manager.h =================================================================== --- libtorrent/src/download/choke_manager.h (revision 1105) +++ libtorrent/src/download/choke_manager.h (working copy) @@ -53,7 +53,7 @@ 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 @@ 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);