From a2849219f7a24f6c4d44d2cf590c331496acc378 Mon Sep 17 00:00:00 2001 From: tearfur <46261767+tearfur@users.noreply.github.com> Date: Tue, 1 Aug 2023 22:56:26 +0800 Subject: [PATCH] feat: split connectable and non-connectable pool (#5801) --- libtransmission/global-ip-cache.cc | 2 +- libtransmission/net.h | 9 +- libtransmission/peer-common.h | 27 +- libtransmission/peer-io.h | 2 +- libtransmission/peer-mgr.cc | 482 +++++++++++++++++++---------- libtransmission/peer-mgr.h | 171 ++++++++-- libtransmission/peer-msgs.cc | 38 ++- libtransmission/peer-msgs.h | 24 +- libtransmission/peer-socket.h | 2 +- libtransmission/rpc-server.cc | 2 +- libtransmission/webseed.cc | 6 +- libtransmission/webseed.h | 4 +- 12 files changed, 527 insertions(+), 242 deletions(-) diff --git a/libtransmission/global-ip-cache.cc b/libtransmission/global-ip-cache.cc index d944eaab7..aef3d6744 100644 --- a/libtransmission/global-ip-cache.cc +++ b/libtransmission/global-ip-cache.cc @@ -69,7 +69,7 @@ namespace global_source_ip_helpers auto const save = errno; auto const [dst_ss, dst_sslen] = dst_addr.to_sockaddr(dst_port); - auto const [bind_ss, bind_sslen] = bind_addr.to_sockaddr(tr_port::fromHost(0)); + auto const [bind_ss, bind_sslen] = bind_addr.to_sockaddr(tr_port{}); if (auto const sock = socket(dst_ss.ss_family, SOCK_DGRAM, 0); sock != TR_BAD_SOCKET) { if (bind(sock, reinterpret_cast(&bind_ss), bind_sslen) == 0) diff --git a/libtransmission/net.h b/libtransmission/net.h index 47b94afcf..20932d3af 100644 --- a/libtransmission/net.h +++ b/libtransmission/net.h @@ -389,12 +389,12 @@ public: std::size_t operator()(tr_socket_address const& socket_address) const noexcept { auto const& [addr, port] = socket_address; - return hash_combine(ip_hash(addr), port_hash(port)); + return hash_combine(ip_hash(addr), PortHasher(port.host())); } private: // https://stackoverflow.com/a/27952689/11390656 - [[nodiscard]] static constexpr std::size_t hash_combine(std::size_t const& a, std::size_t const& b) + [[nodiscard]] static constexpr std::size_t hash_combine(std::size_t const a, std::size_t const b) { return a ^ (b + 0x9e3779b9U + (a << 6U) + (a >> 2U)); } @@ -414,11 +414,6 @@ private: } } - [[nodiscard]] static std::size_t port_hash(tr_port const& port) noexcept - { - return PortHasher(port.host()); - } - constexpr static std::hash IPv4Hasher{}; constexpr static std::hash IPv6Hasher{}; constexpr static std::hash PortHasher{}; diff --git a/libtransmission/peer-common.h b/libtransmission/peer-common.h index e36195ff4..db6c343a3 100644 --- a/libtransmission/peer-common.h +++ b/libtransmission/peer-common.h @@ -14,12 +14,12 @@ #include // uint8_t, uint32_t, uint64_t #include -#include "transmission.h" +#include "libtransmission/transmission.h" -#include "bitfield.h" -#include "block-info.h" -#include "history.h" -#include "net.h" // tr_port +#include "libtransmission/bitfield.h" +#include "libtransmission/block-info.h" +#include "libtransmission/history.h" +#include "libtransmission/net.h" // tr_port /** * @addtogroup peers Peers @@ -28,7 +28,6 @@ class tr_peer; class tr_swarm; -class tr_peer_info; struct tr_bandwidth; // --- Peer Publish / Subscribe @@ -38,19 +37,20 @@ class tr_peer_event public: enum class Type { - ClientGotBlock, + // Unless otherwise specified, all events are for BT peers only + ClientGotBlock, // applies to webseed too ClientGotChoke, - ClientGotPieceData, + ClientGotPieceData, // applies to webseed too ClientGotAllowedFast, ClientGotSuggest, ClientGotPort, - ClientGotRej, + ClientGotRej, // applies to webseed too ClientGotBitfield, ClientGotHave, ClientGotHaveAll, ClientGotHaveNone, ClientSentPieceData, - Error + Error // generic }; Type type = Type::Error; @@ -170,7 +170,7 @@ public: } }; -using tr_peer_callback = void (*)(tr_peer* peer, tr_peer_event const& event, void* client_data); +using tr_peer_callback_generic = void (*)(tr_peer* peer, tr_peer_event const& event, void* client_data); /** * State information about a connected peer. @@ -181,7 +181,7 @@ using tr_peer_callback = void (*)(tr_peer* peer, tr_peer_event const& event, voi class tr_peer { public: - tr_peer(tr_torrent const* tor, tr_peer_info* const peer_info = nullptr); + tr_peer(tr_torrent const* tor); virtual ~tr_peer(); virtual bool isTransferringPieces(uint64_t now, tr_direction dir, tr_bytes_per_second_t* setme_bytes_per_second) const = 0; @@ -241,9 +241,6 @@ public: /// The following fields are only to be used in peer-mgr.cc. /// TODO(ckerr): refactor them out of `tr_peer` - // hook to private peer-mgr information - tr_peer_info* const peer_info; - // whether or not this peer sent us any given block tr_bitfield blame; diff --git a/libtransmission/peer-io.h b/libtransmission/peer-io.h index 06f4ad8fe..d65f7234b 100644 --- a/libtransmission/peer-io.h +++ b/libtransmission/peer-io.h @@ -265,7 +265,7 @@ public: [[nodiscard]] constexpr auto const& socket_address() const noexcept { - return socket_.socketAddress(); + return socket_.socket_address(); } [[nodiscard]] auto display_name() const diff --git a/libtransmission/peer-mgr.cc b/libtransmission/peer-mgr.cc index d9f38cef5..4b614ca29 100644 --- a/libtransmission/peer-mgr.cc +++ b/libtransmission/peer-mgr.cc @@ -12,7 +12,6 @@ #include #include // time_t #include // std::back_inserter -#include #include #include // std::tie #include @@ -58,6 +57,9 @@ static auto constexpr CancelHistorySec = int{ 60 }; // --- +namespace +{ + class HandshakeMediator final : public tr_handshake::Mediator { private: @@ -120,6 +122,10 @@ private: tr_session& session_; }; +using Handshakes = std::unordered_map; + +} // anonymous namespace + bool tr_peer_info::is_blocklisted(tr_session const* session) const { if (blocklisted_) @@ -127,20 +133,55 @@ bool tr_peer_info::is_blocklisted(tr_session const* session) const return *blocklisted_; } - auto const value = session->addressIsBlocked(addr()); + auto const value = session->addressIsBlocked(listen_address()); blocklisted_ = value; return value; } -using Handshakes = std::unordered_map; - #define tr_logAddDebugSwarm(swarm, msg) tr_logAddDebugTor((swarm)->tor, msg) #define tr_logAddTraceSwarm(swarm, msg) tr_logAddTraceTor((swarm)->tor, msg) +namespace +{ + +/* better goes first */ +constexpr struct +{ + [[nodiscard]] constexpr static int compare(tr_peer_info const& a, tr_peer_info const& b) noexcept // <=> + { + if (auto const val = a.compare_by_piece_data_time(b); val != 0) + { + return -val; + } + + if (auto const val = tr_compare_3way(a.from_best(), b.from_best()); val != 0) + { + return val; + } + + return a.compare_by_failure_count(b); + } + + [[nodiscard]] constexpr bool operator()(tr_peer_info const& a, tr_peer_info const& b) const noexcept + { + return compare(a, b) < 0; + } + + [[nodiscard]] constexpr bool operator()(tr_peer_info const* a, tr_peer_info const* b) const noexcept + { + return compare(*a, *b) < 0; + } +} CompareAtomsByUsefulness{}; + +} // namespace + /** @brief Opaque, per-torrent data structure for peer connection information */ class tr_swarm { public: + using Peers = std::vector; + using Pool = std::unordered_map; + [[nodiscard]] auto unique_lock() const { return tor->unique_lock(); @@ -222,37 +263,48 @@ public: auto const lock = unique_lock(); is_running = false; - removeAllPeers(); + remove_all_peers(); outgoing_handshakes.clear(); } - void removePeer(tr_peer* peer) + void remove_peer(tr_peerMsgs* peer) { auto const lock = unique_lock(); auto* const peer_info = peer->peer_info; + auto const socket_address = peer->socket_address(); + auto const was_incoming = peer->is_incoming_connection(); TR_ASSERT(peer_info != nullptr); - if (auto iter = std::find(std::begin(peers), std::end(peers), peer); iter != std::end(peers)) - { - peers.erase(iter); - } - --stats.peer_count; --stats.peer_from_count[peer_info->from_first()]; - TR_ASSERT(stats.peer_count == peerCount()); + if (auto iter = std::find(std::begin(peers), std::end(peers), peer); iter != std::end(peers)) + { + peers.erase(iter); + TR_ASSERT(stats.peer_count == peerCount()); + } delete peer; + + if (was_incoming) + { + [[maybe_unused]] auto const port_empty = std::empty(peer_info->listen_port()); + if (incoming_pool.erase(socket_address) != 0U) + { + TR_ASSERT(port_empty); + } + } + graveyard_pool.erase(socket_address); } - void removeAllPeers() + void remove_all_peers() { - auto tmp = peers; - + auto tmp = Peers{}; + std::swap(tmp, peers); for (auto* peer : tmp) { - removePeer(peer); + remove_peer(peer); } TR_ASSERT(stats.peer_count == 0); @@ -270,7 +322,7 @@ public: return is_endgame_; } - void addStrike(tr_peer* peer) const + void addStrike(tr_peerMsgs* peer) const { tr_logAddTraceSwarm( this, @@ -292,20 +344,20 @@ public: webseeds.reserve(n); for (size_t i = 0; i < n; ++i) { - webseeds.emplace_back(tr_webseedNew(tor, tor->webseed(i), &tr_swarm::peerCallbackFunc, this)); + webseeds.emplace_back(tr_webseedNew(tor, tor->webseed(i), &tr_swarm::peer_callback_webseed, this)); } webseeds.shrink_to_fit(); stats.active_webseed_count = 0; } - [[nodiscard]] TR_CONSTEXPR20 auto isAllSeeds() const noexcept + [[nodiscard]] TR_CONSTEXPR20 auto is_all_seeds() const noexcept { if (!pool_is_all_seeds_) { pool_is_all_seeds_ = std::all_of( - std::begin(pool), - std::end(pool), + std::begin(connectable_pool), + std::end(connectable_pool), [](auto const& key_val) { return key_val.second.is_seed(); }); } @@ -314,17 +366,22 @@ public: [[nodiscard]] tr_peer_info* get_existing_peer_info(tr_socket_address const& socket_address) noexcept { - auto&& it = pool.find(socket_address); - return it != pool.end() ? &it->second : nullptr; + auto&& it = connectable_pool.find(socket_address); + return it != connectable_pool.end() ? &it->second : nullptr; } - tr_peer_info& ensure_info_exists(tr_socket_address const& socket_address, uint8_t const flags, tr_peer_from const from) + tr_peer_info& ensure_info_exists( + tr_socket_address const& socket_address, + uint8_t const flags, + tr_peer_from const from, + bool is_connectable) { TR_ASSERT(socket_address.is_valid()); TR_ASSERT(from < TR_PEER_FROM__MAX); - auto&& [info_it, is_new] = pool.try_emplace(socket_address, socket_address, flags, from); - auto& peer_info = info_it->second; + auto&& [it, is_new] = is_connectable ? connectable_pool.try_emplace(socket_address, socket_address, flags, from) : + incoming_pool.try_emplace(socket_address, socket_address.address(), flags, from); + auto& peer_info = it->second; if (!is_new) { peer_info.found_at(from); @@ -343,12 +400,31 @@ public: mark_all_seeds_flag_dirty(); } - static void peerCallbackFunc(tr_peer* peer, tr_peer_event const& event, void* vs) + static void peer_callback_webseed(tr_peer* const peer, tr_peer_event const& event, void* const vs) { TR_ASSERT(peer != nullptr); auto* s = static_cast(vs); auto const lock = s->unique_lock(); + switch (event.type) + { + case tr_peer_event::Type::ClientGotPieceData: + on_client_got_piece_data(s->tor, event.length, tr_time()); + break; + + default: + peer_callback_common(peer, event, s); + break; + } + } + + static void peer_callback_bt(tr_peerMsgs* const msgs, tr_peer_event const& event, void* const vs) + { + TR_ASSERT(msgs != nullptr); + auto* s = static_cast(vs); + TR_ASSERT(msgs->swarm == s); + auto const lock = s->unique_lock(); + switch (event.type) { case tr_peer_event::Type::ClientSentPieceData: @@ -362,32 +438,20 @@ public: tor->set_dirty(); tor->session->add_uploaded(event.length); - // this should always be a tr_peerMsgs since it's - // impossible to upload piece data to a webseed... - TR_ASSERT(peer->peer_info != nullptr); - peer->peer_info->set_latest_piece_data_time(now); - - break; + msgs->peer_info->set_latest_piece_data_time(now); } + break; + case tr_peer_event::Type::ClientGotPieceData: { auto const now = tr_time(); - auto* const tor = s->tor; - - tor->downloadedCur += event.length; - tor->set_date_active(now); - tor->set_dirty(); - tor->session->add_downloaded(event.length); - - if (auto* const info = peer->peer_info; info != nullptr) - { - info->set_latest_piece_data_time(now); - } - - break; + on_client_got_piece_data(s->tor, event.length, now); + msgs->peer_info->set_latest_piece_data_time(now); } + break; + case tr_peer_event::Type::ClientGotHave: case tr_peer_event::Type::ClientGotHaveAll: case tr_peer_event::Type::ClientGotHaveNone: @@ -396,18 +460,24 @@ public: /* noop */ break; - case tr_peer_event::Type::ClientGotRej: - s->active_requests.remove(s->tor->piece_loc(event.pieceIndex, event.offset).block, peer); - break; - case tr_peer_event::Type::ClientGotChoke: - s->active_requests.remove(peer); + s->active_requests.remove(msgs); break; case tr_peer_event::Type::ClientGotPort: - if (auto* const info = peer->peer_info; info != nullptr) + if (std::empty(event.port)) { - info->port() = event.port; + // Do nothing + } + // If we don't know the listening port of this peer (i.e. incoming connection and first time ClientGotPort) + else if (auto const& info = *msgs->peer_info; std::empty(info.listen_port())) + { + s->on_got_port(msgs, event, false); + } + // If we got a new listening port from a known connectable peer + else if (info.listen_port() != event.port) + { + s->on_got_port(msgs, event, true); } break; @@ -417,32 +487,8 @@ public: // not currently supported break; - case tr_peer_event::Type::ClientGotBlock: - { - auto* const tor = s->tor; - auto const loc = tor->piece_loc(event.pieceIndex, event.offset); - s->cancelAllRequestsForBlock(loc.block, peer); - peer->blocks_sent_to_client.add(tr_time(), 1); - tr_torrentGotBlock(tor, loc.block); - break; - } - - case tr_peer_event::Type::Error: - if (event.err == ERANGE || event.err == EMSGSIZE || event.err == ENOTCONN) - { - /* some protocol error from the peer */ - peer->do_purge = true; - tr_logAddDebugSwarm( - s, - fmt::format( - "setting {} do_purge flag because we got an ERANGE, EMSGSIZE, or ENOTCONN error", - peer->display_name())); - } - else - { - tr_logAddDebugSwarm(s, fmt::format("unhandled error: {}", tr_strerror(event.err))); - } - + default: + peer_callback_common(msgs, event, s); break; } } @@ -465,11 +511,13 @@ public: std::vector> webseeds; // depends-on: active_requests - std::vector peers; + Peers peers; - // tr_peers hold pointers to the items in this container, + // tr_peerMsgs hold pointers to the items in these containers, // therefore references to elements within cannot invalidate - std::unordered_map pool; + Pool incoming_pool; + Pool connectable_pool; + Pool graveyard_pool; tr_peerMsgs* optimistic = nullptr; /* the optimistic peer, or nullptr if none */ @@ -508,7 +556,7 @@ private: { auto const lock = tor->unique_lock(); - for (auto& [socket_address, atom] : pool) + for (auto& [socket_address, atom] : connectable_pool) { mark_peer_as_seed(atom); } @@ -580,6 +628,146 @@ private: void on_torrent_started(); void on_torrent_stopped(); + // --- + + static void peer_callback_common(tr_peer* const peer, tr_peer_event const& event, tr_swarm* const s) + { + switch (event.type) + { + case tr_peer_event::Type::ClientGotRej: + s->active_requests.remove(s->tor->piece_loc(event.pieceIndex, event.offset).block, peer); + break; + + case tr_peer_event::Type::ClientGotBlock: + { + auto* const tor = s->tor; + auto const loc = tor->piece_loc(event.pieceIndex, event.offset); + s->cancelAllRequestsForBlock(loc.block, peer); + peer->blocks_sent_to_client.add(tr_time(), 1); + tr_torrentGotBlock(tor, loc.block); + } + + break; + + case tr_peer_event::Type::Error: + if (event.err == ERANGE || event.err == EMSGSIZE || event.err == ENOTCONN) + { + /* some protocol error from the peer */ + peer->do_purge = true; + tr_logAddDebugSwarm( + s, + fmt::format( + "setting {} do_purge flag because we got an ERANGE, EMSGSIZE, or ENOTCONN error", + peer->display_name())); + } + else + { + tr_logAddDebugSwarm(s, fmt::format("unhandled error: {}", tr_strerror(event.err))); + } + + break; + + default: + TR_ASSERT_MSG(false, "This should be unreachable code"); + break; + } + } + + static void on_client_got_piece_data(tr_torrent* const tor, uint32_t const sent_length, time_t const now) + { + tor->downloadedCur += sent_length; + tor->set_date_active(now); + tor->set_dirty(); + tor->session->add_downloaded(sent_length); + } + + void on_got_port(tr_peerMsgs* const msgs, tr_peer_event const& event, bool was_connectable) + { + auto& info_this = *msgs->peer_info; + TR_ASSERT(info_this.is_connected()); + TR_ASSERT(was_connectable != std::empty(info_this.listen_port())); + + // If we already know about this peer, merge the info objects without invalidating references + if (auto it_that = connectable_pool.find({ info_this.listen_address(), event.port }); + it_that != std::end(connectable_pool)) + { + auto& info_that = it_that->second; + TR_ASSERT(it_that->first == info_that.listen_socket_address()); + TR_ASSERT(it_that->first.address() == info_this.listen_address()); + TR_ASSERT(it_that->first.port() != info_this.listen_port()); + + // If there is an existing connection to this peer, keep the better one + if (info_that.is_connected() && on_got_port_duplicate_connection(msgs, it_that, was_connectable)) + { + return; + } + + info_this.merge(info_that); + connectable_pool.erase(info_that.listen_socket_address()); + } + else if (!was_connectable) + { + info_this.set_connectable(); + } + + auto nh = was_connectable ? connectable_pool.extract(info_this.listen_socket_address()) : + incoming_pool.extract(msgs->socket_address()); + TR_ASSERT(!std::empty(nh)); + if (was_connectable) + { + TR_ASSERT(nh.key() == nh.mapped().listen_socket_address()); + } + else + { + TR_ASSERT(nh.key().address() == nh.mapped().listen_address()); + } + nh.key().port_ = event.port; + [[maybe_unused]] auto const inserted = connectable_pool.insert(std::move(nh)).inserted; + TR_ASSERT(inserted); + info_this.set_listen_port(event.port); + + mark_all_seeds_flag_dirty(); + } + + bool on_got_port_duplicate_connection(tr_peerMsgs* const msgs, Pool::iterator& it_that, bool was_connectable) + { + auto& info_this = *msgs->peer_info; + auto& info_that = it_that->second; + + TR_ASSERT(info_that.is_connected()); + + if (CompareAtomsByUsefulness(info_this, info_that)) + { + auto it = std::find_if( + std::begin(peers), + std::end(peers), + [&info_that](tr_peerMsgs const* const peer) { return peer->peer_info == &info_that; }); + TR_ASSERT(it != std::end(peers)); + (*it)->do_purge = true; + + if (was_connectable) + { + // Note that it_that is invalid after this point + graveyard_pool.insert(connectable_pool.extract(it_that)); + } + + return false; + } + + info_that.merge(info_this); + msgs->do_purge = true; + + if (was_connectable) + { + graveyard_pool.insert(connectable_pool.extract(info_this.listen_socket_address())); + } + + mark_all_seeds_flag_dirty(); + return true; + } + + // --- + // number of bad pieces a peer is allowed to send before we ban them static auto constexpr MaxBadPiecesPerPeer = int{ 5 }; @@ -685,9 +873,12 @@ private: since the blocklist has changed, erase that cached value */ for (auto* const tor : session->torrents()) { - for (auto& [socket_address, atom] : tor->swarm->pool) + for (auto& pool : { std::ref(tor->swarm->connectable_pool), std::ref(tor->swarm->incoming_pool) }) { - atom.set_blocklisted_dirty(); + for (auto& [socket_address, atom] : pool.get()) + { + atom.set_blocklisted_dirty(); + } } } } @@ -703,16 +894,11 @@ private: // --- tr_peer virtual functions -tr_peer::tr_peer(tr_torrent const* tor, tr_peer_info* peer_info_in) +tr_peer::tr_peer(tr_torrent const* tor) : session{ tor->session } , swarm{ tor->swarm } - , peer_info{ peer_info_in } , blame{ tor->block_count() } { - if (auto* const info = peer_info; info != nullptr) - { - info->set_connected(tr_time()); - } } tr_peer::~tr_peer() @@ -721,11 +907,6 @@ tr_peer::~tr_peer() { swarm->active_requests.remove(this); } - - if (auto* const info = peer_info; info != nullptr) - { - info->set_connected(tr_time(), false); - } } // --- @@ -879,7 +1060,7 @@ void create_bit_torrent_peer(tr_torrent* tor, std::shared_ptr io, tr_ tr_swarm* swarm = tor->swarm; - auto* peer = tr_peerMsgsNew(tor, peer_info, std::move(io), client, &tr_swarm::peerCallbackFunc, swarm); + auto* peer = tr_peerMsgsNew(tor, peer_info, std::move(io), client, &tr_swarm::peer_callback_bt, swarm); swarm->peers.push_back(peer); @@ -896,7 +1077,6 @@ void create_bit_torrent_peer(tr_torrent* tor, std::shared_ptr io, tr_ TR_ASSERT(result.io != nullptr); bool const ok = result.is_connected; - bool success = false; auto* const s = manager->get_existing_swarm(result.io->torrent_hash()); @@ -936,7 +1116,9 @@ void create_bit_torrent_peer(tr_torrent* tor, std::shared_ptr io, tr_ } else /* looking good */ { - auto& info = s->ensure_info_exists(socket_address, 0, TR_PEER_FROM_INCOMING); + // If this is an outgoing connection, then we are sure we already have the peer info object + auto& info = result.io->is_incoming() ? s->ensure_info_exists(socket_address, 0U, TR_PEER_FROM_INCOMING, false) : + *s->get_existing_peer_info(socket_address); if (!result.io->is_incoming()) { @@ -953,7 +1135,7 @@ void create_bit_torrent_peer(tr_torrent* tor, std::shared_ptr io, tr_ { tr_logAddTraceSwarm(s, fmt::format("banned peer {} tried to reconnect", info.display_name())); } - else if (result.io->is_incoming() && s->peerCount() >= s->tor->peer_limit()) + else if (s->peerCount() >= s->tor->peer_limit()) { // too many peers already } @@ -974,11 +1156,11 @@ void create_bit_torrent_peer(tr_torrent* tor, std::shared_ptr io, tr_ result.io->set_bandwidth(&s->tor->bandwidth_); create_bit_torrent_peer(s->tor, result.io, &info, client); - success = true; + return true; } } - return success; + return false; } } // namespace handshake_helpers } // namespace @@ -997,13 +1179,13 @@ void tr_peerMgrAddIncoming(tr_peerMgr* manager, tr_peer_socket&& socket) tr_logAddTrace(fmt::format("Banned IP address '{}' tried to connect to us", socket.display_name())); socket.close(); } - else if (manager->incoming_handshakes.count(socket.socketAddress()) != 0U) + else if (manager->incoming_handshakes.count(socket.socket_address()) != 0U) { socket.close(); } else /* we don't have a connection to them yet... */ { - auto socket_address = socket.socketAddress(); + auto socket_address = socket.socket_address(); manager->incoming_handshakes.try_emplace( socket_address, &manager->handshake_mediator_, @@ -1022,9 +1204,12 @@ size_t tr_peerMgrAddPex(tr_torrent* tor, tr_peer_from from, tr_pex const* pex, s for (tr_pex const* const end = pex + n_pex; pex != end; ++pex) { if (tr_isPex(pex) && /* safeguard against corrupt data */ - !s->manager->session->addressIsBlocked(pex->addr) && pex->is_valid_for_peers()) + !s->manager->session->addressIsBlocked(pex->addr) && pex->is_valid_for_peers() && from != TR_PEER_FROM_INCOMING && + (from != TR_PEER_FROM_PEX || (pex->flags & ADDED_F_CONNECTABLE) != 0)) { - s->ensure_info_exists({ pex->addr, pex->port }, pex->flags, from); + // 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->addr, pex->port }, pex->flags, from, true); ++n_used; } } @@ -1087,35 +1272,6 @@ namespace namespace get_peers_helpers { -/* better goes first */ -constexpr struct -{ - [[nodiscard]] constexpr static int compare(tr_peer_info const& a, tr_peer_info const& b) noexcept // <=> - { - if (auto const val = a.compare_by_piece_data_time(b); val != 0) - { - return -val; - } - - if (auto const val = tr_compare_3way(a.from_best(), b.from_best()); val != 0) - { - return val; - } - - return a.compare_by_failure_count(b); - } - - [[nodiscard]] constexpr bool operator()(tr_peer_info const& a, tr_peer_info const& b) const noexcept - { - return compare(a, b) < 0; - } - - [[nodiscard]] constexpr bool operator()(tr_peer_info const* a, tr_peer_info const* b) const noexcept - { - return compare(*a, *b) < 0; - } -} CompareAtomsByUsefulness{}; - [[nodiscard]] bool is_peer_interesting(tr_torrent const* tor, tr_peer_info const& info) { if (tor->is_done() && info.is_seed()) @@ -1171,7 +1327,7 @@ std::vector tr_peerMgrGetPeers(tr_torrent const* tor, uint8_t address_ty } else /* TR_PEERS_INTERESTING */ { - for (auto const& [socket_address, peer_info] : s->pool) + for (auto const& [socket_address, peer_info] : s->connectable_pool) { if (is_peer_interesting(tor, peer_info)) { @@ -1191,7 +1347,7 @@ std::vector tr_peerMgrGetPeers(tr_torrent const* tor, uint8_t address_ty for (auto const* const info : infos) { - auto const& [addr, port] = info->socket_address(); + auto const& [addr, port] = info->listen_socket_address(); if (addr.type == address_type) { @@ -1338,7 +1494,7 @@ namespace peer_stat_helpers { auto stats = tr_peer_stat{}; - auto const [addr, port] = peer->socketAddress(); + auto const [addr, port] = peer->socket_address(); addr.display_name(stats.addr, sizeof(stats.addr)); stats.client = peer->user_agent().c_str(); @@ -1788,16 +1944,15 @@ auto constexpr MaxUploadIdleSecs = time_t{ 60 * 5 }; return false; } -void close_peer(tr_peer* peer) +void close_peer(tr_peerMsgs* peer) { TR_ASSERT(peer != nullptr); - tr_logAddTraceSwarm(peer->swarm, fmt::format("removing bad peer {}", peer->display_name())); - peer->swarm->removePeer(peer); + peer->swarm->remove_peer(peer); } constexpr struct { - [[nodiscard]] constexpr static int compare(tr_peer const* a, tr_peer const* b) // <=> + [[nodiscard]] constexpr static int compare(tr_peerMsgs const* a, tr_peerMsgs const* b) // <=> { if (a->do_purge != b->do_purge) { @@ -1807,18 +1962,18 @@ constexpr struct return -a->peer_info->compare_by_piece_data_time(*b->peer_info); } - [[nodiscard]] constexpr bool operator()(tr_peer const* a, tr_peer const* b) const // less than + [[nodiscard]] constexpr bool operator()(tr_peerMsgs const* a, tr_peerMsgs const* b) const // less than { return compare(a, b) < 0; } } ComparePeerByMostActive{}; -constexpr auto ComparePeerByLeastActive = [](tr_peer const* a, tr_peer const* b) +constexpr auto ComparePeerByLeastActive = [](tr_peerMsgs const* a, tr_peerMsgs const* b) { return ComparePeerByMostActive(b, a); }; -using bad_peers_t = small::vector; +using bad_peers_t = small::vector; bad_peers_t& get_peers_to_close(tr_swarm const* const swarm, time_t const now_sec, bad_peers_t& bad_peers_buf) { @@ -1842,6 +1997,7 @@ void close_bad_peers(tr_swarm* s, time_t const now_sec, bad_peers_t& bad_peers_b { for (auto* peer : get_peers_to_close(s, now_sec, bad_peers_buf)) { + tr_logAddTraceSwarm(peer->swarm, fmt::format("removing bad peer {}", peer->display_name())); close_peer(peer); } } @@ -1876,7 +2032,7 @@ void enforceSessionPeerLimit(tr_session* session) } // Make a list of all the peers. - auto peers = std::vector{}; + auto peers = std::vector{}; peers.reserve(tr_peerMsgs::size()); for (auto const* const tor : session->torrents()) { @@ -1908,7 +2064,7 @@ void tr_peerMgr::reconnectPulse() if (!swarm->is_running) { - swarm->removeAllPeers(); + swarm->remove_all_peers(); } else { @@ -2001,8 +2157,8 @@ void tr_peerMgr::bandwidthPulse() bool tr_swarm::peer_is_in_use(tr_peer_info const& peer_info) const { - return peer_info.is_connected() || outgoing_handshakes.count(peer_info.socket_address()) != 0U || - manager->incoming_handshakes.count(peer_info.socket_address()) != 0U; + // TODO(tearfur): maybe it's possible to store each handshake in the peer_info objects + return peer_info.is_connected() || outgoing_handshakes.count(peer_info.listen_socket_address()) != 0U; } namespace @@ -2010,7 +2166,7 @@ namespace namespace connect_helpers { /* is this atom someone that we'd want to initiate a connection to? */ -[[nodiscard]] bool isPeerCandidate(tr_torrent const* tor, tr_peer_info const& peer_info, time_t const now) +[[nodiscard]] bool is_peer_candidate(tr_torrent const* tor, tr_peer_info const& peer_info, time_t const now) { // have we already tried and failed to connect? if (auto const conn = peer_info.is_connectable(); conn && !*conn) @@ -2055,7 +2211,7 @@ struct peer_candidate { peer_candidate() = default; - peer_candidate(uint64_t score_in, tr_torrent* tor_in, tr_peer_info* peer_info_in) + peer_candidate(uint64_t score_in, tr_torrent const* const tor_in, tr_peer_info const* const peer_info_in) : score{ score_in } , tor{ tor_in } , peer_info{ peer_info_in } @@ -2063,8 +2219,8 @@ struct peer_candidate } uint64_t score; - tr_torrent* tor; - tr_peer_info* peer_info; + tr_torrent const* tor; + tr_peer_info const* peer_info; }; [[nodiscard]] bool torrentWasRecentlyStarted(tr_torrent const* tor) @@ -2149,7 +2305,7 @@ struct peer_candidate } auto candidates = std::vector{}; - candidates.reserve(tr_peer_info::known_peer_count()); + candidates.reserve(tr_peer_info::known_connectable_count()); /* populate the candidate array */ auto salter = tr_salt_shaker{}; @@ -2165,7 +2321,7 @@ struct peer_candidate /* if everyone in the swarm is seeds and pex is disabled because * the torrent is private, then don't initiate connections */ bool const seeding = tor->is_done(); - if (seeding && swarm->isAllSeeds() && tor->is_private()) + if (seeding && swarm->is_all_seeds() && tor->is_private()) { continue; } @@ -2182,9 +2338,9 @@ struct peer_candidate continue; } - for (auto& [socket_address, atom] : swarm->pool) + for (auto const& [socket_address, atom] : swarm->connectable_pool) { - if (isPeerCandidate(tor, atom, now)) + if (is_peer_candidate(tor, atom, now)) { candidates.emplace_back(getPeerCandidateScore(tor, atom, salter()), tor, &atom); } @@ -2206,12 +2362,12 @@ struct peer_candidate auto ret = tr_peerMgr::OutboundCandidates{}; for (auto it = std::crbegin(candidates), end = std::crend(candidates); it != end; ++it) { - ret.emplace_back(it->tor->id(), it->peer_info->socket_address()); + ret.emplace_back(it->tor->id(), it->peer_info->listen_socket_address()); } return ret; } -void initiateConnection(tr_peerMgr* mgr, tr_swarm* s, tr_peer_info& peer_info) +void initiate_connection(tr_peerMgr* mgr, tr_swarm* s, tr_peer_info& peer_info) { using namespace handshake_helpers; @@ -2231,9 +2387,9 @@ void initiateConnection(tr_peerMgr* mgr, tr_swarm* s, tr_peer_info& peer_info) auto peer_io = tr_peerIo::new_outgoing( session, &session->top_bandwidth_, - peer_info.socket_address(), + peer_info.listen_socket_address(), s->tor->info_hash(), - s->tor->completeness == TR_SEED, + s->tor->is_seed(), utp); if (!peer_io) @@ -2245,7 +2401,7 @@ void initiateConnection(tr_peerMgr* mgr, tr_swarm* s, tr_peer_info& peer_info) else { s->outgoing_handshakes.try_emplace( - peer_info.socket_address(), + peer_info.listen_socket_address(), &mgr->handshake_mediator_, peer_io, session->encryptionMode(), @@ -2281,7 +2437,7 @@ void tr_peerMgr::make_new_peer_connections() { if (auto* const peer_info = tor->swarm->get_existing_peer_info(sock_addr); peer_info != nullptr) { - initiateConnection(this, tor->swarm, *peer_info); + initiate_connection(this, tor->swarm, *peer_info); } } } diff --git a/libtransmission/peer-mgr.h b/libtransmission/peer-mgr.h index 066fc25d4..2f97fd1e6 100644 --- a/libtransmission/peer-mgr.h +++ b/libtransmission/peer-mgr.h @@ -9,7 +9,6 @@ #error only libtransmission should #include this header. #endif -#include #include // size_t #include // uint8_t, uint64_t #include @@ -60,12 +59,20 @@ class tr_peer_info { public: tr_peer_info(tr_socket_address socket_address, uint8_t pex_flags, tr_peer_from from) - : socket_address_{ std::move(socket_address) } + : listen_socket_address_{ socket_address } , from_first_{ from } , from_best_{ from } { - ++n_known_peers; + TR_ASSERT(!std::empty(socket_address.port())); + ++n_known_connectable_; + set_pex_flags(pex_flags); + } + tr_peer_info(tr_address address, uint8_t pex_flags, tr_peer_from from) + : listen_socket_address_{ address, tr_port{} } + , from_first_{ from } + , from_best_{ from } + { set_pex_flags(pex_flags); } @@ -76,30 +83,51 @@ public: ~tr_peer_info() { - [[maybe_unused]] auto const n_prev = n_known_peers--; - TR_ASSERT(n_prev > 0U); + if (!std::empty(listen_socket_address_.port())) + { + [[maybe_unused]] auto const n_prev = n_known_connectable_--; + TR_ASSERT(n_prev > 0U); + } } - [[nodiscard]] static auto known_peer_count() noexcept + [[nodiscard]] static auto known_connectable_count() noexcept { - return n_known_peers.load(); + return n_known_connectable_; } // --- - [[nodiscard]] constexpr auto const& socket_address() const noexcept + [[nodiscard]] constexpr auto const& listen_socket_address() const noexcept { - return socket_address_; + return listen_socket_address_; } - [[nodiscard]] constexpr auto& port() noexcept + [[nodiscard]] constexpr auto const& listen_address() const noexcept { - return socket_address_.port_; + return listen_socket_address_.address(); + } + + [[nodiscard]] constexpr auto listen_port() const noexcept + { + return listen_socket_address_.port(); + } + + void set_listen_port(tr_port port_in) noexcept + { + if (!std::empty(port_in)) + { + auto& port = listen_socket_address_.port_; + if (std::empty(port)) // increment known connectable peers if we did not know the listening port of this peer before + { + ++n_known_connectable_; + } + port = port_in; + } } [[nodiscard]] auto display_name() const { - return socket_address_.display_name(); + return listen_socket_address_.display_name(); } // --- @@ -263,9 +291,16 @@ public: // --- - constexpr void set_pex_flags(uint8_t pex_flags) noexcept + constexpr void set_pex_flags(uint8_t pex_flags, bool merge_unsupported = false) noexcept { - pex_flags_ = pex_flags; + if (merge_unsupported) + { + pex_flags_ |= pex_flags; + } + else + { + pex_flags_ = pex_flags; + } if ((pex_flags & ADDED_F_CONNECTABLE) != 0U) { @@ -316,17 +351,100 @@ public: return ret; } + // --- + + // merge two peer info objects that supposedly describes the same peer + void merge(tr_peer_info const& that) noexcept + { + TR_ASSERT(is_connectable_.value_or(true) || !is_connected()); + TR_ASSERT(that.is_connectable_.value_or(true) || !that.is_connected()); + + connection_attempted_at_ = std::max(connection_attempted_at_, that.connection_attempted_at_); + connection_changed_at_ = std::max(connection_changed_at_, that.connection_changed_at_); + piece_data_at_ = std::max(piece_data_at_, that.piece_data_at_); + + /* no need to merge blocklist since it gets updated elsewhere */ + + { + // This part is frankly convoluted and confusing, but the idea is: + // 1. If the two peer info objects agree that this peer is connectable/non-connectable, + // then the answer is straightforward: We keep the agreed value. + // 2. If the two peer info objects disagrees as to whether this peer is connectable, + // then we reset the flag to an empty value, so that we can try for ourselves when + // initiating outgoing connections. + // 3. If one object has knowledge and the other doesn't, then we take the word of the + // peer info object with knowledge with one exception: + // - If the object with knowledge says the peer is not connectable, but we are + // currently connected to the peer, then we give it the benefit of the doubt. + // The connectable flag will be reset to an empty value. + // 4. In case both objects have no knowledge about whether this peer is connectable, + // we shall not make any assumptions: We keep the flag empty. + // + // Truth table: + // +-----------------+---------------+----------------------+--------------------+---------+ + // | is_connectable_ | is_connected_ | that.is_connectable_ | that.is_connected_ | Result | + // +=================+===============+======================+====================+=========+ + // | T | T | T | T | T | + // | T | T | T | F | T | + // | T | T | F | F | ? | + // | T | T | ? | T | T | + // | T | T | ? | F | T | + // | T | F | T | T | T | + // | T | F | T | F | T | + // | T | F | F | F | ? | + // | T | F | ? | T | T | + // | T | F | ? | F | T | + // | F | F | T | T | ? | + // | F | F | T | F | ? | + // | F | F | F | F | F | + // | F | F | ? | T | ? | + // | F | F | ? | F | F | + // | ? | T | T | T | T | + // | ? | T | T | F | T | + // | ? | T | F | F | ? | + // | ? | T | ? | T | ? | + // | ? | T | ? | F | ? | + // | ? | F | T | T | T | + // | ? | F | T | F | T | + // | ? | F | F | F | F | + // | ? | F | ? | T | ? | + // | ? | F | ? | F | ? | + // | N/A | N/A | F | T | Invalid | + // | F | T | N/A | N/A | Invalid | + // +-----------------+---------------+----------------------+--------------------+---------+ + + auto const conn_this = is_connectable_ && *is_connectable_; + auto const conn_that = that.is_connectable_ && *that.is_connectable_; + + if ((!is_connectable_ && !that.is_connectable_) || + is_connectable_.value_or(conn_that || is_connected()) != + that.is_connectable_.value_or(conn_this || that.is_connected())) + { + is_connectable_.reset(); + } + else + { + set_connectable(conn_this || conn_that); + } + } + + set_utp_supported(supports_utp() || that.supports_utp()); + + /* from_first_ should never be modified */ + found_at(that.from_best()); + + /* num_consecutive_fails_ is already the latest */ + set_pex_flags(that.pex_flags(), true); + + if (that.is_banned()) + { + ban(); + } + /* is_connected_ should already be set */ + set_seed(is_seed() || that.is_seed()); + } + private: - [[nodiscard]] constexpr tr_address const& addr() const noexcept - { - return socket_address_.address(); - } - - [[nodiscard]] constexpr auto port() const noexcept - { - return socket_address_.port(); - } - [[nodiscard]] constexpr time_t get_reconnect_interval_secs(time_t const now) const noexcept { // if we were recently connected to this peer and transferring piece @@ -369,9 +487,10 @@ private: // the minimum we'll wait before attempting to reconnect to a peer static auto constexpr MinimumReconnectIntervalSecs = time_t{ 5U }; - static auto inline n_known_peers = std::atomic{}; + static auto inline n_known_connectable_ = size_t{}; - tr_socket_address socket_address_; + // if the port is 0, it SHOULD mean we don't know this peer's listen socket address + tr_socket_address listen_socket_address_; time_t connection_attempted_at_ = {}; time_t connection_changed_at_ = {}; diff --git a/libtransmission/peer-msgs.cc b/libtransmission/peer-msgs.cc index 780e2434c..6b72e6065 100644 --- a/libtransmission/peer-msgs.cc +++ b/libtransmission/peer-msgs.cc @@ -46,7 +46,6 @@ #include "libtransmission/variant.h" #include "libtransmission/version.h" -class tr_peer_info; struct tr_error; #ifndef EBADMSG @@ -328,7 +327,7 @@ public: tr_peer_info* const peer_info_in, std::shared_ptr io_in, tr_interned_string client, - tr_peer_callback callback, + tr_peer_callback_bt callback, void* callback_data) : tr_peerMsgs{ torrent_in, peer_info_in, client, io_in->is_encrypted(), io_in->is_incoming(), io_in->is_utp() } , torrent{ torrent_in } @@ -375,9 +374,9 @@ public: set_active(TR_UP, false); set_active(TR_DOWN, false); - if (this->io) + if (io) { - this->io->clear(); + io->clear(); } } @@ -409,14 +408,14 @@ public: } } - [[nodiscard]] tr_socket_address socketAddress() const override + [[nodiscard]] tr_socket_address socket_address() const override { return io->socket_address(); } [[nodiscard]] std::string display_name() const override { - auto const [addr, port] = socketAddress(); + auto const [addr, port] = socket_address(); return addr.display_name(port); } @@ -679,7 +678,7 @@ private: friend void parseLtepHandshake(tr_peerMsgsImpl* msgs, MessageReader& payload); friend void parseUtMetadata(tr_peerMsgsImpl* msgs, MessageReader& payload_in); - tr_peer_callback const callback_; + tr_peer_callback_bt const callback_; void* const callback_data_; // seconds between periodic sendPex() calls @@ -937,7 +936,7 @@ void sendLtepHandshake(tr_peerMsgsImpl* msgs) bool const allow_metadata_xfer = msgs->torrent->is_public(); /* decide if we want to advertise pex support */ - bool const allow_pex = msgs->session->allows_pex() && msgs->torrent->allows_pex(); + bool const allow_pex = msgs->torrent->allows_pex(); auto val = tr_variant{}; tr_variantInitDict(&val, 8); @@ -1107,7 +1106,7 @@ void parseLtepHandshake(tr_peerMsgsImpl* msgs, MessageReader& payload) } /* get peer's listening port */ - if (tr_variantDictFindInt(&val, TR_KEY_p, &i)) + if (tr_variantDictFindInt(&val, TR_KEY_p, &i) && i > 0) { pex.port.setHost(i); msgs->publish(tr_peer_event::GotPort(pex.port)); @@ -2122,8 +2121,27 @@ void tr_peerMsgsImpl::sendPex() } // namespace +tr_peerMsgs::tr_peerMsgs( + tr_torrent const* tor, + tr_peer_info* peer_info_in, + tr_interned_string user_agent, + bool connection_is_encrypted, + bool connection_is_incoming, + bool connection_is_utp) + : tr_peer{ tor } + , peer_info{ peer_info_in } + , user_agent_{ user_agent } + , connection_is_encrypted_{ connection_is_encrypted } + , connection_is_incoming_{ connection_is_incoming } + , connection_is_utp_{ connection_is_utp } +{ + peer_info->set_connected(tr_time()); + ++n_peers; +} + tr_peerMsgs::~tr_peerMsgs() { + peer_info->set_connected(tr_time(), false); [[maybe_unused]] auto const n_prev = n_peers--; TR_ASSERT(n_prev > 0U); } @@ -2133,7 +2151,7 @@ tr_peerMsgs* tr_peerMsgsNew( tr_peer_info* const peer_info, std::shared_ptr io, tr_interned_string user_agent, - tr_peer_callback callback, + tr_peer_callback_bt callback, void* callback_data) { return new tr_peerMsgsImpl(torrent, peer_info, std::move(io), user_agent, callback, callback_data); diff --git a/libtransmission/peer-msgs.h b/libtransmission/peer-msgs.h index b5d535f47..ac625c57f 100644 --- a/libtransmission/peer-msgs.h +++ b/libtransmission/peer-msgs.h @@ -13,7 +13,6 @@ #include #include // for size_t #include -#include // for std::pair<> #include "libtransmission/transmission.h" // for tr_direction, tr_block_ind... @@ -22,6 +21,7 @@ #include "libtransmission/peer-common.h" // for tr_peer class tr_peerIo; +class tr_peerMsgs; class tr_peer_info; struct tr_torrent; @@ -30,6 +30,8 @@ struct tr_torrent; * @{ */ +using tr_peer_callback_bt = void (*)(tr_peerMsgs* peer, tr_peer_event const& event, void* client_data); + class tr_peerMsgs : public tr_peer { public: @@ -39,17 +41,9 @@ public: tr_interned_string user_agent, bool connection_is_encrypted, bool connection_is_incoming, - bool connection_is_utp) - : tr_peer{ tor, peer_info_in } - , user_agent_{ user_agent } - , connection_is_encrypted_{ connection_is_encrypted } - , connection_is_incoming_{ connection_is_incoming } - , connection_is_utp_{ connection_is_utp } - { - ++n_peers; - } + bool connection_is_utp); - virtual ~tr_peerMsgs() override; + ~tr_peerMsgs() override; [[nodiscard]] static auto size() noexcept { @@ -101,7 +95,7 @@ public: return is_active_[direction]; } - [[nodiscard]] virtual tr_socket_address socketAddress() const = 0; + [[nodiscard]] virtual tr_socket_address socket_address() const = 0; virtual void cancel_block_request(tr_block_index_t block) = 0; @@ -145,6 +139,10 @@ protected: user_agent_ = val; } +public: + // TODO(tearfur): change this to reference + tr_peer_info* const peer_info; + private: static inline auto n_peers = std::atomic{}; @@ -176,7 +174,7 @@ tr_peerMsgs* tr_peerMsgsNew( tr_peer_info* peer_info, std::shared_ptr io, tr_interned_string user_agent, - tr_peer_callback callback, + tr_peer_callback_bt callback, void* callback_data); /* @} */ diff --git a/libtransmission/peer-socket.h b/libtransmission/peer-socket.h index 4d4cb8d9a..b31c16048 100644 --- a/libtransmission/peer-socket.h +++ b/libtransmission/peer-socket.h @@ -60,7 +60,7 @@ public: size_t try_read(InBuf& buf, size_t max, bool buf_is_empty, tr_error** error) const; size_t try_write(OutBuf& buf, size_t max, tr_error** error) const; - [[nodiscard]] constexpr auto const& socketAddress() const noexcept + [[nodiscard]] constexpr auto const& socket_address() const noexcept { return socket_address_; } diff --git a/libtransmission/rpc-server.cc b/libtransmission/rpc-server.cc index 4291363c9..199248d13 100644 --- a/libtransmission/rpc-server.cc +++ b/libtransmission/rpc-server.cc @@ -158,7 +158,7 @@ public: return unix_addr_.to_string(); } - if (port.empty()) + if (std::empty(port)) { return { inet_addr_.display_name() }; } diff --git a/libtransmission/webseed.cc b/libtransmission/webseed.cc index bf0df32a0..f88eb849a 100644 --- a/libtransmission/webseed.cc +++ b/libtransmission/webseed.cc @@ -166,7 +166,7 @@ void onBufferGotData(evbuffer* /*buf*/, evbuffer_cb_info const* info, void* vtas class tr_webseed final : public tr_peer { public: - tr_webseed(struct tr_torrent* tor, std::string_view url, tr_peer_callback callback_in, void* callback_data_in) + tr_webseed(struct tr_torrent* tor, std::string_view url, tr_peer_callback_webseed callback_in, void* callback_data_in) : tr_peer{ tor } , torrent_id{ tr_torrentId(tor) } , base_url{ url } @@ -314,7 +314,7 @@ public: tr_torrent_id_t const torrent_id; std::string const base_url; - tr_peer_callback const callback; + tr_peer_callback_webseed const callback; void* const callback_data; ConnectionLimiter connection_limiter; @@ -538,7 +538,7 @@ void task_request_next_chunk(tr_webseed_task* task) // --- -tr_peer* tr_webseedNew(tr_torrent* torrent, std::string_view url, tr_peer_callback callback, void* callback_data) +tr_peer* tr_webseedNew(tr_torrent* torrent, std::string_view url, tr_peer_callback_webseed callback, void* callback_data) { return new tr_webseed(torrent, url, callback, callback_data); } diff --git a/libtransmission/webseed.h b/libtransmission/webseed.h index 8e34a469a..e0ac1e4db 100644 --- a/libtransmission/webseed.h +++ b/libtransmission/webseed.h @@ -15,6 +15,8 @@ #include "libtransmission/peer-common.h" -tr_peer* tr_webseedNew(struct tr_torrent* torrent, std::string_view, tr_peer_callback callback, void* callback_data); +using tr_peer_callback_webseed = tr_peer_callback_generic; + +tr_peer* tr_webseedNew(struct tr_torrent* torrent, std::string_view, tr_peer_callback_webseed callback, void* callback_data); tr_webseed_view tr_webseedView(tr_peer const* peer);