feat: periodically refresh dht node id (#6695)
This commit is contained in:
parent
1edd9193a3
commit
2548dd478f
|
@ -148,6 +148,7 @@ auto constexpr MyStatic = std::array<std::string_view, TR_N_KEYS>{
|
|||
"honorsSessionLimits"sv,
|
||||
"host"sv,
|
||||
"id"sv,
|
||||
"id_timestamp"sv,
|
||||
"idle-limit"sv,
|
||||
"idle-mode"sv,
|
||||
"idle-seeding-limit"sv,
|
||||
|
|
|
@ -149,6 +149,7 @@ enum
|
|||
TR_KEY_honorsSessionLimits,
|
||||
TR_KEY_host,
|
||||
TR_KEY_id,
|
||||
TR_KEY_id_timestamp,
|
||||
TR_KEY_idle_limit,
|
||||
TR_KEY_idle_mode,
|
||||
TR_KEY_idle_seeding_limit,
|
||||
|
|
|
@ -140,7 +140,7 @@ public:
|
|||
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
|
||||
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_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_variant benc;
|
||||
tr_variantInitDict(&benc, 3);
|
||||
tr_variantInitDict(&benc, 4);
|
||||
tr_variantDictAddRaw(&benc, TR_KEY_id, std::data(id_), std::size(id_));
|
||||
tr_variantDictAddInt(&benc, TR_KEY_id_timestamp, id_timestamp_);
|
||||
|
||||
if (num4 > 0)
|
||||
{
|
||||
|
@ -462,32 +463,38 @@ private:
|
|||
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,
|
||||
// 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)))
|
||||
{
|
||||
return { id, {} };
|
||||
return;
|
||||
}
|
||||
|
||||
auto otop = tr_variant_serde::benc().parse_file(filename);
|
||||
if (!otop)
|
||||
{
|
||||
return { id, {} };
|
||||
return;
|
||||
}
|
||||
|
||||
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 IdTtl = time_t{ 30 * 24 * 60 * 60 }; // 30 days
|
||||
|
||||
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;
|
||||
|
@ -502,7 +509,7 @@ private:
|
|||
auto port = tr_port{};
|
||||
std::tie(addr, walk) = tr_address::from_compact_ipv4(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{};
|
||||
std::tie(addr, walk) = tr_address::from_compact_ipv6(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_;
|
||||
|
||||
Id id_ = {};
|
||||
int64_t id_timestamp_ = {};
|
||||
|
||||
Nodes bootstrap_queue_;
|
||||
size_t n_bootstrapped_ = 0;
|
||||
|
|
|
@ -85,6 +85,7 @@ protected:
|
|||
// Fake data to be written to the test state file
|
||||
|
||||
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) },
|
||||
{ *tr_address::from_string("10.10.10.2"), tr_port::from_host(129) },
|
||||
|
@ -128,6 +129,7 @@ protected:
|
|||
auto dict = tr_variant{};
|
||||
tr_variantInitDict(&dict, 3U);
|
||||
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>{};
|
||||
for (auto const& socket_address : ipv4_nodes_)
|
||||
{
|
||||
|
@ -262,6 +264,7 @@ protected:
|
|||
std::vector<Pinged> pinged_;
|
||||
std::vector<Searched> searched_;
|
||||
std::array<char, IdLength> id_ = {};
|
||||
int64_t id_timestamp_ = {};
|
||||
tr_socket_t dht_socket_ = TR_BAD_SOCKET;
|
||||
tr_socket_t dht_socket6_ = TR_BAD_SOCKET;
|
||||
};
|
||||
|
@ -476,6 +479,8 @@ TEST_F(DhtTest, loadsStateFromStateFile)
|
|||
auto const state_file = MockStateFile{};
|
||||
state_file.save(sandboxDir());
|
||||
|
||||
tr_timeUpdate(time(nullptr));
|
||||
|
||||
// Make the DHT
|
||||
auto mediator = MockMediator{ event_base_ };
|
||||
mediator.config_dir_ = sandboxDir();
|
||||
|
@ -501,6 +506,41 @@ TEST_F(DhtTest, loadsStateFromStateFile)
|
|||
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)
|
||||
{
|
||||
auto const state_file = MockStateFile{};
|
||||
|
|
Loading…
Reference in New Issue