feat: periodically refresh dht node id (#6695)

This commit is contained in:
Yat Ho 2024-03-18 07:24:36 +08:00 committed by GitHub
parent 1edd9193a3
commit 2548dd478f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 61 additions and 13 deletions

View File

@ -148,6 +148,7 @@ auto constexpr MyStatic = std::array<std::string_view, TR_N_KEYS>{
"honorsSessionLimits"sv, "honorsSessionLimits"sv,
"host"sv, "host"sv,
"id"sv, "id"sv,
"id_timestamp"sv,
"idle-limit"sv, "idle-limit"sv,
"idle-mode"sv, "idle-mode"sv,
"idle-seeding-limit"sv, "idle-seeding-limit"sv,

View File

@ -149,6 +149,7 @@ enum
TR_KEY_honorsSessionLimits, TR_KEY_honorsSessionLimits,
TR_KEY_host, TR_KEY_host,
TR_KEY_id, TR_KEY_id,
TR_KEY_id_timestamp,
TR_KEY_idle_limit, TR_KEY_idle_limit,
TR_KEY_idle_mode, TR_KEY_idle_mode,
TR_KEY_idle_seeding_limit, TR_KEY_idle_seeding_limit,

View File

@ -140,7 +140,7 @@ public:
tr_logAddDebug(fmt::format("Starting DHT on port {port}", fmt::arg("port", peer_port.host()))); tr_logAddDebug(fmt::format("Starting DHT on port {port}", fmt::arg("port", peer_port.host())));
// init state from scratch, or load from state file if it exists // init state from scratch, or load from state file if it exists
std::tie(id_, bootstrap_queue_) = init_state(state_filename_); init_state(state_filename_);
get_nodes_from_bootstrap_file(tr_pathbuf{ mediator_.config_dir(), "/dht.bootstrap"sv }, bootstrap_queue_); get_nodes_from_bootstrap_file(tr_pathbuf{ mediator_.config_dir(), "/dht.bootstrap"sv }, bootstrap_queue_);
get_nodes_from_name("dht.transmissionbt.com", tr_port::from_host(6881), bootstrap_queue_); get_nodes_from_name("dht.transmissionbt.com", tr_port::from_host(6881), bootstrap_queue_);
@ -426,8 +426,9 @@ private:
tr_logAddTrace(fmt::format("Saving {} ({} + {}) nodes", n, num4, num6)); tr_logAddTrace(fmt::format("Saving {} ({} + {}) nodes", n, num4, num6));
tr_variant benc; tr_variant benc;
tr_variantInitDict(&benc, 3); tr_variantInitDict(&benc, 4);
tr_variantDictAddRaw(&benc, TR_KEY_id, std::data(id_), std::size(id_)); tr_variantDictAddRaw(&benc, TR_KEY_id, std::data(id_), std::size(id_));
tr_variantDictAddInt(&benc, TR_KEY_id_timestamp, id_timestamp_);
if (num4 > 0) if (num4 > 0)
{ {
@ -462,32 +463,38 @@ private:
tr_variant_serde::benc().to_file(benc, state_filename_); tr_variant_serde::benc().to_file(benc, state_filename_);
} }
[[nodiscard]] static std::pair<Id, Nodes> init_state(std::string_view filename) void init_state(std::string_view filename)
{ {
// Note that DHT ids need to be distributed uniformly, // Note that DHT ids need to be distributed uniformly,
// so it should be something truly random // so it should be something truly random
auto id = tr_rand_obj<Id>(); id_ = tr_rand_obj<Id>();
id_timestamp_ = tr_time();
if (!tr_sys_path_exists(std::data(filename))) if (!tr_sys_path_exists(std::data(filename)))
{ {
return { id, {} }; return;
} }
auto otop = tr_variant_serde::benc().parse_file(filename); auto otop = tr_variant_serde::benc().parse_file(filename);
if (!otop) if (!otop)
{ {
return { id, {} }; return;
} }
static auto constexpr CompactLen = tr_socket_address::CompactSockAddrBytes[TR_AF_INET]; static auto constexpr CompactLen = tr_socket_address::CompactSockAddrBytes[TR_AF_INET];
static auto constexpr Compact6Len = tr_socket_address::CompactSockAddrBytes[TR_AF_INET6]; static auto constexpr Compact6Len = tr_socket_address::CompactSockAddrBytes[TR_AF_INET6];
static auto constexpr IdTtl = time_t{ 30 * 24 * 60 * 60 }; // 30 days
auto& top = *otop; auto& top = *otop;
auto nodes = Nodes{};
if (auto sv = std::string_view{}; tr_variantDictFindStrView(&top, TR_KEY_id, &sv) && std::size(sv) == std::size(id)) if (auto t = int64_t{}; tr_variantDictFindInt(&top, TR_KEY_id_timestamp, &t) && t + IdTtl > id_timestamp_)
{ {
std::copy(std::begin(sv), std::end(sv), std::begin(id)); if (auto sv = std::string_view{};
tr_variantDictFindStrView(&top, TR_KEY_id, &sv) && std::size(sv) == std::size(id_))
{
id_timestamp_ = t;
std::copy(std::begin(sv), std::end(sv), std::begin(id_));
}
} }
size_t raw_len = 0U; size_t raw_len = 0U;
@ -502,7 +509,7 @@ private:
auto port = tr_port{}; auto port = tr_port{};
std::tie(addr, walk) = tr_address::from_compact_ipv4(walk); std::tie(addr, walk) = tr_address::from_compact_ipv4(walk);
std::tie(port, walk) = tr_port::from_compact(walk); std::tie(port, walk) = tr_port::from_compact(walk);
nodes.emplace_back(addr, port); bootstrap_queue_.emplace_back(addr, port);
} }
} }
@ -516,11 +523,9 @@ private:
auto port = tr_port{}; auto port = tr_port{};
std::tie(addr, walk) = tr_address::from_compact_ipv6(walk); std::tie(addr, walk) = tr_address::from_compact_ipv6(walk);
std::tie(port, walk) = tr_port::from_compact(walk); std::tie(port, walk) = tr_port::from_compact(walk);
nodes.emplace_back(addr, port); bootstrap_queue_.emplace_back(addr, port);
} }
} }
return { id, std::move(nodes) };
} }
/// ///
@ -601,6 +606,7 @@ private:
std::unique_ptr<libtransmission::Timer> const periodic_timer_; std::unique_ptr<libtransmission::Timer> const periodic_timer_;
Id id_ = {}; Id id_ = {};
int64_t id_timestamp_ = {};
Nodes bootstrap_queue_; Nodes bootstrap_queue_;
size_t n_bootstrapped_ = 0; size_t n_bootstrapped_ = 0;

