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,
|
"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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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{};
|
||||||
|
|
Loading…
Reference in New Issue