diff --git a/libtransmission/announcer.cc b/libtransmission/announcer.cc index e6ad7dad4..6cfbbe7b5 100644 --- a/libtransmission/announcer.cc +++ b/libtransmission/announcer.cc @@ -690,7 +690,11 @@ void publishError(tr_tier* tier, std::string_view msg) publishMessage(tier, msg, tr_tracker_event::Type::Error); } -void publishPeerCounts(tr_tier* tier, std::optional seeders, std::optional leechers) +void publishPeerCounts( + tr_tier* tier, + std::optional seeders, + std::optional leechers, + std::optional downloaders) { if (tier->tor->torrent_announcer->callback != nullptr) { @@ -698,9 +702,14 @@ void publishPeerCounts(tr_tier* tier, std::optional seeders, std::optio e.type = tr_tracker_event::Type::Counts; e.seeders = seeders; e.leechers = leechers; + e.downloaders = downloaders; tr_logAddDebugTier( tier, - fmt::format("peer counts: {} seeders, {} leechers.", seeders.value_or(-1), leechers.value_or(-1))); + fmt::format( + "peer counts: {} seeders, {} leechers, {} downloaders.", + seeders.value_or(-1), + leechers.value_or(-1), + downloaders.value_or(-1))); tier->tor->torrent_announcer->callback(*tier->tor, &e); } @@ -1099,7 +1108,7 @@ void tr_announcer_impl::onAnnounceDone( publishPeersPex(tier, response.pex6); } - publishPeerCounts(tier, response.seeders, response.leechers); + publishPeerCounts(tier, response.seeders, response.leechers, {}); tier->isRunning = is_running_on_success; @@ -1371,7 +1380,7 @@ void tr_announcer_impl::onScrapeDone(tr_scrape_response const& response) tracker->consecutive_failures = 0; } - publishPeerCounts(tier, row.seeders, row.leechers); + publishPeerCounts(tier, row.seeders, row.leechers, row.downloaders); } } diff --git a/libtransmission/announcer.h b/libtransmission/announcer.h index f5e696b19..33a6ce18a 100644 --- a/libtransmission/announcer.h +++ b/libtransmission/announcer.h @@ -62,6 +62,7 @@ struct tr_tracker_event // for Peers and Counts events std::optional leechers; std::optional seeders; + std::optional downloaders; }; using tr_tracker_callback = std::function; diff --git a/libtransmission/peer-io.cc b/libtransmission/peer-io.cc index 9f909989e..fca4a5c88 100644 --- a/libtransmission/peer-io.cc +++ b/libtransmission/peer-io.cc @@ -85,12 +85,12 @@ tr_peerIo::tr_peerIo( tr_session* session, tr_sha1_digest_t const* info_hash, bool is_incoming, - bool is_seed, + bool client_is_seed, tr_bandwidth* parent_bandwidth) : bandwidth_{ parent_bandwidth } , info_hash_{ info_hash != nullptr ? *info_hash : tr_sha1_digest_t{} } , session_{ session } - , is_seed_{ is_seed } + , client_is_seed_{ client_is_seed } , is_incoming_{ is_incoming } { } @@ -125,7 +125,7 @@ std::shared_ptr tr_peerIo::new_outgoing( tr_bandwidth* parent, tr_socket_address const& socket_address, tr_sha1_digest_t const& info_hash, - bool is_seed, + bool client_is_seed, bool utp) { using preferred_key_t = std::underlying_type_t; @@ -136,7 +136,7 @@ std::shared_ptr tr_peerIo::new_outgoing( TR_ASSERT(socket_address.is_valid()); TR_ASSERT(utp || session->allowsTCP()); - auto peer_io = tr_peerIo::create(session, parent, &info_hash, false, is_seed); + auto peer_io = tr_peerIo::create(session, parent, &info_hash, false, client_is_seed); auto const func = small::max_size_map, TR_NUM_PREFERRED_TRANSPORT>{ { TR_PREFER_UTP, [&]() @@ -162,7 +162,7 @@ std::shared_ptr tr_peerIo::new_outgoing( { if (!peer_io->socket_.is_valid()) { - if (auto sock = tr_netOpenPeerSocket(session, socket_address, is_seed); sock.is_valid()) + if (auto sock = tr_netOpenPeerSocket(session, socket_address, client_is_seed); sock.is_valid()) { peer_io->set_socket(std::move(sock)); return true; @@ -254,7 +254,7 @@ bool tr_peerIo::reconnect() return false; } - auto sock = tr_netOpenPeerSocket(session_, socket_address(), is_seed()); + auto sock = tr_netOpenPeerSocket(session_, socket_address(), client_is_seed()); if (!sock.is_tcp()) { return false; diff --git a/libtransmission/peer-io.h b/libtransmission/peer-io.h index c5b9ec2c8..d5d9168a6 100644 --- a/libtransmission/peer-io.h +++ b/libtransmission/peer-io.h @@ -68,7 +68,7 @@ public: tr_session* session_in, tr_sha1_digest_t const* info_hash, bool is_incoming, - bool is_seed, + bool client_is_seed, tr_bandwidth* parent_bandwidth); ~tr_peerIo(); @@ -78,7 +78,7 @@ public: tr_bandwidth* parent, tr_socket_address const& socket_address, tr_sha1_digest_t const& info_hash, - bool is_seed, + bool client_is_seed, bool utp); static std::shared_ptr new_incoming(tr_session* session, tr_bandwidth* parent, tr_peer_socket socket); @@ -329,9 +329,9 @@ private: friend class libtransmission::test::HandshakeTest; - [[nodiscard]] constexpr auto is_seed() const noexcept + [[nodiscard]] constexpr auto client_is_seed() const noexcept { - return is_seed_; + return client_is_seed_; } void call_error_callback(tr_error const& error) @@ -398,7 +398,7 @@ private: tr_priority_t priority_ = TR_PRI_NORMAL; - bool const is_seed_; + bool const client_is_seed_; bool const is_incoming_; bool utp_supported_ = false; diff --git a/libtransmission/peer-mgr.cc b/libtransmission/peer-mgr.cc index 2d7ec8e77..6b087afbf 100644 --- a/libtransmission/peer-mgr.cc +++ b/libtransmission/peer-mgr.cc @@ -214,12 +214,25 @@ void tr_peer_info::merge(tr_peer_info& that) noexcept } } - set_utp_supported(supports_utp() || that.supports_utp()); + if (auto const& other = that.supports_utp(); !supports_utp().has_value() && other) + { + set_utp_supported(*other); + } + + if (auto const& other = that.prefers_encryption(); !prefers_encryption().has_value() && other) + { + set_encryption_preferred(*other); + } + + if (auto const& other = that.supports_holepunch(); !supports_holepunch().has_value() && other) + { + set_holepunch_supported(*other); + } /* from_first_ should never be modified */ found_at(that.from_best()); - /* num_consecutive_fails_ is already the latest */ + /* num_consecutive_fruitless_ is already the latest */ pex_flags_ |= that.pex_flags_; if (that.is_banned()) @@ -227,7 +240,8 @@ void tr_peer_info::merge(tr_peer_info& that) noexcept ban(); } /* is_connected_ should already be set */ - set_seed(is_seed() || that.is_seed()); + /* keep is_seed_ as-is */ + /* keep upload_only_ as-is */ if (that.outgoing_handshake_) { @@ -263,7 +277,7 @@ constexpr struct return val; } - return a.compare_by_failure_count(b); + return a.compare_by_fruitless_count(b); } [[nodiscard]] constexpr bool operator()(tr_peer_info const& a, tr_peer_info const& b) const noexcept @@ -347,7 +361,7 @@ public: tor_in->piece_completed_.observe([this](tr_torrent*, tr_piece_index_t p) { on_piece_completed(p); }), tor_in->started_.observe([this](tr_torrent*) { on_torrent_started(); }), tor_in->stopped_.observe([this](tr_torrent*) { on_torrent_stopped(); }), - tor_in->swarm_is_all_seeds_.observe([this](tr_torrent* /*tor*/) { on_swarm_is_all_seeds(); }), + tor_in->swarm_is_all_upload_only_.observe([this](tr_torrent* /*tor*/) { on_swarm_is_all_upload_only(); }), } } { rebuild_webseeds(); @@ -440,17 +454,17 @@ public: return is_endgame_; } - [[nodiscard]] TR_CONSTEXPR20 auto is_all_seeds() const noexcept + [[nodiscard]] TR_CONSTEXPR20 auto is_all_upload_only() const noexcept { - if (!pool_is_all_seeds_) + if (!pool_is_all_upload_only_) { - pool_is_all_seeds_ = std::all_of( + pool_is_all_upload_only_ = std::all_of( std::begin(connectable_pool), std::end(connectable_pool), - [](auto const& key_val) { return key_val.second->is_seed(); }); + [](auto const& key_val) { return key_val.second->is_upload_only(); }); } - return *pool_is_all_seeds_; + return *pool_is_all_upload_only_; } [[nodiscard]] std::shared_ptr get_existing_peer_info(tr_socket_address const& socket_address) const noexcept @@ -485,7 +499,7 @@ public: ++stats.known_peer_from_count[from]; } - mark_all_seeds_flag_dirty(); + mark_all_upload_only_flag_dirty(); return peer_info; } @@ -501,42 +515,37 @@ public: { case tr_peer_event::Type::ClientSentPieceData: { - auto const now = tr_time(); auto* const tor = s->tor; tor->bytes_uploaded_ += event.length; tr_announcerAddBytes(tor, TR_ANN_UP, event.length); - tor->set_date_active(now); + tor->set_date_active(tr_time()); tor->session->add_uploaded(event.length); - - msgs->peer_info->set_latest_piece_data_time(now); } break; case tr_peer_event::Type::ClientGotPieceData: - { - auto const now = tr_time(); - on_client_got_piece_data(s->tor, event.length, now); - msgs->peer_info->set_latest_piece_data_time(now); - } - + on_client_got_piece_data(s->tor, event.length, tr_time()); break; case tr_peer_event::Type::ClientGotHave: s->got_have.emit(s->tor, event.pieceIndex); + s->mark_all_upload_only_flag_dirty(); break; case tr_peer_event::Type::ClientGotHaveAll: s->got_have_all.emit(s->tor); + s->mark_all_upload_only_flag_dirty(); break; case tr_peer_event::Type::ClientGotHaveNone: - // no-op + s->mark_all_upload_only_flag_dirty(); break; case tr_peer_event::Type::ClientGotBitfield: s->got_bitfield.emit(s->tor, msgs->has()); + s->mark_all_upload_only_flag_dirty(); break; case tr_peer_event::Type::ClientGotChoke: @@ -617,13 +626,6 @@ public: tr_peerMsgs* optimistic = nullptr; /* the optimistic peer, or nullptr if none */ private: - void mark_peer_as_seed(tr_peer_info& peer_info) - { - tr_logAddTraceSwarm(this, fmt::format("marking peer {} as a seed", peer_info.display_name())); - peer_info.set_seed(); - mark_all_seeds_flag_dirty(); - } - void rebuild_webseeds() { auto const n = tor->webseed_count(); @@ -681,9 +683,9 @@ private: } } - void mark_all_seeds_flag_dirty() noexcept + void mark_all_upload_only_flag_dirty() noexcept { - pool_is_all_seeds_.reset(); + pool_is_all_upload_only_.reset(); } void on_torrent_doomed() @@ -700,16 +702,16 @@ private: wishlist.reset(); } - void on_swarm_is_all_seeds() + void on_swarm_is_all_upload_only() { auto const lock = unique_lock(); for (auto const& [socket_address, peer_info] : connectable_pool) { - mark_peer_as_seed(*peer_info); + peer_info->set_upload_only(); } - mark_all_seeds_flag_dirty(); + mark_all_upload_only_flag_dirty(); } void on_piece_completed(tr_piece_index_t piece) @@ -770,16 +772,9 @@ private: // the webseed list may have changed... rebuild_webseeds(); - // some peer_msgs' progress fields may not be accurate if we - // didn't have the metadata before now... so refresh them all... for (auto* peer : peers) { peer->on_torrent_got_metainfo(); - - if (peer->is_seed()) - { - mark_peer_as_seed(*peer->peer_info); - } } } @@ -867,11 +862,6 @@ private: // info_that will be replaced by info_this later, so decrement stat --stats.known_peer_from_count[info_that->from_first()]; } - // we are going to insert a brand-new peer info object to the pool - else if (std::empty(info_this->listen_port())) - { - info_this->set_connectable(); - } // erase the old peer info entry stats.known_peer_from_count[info_this->from_first()] -= connectable_pool.erase(info_this->listen_socket_address()); @@ -884,7 +874,7 @@ private: connectable_pool.insert_or_assign(info_this->listen_socket_address(), std::move(info_this)); EXIT: - mark_all_seeds_flag_dirty(); + mark_all_upload_only_flag_dirty(); } bool on_got_port_duplicate_connection(tr_peerMsgs* const msgs, std::shared_ptr info_that) @@ -922,7 +912,7 @@ EXIT: std::array const tags_; - mutable std::optional pool_is_all_seeds_; + mutable std::optional pool_is_all_upload_only_; bool is_endgame_ = false; }; @@ -1302,16 +1292,16 @@ void create_bit_torrent_peer( { if (info && !info->is_connected()) { - info->on_connection_failed(); + info->on_fruitless_connection(); if (!result.read_anything_from_peer) { tr_logAddTraceSwarm( swarm, fmt::format( - "marking peer {} as unreachable... num_fails is {}", + "marking peer {} as unreachable... num_fruitless is {}", info->display_name(), - info->connection_failure_count())); + info->fruitless_connection_count())); info->set_connectable(false); } } @@ -1410,10 +1400,8 @@ size_t tr_peerMgrAddPex(tr_torrent* tor, tr_peer_from from, tr_pex const* pex, s { if (tr_isPex(pex) && /* safeguard against corrupt data */ !s->manager->blocklists_.contains(pex->socket_address.address()) && pex->is_valid_for_peers(from) && - from != TR_PEER_FROM_INCOMING && (from != TR_PEER_FROM_PEX || (pex->flags & ADDED_F_CONNECTABLE) != 0)) + from != TR_PEER_FROM_INCOMING) { - // we store this peer since it is supposedly connectable (socket address should be the peer's listening address) - // don't care about non-connectable peers that we are not connected to s->ensure_info_exists(pex->socket_address, pex->flags, from); ++n_used; } @@ -1477,7 +1465,7 @@ namespace get_peers_helpers [[nodiscard]] bool is_peer_interesting(tr_torrent const* tor, tr_peer_info const& info) { - if (tor->is_done() && info.is_seed()) + if (tor->is_done() && info.is_upload_only()) { return false; } @@ -2443,8 +2431,8 @@ namespace connect_helpers return false; } - // not if we're both seeds - if (tor->is_done() && peer_info.is_seed()) + // not if we're both upload only and pex is disabled + if (tor->is_done() && peer_info.is_upload_only() && !tor->allows_pex()) { return false; } @@ -2489,8 +2477,8 @@ namespace connect_helpers auto i = uint64_t{}; auto score = uint64_t{}; - /* prefer peers we've connected to, or never tried, over peers we failed to connect to. */ - i = peer_info.connection_failure_count() != 0U ? 1U : 0U; + /* prefer peers we've exchanged piece data with, or never tried, over other peers. */ + i = peer_info.fruitless_connection_count() != 0U ? 1U : 0U; score = addValToKey(score, 1U, i); /* prefer the one we attempted least recently (to cycle through all peers) */ @@ -2517,7 +2505,7 @@ namespace connect_helpers break; } - score = addValToKey(score, 4U, i); + score = addValToKey(score, 2U, i); // prefer recently-started torrents i = tor->started_recently(tr_time()) ? 0 : 1; @@ -2532,12 +2520,12 @@ namespace connect_helpers score = addValToKey(score, 1U, i); /* prefer peers that we might be able to upload to */ - i = peer_info.is_seed() ? 0 : 1; + i = peer_info.is_upload_only() ? 1 : 0; score = addValToKey(score, 1U, i); /* Prefer peers that we got from more trusted sources. * lower `fromBest` values indicate more trusted sources */ - score = addValToKey(score, 4U, peer_info.from_best()); + score = addValToKey(score, 4U, peer_info.from_best()); // TODO(tearfur): use std::bit_width(TR_PEER_FROM__MAX - 1) /* salt */ score = addValToKey(score, 8U, salt); @@ -2588,10 +2576,10 @@ void get_peer_candidates(size_t global_peer_limit, tr_torrents& torrents, tr_pee continue; } - /* if everyone in the swarm is seeds and pex is disabled, + /* if everyone in the swarm is upload only and pex is disabled, * then don't initiate connections */ bool const seeding = tor->is_done(); - if (seeding && swarm->is_all_seeds() && !tor->allows_pex()) + if (seeding && swarm->is_all_upload_only() && !tor->allows_pex()) { continue; } @@ -2664,7 +2652,7 @@ void initiate_connection(tr_peerMgr* mgr, tr_swarm* s, tr_peer_info& peer_info) { tr_logAddTraceSwarm(s, fmt::format("peerIo not created; marking peer {} as unreachable", peer_info.display_name())); peer_info.set_connectable(false); - peer_info.on_connection_failed(); + peer_info.on_fruitless_connection(); } else { diff --git a/libtransmission/peer-mgr.h b/libtransmission/peer-mgr.h index 7c0b4915c..9d707b002 100644 --- a/libtransmission/peer-mgr.h +++ b/libtransmission/peer-mgr.h @@ -43,7 +43,7 @@ enum /* true if the peer supports encryption */ ADDED_F_ENCRYPTION_FLAG = 1, /* true if the peer is a seed or partial seed */ - ADDED_F_SEED_FLAG = 2, + ADDED_F_UPLOAD_ONLY_FLAG = 2, /* true if the peer supports µTP */ ADDED_F_UTP_FLAGS = 4, /* true if the peer has holepunch support */ @@ -161,6 +161,16 @@ public: return is_seed_; } + constexpr void set_upload_only(bool value = true) noexcept + { + is_upload_only_ = value; + } + + [[nodiscard]] constexpr auto is_upload_only() const noexcept + { + return is_upload_only_ || is_seed(); + } + // --- void set_connectable(bool value = true) noexcept @@ -187,9 +197,33 @@ public: // --- - [[nodiscard]] constexpr auto compare_by_failure_count(tr_peer_info const& that) const noexcept + void set_encryption_preferred(bool value = true) noexcept { - return tr_compare_3way(num_consecutive_fails_, that.num_consecutive_fails_); + is_encryption_preferred_ = value; + } + + [[nodiscard]] constexpr auto const& prefers_encryption() const noexcept + { + return is_encryption_preferred_; + } + + // --- + + void set_holepunch_supported(bool value = true) noexcept + { + is_holepunch_supported_ = value; + } + + [[nodiscard]] constexpr auto const& supports_holepunch() const noexcept + { + return is_holepunch_supported_; + } + + // --- + + [[nodiscard]] constexpr auto compare_by_fruitless_count(tr_peer_info const& that) const noexcept + { + return tr_compare_3way(num_consecutive_fruitless_, that.num_consecutive_fruitless_); } [[nodiscard]] constexpr auto compare_by_piece_data_time(tr_peer_info const& that) const noexcept @@ -201,15 +235,27 @@ public: constexpr auto set_connected(time_t now, bool is_connected = true) noexcept { + if (is_connected_ == is_connected) + { + return; + } + connection_changed_at_ = now; is_connected_ = is_connected; if (is_connected_) { - num_consecutive_fails_ = {}; piece_data_at_ = {}; } + else if (has_transferred_piece_data()) + { + num_consecutive_fruitless_ = {}; + } + else + { + on_fruitless_connection(); + } } [[nodiscard]] constexpr auto is_connected() const noexcept @@ -316,17 +362,17 @@ public: // --- - constexpr void on_connection_failed() noexcept + constexpr void on_fruitless_connection() noexcept { - if (num_consecutive_fails_ != std::numeric_limits::max()) + if (num_consecutive_fruitless_ != std::numeric_limits::max()) { - ++num_consecutive_fails_; + ++num_consecutive_fruitless_; } } - [[nodiscard]] constexpr auto connection_failure_count() const noexcept + [[nodiscard]] constexpr auto fruitless_connection_count() const noexcept { - return num_consecutive_fails_; + return num_consecutive_fruitless_; } // --- @@ -345,7 +391,20 @@ public: set_utp_supported(); } - is_seed_ = (pex_flags & ADDED_F_SEED_FLAG) != 0U; + if ((pex_flags & ADDED_F_ENCRYPTION_FLAG) != 0U) + { + set_encryption_preferred(); + } + + if ((pex_flags & ADDED_F_HOLEPUNCH) != 0U) + { + set_holepunch_supported(); + } + + if ((pex_flags & ADDED_F_UPLOAD_ONLY_FLAG) != 0U) + { + set_upload_only(); + } } [[nodiscard]] constexpr uint8_t pex_flags() const noexcept @@ -376,9 +435,37 @@ public: } } - if (is_seed_) + if (is_encryption_preferred_) { - ret |= ADDED_F_SEED_FLAG; + if (*is_encryption_preferred_) + { + ret |= ADDED_F_ENCRYPTION_FLAG; + } + else + { + ret &= ~ADDED_F_ENCRYPTION_FLAG; + } + } + + if (is_holepunch_supported_) + { + if (*is_holepunch_supported_) + { + ret |= ADDED_F_HOLEPUNCH; + } + else + { + ret &= ~ADDED_F_HOLEPUNCH; + } + } + + if (is_upload_only()) + { + ret |= ADDED_F_UPLOAD_ONLY_FLAG; + } + else + { + ret &= ~ADDED_F_UPLOAD_ONLY_FLAG; } return ret; @@ -404,7 +491,7 @@ private: // otherwise, the interval depends on how many times we've tried // and failed to connect to the peer. Penalize peers that were // unreachable the last time we tried - auto step = this->num_consecutive_fails_; + auto step = num_consecutive_fruitless_; if (unreachable) { step += 2; @@ -445,16 +532,19 @@ private: mutable std::optional blocklisted_; std::optional is_connectable_; std::optional is_utp_supported_; + std::optional is_encryption_preferred_; + std::optional is_holepunch_supported_; - tr_peer_from from_first_; // where the peer was first found + tr_peer_from const from_first_; // where the peer was first found tr_peer_from from_best_; // the "best" place where this peer was found - uint8_t num_consecutive_fails_ = {}; + uint8_t num_consecutive_fruitless_ = {}; uint8_t pex_flags_ = {}; bool is_banned_ = false; bool is_connected_ = false; bool is_seed_ = false; + bool is_upload_only_ = false; std::unique_ptr outgoing_handshake_; }; diff --git a/libtransmission/peer-msgs.cc b/libtransmission/peer-msgs.cc index 8d0ffb824..54603e206 100644 --- a/libtransmission/peer-msgs.cc +++ b/libtransmission/peer-msgs.cc @@ -191,15 +191,6 @@ auto constexpr MaxPexPeerCount = size_t{ 50U }; // --- -enum class EncryptionPreference : uint8_t -{ - Unknown, - Yes, - No -}; - -// --- - struct peer_request { uint32_t index = 0; @@ -405,6 +396,8 @@ public: void on_torrent_got_metainfo() noexcept override { + // A peer may not be interesting to us anymore after + // sending us metadata, so do a status update update_active(); } @@ -496,8 +489,8 @@ public: void update_active() { - update_active(TR_UP); - update_active(TR_DOWN); + update_active(TR_CLIENT_TO_PEER); + update_active(TR_PEER_TO_CLIENT); } void update_active(tr_direction direction) @@ -693,8 +686,6 @@ private: tr_port dht_port_; - EncryptionPreference encryption_preference_ = EncryptionPreference::Unknown; - tr_torrent& tor_; std::shared_ptr const io_; @@ -1203,21 +1194,15 @@ void tr_peerMsgsImpl::parse_ltep_handshake(MessageReader& payload) logtrace(this, fmt::format("here is the base64-encoded handshake: [{:s}]", tr_base64_encode(handshake_sv))); // does the peer prefer encrypted connections? - auto pex = tr_pex{}; - auto& [addr, port] = pex.socket_address; if (auto e = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_e, &e)) { - encryption_preference_ = e != 0 ? EncryptionPreference::Yes : EncryptionPreference::No; - - if (encryption_preference_ == EncryptionPreference::Yes) - { - pex.flags |= ADDED_F_ENCRYPTION_FLAG; - } + peer_info->set_encryption_preferred(e != 0); } // check supported messages for utorrent pex peer_supports_pex_ = false; peer_supports_metadata_xfer_ = false; + auto holepunch_supported = false; if (tr_variant* sub = nullptr; tr_variantDictFindDict(&*var, TR_KEY_m, &sub)) { @@ -1237,15 +1222,24 @@ void tr_peerMsgsImpl::parse_ltep_handshake(MessageReader& payload) if (auto ut_holepunch = int64_t{}; tr_variantDictFindInt(sub, TR_KEY_ut_holepunch, &ut_holepunch)) { - // Transmission doesn't support this extension yet. - // But its presence does indicate µTP supports, - // which we do care about... - peer_info->set_utp_supported(true); + holepunch_supported = ut_holepunch != 0; } } + // Transmission doesn't support this extension yet. + // But its presence does indicate µTP support, + // which we do care about... + if (holepunch_supported) + { + peer_info->set_utp_supported(); + } + // Even though we don't support it, no reason not to + // help pass this flag to other peers who do. + peer_info->set_holepunch_supported(holepunch_supported); + // look for metainfo size (BEP 9) - if (auto metadata_size = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_metadata_size, &metadata_size)) + if (auto metadata_size = int64_t{}; + peer_supports_metadata_xfer_ && tr_variantDictFindInt(&*var, TR_KEY_metadata_size, &metadata_size)) { if (!tr_metadata_download::is_valid_metadata_size(metadata_size)) { @@ -1260,7 +1254,7 @@ void tr_peerMsgsImpl::parse_ltep_handshake(MessageReader& payload) // look for upload_only (BEP 21) if (auto upload_only = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_upload_only, &upload_only)) { - pex.flags |= ADDED_F_SEED_FLAG; + peer_info->set_upload_only(upload_only != 0); } // https://www.bittorrent.org/beps/bep_0010.html @@ -1275,8 +1269,7 @@ void tr_peerMsgsImpl::parse_ltep_handshake(MessageReader& payload) /* get peer's listening port */ if (auto p = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_p, &p) && p > 0) { - port.set_host(p); - publish(tr_peer_event::GotPort(port)); + publish(tr_peer_event::GotPort(tr_port::from_host(p))); logtrace(this, fmt::format("peer's port is now {:d}", p)); } @@ -1285,19 +1278,21 @@ void tr_peerMsgsImpl::parse_ltep_handshake(MessageReader& payload) if (io_->is_incoming() && tr_variantDictFindRaw(&*var, TR_KEY_ipv4, &addr_compact, &addr_len) && addr_len == tr_address::CompactAddrBytes[TR_AF_INET]) { - std::tie(addr, std::ignore) = tr_address::from_compact_ipv4(addr_compact); + auto pex = tr_pex{ peer_info->listen_socket_address(), peer_info->pex_flags() }; + pex.socket_address.address_ = tr_address::from_compact_ipv4(addr_compact).first; tr_peerMgrAddPex(&tor_, TR_PEER_FROM_LTEP, &pex, 1); } if (io_->is_incoming() && tr_variantDictFindRaw(&*var, TR_KEY_ipv6, &addr_compact, &addr_len) && addr_len == tr_address::CompactAddrBytes[TR_AF_INET6]) { - std::tie(addr, std::ignore) = tr_address::from_compact_ipv6(addr_compact); + auto pex = tr_pex{ peer_info->listen_socket_address(), peer_info->pex_flags() }; + pex.socket_address.address_ = tr_address::from_compact_ipv6(addr_compact).first; tr_peerMgrAddPex(&tor_, TR_PEER_FROM_LTEP, &pex, 1); } /* get peer's maximum request queue size */ - if (auto reqq_in = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_reqq, &reqq_in)) + if (auto reqq_in = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_reqq, &reqq_in) && reqq_in > 0) { peer_reqq_ = reqq_in; } @@ -1427,6 +1422,7 @@ ReadResult tr_peerMsgsImpl::process_peer_message(uint8_t id, MessageReader& payl if (!have_.test(ui32)) { have_.set(ui32); + peer_info->set_seed(is_seed()); publish(tr_peer_event::GotHave(ui32)); } @@ -1436,6 +1432,7 @@ ReadResult tr_peerMsgsImpl::process_peer_message(uint8_t id, MessageReader& payl logtrace(this, "got a bitfield"); have_ = tr_bitfield{ tor_.has_metainfo() ? tor_.piece_count() : std::size(payload) * 8 }; have_.set_raw(reinterpret_cast(std::data(payload)), std::size(payload)); + peer_info->set_seed(is_seed()); publish(tr_peer_event::GotBitfield(&have_)); break; @@ -1535,6 +1532,7 @@ ReadResult tr_peerMsgsImpl::process_peer_message(uint8_t id, MessageReader& payl if (fext) { have_.set_has_all(); + peer_info->set_seed(); publish(tr_peer_event::GotHaveAll()); } else @@ -1551,6 +1549,7 @@ ReadResult tr_peerMsgsImpl::process_peer_message(uint8_t id, MessageReader& payl if (fext) { have_.set_has_none(); + peer_info->set_seed(false); publish(tr_peer_event::GotHaveNone()); } else @@ -1619,6 +1618,7 @@ ReadResult tr_peerMsgsImpl::read_piece_data(MessageReader& payload) return { ReadState::Err, len }; } + peer_info->set_latest_piece_data_time(tr_time()); publish(tr_peer_event::GotPieceData(len)); if (loc.block_offset == 0U && len == block_size) // simple case: one message has entire block @@ -1701,6 +1701,7 @@ void tr_peerMsgsImpl::did_write(tr_peerIo* /*io*/, size_t bytes_written, bool wa if (was_piece_data) { + msgs->peer_info->set_latest_piece_data_time(tr_time()); msgs->publish(tr_peer_event::SentPieceData(bytes_written)); } diff --git a/libtransmission/torrent.cc b/libtransmission/torrent.cc index fecf7ae83..d83c2908d 100644 --- a/libtransmission/torrent.cc +++ b/libtransmission/torrent.cc @@ -2102,9 +2102,9 @@ void tr_torrent::on_tracker_response(tr_tracker_event const* event) break; case tr_tracker_event::Type::Counts: - if (is_private() && (event->leechers == 0)) + if (is_private() && (event->leechers == 0 || event->downloaders == 0)) { - swarm_is_all_seeds_.emit(this); + swarm_is_all_upload_only_.emit(this); } break; diff --git a/libtransmission/torrent.h b/libtransmission/torrent.h index d15997adb..6efe19a4b 100644 --- a/libtransmission/torrent.h +++ b/libtransmission/torrent.h @@ -961,7 +961,7 @@ struct tr_torrent libtransmission::SimpleObservable got_metainfo_; libtransmission::SimpleObservable started_; libtransmission::SimpleObservable stopped_; - libtransmission::SimpleObservable swarm_is_all_seeds_; + libtransmission::SimpleObservable swarm_is_all_upload_only_; libtransmission::SimpleObservable priority_changed_; libtransmission::SimpleObservable sequential_download_changed_;