View File

@ -85,6 +85,7 @@ protected:
// Fake data to be written to the test state file // Fake data to be written to the test state file
std::array<char, IdLength> const id_ = tr_rand_obj<std::array<char, IdLength>>(); std::array<char, IdLength> const id_ = tr_rand_obj<std::array<char, IdLength>>();
int64_t id_timestamp_ = std::time(nullptr);
std::vector<tr_socket_address> ipv4_nodes_ = { { *tr_address::from_string("10.10.10.1"), tr_port::from_host(128) }, std::vector<tr_socket_address> ipv4_nodes_ = { { *tr_address::from_string("10.10.10.1"), tr_port::from_host(128) },
{ *tr_address::from_string("10.10.10.2"), tr_port::from_host(129) }, { *tr_address::from_string("10.10.10.2"), tr_port::from_host(129) },
@ -128,6 +129,7 @@ protected:
auto dict = tr_variant{}; auto dict = tr_variant{};
tr_variantInitDict(&dict, 3U); tr_variantInitDict(&dict, 3U);
tr_variantDictAddRaw(&dict, TR_KEY_id, std::data(id_), std::size(id_)); tr_variantDictAddRaw(&dict, TR_KEY_id, std::data(id_), std::size(id_));
tr_variantDictAddInt(&dict, TR_KEY_id_timestamp, id_timestamp_);
auto compact = std::vector<std::byte>{}; auto compact = std::vector<std::byte>{};
for (auto const& socket_address : ipv4_nodes_) for (auto const& socket_address : ipv4_nodes_)
{ {
@ -262,6 +264,7 @@ protected:
std::vector<Pinged> pinged_; std::vector<Pinged> pinged_;
std::vector<Searched> searched_; std::vector<Searched> searched_;
std::array<char, IdLength> id_ = {}; std::array<char, IdLength> id_ = {};
int64_t id_timestamp_ = {};
tr_socket_t dht_socket_ = TR_BAD_SOCKET; tr_socket_t dht_socket_ = TR_BAD_SOCKET;
tr_socket_t dht_socket6_ = TR_BAD_SOCKET; tr_socket_t dht_socket6_ = TR_BAD_SOCKET;
}; };
@ -476,6 +479,8 @@ TEST_F(DhtTest, loadsStateFromStateFile)
auto const state_file = MockStateFile{}; auto const state_file = MockStateFile{};
state_file.save(sandboxDir()); state_file.save(sandboxDir());
tr_timeUpdate(time(nullptr));
// Make the DHT // Make the DHT
auto mediator = MockMediator{ event_base_ }; auto mediator = MockMediator{ event_base_ };
mediator.config_dir_ = sandboxDir(); mediator.config_dir_ = sandboxDir();
@ -501,6 +506,41 @@ TEST_F(DhtTest, loadsStateFromStateFile)
EXPECT_EQ(state_file.nodesString(), actual_nodes_str); EXPECT_EQ(state_file.nodesString(), actual_nodes_str);
} }
TEST_F(DhtTest, loadsStateFromStateFileExpiredId)
{
auto state_file = MockStateFile{};
state_file.id_timestamp_ = 0;
state_file.save(sandboxDir());
tr_timeUpdate(time(nullptr));
// Make the DHT
auto mediator = MockMediator{ event_base_ };
mediator.config_dir_ = sandboxDir();
auto dht = tr_dht::create(mediator, ArbitraryPeerPort, ArbitrarySock4, ArbitrarySock6);
// Wait for all the state nodes to be pinged
auto& pinged = mediator.mock_dht_.pinged_;
auto const n_expected_nodes = std::size(state_file.ipv4_nodes_) + std::size(state_file.ipv6_nodes_);
waitFor(event_base_, [&pinged, n_expected_nodes]() { return std::size(pinged) >= n_expected_nodes; });
auto actual_nodes_str = std::string{};
for (auto const& [addrport, timestamp] : pinged)
{
actual_nodes_str += addrport.display_name();
actual_nodes_str += ',';
}
/// Confirm that the state was loaded
// dht_init() should have been called with the state file's id
// N.B. There is a minuscule chance for this to fail, this is
// normal because id generation is random
EXPECT_NE(state_file.id_, mediator.mock_dht_.id_);
// dht_ping_nodedht_init() should have been called with state file's nodes
EXPECT_EQ(state_file.nodesString(), actual_nodes_str);
}
TEST_F(DhtTest, stopsBootstrappingWhenSwarmHealthIsGoodEnough) TEST_F(DhtTest, stopsBootstrappingWhenSwarmHealthIsGoodEnough)
{ {
auto const state_file = MockStateFile{}; auto const state_file = MockStateFile{};