mirror of
https://github.com/transmission/transmission
synced 2025-03-16 08:49:47 +00:00
refactor: local peer discovery (#3696)
This commit is contained in:
parent
ab09718342
commit
e8686095ed
14 changed files with 900 additions and 633 deletions
|
@ -20,6 +20,7 @@ include(CheckIncludeFile)
|
|||
include(CheckIncludeFiles)
|
||||
include(CheckFunctionExists)
|
||||
include(CheckLibraryExists)
|
||||
include(CheckSymbolExists)
|
||||
include(ExternalProject)
|
||||
include(GNUInstallDirs)
|
||||
include(TrMacros)
|
||||
|
@ -611,6 +612,8 @@ if(HAVE_LIBM)
|
|||
set(LIBM_LIBRARY m)
|
||||
endif()
|
||||
|
||||
check_symbol_exists(SO_REUSEPORT "sys/types.h;sys/socket.h" HAVE_SO_REUSEPORT)
|
||||
|
||||
set(TR_NETWORK_LIBRARIES)
|
||||
if(WIN32)
|
||||
list(APPEND TR_NETWORK_LIBRARIES iphlpapi ws2_32)
|
||||
|
|
|
@ -12,6 +12,7 @@ Checks: >
|
|||
clang-analyzer-core.*,
|
||||
clang-analyzer-cplusplus.*,
|
||||
clang-analyzer-deadcode.*,
|
||||
clang-analyzer-nullability.*,
|
||||
clang-analyzer-optin.cplusplus.*,
|
||||
clang-analyzer-security.*,
|
||||
clang-analyzer-valist.*,
|
||||
|
@ -28,6 +29,7 @@ Checks: >
|
|||
-modernize-avoid-c-arrays,
|
||||
-modernize-use-trailing-return-type,
|
||||
performance-*,
|
||||
portability-*,
|
||||
readability-*,
|
||||
-readability-function-cognitive-complexity,
|
||||
-readability-identifier-length,
|
||||
|
|
|
@ -254,6 +254,10 @@ if(POLARSSL_IS_MBEDTLS)
|
|||
add_definitions(-DPOLARSSL_IS_MBEDTLS)
|
||||
endif()
|
||||
|
||||
if(HAVE_SO_REUSEPORT)
|
||||
add_definitions(-DHAVE_SO_REUSEPORT=1)
|
||||
endif()
|
||||
|
||||
include_directories(
|
||||
${PROJECT_SOURCE_DIR}
|
||||
${PROJECT_BINARY_DIR}
|
||||
|
|
|
@ -137,7 +137,7 @@ std::string tr_base64_decode(std::string_view input);
|
|||
std::string tr_sha1_to_string(tr_sha1_digest_t const&);
|
||||
|
||||
/**
|
||||
* @brief Generate a sha256 digest from a hex string.
|
||||
* @brief Generate a sha1 digest from a hex string.
|
||||
*/
|
||||
std::optional<tr_sha1_digest_t> tr_sha1_from_string(std::string_view hex);
|
||||
|
||||
|
|
|
@ -128,6 +128,58 @@ tr_peer_id_t tr_peerIdInit()
|
|||
****
|
||||
***/
|
||||
|
||||
bool tr_session::LpdMediator::onPeerFound(std::string_view info_hash_str, tr_address address, tr_port port)
|
||||
{
|
||||
auto const digest = tr_sha1_from_string(info_hash_str);
|
||||
if (!digest)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
tr_torrent* const tor = session_.torrents_.get(*digest);
|
||||
if (!tr_isTorrent(tor) || !tor->allowsLpd())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// we found a suitable peer, add it to the torrent
|
||||
auto pex = tr_pex{ address, port };
|
||||
tr_peerMgrAddPex(tor, TR_PEER_FROM_LPD, &pex, 1U);
|
||||
tr_logAddDebugTor(tor, fmt::format(FMT_STRING("Found a local peer from LPD ({:s})"), address.readable(port)));
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<tr_lpd::Mediator::TorrentInfo> tr_session::LpdMediator::torrents() const
|
||||
{
|
||||
auto ret = std::vector<tr_lpd::Mediator::TorrentInfo>{};
|
||||
ret.reserve(std::size(session_.torrents()));
|
||||
for (auto const* const tor : session_.torrents())
|
||||
{
|
||||
auto info = tr_lpd::Mediator::TorrentInfo{};
|
||||
info.info_hash_str = tor->infoHashString();
|
||||
info.activity = tr_torrentGetActivity(tor);
|
||||
info.allows_lpd = tor->allowsLpd();
|
||||
info.announce_after = tor->lpdAnnounceAt;
|
||||
ret.emplace_back(info);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void tr_session::LpdMediator::setNextAnnounceTime(std::string_view info_hash_str, time_t announce_after)
|
||||
{
|
||||
if (auto digest = tr_sha1_from_string(info_hash_str); digest)
|
||||
{
|
||||
if (tr_torrent* const tor = session_.torrents_.get(*digest); tr_isTorrent(tor))
|
||||
{
|
||||
tor->lpdAnnounceAt = announce_after;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
std::optional<std::string> tr_session::WebMediator::cookieFile() const
|
||||
{
|
||||
auto const path = tr_pathbuf{ session_->configDir(), "/cookies.txt"sv };
|
||||
|
@ -700,7 +752,7 @@ void tr_session::initImpl(init_data& data)
|
|||
|
||||
if (this->allowsLPD())
|
||||
{
|
||||
tr_lpdInit(this, &this->bind_ipv4.addr_);
|
||||
this->lpd_ = tr_lpd::create(lpd_mediator_, timerMaker(), eventBase());
|
||||
}
|
||||
|
||||
tr_utpInit(this);
|
||||
|
@ -1743,10 +1795,7 @@ void tr_session::closeImplStart()
|
|||
{
|
||||
is_closing_ = true;
|
||||
|
||||
if (this->allowsLPD())
|
||||
{
|
||||
tr_lpdUninit(this);
|
||||
}
|
||||
lpd_.reset();
|
||||
|
||||
tr_dhtUninit(this);
|
||||
|
||||
|
@ -2082,16 +2131,11 @@ void tr_sessionSetLPDEnabled(tr_session* session, bool enabled)
|
|||
session,
|
||||
[session, enabled]()
|
||||
{
|
||||
if (session->allowsLPD())
|
||||
{
|
||||
tr_lpdUninit(session);
|
||||
}
|
||||
|
||||
session->lpd_.reset();
|
||||
session->is_lpd_enabled_ = enabled;
|
||||
|
||||
if (session->allowsLPD())
|
||||
if (enabled)
|
||||
{
|
||||
tr_lpdInit(session, &session->bind_ipv4.addr_);
|
||||
session->lpd_ = tr_lpd::create(session->lpd_mediator_, session->timerMaker(), session->eventBase());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#include "stats.h"
|
||||
#include "timer.h"
|
||||
#include "torrents.h"
|
||||
#include "tr-lpd.h"
|
||||
#include "web.h"
|
||||
|
||||
enum tr_auto_switch_state_t
|
||||
|
@ -53,6 +54,7 @@ struct evdns_base;
|
|||
|
||||
class tr_rpc_server;
|
||||
class tr_web;
|
||||
class tr_lpd;
|
||||
struct BlocklistFile;
|
||||
struct struct_utp_context;
|
||||
struct tr_announcer;
|
||||
|
@ -503,6 +505,7 @@ public:
|
|||
std::unique_ptr<Cache> cache;
|
||||
|
||||
std::unique_ptr<tr_web> web;
|
||||
std::unique_ptr<tr_lpd> lpd_;
|
||||
|
||||
struct tr_announcer* announcer = nullptr;
|
||||
struct tr_announcer_udp* announcer_udp = nullptr;
|
||||
|
@ -839,6 +842,37 @@ private:
|
|||
|
||||
WebMediator web_mediator_{ this };
|
||||
|
||||
class LpdMediator final : public tr_lpd::Mediator
|
||||
{
|
||||
public:
|
||||
explicit LpdMediator(tr_session& session)
|
||||
: session_{ session }
|
||||
{
|
||||
}
|
||||
~LpdMediator() override = default;
|
||||
|
||||
[[nodiscard]] tr_port port() const override
|
||||
{
|
||||
return session_.peerPort();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool allowsLPD() const override
|
||||
{
|
||||
return session_.allowsLPD();
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<TorrentInfo> torrents() const override;
|
||||
|
||||
bool onPeerFound(std::string_view info_hash_str, tr_address address, tr_port port) override;
|
||||
|
||||
void setNextAnnounceTime(std::string_view info_hash_str, time_t announce_after) override;
|
||||
|
||||
private:
|
||||
tr_session& session_;
|
||||
};
|
||||
|
||||
LpdMediator lpd_mediator_{ *this };
|
||||
|
||||
std::shared_ptr<event_base> const event_base_;
|
||||
std::shared_ptr<evdns_base> const evdns_base_;
|
||||
std::unique_ptr<libtransmission::TimerMaker> const timer_maker_;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include "transmission.h"
|
||||
|
||||
#include "file.h"
|
||||
#include "stats.h"
|
||||
#include "tr-strbuf.h"
|
||||
#include "utils.h" // for tr_getRatio(), tr_time()
|
||||
|
@ -18,14 +19,14 @@ tr_session_stats tr_stats::loadOldStats(std::string_view config_dir)
|
|||
|
||||
auto top = tr_variant{};
|
||||
auto filename = tr_pathbuf{ config_dir, "/stats.json"sv };
|
||||
bool loaded = tr_variantFromFile(&top, TR_VARIANT_PARSE_JSON, filename.sv(), nullptr);
|
||||
bool loaded = tr_sys_path_exists(filename) && tr_variantFromFile(&top, TR_VARIANT_PARSE_JSON, filename.sv(), nullptr);
|
||||
|
||||
if (!loaded)
|
||||
{
|
||||
// maybe the user just upgraded from an old version of Transmission
|
||||
// that was still using stats.benc
|
||||
filename.assign(config_dir, "/stats.benc");
|
||||
loaded = tr_variantFromFile(&top, TR_VARIANT_PARSE_BENC, filename.sv(), nullptr);
|
||||
loaded = tr_sys_path_exists(filename) && tr_variantFromFile(&top, TR_VARIANT_PARSE_BENC, filename.sv(), nullptr);
|
||||
}
|
||||
|
||||
if (loaded)
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -8,9 +8,48 @@
|
|||
#error only libtransmission should #include this header.
|
||||
#endif
|
||||
|
||||
int tr_lpdInit(tr_session*, tr_address*);
|
||||
void tr_lpdUninit(tr_session*);
|
||||
bool tr_lpdSendAnnounce(tr_torrent const*);
|
||||
#include <ctime>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* @} */
|
||||
#include "transmission.h"
|
||||
|
||||
#include "net.h" // for tr_address, tr_port
|
||||
#include "timer.h"
|
||||
|
||||
class tr_torrents;
|
||||
struct tr_session;
|
||||
struct event_base;
|
||||
|
||||
class tr_lpd
|
||||
{
|
||||
public:
|
||||
class Mediator
|
||||
{
|
||||
public:
|
||||
struct TorrentInfo
|
||||
{
|
||||
std::string_view info_hash_str;
|
||||
tr_torrent_activity activity;
|
||||
bool allows_lpd;
|
||||
time_t announce_after;
|
||||
};
|
||||
|
||||
virtual ~Mediator() = default;
|
||||
|
||||
[[nodiscard]] virtual tr_port port() const = 0;
|
||||
|
||||
[[nodiscard]] virtual bool allowsLPD() const = 0;
|
||||
|
||||
[[nodiscard]] virtual std::vector<TorrentInfo> torrents() const = 0;
|
||||
|
||||
virtual void setNextAnnounceTime(std::string_view info_hash_str, time_t announce_at) = 0;
|
||||
|
||||
// returns true if info was used
|
||||
virtual bool onPeerFound(std::string_view info_hash_str, tr_address address, tr_port port) = 0;
|
||||
};
|
||||
|
||||
virtual ~tr_lpd() = default;
|
||||
static std::unique_ptr<tr_lpd> create(Mediator& mediator, libtransmission::TimerMaker&, event_base* event_base);
|
||||
};
|
||||
|
|
|
@ -1135,6 +1135,7 @@ template std::optional<char> tr_parseNum(std::string_view& sv, int base);
|
|||
template std::optional<unsigned long long> tr_parseNum(std::string_view& sv, int base);
|
||||
template std::optional<unsigned long> tr_parseNum(std::string_view& sv, int base);
|
||||
template std::optional<unsigned int> tr_parseNum(std::string_view& sv, int base);
|
||||
template std::optional<unsigned short> tr_parseNum(std::string_view& sv, int base);
|
||||
template std::optional<unsigned char> tr_parseNum(std::string_view& sv, int base);
|
||||
|
||||
template<typename T, std::enable_if_t<std::is_floating_point<T>::value, bool>>
|
||||
|
|
|
@ -17,6 +17,7 @@ add_executable(libtransmission-test
|
|||
handshake-test.cc
|
||||
history-test.cc
|
||||
json-test.cc
|
||||
lpd-test.cc
|
||||
magnet-metainfo-test.cc
|
||||
makemeta-test.cc
|
||||
move-test.cc
|
||||
|
|
202
tests/libtransmission/lpd-test.cc
Normal file
202
tests/libtransmission/lpd-test.cc
Normal file
|
@ -0,0 +1,202 @@
|
|||
// This file Copyright (C) 2022 Mnemosyne LLC.
|
||||
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
|
||||
// or any future license endorsed by Mnemosyne LLC.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#include <chrono>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "transmission.h"
|
||||
|
||||
#include "crypto-utils.h"
|
||||
#include "session.h"
|
||||
#include "tr-lpd.h"
|
||||
|
||||
#include "test-fixtures.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace libtransmission
|
||||
{
|
||||
namespace test
|
||||
{
|
||||
|
||||
using LpdTest = SessionTest;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
class MyMediator final : public tr_lpd::Mediator
|
||||
{
|
||||
public:
|
||||
MyMediator() = default;
|
||||
~MyMediator() override = default;
|
||||
|
||||
[[nodiscard]] tr_port port() const override
|
||||
{
|
||||
return port_;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool allowsLPD() const override
|
||||
{
|
||||
return allows_lpd_;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<TorrentInfo> torrents() const override
|
||||
{
|
||||
return torrents_;
|
||||
}
|
||||
|
||||
void setNextAnnounceTime(std::string_view info_hash_str, time_t announce_after) override
|
||||
{
|
||||
for (auto& tor : torrents_)
|
||||
{
|
||||
if (tor.info_hash_str == info_hash_str)
|
||||
{
|
||||
tor.announce_after = announce_after;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool onPeerFound(std::string_view info_hash_str, tr_address /*address*/, tr_port /*port*/) override
|
||||
{
|
||||
found_.insert(std::string{ info_hash_str });
|
||||
return found_returns_;
|
||||
}
|
||||
|
||||
tr_port port_ = tr_port::fromHost(51413);
|
||||
bool allows_lpd_ = true;
|
||||
std::vector<TorrentInfo> torrents_;
|
||||
std::set<std::string> found_;
|
||||
bool found_returns_ = true;
|
||||
};
|
||||
|
||||
auto makeRandomHash()
|
||||
{
|
||||
auto buf = std::array<char, 256>{};
|
||||
tr_rand_buffer(std::data(buf), std::size(buf));
|
||||
return tr_sha1::digest(buf);
|
||||
}
|
||||
|
||||
auto makeRandomHashString()
|
||||
{
|
||||
return tr_strupper(tr_sha1_to_string(makeRandomHash()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_F(LpdTest, HelloWorld)
|
||||
{
|
||||
auto mediator = MyMediator{};
|
||||
auto lpd = tr_lpd::create(mediator, session_->timerMaker(), session_->eventBase());
|
||||
EXPECT_TRUE(lpd);
|
||||
EXPECT_EQ(0U, std::size(mediator.found_));
|
||||
}
|
||||
|
||||
TEST_F(LpdTest, CanAnnounceAndRead)
|
||||
{
|
||||
auto mediator_a = MyMediator{};
|
||||
auto lpd_a = tr_lpd::create(mediator_a, session_->timerMaker(), session_->eventBase());
|
||||
EXPECT_TRUE(lpd_a);
|
||||
|
||||
auto const info_hash_str = makeRandomHashString();
|
||||
auto info = tr_lpd::Mediator::TorrentInfo{};
|
||||
info.info_hash_str = info_hash_str;
|
||||
info.activity = TR_STATUS_SEED;
|
||||
info.allows_lpd = true;
|
||||
info.announce_after = 0; // never announced
|
||||
|
||||
auto mediator_b = MyMediator{};
|
||||
mediator_b.torrents_.push_back(info);
|
||||
auto lpd_b = tr_lpd::create(mediator_b, session_->timerMaker(), session_->eventBase());
|
||||
|
||||
waitFor([&mediator_a]() { return !std::empty(mediator_a.found_); }, 1s);
|
||||
EXPECT_EQ(1U, mediator_a.found_.count(info_hash_str));
|
||||
EXPECT_EQ(0U, mediator_b.found_.count(info_hash_str));
|
||||
}
|
||||
|
||||
TEST_F(LpdTest, canMultiAnnounce)
|
||||
{
|
||||
auto mediator_a = MyMediator{};
|
||||
auto lpd_a = tr_lpd::create(mediator_a, session_->timerMaker(), session_->eventBase());
|
||||
EXPECT_TRUE(lpd_a);
|
||||
|
||||
auto info_hash_strings = std::array<std::string, 2>{};
|
||||
auto infos = std::array<tr_lpd::Mediator::TorrentInfo, 2>{};
|
||||
auto mediator_b = MyMediator{};
|
||||
for (size_t i = 0; i < std::size(info_hash_strings); ++i)
|
||||
{
|
||||
auto& info_hash_string = info_hash_strings[i];
|
||||
auto& info = infos[i];
|
||||
|
||||
info_hash_string = makeRandomHashString();
|
||||
|
||||
info.info_hash_str = info_hash_string;
|
||||
info.activity = TR_STATUS_SEED;
|
||||
info.allows_lpd = true;
|
||||
info.announce_after = 0; // never announced
|
||||
}
|
||||
|
||||
for (auto const& info : infos)
|
||||
{
|
||||
mediator_b.torrents_.push_back(info);
|
||||
}
|
||||
|
||||
auto lpd_b = tr_lpd::create(mediator_b, session_->timerMaker(), session_->eventBase());
|
||||
waitFor([&mediator_a]() { return !std::empty(mediator_a.found_); }, 1s);
|
||||
|
||||
for (auto const& info : infos)
|
||||
{
|
||||
EXPECT_EQ(1U, mediator_a.found_.count(std::string{ info.info_hash_str }));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(LpdTest, DoesNotReannounceTooSoon)
|
||||
{
|
||||
auto mediator_a = MyMediator{};
|
||||
auto lpd_a = tr_lpd::create(mediator_a, session_->timerMaker(), session_->eventBase());
|
||||
EXPECT_TRUE(lpd_a);
|
||||
|
||||
// similar to canMultiAnnounce...
|
||||
auto info_hash_strings = std::array<std::string, 2>{};
|
||||
auto infos = std::array<tr_lpd::Mediator::TorrentInfo, 2>{};
|
||||
auto mediator_b = MyMediator{};
|
||||
for (size_t i = 0; i < std::size(info_hash_strings); ++i)
|
||||
{
|
||||
auto& info_hash_string = info_hash_strings[i];
|
||||
auto& info = infos[i];
|
||||
|
||||
info_hash_string = makeRandomHashString();
|
||||
|
||||
info.info_hash_str = info_hash_string;
|
||||
info.activity = TR_STATUS_SEED;
|
||||
info.allows_lpd = true;
|
||||
info.announce_after = 0; // never announced
|
||||
}
|
||||
|
||||
// ...except one torrent has already been announced
|
||||
// and doesn't need to be reannounced until later
|
||||
auto const now = time(nullptr);
|
||||
infos[0].announce_after = now + 60;
|
||||
|
||||
for (auto const& info : infos)
|
||||
{
|
||||
mediator_b.torrents_.push_back(info);
|
||||
}
|
||||
|
||||
auto lpd_b = tr_lpd::create(mediator_b, session_->timerMaker(), session_->eventBase());
|
||||
waitFor([&mediator_a]() { return !std::empty(mediator_a.found_); }, 1s);
|
||||
|
||||
for (auto& info : infos)
|
||||
{
|
||||
auto const expected_count = info.announce_after <= now ? 1U : 0U;
|
||||
EXPECT_EQ(expected_count, mediator_a.found_.count(std::string{ info.info_hash_str }));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace libtransmission
|
|
@ -33,7 +33,7 @@ class RenameTest : public SessionTest
|
|||
static auto constexpr MaxWaitMsec = 3000;
|
||||
|
||||
protected:
|
||||
void torrentRemoveAndWait(tr_torrent* tor, int expected_torrent_count)
|
||||
void torrentRemoveAndWait(tr_torrent* tor, size_t expected_torrent_count)
|
||||
{
|
||||
tr_torrentRemove(tor, false, nullptr);
|
||||
auto const test = [this, expected_torrent_count]()
|
||||
|
|
|
@ -71,10 +71,9 @@ static void depthFirstWalk(char const* path, file_func_t func)
|
|||
func(path);
|
||||
}
|
||||
|
||||
inline bool waitFor(std::function<bool()> const& test, int msec)
|
||||
inline bool waitFor(std::function<bool()> const& test, std::chrono::milliseconds msec)
|
||||
{
|
||||
auto const deadline = std::chrono::milliseconds{ msec };
|
||||
auto const begin = std::chrono::steady_clock::now();
|
||||
auto const deadline = std::chrono::steady_clock::now() + msec;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
|
@ -83,7 +82,7 @@ inline bool waitFor(std::function<bool()> const& test, int msec)
|
|||
return true;
|
||||
}
|
||||
|
||||
if ((std::chrono::steady_clock::now() - begin) >= deadline)
|
||||
if (std::chrono::steady_clock::now() > deadline)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -92,6 +91,11 @@ inline bool waitFor(std::function<bool()> const& test, int msec)
|
|||
}
|
||||
}
|
||||
|
||||
inline bool waitFor(std::function<bool()> const& test, int msec)
|
||||
{
|
||||
return waitFor(test, std::chrono::milliseconds{ msec });
|
||||
}
|
||||
|
||||
class Sandbox
|
||||
{
|
||||
public:
|
||||
|
|
Loading…
Add table
Reference in a new issue