1
0
Fork 0
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:
Charles Kerr 2022-08-24 16:03:30 -05:00 committed by GitHub
parent ab09718342
commit e8686095ed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 900 additions and 633 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -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());
}
});
}

View file

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

View file

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

View file

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

View file

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

View file

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

View 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

View file

@ -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]()

View file

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