2021-06-06 12:32:23 +00:00
|
|
|
|
|
|
|
#include "reachability_testing.hpp"
|
|
|
|
#include <chrono>
|
|
|
|
#include <llarp/router/abstractrouter.hpp>
|
|
|
|
#include <llarp/util/logging/logger.hpp>
|
|
|
|
#include <llarp/crypto/crypto.hpp>
|
|
|
|
|
|
|
|
using std::chrono::steady_clock;
|
|
|
|
|
|
|
|
namespace llarp::consensus
|
|
|
|
{
|
|
|
|
using fseconds = std::chrono::duration<float, std::chrono::seconds::period>;
|
|
|
|
using fminutes = std::chrono::duration<float, std::chrono::minutes::period>;
|
|
|
|
|
|
|
|
static void
|
|
|
|
check_incoming_tests_impl(
|
|
|
|
std::string_view name,
|
|
|
|
const time_point_t& now,
|
|
|
|
const time_point_t& startup,
|
|
|
|
detail::incoming_test_state& incoming)
|
|
|
|
{
|
|
|
|
const auto elapsed = now - std::max(startup, incoming.last_test);
|
|
|
|
bool failing = elapsed > reachability_testing::MAX_TIME_WITHOUT_PING;
|
|
|
|
bool whine = failing != incoming.was_failing
|
|
|
|
|| (failing && now - incoming.last_whine > reachability_testing::WHINING_INTERVAL);
|
|
|
|
|
|
|
|
incoming.was_failing = failing;
|
|
|
|
|
|
|
|
if (whine)
|
|
|
|
{
|
|
|
|
incoming.last_whine = now;
|
|
|
|
if (!failing)
|
|
|
|
{
|
|
|
|
LogInfo(name, " ping received; port is likely reachable again");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (incoming.last_test.time_since_epoch() == 0s)
|
|
|
|
{
|
|
|
|
LogWarn("Have NEVER received ", name, " pings!");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
LogWarn(
|
|
|
|
"Have not received ",
|
|
|
|
name,
|
|
|
|
" pings for a long time: ",
|
|
|
|
fminutes{elapsed}.count(),
|
|
|
|
" minutes");
|
|
|
|
}
|
|
|
|
LogWarn(
|
|
|
|
"Please check your ",
|
|
|
|
name,
|
|
|
|
" port. Not being reachable "
|
|
|
|
"over ",
|
|
|
|
name,
|
|
|
|
" may result in a deregistration!");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
reachability_testing::check_incoming_tests(const time_point_t& now)
|
|
|
|
{
|
|
|
|
check_incoming_tests_impl("lokinet", now, startup, last);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
reachability_testing::incoming_ping(const time_point_t& now)
|
|
|
|
{
|
|
|
|
last.last_test = now;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<RouterID>
|
|
|
|
reachability_testing::next_random(AbstractRouter* router, const time_point_t& now, bool requeue)
|
|
|
|
{
|
|
|
|
if (next_general_test > now)
|
|
|
|
return std::nullopt;
|
2021-06-07 12:39:51 +00:00
|
|
|
CSRNG rng;
|
2021-06-07 14:57:33 +00:00
|
|
|
next_general_test =
|
|
|
|
now + std::chrono::duration_cast<time_point_t::duration>(fseconds(TESTING_INTERVAL(rng)));
|
2021-06-06 12:32:23 +00:00
|
|
|
|
|
|
|
// Pull the next element off the queue, but skip ourself, any that are no longer registered, and
|
|
|
|
// any that are currently known to be failing (those are queued for testing separately).
|
|
|
|
RouterID my_pk{router->pubkey()};
|
|
|
|
while (!testing_queue.empty())
|
|
|
|
{
|
|
|
|
auto& pk = testing_queue.back();
|
|
|
|
std::optional<RouterID> sn;
|
|
|
|
if (pk != my_pk && !failing.count(pk))
|
|
|
|
sn = pk;
|
|
|
|
testing_queue.pop_back();
|
|
|
|
if (sn)
|
|
|
|
return sn;
|
|
|
|
}
|
|
|
|
if (!requeue)
|
|
|
|
return std::nullopt;
|
|
|
|
|
|
|
|
// FIXME: when a *new* node comes online we need to inject it into a random position in the SN
|
|
|
|
// list with probability (L/N) [L = current list size, N = potential list size]
|
|
|
|
//
|
|
|
|
// (FIXME: put this FIXME in a better place ;-) )
|
|
|
|
|
|
|
|
// We exhausted the queue so repopulate it and try again
|
|
|
|
|
|
|
|
testing_queue.clear();
|
|
|
|
const auto all = router->GetRouterWhitelist();
|
|
|
|
testing_queue.insert(testing_queue.begin(), all.begin(), all.end());
|
|
|
|
|
2021-06-07 12:39:51 +00:00
|
|
|
std::shuffle(testing_queue.begin(), testing_queue.end(), rng);
|
2021-06-06 12:32:23 +00:00
|
|
|
|
|
|
|
// Recurse with the rebuild list, but don't let it try rebuilding again
|
|
|
|
return next_random(router, now, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<std::pair<RouterID, int>>
|
2021-06-08 14:47:27 +00:00
|
|
|
reachability_testing::get_failing(const time_point_t& now)
|
2021-06-06 12:32:23 +00:00
|
|
|
{
|
|
|
|
// Our failing_queue puts the oldest retest times at the top, so pop them off into our result
|
|
|
|
// until the top node should be retested sometime in the future
|
|
|
|
std::vector<std::pair<RouterID, int>> result;
|
|
|
|
while (result.size() < MAX_RETESTS_PER_TICK && !failing_queue.empty())
|
|
|
|
{
|
|
|
|
auto& [pk, retest_time, failures] = failing_queue.top();
|
|
|
|
if (retest_time > now)
|
|
|
|
break;
|
|
|
|
result.emplace_back(pk, failures);
|
|
|
|
failing_queue.pop();
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
reachability_testing::add_failing_node(const RouterID& pk, int previous_failures)
|
|
|
|
{
|
|
|
|
using namespace std::chrono;
|
|
|
|
|
|
|
|
if (previous_failures < 0)
|
|
|
|
previous_failures = 0;
|
2021-06-07 12:39:51 +00:00
|
|
|
CSRNG rng;
|
2021-06-06 12:32:23 +00:00
|
|
|
auto next_test_in = duration_cast<time_point_t::duration>(
|
2021-06-07 12:39:51 +00:00
|
|
|
previous_failures * TESTING_BACKOFF + fseconds{TESTING_INTERVAL(rng)});
|
2021-06-06 12:32:23 +00:00
|
|
|
if (next_test_in > TESTING_BACKOFF_MAX)
|
|
|
|
next_test_in = TESTING_BACKOFF_MAX;
|
|
|
|
|
2021-06-07 19:00:05 +00:00
|
|
|
failing.insert(pk);
|
2021-06-06 12:32:23 +00:00
|
|
|
failing_queue.emplace(pk, steady_clock::now() + next_test_in, previous_failures + 1);
|
|
|
|
}
|
|
|
|
|
2021-06-08 14:47:27 +00:00
|
|
|
void
|
|
|
|
reachability_testing::remove_node_from_failing(const RouterID& pk)
|
|
|
|
{
|
|
|
|
failing.erase(pk);
|
|
|
|
}
|
|
|
|
|
2021-06-06 12:32:23 +00:00
|
|
|
} // namespace llarp::consensus
|