fix: various pex flag bugs and cleanup (#6917)

* fix: allow connection between seeds when pex is enabled

* chore: add comment to explain `tr_peerMsgs::on_torrent_got_metainfo()`

* refactor: remove `tr_swarm::mark_peer_as_seed()`

* fix: update seed flag in response to BT msgs

Regression from 81a42c6bb6

* chore: remove redundant code to update peer seed flag

* refactor: inc failure count if there were no piece data exchanged

* fix: save information from ltep handshake

* refactor: rename `tr_peerIo::is_seed_` to disambiguate

* fix: add instead of set pex flags when adding non-pex and non-resume peers

* fix: don't mark peer as connectable on getting ltep port msg

By BEP-11's definition, this flag is only set for peers whom we successfully initiated an outgoing connection with.

* refactor: set holepunch flag when we get it from ltep handshake

* fix: only accept positive `reqq` in ltep handshake

* refactor: handle encryption preference in `tr_peer_info`

* refactor: prefer own value for utp support

* refactor: make `tr_peer_info::from_first_` const

* refactor: handle holepunch support in `tr_peer_info`

* fix: parse metadata size only if we have a valid extention id for metadata xfer

* refactor: remove `tr_peer_info::add_pex_flags()` as it's no longer needed

* fix: correctly handle holepunch support when there is no `m` key in ltep handshake

* fix: distinguish between upload only and seed

Say we just connected to a partial seed, the peer sends an ltep handshake that has the `upload_only` key, then a BT `Bitfield` message:

Without this change, the pex seed flag would be set when parsing the ltep handshake, then immediately unset when parsing the `Bitfield` message.

We don't want that.

* fix: don't update `tr_peer_info::is_seed_` when merging peer info objects

* perf: priority in peer candidate score need 2 bits only

* fix: prefer to connect to downloading peers

Regression from c867f00153

* chore: add TODO for C++20 opportunity

* refactor: don't filter out peers without `ADDED_F_CONNECTABLE`

revert change from a2849219f7

* refactor: move peer state updates out of peermgr code

---------

Co-authored-by: Charles Kerr <charles@charleskerr.com>
This commit is contained in:
Yat Ho 2024-09-09 11:05:03 +08:00 committed by GitHub
parent 7c7046be6e
commit 9ff95d162e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 219 additions and 130 deletions

View File

@ -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<int64_t> seeders, std::optional<int64_t> leechers)
void publishPeerCounts(
tr_tier* tier,
std::optional<int64_t> seeders,
std::optional<int64_t> leechers,
std::optional<int64_t> downloaders)
{
if (tier->tor->torrent_announcer->callback != nullptr)
{
@ -698,9 +702,14 @@ void publishPeerCounts(tr_tier* tier, std::optional<int64_t> 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);
}
}

View File

@ -62,6 +62,7 @@ struct tr_tracker_event
// for Peers and Counts events
std::optional<int64_t> leechers;
std::optional<int64_t> seeders;
std::optional<int64_t> downloaders;
};
using tr_tracker_callback = std::function<void(tr_torrent&, tr_tracker_event const*)>;

View File

@ -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> 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<tr_preferred_transport>;
@ -136,7 +136,7 @@ std::shared_ptr<tr_peerIo> 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<preferred_key_t, std::function<bool()>, TR_NUM_PREFERRED_TRANSPORT>{
{ TR_PREFER_UTP,
[&]()
@ -162,7 +162,7 @@ std::shared_ptr<tr_peerIo> 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;

View File

@ -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<tr_peerIo> 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;

View File

@ -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<tr_peer_info> 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<tr_peer_info> info_that)
@ -922,7 +912,7 @@ EXIT:
std::array<libtransmission::ObserverTag, 8> const tags_;
mutable std::optional<bool> pool_is_all_seeds_;
mutable std::optional<bool> 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
{

View File

@ -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<decltype(num_consecutive_fails_)>::max())
if (num_consecutive_fruitless_ != std::numeric_limits<decltype(num_consecutive_fruitless_)>::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<bool> blocklisted_;
std::optional<bool> is_connectable_;
std::optional<bool> is_utp_supported_;
std::optional<bool> is_encryption_preferred_;
std::optional<bool> 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<tr_handshake> outgoing_handshake_;
};

View File

@ -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<tr_peerIo> 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<uint8_t const*>(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));
}

View File

@ -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;

View File

@ -961,7 +961,7 @@ struct tr_torrent
libtransmission::SimpleObservable<tr_torrent*> got_metainfo_;
libtransmission::SimpleObservable<tr_torrent*> started_;
libtransmission::SimpleObservable<tr_torrent*> stopped_;
libtransmission::SimpleObservable<tr_torrent*> swarm_is_all_seeds_;
libtransmission::SimpleObservable<tr_torrent*> swarm_is_all_upload_only_;
libtransmission::SimpleObservable<tr_torrent*, tr_file_index_t const*, tr_file_index_t, tr_priority_t> priority_changed_;
libtransmission::SimpleObservable<tr_torrent*, bool> sequential_download_changed_;