1
1
Fork 0
mirror of https://github.com/oxen-io/lokinet synced 2023-12-14 06:53:00 +01:00

Merge pull request #1318 from notlesh/peer-stats-follow-up-2020-07-09

Peer stats follow up 2020 07 09
This commit is contained in:
Jeff 2020-07-27 07:17:00 -04:00 committed by GitHub
commit 998d4c4ec3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
58 changed files with 2265 additions and 325 deletions

View file

@ -1,4 +1,4 @@
local default_deps_base='libsystemd-dev python3-dev libcurl4-openssl-dev libuv1-dev libunbound-dev nettle-dev libssl-dev libevent-dev'; local default_deps_base='libsystemd-dev python3-dev libcurl4-openssl-dev libuv1-dev libunbound-dev nettle-dev libssl-dev libevent-dev libsqlite3-dev';
local default_deps_nocxx='libsodium-dev ' + default_deps_base; // libsodium-dev needs to be >= 1.0.18 local default_deps_nocxx='libsodium-dev ' + default_deps_base; // libsodium-dev needs to be >= 1.0.18
local default_deps='g++ ' + default_deps_nocxx; // g++ sometimes needs replacement local default_deps='g++ ' + default_deps_nocxx; // g++ sometimes needs replacement
local default_windows_deps='mingw-w64-binutils mingw-w64-gcc mingw-w64-crt mingw-w64-headers mingw-w64-winpthreads perl openssh zip bash'; // deps for windows cross compile local default_windows_deps='mingw-w64-binutils mingw-w64-gcc mingw-w64-crt mingw-w64-headers mingw-w64-winpthreads perl openssh zip bash'; // deps for windows cross compile

3
.gitmodules vendored
View file

@ -31,3 +31,6 @@
path = external/loki-mq path = external/loki-mq
url = https://github.com/loki-project/loki-mq url = https://github.com/loki-project/loki-mq
branch = dev branch = dev
[submodule "external/sqlite_orm"]
path = external/sqlite_orm
url = https://github.com/fnc12/sqlite_orm

View file

@ -305,6 +305,7 @@ if(SUBMODULE_CHECK)
check_submodule(external/ghc-filesystem) check_submodule(external/ghc-filesystem)
check_submodule(external/date) check_submodule(external/date)
check_submodule(external/pybind11) check_submodule(external/pybind11)
check_submodule(external/sqlite_orm)
if (NOT WIN32) # we grab libuv for windows separately in win32-setup/libuv. see note in cmake/win32.cmake. if (NOT WIN32) # we grab libuv for windows separately in win32-setup/libuv. see note in cmake/win32.cmake.
check_submodule(external/libuv) check_submodule(external/libuv)
endif() endif()
@ -324,6 +325,8 @@ add_subdirectory(external/nlohmann EXCLUDE_FROM_ALL)
add_subdirectory(external/cxxopts EXCLUDE_FROM_ALL) add_subdirectory(external/cxxopts EXCLUDE_FROM_ALL)
add_subdirectory(external/date EXCLUDE_FROM_ALL) add_subdirectory(external/date EXCLUDE_FROM_ALL)
include_directories(SYSTEM external/sqlite_orm/include)
add_subdirectory(vendor) add_subdirectory(vendor)
if(ANDROID) if(ANDROID)

View file

@ -25,6 +25,13 @@ set(UNBOUND_SOURCE unbound-${UNBOUND_VERSION}.tar.gz)
set(UNBOUND_HASH SHA256=b73677c21a71cf92f15cc8cfe76a3d875e40f65b6150081c39620b286582d536 set(UNBOUND_HASH SHA256=b73677c21a71cf92f15cc8cfe76a3d875e40f65b6150081c39620b286582d536
CACHE STRING "unbound source hash") CACHE STRING "unbound source hash")
set(SQLITE3_VERSION 3320200 CACHE STRING "sqlite3 version")
set(SQLITE3_MIRROR ${LOCAL_MIRROR} https://www.sqlite.org/2020
CACHE STRING "sqlite3 download mirror(s)")
set(SQLITE3_SOURCE sqlite-autoconf-${SQLITE3_VERSION}.tar.gz)
set(SQLITE3_HASH SHA512=5b551a1366ce4fd5dfaa687e5021194d34315935b26dd7d71f8abc9935d03c3caea323263a8330fb42038c487cd399e95de68e451cc26d573f852f219c00a02f
CACHE STRING "sqlite3 source hash")
set(SODIUM_VERSION 1.0.18 CACHE STRING "libsodium version") set(SODIUM_VERSION 1.0.18 CACHE STRING "libsodium version")
set(SODIUM_MIRROR ${LOCAL_MIRROR} set(SODIUM_MIRROR ${LOCAL_MIRROR}
https://download.libsodium.org/libsodium/releases https://download.libsodium.org/libsodium/releases
@ -193,6 +200,9 @@ endif()
build_external(sodium) build_external(sodium)
add_static_target(sodium sodium_external libsodium.a) add_static_target(sodium sodium_external libsodium.a)
build_external(sqlite3)
add_static_target(sqlite3 sqlite3_external libsqlite3.a)
if(ZMQ_VERSION VERSION_LESS 4.3.3 AND CMAKE_CROSSCOMPILING AND ARCH_TRIPLET MATCHES mingw) if(ZMQ_VERSION VERSION_LESS 4.3.3 AND CMAKE_CROSSCOMPILING AND ARCH_TRIPLET MATCHES mingw)
set(zmq_patch PATCH_COMMAND patch -p1 -i ${PROJECT_SOURCE_DIR}/contrib/cross/patches/libzmq-pr3601-mingw-build-fix.patch set(zmq_patch PATCH_COMMAND patch -p1 -i ${PROJECT_SOURCE_DIR}/contrib/cross/patches/libzmq-pr3601-mingw-build-fix.patch

View file

@ -98,8 +98,8 @@ run_main_context(const fs::path confFile, const llarp::RuntimeOptions opts)
llarp::Config conf; llarp::Config conf;
conf.Load(confFile, opts.isRouter, confFile.parent_path()); conf.Load(confFile, opts.isRouter, confFile.parent_path());
ctx = std::shared_ptr<llarp::Context>(); ctx = std::make_shared<llarp::Context>();
ctx->Configure(opts, {}); ctx->Configure(conf);
signal(SIGINT, handle_signal); signal(SIGINT, handle_signal);
signal(SIGTERM, handle_signal); signal(SIGTERM, handle_signal);
@ -297,7 +297,23 @@ main(int argc, char* argv[])
} while (ftr.wait_for(std::chrono::seconds(1)) != std::future_status::ready); } while (ftr.wait_for(std::chrono::seconds(1)) != std::future_status::ready);
main_thread.join(); main_thread.join();
const auto code = ftr.get();
int code = 0;
try
{
code = ftr.get();
}
catch (const std::exception& e)
{
std::cerr << "main thread threw exception: " << e.what() << std::endl;
code = 1;
}
catch (...)
{
std::cerr << "main thread threw non-standard exception" << std::endl;
code = 2;
}
llarp::LogContext::Instance().ImmediateFlush(); llarp::LogContext::Instance().ImmediateFlush();
#ifdef _WIN32 #ifdef _WIN32

1
external/sqlite_orm vendored Submodule

@ -0,0 +1 @@
Subproject commit f7ef17a6bde6162e8b487deb36519bace412920a

View file

@ -5,6 +5,8 @@
#include <util/types.hpp> #include <util/types.hpp>
#include <ev/ev.hpp> #include <ev/ev.hpp>
#include <nodedb.hpp> #include <nodedb.hpp>
#include <crypto/crypto.hpp>
#include <router/abstractrouter.hpp>
#include <iostream> #include <iostream>
#include <map> #include <map>
@ -14,20 +16,10 @@
struct llarp_ev_loop; struct llarp_ev_loop;
#ifdef LOKINET_HIVE
namespace tooling
{
struct RouterHive;
} // namespace tooling
#endif
namespace llarp namespace llarp
{ {
class Logic; class Logic;
struct AbstractRouter;
struct Config; struct Config;
struct Crypto;
struct CryptoManager;
struct RouterContact; struct RouterContact;
namespace thread namespace thread
{ {
@ -43,12 +35,11 @@ namespace llarp
struct Context struct Context
{ {
std::unique_ptr<Crypto> crypto; std::unique_ptr<Crypto> crypto = nullptr;
std::unique_ptr<CryptoManager> cryptoManager; std::unique_ptr<CryptoManager> cryptoManager = nullptr;
std::unique_ptr<AbstractRouter> router; std::unique_ptr<AbstractRouter> router = nullptr;
std::shared_ptr<Logic> logic; std::shared_ptr<Logic> logic = nullptr;
std::unique_ptr<Config> config; std::unique_ptr<llarp_nodedb> nodedb = nullptr;
std::unique_ptr<llarp_nodedb> nodedb;
llarp_ev_loop_ptr mainloop; llarp_ev_loop_ptr mainloop;
std::string nodedb_dir; std::string nodedb_dir;
@ -67,8 +58,11 @@ namespace llarp
void void
HandleSignal(int sig); HandleSignal(int sig);
bool /// Configure given the specified config.
Configure(const RuntimeOptions& opts, std::optional<fs::path> dataDir); ///
/// note: consider using std::move() when passing conf in.
void
Configure(Config conf);
bool bool
IsUp() const; IsUp() const;
@ -90,16 +84,20 @@ namespace llarp
bool bool
CallSafe(std::function<void(void)> f); CallSafe(std::function<void(void)> f);
#ifdef LOKINET_HIVE /// Creates a router. Can be overridden to allow a different class of router
void /// to be created instead. Defaults to llarp::Router.
InjectHive(tooling::RouterHive* hive); virtual std::unique_ptr<AbstractRouter>
#endif makeRouter(
llarp_ev_loop_ptr __netloop,
std::shared_ptr<Logic> logic);
protected:
std::unique_ptr<Config> config = nullptr;
private: private:
void void
SigINT(); SigINT();
std::string configfile;
std::unique_ptr<std::promise<void>> closeWaiter; std::unique_ptr<std::promise<void>> closeWaiter;
}; };

View file

@ -28,19 +28,25 @@ add_dependencies(lokinet-util genversion)
target_include_directories(lokinet-util PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/include) target_include_directories(lokinet-util PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/include)
if(NOT TARGET sqlite3)
add_library(sqlite3 INTERFACE)
pkg_check_modules(SQLITE3 REQUIRED IMPORTED_TARGET sqlite3)
target_link_libraries(sqlite3 INTERFACE PkgConfig::SQLITE3)
endif()
target_link_libraries(lokinet-util PUBLIC target_link_libraries(lokinet-util PUBLIC
lokinet-cryptography lokinet-cryptography
nlohmann_json::nlohmann_json nlohmann_json::nlohmann_json
filesystem filesystem
date::date date::date
lokimq lokimq
sqlite3
) )
if(ANDROID) if(ANDROID)
target_link_libraries(lokinet-util PUBLIC log) target_link_libraries(lokinet-util PUBLIC log)
endif() endif()
add_library(lokinet-platform add_library(lokinet-platform
# for networking # for networking
ev/ev.cpp ev/ev.cpp
@ -156,6 +162,8 @@ add_library(liblokinet
path/pathbuilder.cpp path/pathbuilder.cpp
path/pathset.cpp path/pathset.cpp
path/transit_hop.cpp path/transit_hop.cpp
peerstats/peer_db.cpp
peerstats/types.cpp
pow.cpp pow.cpp
profiling.cpp profiling.cpp
router/outbound_message_handler.cpp router/outbound_message_handler.cpp
@ -219,7 +227,11 @@ if(TESTNET)
endif() endif()
if(WITH_HIVE) if(WITH_HIVE)
target_sources(liblokinet PRIVATE tooling/router_hive.cpp) target_sources(liblokinet PRIVATE
tooling/router_hive.cpp
tooling/hive_router.cpp
tooling/hive_context.cpp
)
endif() endif()
target_link_libraries(liblokinet PUBLIC cxxopts lokinet-platform lokinet-util lokinet-cryptography) target_link_libraries(liblokinet PUBLIC cxxopts lokinet-platform lokinet-util lokinet-cryptography)

View file

@ -36,6 +36,7 @@ namespace llarp
constexpr int DefaultWorkerThreads = 1; constexpr int DefaultWorkerThreads = 1;
constexpr int DefaultNetThreads = 1; constexpr int DefaultNetThreads = 1;
constexpr bool DefaultBlockBogons = true; constexpr bool DefaultBlockBogons = true;
constexpr bool DefaultEnablePeerStats = false;
conf.defineOption<int>("router", "job-queue-size", false, DefaultJobQueueSize, [this](int arg) { conf.defineOption<int>("router", "job-queue-size", false, DefaultJobQueueSize, [this](int arg) {
if (arg < 1024) if (arg < 1024)
@ -128,6 +129,21 @@ namespace llarp
conf.defineOption<std::string>( conf.defineOption<std::string>(
"router", "transport-privkey", false, "", AssignmentAcceptor(m_transportKeyFile)); "router", "transport-privkey", false, "", AssignmentAcceptor(m_transportKeyFile));
if (not params.isRelay)
{
// TODO: remove this -- all service nodes should run peer db
conf.defineOption<bool>(
"router",
"enable-peer-stats",
false,
DefaultEnablePeerStats,
AssignmentAcceptor(m_enablePeerStats));
}
else
{
m_enablePeerStats = true;
}
} }
void void
@ -987,6 +1003,40 @@ namespace llarp
"File containing service node's seed.", "File containing service node's seed.",
}); });
// extra [network] options
// TODO: probably better to create an [exit] section and only allow it for routers
def.addOptionComments(
"network",
"exit",
{
"Whether or not we should act as an exit node. Beware that this increases demand",
"on the server and may pose liability concerns. Enable at your own risk.",
});
// TODO: define the order of precedence (e.g. is whitelist applied before blacklist?)
// additionally, what's default? What if I don't whitelist anything?
def.addOptionComments(
"network",
"exit-whitelist",
{
"List of destination protocol:port pairs to whitelist, example: udp:*",
"or tcp:80. Multiple values supported.",
});
def.addOptionComments(
"network",
"exit-blacklist",
{
"Blacklist of destinations (same format as whitelist).",
});
def.addOptionComments(
"router",
"enable-peer-stats",
{
"Enable collection of SNode peer stats",
});
return def.generateINIConfig(true); return def.generateINIConfig(true);
} }

View file

@ -24,8 +24,6 @@
#include <lokimq/address.h> #include <lokimq/address.h>
struct llarp_config;
namespace llarp namespace llarp
{ {
using SectionValues_t = llarp::ConfigParser::SectionValues_t; using SectionValues_t = llarp::ConfigParser::SectionValues_t;
@ -64,6 +62,8 @@ namespace llarp
std::string m_identityKeyFile; std::string m_identityKeyFile;
std::string m_transportKeyFile; std::string m_transportKeyFile;
bool m_enablePeerStats = false;
void void
defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params); defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params);
}; };
@ -222,9 +222,6 @@ namespace llarp
std::string std::string
generateBaseRouterConfig(fs::path defaultDataDir); generateBaseRouterConfig(fs::path defaultDataDir);
llarp_config*
Copy() const;
}; };
void void

View file

@ -27,31 +27,17 @@ namespace llarp
return logic && LogicCall(logic, f); return logic && LogicCall(logic, f);
} }
bool void
Context::Configure(const RuntimeOptions& opts, std::optional<fs::path> dataDir) Context::Configure(Config conf)
{ {
if (config) if (nullptr != config.get())
throw std::runtime_error("Re-configure not supported"); throw std::runtime_error("Config already exists");
config = std::make_unique<Config>(); config = std::make_unique<Config>(std::move(conf));
fs::path defaultDataDir = dataDir ? *dataDir : GetDefaultDataDir();
if (configfile.size())
{
if (!config->Load(configfile.c_str(), opts.isRouter, defaultDataDir))
{
config.release();
llarp::LogError("failed to load config file ", configfile);
return false;
}
}
logic = std::make_shared<Logic>(); logic = std::make_shared<Logic>();
nodedb_dir = fs::path(config->router.m_dataDir / nodedb_dirname).string(); nodedb_dir = fs::path(config->router.m_dataDir / nodedb_dirname).string();
return true;
} }
bool bool
@ -76,6 +62,10 @@ namespace llarp
void void
Context::Setup(const RuntimeOptions& opts) Context::Setup(const RuntimeOptions& opts)
{ {
/// Call one of the Configure() methods before calling Setup()
if (not config)
throw std::runtime_error("Cannot call Setup() on context without a Config");
llarp::LogInfo(llarp::VERSION_FULL, " ", llarp::RELEASE_MOTTO); llarp::LogInfo(llarp::VERSION_FULL, " ", llarp::RELEASE_MOTTO);
llarp::LogInfo("starting up"); llarp::LogInfo("starting up");
if (mainloop == nullptr) if (mainloop == nullptr)
@ -90,12 +80,12 @@ namespace llarp
crypto = std::make_unique<sodium::CryptoLibSodium>(); crypto = std::make_unique<sodium::CryptoLibSodium>();
cryptoManager = std::make_unique<CryptoManager>(crypto.get()); cryptoManager = std::make_unique<CryptoManager>(crypto.get());
router = std::make_unique<Router>(mainloop, logic); router = makeRouter(mainloop, logic);
nodedb = std::make_unique<llarp_nodedb>( nodedb = std::make_unique<llarp_nodedb>(
nodedb_dir, [r = router.get()](auto call) { r->QueueDiskIO(std::move(call)); }); nodedb_dir, [r = router.get()](auto call) { r->QueueDiskIO(std::move(call)); });
if (!router->Configure(config.get(), opts.isRouter, nodedb.get())) if (!router->Configure(*config.get(), opts.isRouter, nodedb.get()))
throw std::runtime_error("Failed to configure router"); throw std::runtime_error("Failed to configure router");
// must be done after router is made so we can use its disk io worker // must be done after router is made so we can use its disk io worker
@ -105,6 +95,14 @@ namespace llarp
throw std::runtime_error("Config::Setup() failed to load database"); throw std::runtime_error("Config::Setup() failed to load database");
} }
std::unique_ptr<AbstractRouter>
Context::makeRouter(
llarp_ev_loop_ptr netloop,
std::shared_ptr<Logic> logic)
{
return std::make_unique<Router>(netloop, logic);
}
int int
Context::Run(const RuntimeOptions& opts) Context::Run(const RuntimeOptions& opts)
{ {
@ -197,14 +195,6 @@ namespace llarp
llarp::LogDebug("free logic"); llarp::LogDebug("free logic");
logic.reset(); logic.reset();
} }
#ifdef LOKINET_HIVE
void
Context::InjectHive(tooling::RouterHive* hive)
{
router->hive = hive;
}
#endif
} // namespace llarp } // namespace llarp
extern "C" extern "C"

View file

@ -127,6 +127,10 @@ namespace llarp
auto* router = dht.GetRouter(); auto* router = dht.GetRouter();
router->NotifyRouterEvent<tooling::RCGossipReceivedEvent>(router->pubkey(), rc); router->NotifyRouterEvent<tooling::RCGossipReceivedEvent>(router->pubkey(), rc);
router->GossipRCIfNeeded(rc); router->GossipRCIfNeeded(rc);
auto peerDb = router->peerDb();
if (peerDb)
peerDb->handleGossipedRC(rc);
} }
} }
return true; return true;

View file

@ -76,7 +76,7 @@ namespace llarp
GotLIM = util::memFn(&Session::GotRenegLIM, this); GotLIM = util::memFn(&Session::GotRenegLIM, this);
m_RemoteRC = msg->rc; m_RemoteRC = msg->rc;
m_Parent->MapAddr(m_RemoteRC.pubkey, this); m_Parent->MapAddr(m_RemoteRC.pubkey, this);
return m_Parent->SessionEstablished(this); return m_Parent->SessionEstablished(this, true);
} }
bool bool
@ -96,7 +96,7 @@ namespace llarp
{ {
self->m_State = State::Ready; self->m_State = State::Ready;
self->m_Parent->MapAddr(self->m_RemoteRC.pubkey, self.get()); self->m_Parent->MapAddr(self->m_RemoteRC.pubkey, self.get());
self->m_Parent->SessionEstablished(self.get()); self->m_Parent->SessionEstablished(self.get(), false);
} }
}); });
return true; return true;
@ -281,6 +281,13 @@ namespace llarp
return false; return false;
} }
SessionStats
Session::GetSessionStats() const
{
// TODO: thread safety
return m_Stats;
}
util::StatusObject util::StatusObject
Session::ExtractStatus() const Session::ExtractStatus() const
{ {

View file

@ -115,6 +115,9 @@ namespace llarp
bool bool
ShouldPing() const override; ShouldPing() const override;
SessionStats
GetSessionStats() const override;
util::StatusObject util::StatusObject
ExtractStatus() const override; ExtractStatus() const override;
@ -141,20 +144,7 @@ namespace llarp
static std::string static std::string
StateToString(State state); StateToString(State state);
State m_State; State m_State;
SessionStats m_Stats;
struct Stats
{
// rate
uint64_t currentRateRX = 0;
uint64_t currentRateTX = 0;
uint64_t totalPacketsRX = 0;
uint64_t totalAckedTX = 0;
uint64_t totalDroppedTX = 0;
uint64_t totalInFlightTX = 0;
};
Stats m_Stats;
/// are we inbound session ? /// are we inbound session ?
const bool m_Inbound; const bool m_Inbound;

View file

@ -4,6 +4,7 @@
#include <link/server.hpp> #include <link/server.hpp>
#include <util/thread/logic.hpp> #include <util/thread/logic.hpp>
#include <util/types.hpp> #include <util/types.hpp>
#include <peerstats/peer_db.hpp>
#include <functional> #include <functional>
@ -77,6 +78,9 @@ namespace llarp
virtual void virtual void
CheckPersistingSessions(llarp_time_t now) = 0; CheckPersistingSessions(llarp_time_t now) = 0;
virtual void
updatePeerDb(std::shared_ptr<PeerDb> peerDb) = 0;
virtual util::StatusObject virtual util::StatusObject
ExtractStatus() const = 0; ExtractStatus() const = 0;
}; };

View file

@ -327,6 +327,55 @@ namespace llarp
} }
} }
void
LinkManager::updatePeerDb(std::shared_ptr<PeerDb> peerDb)
{
std::vector<std::pair<RouterID, SessionStats>> statsToUpdate;
int64_t diffTotalTX = 0;
ForEachPeer([&](ILinkSession* session) {
// derive RouterID
RouterID id = RouterID(session->GetRemoteRC().pubkey);
SessionStats sessionStats = session->GetSessionStats();
SessionStats diff;
SessionStats& lastStats = m_lastRouterStats[id];
// TODO: operator overloads / member func for diff
diff.currentRateRX = std::max(sessionStats.currentRateRX, lastStats.currentRateRX);
diff.currentRateTX = std::max(sessionStats.currentRateTX, lastStats.currentRateTX);
diff.totalPacketsRX = sessionStats.totalPacketsRX - lastStats.totalPacketsRX;
diff.totalAckedTX = sessionStats.totalAckedTX - lastStats.totalAckedTX;
diff.totalDroppedTX = sessionStats.totalDroppedTX - lastStats.totalDroppedTX;
diffTotalTX = diff.totalAckedTX + diff.totalDroppedTX + diff.totalInFlightTX;
lastStats = sessionStats;
// TODO: if we have both inbound and outbound session, this will overwrite
statsToUpdate.push_back({id, diff});
});
for (auto& routerStats : statsToUpdate)
{
peerDb->modifyPeerStats(routerStats.first, [&](PeerStats& stats) {
// TODO: store separate stats for up vs down
const auto& diff = routerStats.second;
// note that 'currentRateRX' and 'currentRateTX' are per-second
stats.peakBandwidthBytesPerSec = std::max(
stats.peakBandwidthBytesPerSec,
(double)std::max(diff.currentRateRX, diff.currentRateTX));
stats.numPacketsDropped += diff.totalDroppedTX;
stats.numPacketsSent = diff.totalAckedTX;
stats.numPacketsAttempted = diffTotalTX;
// TODO: others -- we have slight mismatch on what we store
});
}
}
util::StatusObject util::StatusObject
LinkManager::ExtractStatus() const LinkManager::ExtractStatus() const
{ {

View file

@ -74,6 +74,9 @@ namespace llarp
void void
CheckPersistingSessions(llarp_time_t now) override; CheckPersistingSessions(llarp_time_t now) override;
void
updatePeerDb(std::shared_ptr<PeerDb> peerDb) override;
util::StatusObject util::StatusObject
ExtractStatus() const override; ExtractStatus() const override;
@ -96,6 +99,8 @@ namespace llarp
std::unordered_map<RouterID, llarp_time_t, RouterID::Hash> m_PersistingSessions std::unordered_map<RouterID, llarp_time_t, RouterID::Hash> m_PersistingSessions
GUARDED_BY(_mutex); GUARDED_BY(_mutex);
std::unordered_map<RouterID, SessionStats, RouterID::Hash> m_lastRouterStats;
IOutboundSessionMaker* _sessionMaker; IOutboundSessionMaker* _sessionMaker;
}; };

View file

@ -17,34 +17,53 @@
namespace llarp namespace llarp
{ {
/// handle a link layer message /// handle a link layer message. this allows for the message to be handled by "upper layers"
///
/// currently called from iwp::Session when messages are sent or received.
using LinkMessageHandler = std::function<bool(ILinkSession*, const llarp_buffer_t&)>; using LinkMessageHandler = std::function<bool(ILinkSession*, const llarp_buffer_t&)>;
/// sign a buffer with identity key /// sign a buffer with identity key. this function should take the given `llarp_buffer_t` and
/// sign it, prividing the signature in the out variable `Signature&`.
///
/// currently called from iwp::Session for signing LIMs (link introduction messages)
using SignBufferFunc = std::function<bool(Signature&, const llarp_buffer_t&)>; using SignBufferFunc = std::function<bool(Signature&, const llarp_buffer_t&)>;
/// handle connection timeout /// handle connection timeout
///
/// currently called from ILinkLayer::Pump() when an unestablished session times out
using TimeoutHandler = std::function<void(ILinkSession*)>; using TimeoutHandler = std::function<void(ILinkSession*)>;
/// get our RC /// get our RC
///
/// currently called by iwp::Session to include as part of a LIM (link introduction message)
using GetRCFunc = std::function<const llarp::RouterContact&(void)>; using GetRCFunc = std::function<const llarp::RouterContact&(void)>;
/// handler of session established /// handler of session established
/// return false to reject /// return false to reject
/// return true to accept /// return true to accept
using SessionEstablishedHandler = std::function<bool(ILinkSession*)>; ///
/// currently called in iwp::Session when a valid LIM is received.
using SessionEstablishedHandler = std::function<bool(ILinkSession*, bool)>;
/// f(new, old) /// f(new, old)
/// handler of session renegotiation /// handler of session renegotiation
/// returns true if the new rc is valid /// returns true if the new rc is valid
/// returns false otherwise and the session is terminated /// returns false otherwise and the session is terminated
///
/// currently called from iwp::Session when we receive a renegotiation LIM
using SessionRenegotiateHandler = std::function<bool(llarp::RouterContact, llarp::RouterContact)>; using SessionRenegotiateHandler = std::function<bool(llarp::RouterContact, llarp::RouterContact)>;
/// handles close of all sessions with pubkey /// handles close of all sessions with pubkey
///
/// Note that this handler is called while m_AuthedLinksMutex is held
///
/// currently called from iwp::ILinkSession when a previously established session times out
using SessionClosedHandler = std::function<void(llarp::RouterID)>; using SessionClosedHandler = std::function<void(llarp::RouterID)>;
/// notifies router that a link session has ended its pump and we should flush /// notifies router that a link session has ended its pump and we should flush
/// messages to upper layers /// messages to upper layers
///
/// currently called at the end of every iwp::Session::Pump() call
using PumpDoneHandler = std::function<void(void)>; using PumpDoneHandler = std::function<void(void)>;
using Work_t = std::function<void(void)>; using Work_t = std::function<void(void)>;

View file

@ -15,6 +15,19 @@ namespace llarp
struct ILinkMessage; struct ILinkMessage;
struct ILinkLayer; struct ILinkLayer;
struct SessionStats
{
// rate
uint64_t currentRateRX = 0;
uint64_t currentRateTX = 0;
uint64_t totalPacketsRX = 0;
uint64_t totalAckedTX = 0;
uint64_t totalDroppedTX = 0;
uint64_t totalInFlightTX = 0;
};
struct ILinkSession struct ILinkSession
{ {
virtual ~ILinkSession() = default; virtual ~ILinkSession() = default;
@ -108,6 +121,10 @@ namespace llarp
virtual bool virtual bool
ShouldPing() const = 0; ShouldPing() const = 0;
/// return the current stats for this session
virtual SessionStats
GetSessionStats() const = 0;
virtual util::StatusObject virtual util::StatusObject
ExtractStatus() const = 0; ExtractStatus() const = 0;
}; };

137
llarp/peerstats/orm.hpp Normal file
View file

@ -0,0 +1,137 @@
#pragma once
#include <sqlite_orm/sqlite_orm.h>
#include <peerstats/types.hpp>
/// Contains some code to help deal with sqlite_orm in hopes of keeping other headers clean
namespace llarp
{
inline auto
initStorage(const std::string& file)
{
using namespace sqlite_orm;
return make_storage(
file,
make_table(
"peerstats",
make_column("routerId", &PeerStats::routerId, primary_key(), unique()),
make_column("numConnectionAttempts", &PeerStats::numConnectionAttempts),
make_column("numConnectionSuccesses", &PeerStats::numConnectionSuccesses),
make_column("numConnectionRejections", &PeerStats::numConnectionRejections),
make_column("numConnectionTimeouts", &PeerStats::numConnectionTimeouts),
make_column("numPathBuilds", &PeerStats::numPathBuilds),
make_column("numPacketsAttempted", &PeerStats::numPacketsAttempted),
make_column("numPacketsSent", &PeerStats::numPacketsSent),
make_column("numPacketsDropped", &PeerStats::numPacketsDropped),
make_column("numPacketsResent", &PeerStats::numPacketsResent),
make_column("numDistinctRCsReceived", &PeerStats::numDistinctRCsReceived),
make_column("numLateRCs", &PeerStats::numLateRCs),
make_column("peakBandwidthBytesPerSec", &PeerStats::peakBandwidthBytesPerSec),
make_column("longestRCReceiveInterval", &PeerStats::longestRCReceiveInterval),
make_column("leastRCRemainingLifetime", &PeerStats::leastRCRemainingLifetime)));
}
using PeerDbStorage = decltype(initStorage(""));
} // namespace llarp
/// "custom" types for sqlite_orm
/// reference: https://github.com/fnc12/sqlite_orm/blob/master/examples/enum_binding.cpp
namespace sqlite_orm
{
/// llarp_time_t serialization
template <>
struct type_printer<llarp_time_t> : public integer_printer
{
};
template <>
struct statement_binder<llarp_time_t>
{
int
bind(sqlite3_stmt* stmt, int index, const llarp_time_t& value)
{
return statement_binder<int64_t>().bind(stmt, index, value.count());
}
};
template <>
struct field_printer<llarp_time_t>
{
std::string
operator()(const llarp_time_t& value) const
{
std::stringstream stream;
stream << value.count();
return stream.str();
}
};
template <>
struct row_extractor<llarp_time_t>
{
llarp_time_t
extract(const char* row_value)
{
int64_t raw = static_cast<int64_t>(atoi(row_value));
return llarp_time_t(raw);
}
llarp_time_t
extract(sqlite3_stmt* stmt, int columnIndex)
{
auto str = sqlite3_column_text(stmt, columnIndex);
return this->extract((const char*)str);
}
};
/// RouterID serialization
template <>
struct type_printer<llarp::RouterID> : public text_printer
{
};
template <>
struct statement_binder<llarp::RouterID>
{
int
bind(sqlite3_stmt* stmt, int index, const llarp::RouterID& value)
{
return statement_binder<std::string>().bind(stmt, index, value.ToString());
}
};
template <>
struct field_printer<llarp::RouterID>
{
std::string
operator()(const llarp::RouterID& value) const
{
return value.ToString();
}
};
template <>
struct row_extractor<llarp::RouterID>
{
llarp::RouterID
extract(const char* row_value)
{
llarp::RouterID id;
if (not id.FromString(row_value))
throw std::runtime_error("Invalid RouterID in sqlite3 database");
return id;
}
llarp::RouterID
extract(sqlite3_stmt* stmt, int columnIndex)
{
auto str = sqlite3_column_text(stmt, columnIndex);
return this->extract((const char*)str);
}
};
} // namespace sqlite_orm

303
llarp/peerstats/peer_db.cpp Normal file
View file

@ -0,0 +1,303 @@
#include <peerstats/peer_db.hpp>
#include <util/logging/logger.hpp>
#include <util/status.hpp>
#include <util/str.hpp>
namespace llarp
{
PeerDb::PeerDb()
{
m_lastFlush.store({});
}
void
PeerDb::loadDatabase(std::optional<fs::path> file)
{
std::lock_guard guard(m_statsLock);
if (m_storage)
throw std::runtime_error("Reloading database not supported"); // TODO
m_peerStats.clear();
// sqlite_orm treats empty-string as an indicator to load a memory-backed database, which we'll
// use if file is an empty-optional
std::string fileString;
if (file.has_value())
{
fileString = file->string();
LogInfo("Loading PeerDb from file ", fileString);
}
else
{
LogInfo("Loading memory-backed PeerDb");
}
m_storage = std::make_unique<PeerDbStorage>(initStorage(fileString));
m_storage->sync_schema(true); // true for "preserve" as in "don't nuke" (how cute!)
auto allStats = m_storage->get_all<PeerStats>();
LogInfo("Loading ", allStats.size(), " PeerStats from table peerstats...");
for (PeerStats& stats : allStats)
{
// we cleared m_peerStats, and the database should enforce that routerId is unique...
assert(m_peerStats.find(stats.routerId) == m_peerStats.end());
stats.stale = false;
m_peerStats[stats.routerId] = stats;
}
}
void
PeerDb::flushDatabase()
{
LogDebug("flushing PeerDb...");
auto start = time_now_ms();
if (not shouldFlush(start))
{
LogWarn("Call to flushDatabase() while already in progress, ignoring");
return;
}
if (not m_storage)
throw std::runtime_error("Cannot flush database before it has been loaded");
std::vector<PeerStats> staleStats;
{
std::lock_guard guard(m_statsLock);
// copy all stale entries
for (auto& entry : m_peerStats)
{
if (entry.second.stale)
{
staleStats.push_back(entry.second);
entry.second.stale = false;
}
}
}
LogInfo("Updating ", staleStats.size(), " stats");
{
auto guard = m_storage->transaction_guard();
for (const auto& stats : staleStats)
{
m_storage->replace(stats);
}
guard.commit();
}
auto end = time_now_ms();
auto elapsed = end - start;
LogInfo("PeerDb flush took about ", elapsed, " seconds");
m_lastFlush.store(end);
}
void
PeerDb::accumulatePeerStats(const RouterID& routerId, const PeerStats& delta)
{
if (routerId != delta.routerId)
throw std::invalid_argument(
stringify("routerId ", routerId, " doesn't match ", delta.routerId));
std::lock_guard guard(m_statsLock);
auto itr = m_peerStats.find(routerId);
if (itr == m_peerStats.end())
itr = m_peerStats.insert({routerId, delta}).first;
else
itr->second += delta;
itr->second.stale = true;
}
void
PeerDb::modifyPeerStats(const RouterID& routerId, std::function<void(PeerStats&)> callback)
{
std::lock_guard guard(m_statsLock);
PeerStats& stats = m_peerStats[routerId];
stats.routerId = routerId;
stats.stale = true;
callback(stats);
}
std::optional<PeerStats>
PeerDb::getCurrentPeerStats(const RouterID& routerId) const
{
std::lock_guard guard(m_statsLock);
auto itr = m_peerStats.find(routerId);
if (itr == m_peerStats.end())
return std::nullopt;
else
return itr->second;
}
std::vector<PeerStats>
PeerDb::listAllPeerStats() const
{
std::lock_guard guard(m_statsLock);
std::vector<PeerStats> statsList;
statsList.reserve(m_peerStats.size());
for (const auto& [routerId, stats] : m_peerStats)
{
statsList.push_back(stats);
}
return statsList;
}
std::vector<PeerStats>
PeerDb::listPeerStats(const std::vector<RouterID>& ids) const
{
std::lock_guard guard(m_statsLock);
std::vector<PeerStats> statsList;
statsList.reserve(ids.size());
for (const auto& id : ids)
{
const auto itr = m_peerStats.find(id);
if (itr != m_peerStats.end())
statsList.push_back(itr->second);
}
return statsList;
}
/// Assume we receive an RC at some point `R` in time which was signed at some point `S` in time
/// and expires at some point `E` in time, as depicted below:
///
/// +-----------------------------+
/// | signed rc | <- useful lifetime of RC
/// +-----------------------------+
/// ^ [ . . . . . . . . ] <----------- window in which we receive this RC gossiped to us
/// | ^ ^
/// | | |
/// S R E
///
/// One useful metric from this is the difference between (E - R), the useful contact time of this
/// RC. As we track this metric over time, the high and low watermarks serve to tell us how
/// quickly we receive signed RCs from a given router and how close to expiration they are when
/// we receive them. The latter is particularly useful, and should always be a positive number for
/// a healthy router. A negative number indicates that we are receiving an expired RC.
///
/// TODO: we actually discard expired RCs, so we currently would not detect a negative value for
/// (E - R)
///
/// Another related metric is the distance between a newly received RC and the previous RC's
/// expiration, which represents how close we came to having no useful RC to work with. This
/// should be a high (positive) number for a healthy router, and if negative indicates that we
/// had no way to contact this router for a period of time.
///
/// E1 E2 E3
/// | | |
/// v | |
/// +-----------------------------+ | |
/// | signed rc 1 | | |
/// +-----------------------------+ | |
/// [ . . . . . ] v |
/// ^ +-----------------------------+ |
/// | | signed rc 2 | |
/// | +-----------------------------+ |
/// | [ . . . . . . . . . . ] v
/// | ^ +-----------------------------+
/// | | | signed rc 3 |
/// | | +-----------------------------+
/// | | [ . . ]
/// | | ^
/// | | |
/// R1 R2 R3
///
/// Example: the delta between (E1 - R2) is healthy, but the delta between (E2 - R3) is indicates
/// that we had a brief period of time where we had no valid (non-expired) RC for this router
/// (because it is negative).
void
PeerDb::handleGossipedRC(const RouterContact& rc, llarp_time_t now)
{
std::lock_guard guard(m_statsLock);
RouterID id(rc.pubkey);
auto& stats = m_peerStats[id];
stats.routerId = id;
const bool isNewRC = (stats.lastRCUpdated < rc.last_updated);
if (isNewRC)
{
stats.numDistinctRCsReceived++;
if (stats.numDistinctRCsReceived > 1)
{
auto prevRCExpiration = (stats.lastRCUpdated + RouterContact::Lifetime);
// we track max expiry as the delta between (last expiration time - time received),
// and this value will be negative for an unhealthy router
// TODO: handle case where new RC is also expired? just ignore?
auto expiry = prevRCExpiration - now;
if (stats.numDistinctRCsReceived == 2)
stats.leastRCRemainingLifetime = expiry;
else
stats.leastRCRemainingLifetime = std::min(stats.leastRCRemainingLifetime, expiry);
}
stats.lastRCUpdated = rc.last_updated;
stats.stale = true;
}
}
void
PeerDb::configure(const RouterConfig& routerConfig)
{
if (not routerConfig.m_enablePeerStats)
throw std::runtime_error("[router]:enable-peer-stats is not enabled");
fs::path dbPath = routerConfig.m_dataDir / "peerstats.sqlite";
loadDatabase(dbPath);
}
bool
PeerDb::shouldFlush(llarp_time_t now)
{
constexpr llarp_time_t TargetFlushInterval = 30s;
return (now - m_lastFlush.load() >= TargetFlushInterval);
}
util::StatusObject
PeerDb::ExtractStatus() const
{
std::lock_guard guard(m_statsLock);
bool loaded = (m_storage.get() != nullptr);
util::StatusObject dbFile = nullptr;
if (loaded)
dbFile = m_storage->filename();
std::vector<util::StatusObject> statsObjs;
statsObjs.reserve(m_peerStats.size());
for (const auto& pair : m_peerStats)
{
statsObjs.push_back(pair.second.toJson());
}
util::StatusObject obj{
{"dbLoaded", loaded},
{"dbFile", dbFile},
{"lastFlushMs", m_lastFlush.load().count()},
{"stats", statsObjs},
};
return obj;
}
}; // namespace llarp

138
llarp/peerstats/peer_db.hpp Normal file
View file

@ -0,0 +1,138 @@
#pragma once
#include <filesystem>
#include <functional>
#include <unordered_map>
#include <sqlite_orm/sqlite_orm.h>
#include <util/fs.hpp>
#include <config/config.hpp>
#include <router_id.hpp>
#include <util/time.hpp>
#include <peerstats/types.hpp>
#include <peerstats/orm.hpp>
namespace llarp
{
/// Maintains a database of stats collected about the connections with our Service Node peers.
/// This uses a sqlite3 database behind the scenes as persistance, but this database is
/// periodically flushed to, meaning that it will become stale as PeerDb accumulates stats without
/// a flush.
struct PeerDb
{
/// Constructor
PeerDb();
/// Loads the database from disk using the provided filepath. If the file is equal to
/// `std::nullopt`, the database will be loaded into memory (useful for testing).
///
/// This must be called prior to calling flushDatabase(), and will truncate any existing data.
///
/// This is a blocking call, both in the sense that it blocks on disk/database I/O and that it
/// will sit on a mutex while the database is loaded.
///
/// @param file is an optional file which doesn't have to exist but must be writable, if a value
/// is provided. If no value is provided, the database will be memory-backed.
/// @throws if sqlite_orm/sqlite3 is unable to open or create a database at the given file
void
loadDatabase(std::optional<fs::path> file);
/// Flushes the database. Must be called after loadDatabase(). This call will block during I/O
/// and should be called in an appropriate threading context. However, it will make a temporary
/// copy of the peer stats so as to avoid sitting on a mutex lock during disk I/O.
///
/// @throws if the database could not be written to (esp. if loadDatabase() has not been called)
void
flushDatabase();
/// Add the given stats to the cummulative stats for the given peer. For cummulative stats, the
/// stats are added together; for watermark stats, the max is kept.
///
/// This is intended to be used in the following pattern:
///
/// 1) Initialize an empty PeerStats
/// 2) Collect relevant stats
/// 3) Call accumulatePeerStats() with the stats
/// 4) Reset the stats to 0
/// 5) <Repeat 2-4 periodically>
///
/// @param routerId is the id of the router whose stats should be modified.
/// @param delta is the stats to add to the existing stats
void
accumulatePeerStats(const RouterID& routerId, const PeerStats& delta);
/// Allows write-access to the stats for a given peer while appropriate mutex lock is held. This
/// is an alternative means of incrementing peer stats that is suitable for one-off
/// modifications.
///
/// Note that this holds m_statsLock during the callback invocation, so the callback should
/// return as quickly as possible.
///
/// @param routerId is the id of the router whose stats should be modified.
/// @param callback is a function which will be called immediately with mutex held
void
modifyPeerStats(const RouterID& routerId, std::function<void(PeerStats&)> callback);
/// Provides a snapshot of the most recent PeerStats we have for the given peer. If we don't
/// have any stats for the peer, std::nullopt
///
/// @param routerId is the RouterID of the requested peer
/// @return a copy of the most recent peer stats or an empty one if no such peer is known
std::optional<PeerStats>
getCurrentPeerStats(const RouterID& routerId) const;
/// Lists all peer stats. This essentially dumps the database into a list of PeerStats objects.
///
/// Note that this avoids disk I/O by copying from our cached map of peers.
///
/// @return a list of all PeerStats we have maintained
std::vector<PeerStats>
listAllPeerStats() const;
/// Lists specific peer stats.
///
/// @param peers is list of RouterIDs which are desired
/// @return a list of the requested peers. Peers not found will be omitted.
std::vector<PeerStats>
listPeerStats(const std::vector<RouterID>& ids) const;
/// Handles a new gossiped RC, updating stats as needed. The database tracks the last
/// advertised update time, so it knows whether this is a new RC or not.
///
/// The given RC is assumed to be valid.
///
/// @param rc is the RouterContact to handle
/// @param now is an optional time representing the current time
void
handleGossipedRC(const RouterContact& rc, llarp_time_t now = time_now_ms());
/// Configures the PeerDb based on RouterConfig
///
/// @param routerConfig
void
configure(const RouterConfig& routerConfig);
/// Returns whether or not we should flush, as determined by the last time we flushed and the
/// configured flush interval.
///
/// @param now is the current[-ish] time
bool
shouldFlush(llarp_time_t now);
/// Get JSON status for API
///
/// @return JSON object representing our current status
util::StatusObject
ExtractStatus() const;
private:
std::unordered_map<RouterID, PeerStats, RouterID::Hash> m_peerStats;
mutable std::mutex m_statsLock;
std::unique_ptr<PeerDbStorage> m_storage;
std::atomic<llarp_time_t> m_lastFlush;
};
} // namespace llarp

159
llarp/peerstats/types.cpp Normal file
View file

@ -0,0 +1,159 @@
#include <peerstats/types.hpp>
#include <util/str.hpp>
#include <stdexcept>
namespace llarp
{
constexpr auto RouterIdKey = "routerId";
constexpr auto NumConnectionAttemptsKey = "numConnectionAttempts";
constexpr auto NumConnectionSuccessesKey = "numConnectionSuccesses";
constexpr auto NumConnectionRejectionsKey = "numConnectionRejections";
constexpr auto NumConnectionTimeoutsKey = "numConnectionTimeouts";
constexpr auto NumPathBuildsKey = "numPathBuilds";
constexpr auto NumPacketsAttemptedKey = "numPacketsAttempted";
constexpr auto NumPacketsSentKey = "numPacketsSent";
constexpr auto NumPacketsDroppedKey = "numPacketsDropped";
constexpr auto NumPacketsResentKey = "numPacketsResent";
constexpr auto NumDistinctRCsReceivedKey = "numDistinctRCsReceived";
constexpr auto NumLateRCsKey = "numLateRCs";
constexpr auto PeakBandwidthBytesPerSecKey = "peakBandwidthBytesPerSec";
constexpr auto LongestRCReceiveIntervalKey = "longestRCReceiveInterval";
constexpr auto LeastRCRemainingLifetimeKey = "leastRCRemainingLifetime";
constexpr auto LastRCUpdatedKey = "lastRCUpdated";
PeerStats::PeerStats() = default;
PeerStats::PeerStats(const RouterID& routerId_) : routerId(routerId_)
{
}
PeerStats&
PeerStats::operator+=(const PeerStats& other)
{
numConnectionAttempts += other.numConnectionAttempts;
numConnectionSuccesses += other.numConnectionSuccesses;
numConnectionRejections += other.numConnectionRejections;
numConnectionTimeouts += other.numConnectionTimeouts;
numPathBuilds += other.numPathBuilds;
numPacketsAttempted += other.numPacketsAttempted;
numPacketsSent += other.numPacketsSent;
numPacketsDropped += other.numPacketsDropped;
numPacketsResent += other.numPacketsResent;
numDistinctRCsReceived += other.numDistinctRCsReceived;
numLateRCs += other.numLateRCs;
peakBandwidthBytesPerSec = std::max(peakBandwidthBytesPerSec, other.peakBandwidthBytesPerSec);
longestRCReceiveInterval = std::max(longestRCReceiveInterval, other.longestRCReceiveInterval);
leastRCRemainingLifetime = std::max(leastRCRemainingLifetime, other.leastRCRemainingLifetime);
lastRCUpdated = std::max(lastRCUpdated, other.lastRCUpdated);
return *this;
}
bool
PeerStats::operator==(const PeerStats& other) const
{
return routerId == other.routerId and numConnectionAttempts == other.numConnectionAttempts
and numConnectionSuccesses == other.numConnectionSuccesses
and numConnectionRejections == other.numConnectionRejections
and numConnectionTimeouts == other.numConnectionTimeouts
and numPathBuilds == other.numPathBuilds
and numPacketsAttempted == other.numPacketsAttempted
and numPacketsSent == other.numPacketsSent and numPacketsDropped == other.numPacketsDropped
and numPacketsResent == other.numPacketsResent
and numDistinctRCsReceived == other.numDistinctRCsReceived
and numLateRCs == other.numLateRCs
and peakBandwidthBytesPerSec == other.peakBandwidthBytesPerSec
and longestRCReceiveInterval == other.longestRCReceiveInterval
and leastRCRemainingLifetime == other.leastRCRemainingLifetime
and lastRCUpdated == other.lastRCUpdated;
}
util::StatusObject
PeerStats::toJson() const
{
return {
{RouterIdKey, routerId.ToString()},
{NumConnectionAttemptsKey, numConnectionAttempts},
{NumConnectionSuccessesKey, numConnectionSuccesses},
{NumConnectionRejectionsKey, numConnectionRejections},
{NumConnectionTimeoutsKey, numConnectionTimeouts},
{NumPathBuildsKey, numPathBuilds},
{NumPacketsAttemptedKey, numPacketsAttempted},
{NumPacketsSentKey, numPacketsSent},
{NumPacketsDroppedKey, numPacketsDropped},
{NumPacketsResentKey, numPacketsResent},
{NumDistinctRCsReceivedKey, numDistinctRCsReceived},
{NumLateRCsKey, numLateRCs},
{PeakBandwidthBytesPerSecKey, peakBandwidthBytesPerSec},
{LongestRCReceiveIntervalKey, longestRCReceiveInterval.count()},
{LeastRCRemainingLifetimeKey, leastRCRemainingLifetime.count()},
{LastRCUpdatedKey, lastRCUpdated.count()},
};
}
void
PeerStats::BEncode(llarp_buffer_t* buf) const
{
if (not buf)
throw std::runtime_error("PeerStats: Can't use null buf");
auto encodeUint64Entry = [&](std::string_view key, uint64_t value) {
if (not bencode_write_uint64_entry(buf, key.data(), key.size(), value))
throw std::runtime_error(stringify("PeerStats: Could not encode ", key));
};
if (not bencode_start_dict(buf))
throw std::runtime_error("PeerStats: Could not create bencode dict");
// TODO: we don't have bencode support for dict entries other than uint64...?
// encodeUint64Entry(RouterIdKey, routerId);
encodeUint64Entry(NumConnectionAttemptsKey, numConnectionAttempts);
encodeUint64Entry(NumConnectionSuccessesKey, numConnectionSuccesses);
encodeUint64Entry(NumConnectionRejectionsKey, numConnectionRejections);
encodeUint64Entry(NumConnectionTimeoutsKey, numConnectionTimeouts);
encodeUint64Entry(NumPathBuildsKey, numPathBuilds);
encodeUint64Entry(NumPacketsAttemptedKey, numPacketsAttempted);
encodeUint64Entry(NumPacketsSentKey, numPacketsSent);
encodeUint64Entry(NumPacketsDroppedKey, numPacketsDropped);
encodeUint64Entry(NumPacketsResentKey, numPacketsResent);
encodeUint64Entry(NumDistinctRCsReceivedKey, numDistinctRCsReceived);
encodeUint64Entry(NumLateRCsKey, numLateRCs);
encodeUint64Entry(PeakBandwidthBytesPerSecKey, (uint64_t)peakBandwidthBytesPerSec);
encodeUint64Entry(LongestRCReceiveIntervalKey, longestRCReceiveInterval.count());
encodeUint64Entry(LeastRCRemainingLifetimeKey, leastRCRemainingLifetime.count());
encodeUint64Entry(LastRCUpdatedKey, lastRCUpdated.count());
if (not bencode_end(buf))
throw std::runtime_error("PeerStats: Could not end bencode dict");
}
void
PeerStats::BEncodeList(const std::vector<PeerStats>& statsList, llarp_buffer_t* buf)
{
if (not buf)
throw std::runtime_error("PeerStats: Can't use null buf");
if (not bencode_start_list(buf))
throw std::runtime_error("PeerStats: Could not create bencode dict");
for (const auto& stats : statsList)
{
stats.BEncode(buf);
}
if (not bencode_end(buf))
throw std::runtime_error("PeerStats: Could not end bencode dict");
}
}; // namespace llarp

59
llarp/peerstats/types.hpp Normal file
View file

@ -0,0 +1,59 @@
#pragma once
#include <chrono>
#include <unordered_map>
#include <router_id.hpp>
#include <util/status.hpp>
#include <util/time.hpp>
/// Types stored in our peerstats database are declared here
namespace llarp
{
// Struct containing stats we know about a peer
struct PeerStats
{
RouterID routerId;
int32_t numConnectionAttempts = 0;
int32_t numConnectionSuccesses = 0;
int32_t numConnectionRejections = 0;
int32_t numConnectionTimeouts = 0;
int32_t numPathBuilds = 0;
int64_t numPacketsAttempted = 0;
int64_t numPacketsSent = 0;
int64_t numPacketsDropped = 0;
int64_t numPacketsResent = 0;
int32_t numDistinctRCsReceived = 0;
int32_t numLateRCs = 0;
double peakBandwidthBytesPerSec = 0;
llarp_time_t longestRCReceiveInterval = 0ms;
llarp_time_t leastRCRemainingLifetime = 0ms;
llarp_time_t lastRCUpdated = 0ms;
// not serialized
bool stale = true;
PeerStats();
PeerStats(const RouterID& routerId);
PeerStats&
operator+=(const PeerStats& other);
bool
operator==(const PeerStats& other) const;
util::StatusObject
toJson() const;
void
BEncode(llarp_buffer_t* buf) const;
static void
BEncodeList(const std::vector<PeerStats>& statsList, llarp_buffer_t* buf);
};
} // namespace llarp

View file

@ -11,9 +11,10 @@
#include <functional> #include <functional>
#include <router_contact.hpp> #include <router_contact.hpp>
#include <tooling/router_event.hpp> #include <tooling/router_event.hpp>
#include <peerstats/peer_db.hpp>
#ifdef LOKINET_HIVE #ifdef LOKINET_HIVE
#include "tooling/router_hive.hpp" #include "tooling/router_event.hpp"
#endif #endif
struct llarp_buffer_t; struct llarp_buffer_t;
@ -151,11 +152,14 @@ namespace llarp
virtual I_RCLookupHandler& virtual I_RCLookupHandler&
rcLookupHandler() = 0; rcLookupHandler() = 0;
virtual std::shared_ptr<PeerDb>
peerDb() = 0;
virtual bool virtual bool
Sign(Signature& sig, const llarp_buffer_t& buf) const = 0; Sign(Signature& sig, const llarp_buffer_t& buf) const = 0;
virtual bool virtual bool
Configure(Config* conf, bool isRouter, llarp_nodedb* nodedb) = 0; Configure(const Config& conf, bool isRouter, llarp_nodedb* nodedb) = 0;
virtual bool virtual bool
IsServiceNode() const = 0; IsServiceNode() const = 0;
@ -193,19 +197,10 @@ namespace llarp
/// connect to N random routers /// connect to N random routers
virtual void virtual void
ConnectToRandomRouters(int N) = 0; ConnectToRandomRouters(int N) = 0;
/// inject configuration and reconfigure router
virtual bool
Reconfigure(Config* conf) = 0;
virtual bool virtual bool
TryConnectAsync(RouterContact rc, uint16_t tries) = 0; TryConnectAsync(RouterContact rc, uint16_t tries) = 0;
/// validate new configuration against old one
/// return true on 100% valid
/// return false if not 100% valid
virtual bool
ValidateConfig(Config* conf) const = 0;
/// called by link when a remote session has no more sessions open /// called by link when a remote session has no more sessions open
virtual void virtual void
SessionClosed(RouterID remote) = 0; SessionClosed(RouterID remote) = 0;
@ -288,14 +283,23 @@ namespace llarp
virtual void virtual void
GossipRCIfNeeded(const RouterContact rc) = 0; GossipRCIfNeeded(const RouterContact rc) = 0;
/// Templated convenience function to generate a RouterHive event and
/// delegate to non-templated (and overridable) function for handling.
template <class EventType, class... Params> template <class EventType, class... Params>
void void
NotifyRouterEvent([[maybe_unused]] Params&&... args) const NotifyRouterEvent([[maybe_unused]] Params&&... args) const
{ {
#ifdef LOKINET_HIVE // TODO: no-op when appropriate
hive->NotifyEvent(std::make_unique<EventType>(std::forward<Params>(args)...)); auto event = std::make_unique<EventType>(args...);
#endif HandleRouterEvent(std::move(event));
} }
protected:
/// Virtual function to handle RouterEvent. HiveRouter overrides this in
/// order to inject the event. The default implementation in Router simply
/// logs it.
virtual void
HandleRouterEvent(tooling::RouterEventPtr event) const = 0;
}; };
} // namespace llarp } // namespace llarp

View file

@ -1,5 +1,7 @@
#include <router/outbound_session_maker.hpp> #include <router/outbound_session_maker.hpp>
#include <router/abstractrouter.hpp>
#include <tooling/peer_stats_event.hpp>
#include <link/server.hpp> #include <link/server.hpp>
#include <router_contact.hpp> #include <router_contact.hpp>
#include <nodedb.hpp> #include <nodedb.hpp>
@ -152,6 +154,7 @@ namespace llarp
void void
OutboundSessionMaker::Init( OutboundSessionMaker::Init(
AbstractRouter* router,
ILinkManager* linkManager, ILinkManager* linkManager,
I_RCLookupHandler* rcLookup, I_RCLookupHandler* rcLookup,
Profiling* profiler, Profiling* profiler,
@ -159,6 +162,7 @@ namespace llarp
llarp_nodedb* nodedb, llarp_nodedb* nodedb,
WorkerFunc_t dowork) WorkerFunc_t dowork)
{ {
_router = router;
_linkManager = linkManager; _linkManager = linkManager;
_rcLookup = rcLookup; _rcLookup = rcLookup;
_logic = logic; _logic = logic;
@ -298,8 +302,18 @@ namespace llarp
void void
OutboundSessionMaker::CreatePendingSession(const RouterID& router) OutboundSessionMaker::CreatePendingSession(const RouterID& router)
{ {
util::Lock l(_mutex); {
pendingSessions.emplace(router, nullptr); util::Lock l(_mutex);
pendingSessions.emplace(router, nullptr);
}
auto peerDb = _router->peerDb();
if (peerDb)
{
peerDb->modifyPeerStats(router, [](PeerStats& stats) { stats.numConnectionAttempts++; });
}
_router->NotifyRouterEvent<tooling::ConnectionAttemptEvent>(_router->pubkey(), router);
} }
void void

View file

@ -58,6 +58,7 @@ namespace llarp
void void
Init( Init(
AbstractRouter* router,
ILinkManager* linkManager, ILinkManager* linkManager,
I_RCLookupHandler* rcLookup, I_RCLookupHandler* rcLookup,
Profiling* profiler, Profiling* profiler,
@ -110,6 +111,7 @@ namespace llarp
std::unordered_map<RouterID, CallbacksQueue, RouterID::Hash> pendingCallbacks std::unordered_map<RouterID, CallbacksQueue, RouterID::Hash> pendingCallbacks
GUARDED_BY(_mutex); GUARDED_BY(_mutex);
AbstractRouter* _router = nullptr;
ILinkManager* _linkManager = nullptr; ILinkManager* _linkManager = nullptr;
I_RCLookupHandler* _rcLookup = nullptr; I_RCLookupHandler* _rcLookup = nullptr;
Profiling* _profiler = nullptr; Profiling* _profiler = nullptr;

View file

@ -21,8 +21,10 @@
#include <util/meta/memfn.hpp> #include <util/meta/memfn.hpp>
#include <util/str.hpp> #include <util/str.hpp>
#include <ev/ev.hpp> #include <ev/ev.hpp>
#include <tooling/peer_stats_event.hpp>
#include "tooling/router_event.hpp" #include "tooling/router_event.hpp"
#include "util/status.hpp"
#include <fstream> #include <fstream>
#include <cstdlib> #include <cstdlib>
@ -80,13 +82,18 @@ namespace llarp
{ {
if (_running) if (_running)
{ {
util::StatusObject peerStatsObj = nullptr;
if (m_peerDb)
peerStatsObj = m_peerDb->ExtractStatus();
return util::StatusObject{{"running", true}, return util::StatusObject{{"running", true},
{"numNodesKnown", _nodedb->num_loaded()}, {"numNodesKnown", _nodedb->num_loaded()},
{"dht", _dht->impl->ExtractStatus()}, {"dht", _dht->impl->ExtractStatus()},
{"services", _hiddenServiceContext.ExtractStatus()}, {"services", _hiddenServiceContext.ExtractStatus()},
{"exit", _exitContext.ExtractStatus()}, {"exit", _exitContext.ExtractStatus()},
{"links", _linkManager.ExtractStatus()}, {"links", _linkManager.ExtractStatus()},
{"outboundMessages", _outboundMessageHandler.ExtractStatus()}}; {"outboundMessages", _outboundMessageHandler.ExtractStatus()},
{"peerStats", peerStatsObj}};
} }
else else
{ {
@ -117,6 +124,9 @@ namespace llarp
void void
Router::GossipRCIfNeeded(const RouterContact rc) Router::GossipRCIfNeeded(const RouterContact rc)
{ {
if (disableGossipingRC_TestingOnly())
return;
/// if we are not a service node forget about gossip /// if we are not a service node forget about gossip
if (not IsServiceNode()) if (not IsServiceNode())
return; return;
@ -212,7 +222,31 @@ namespace llarp
return false; return false;
#endif #endif
#endif #endif
_identity = RpcClient()->ObtainIdentityKey(); constexpr int maxTries = 5;
int numTries = 0;
while (numTries < maxTries)
{
numTries++;
try
{
_identity = RpcClient()->ObtainIdentityKey();
LogWarn("Obtained lokid identity keys");
break;
}
catch (const std::exception& e)
{
LogWarn(
"Failed attempt ",
numTries,
" of ",
maxTries,
" to get lokid identity keys because: ",
e.what());
if (numTries == maxTries)
throw;
}
}
} }
else else
{ {
@ -228,16 +262,16 @@ namespace llarp
} }
bool bool
Router::Configure(Config* conf, bool isRouter, llarp_nodedb* nodedb) Router::Configure(const Config& conf, bool isRouter, llarp_nodedb* nodedb)
{ {
// we need this first so we can start lmq to fetch keys whitelistRouters = conf.lokid.whitelistRouters;
if (conf) if (whitelistRouters)
{ lokidRPCAddr = lokimq::address(conf.lokid.lokidRPCAddr);
enableRPCServer = conf->api.m_enableRPCServer;
rpcBindAddr = lokimq::address(conf->api.m_rpcBindAddr); enableRPCServer = conf.api.m_enableRPCServer;
whitelistRouters = conf->lokid.whitelistRouters; if (enableRPCServer)
lokidRPCAddr = lokimq::address(conf->lokid.lokidRPCAddr); rpcBindAddr = lokimq::address(conf.api.m_rpcBindAddr);
}
if (not StartRpcServer()) if (not StartRpcServer())
throw std::runtime_error("Failed to start rpc server"); throw std::runtime_error("Failed to start rpc server");
@ -251,13 +285,11 @@ namespace llarp
} }
// fetch keys // fetch keys
if (conf) if (not m_keyManager->initialize(conf, true, isRouter))
{ throw std::runtime_error("KeyManager failed to initialize");
if (not m_keyManager->initialize(*conf, true, isRouter)) if (!FromConfig(conf))
throw std::runtime_error("KeyManager failed to initialize"); throw std::runtime_error("FromConfig() failed");
if (!FromConfig(conf))
throw std::runtime_error("FromConfig() failed");
}
if (!InitOutboundLinks()) if (!InitOutboundLinks())
throw std::runtime_error("InitOutboundLinks() failed"); throw std::runtime_error("InitOutboundLinks() failed");
@ -374,12 +406,12 @@ namespace llarp
} }
bool bool
Router::FromConfig(Config* conf) Router::FromConfig(const Config& conf)
{ {
// Set netid before anything else // Set netid before anything else
if (!conf->router.m_netId.empty() && strcmp(conf->router.m_netId.c_str(), llarp::DEFAULT_NETID)) if (!conf.router.m_netId.empty() && strcmp(conf.router.m_netId.c_str(), llarp::DEFAULT_NETID))
{ {
const auto& netid = conf->router.m_netId; const auto& netid = conf.router.m_netId;
llarp::LogWarn( llarp::LogWarn(
"!!!! you have manually set netid to be '", "!!!! you have manually set netid to be '",
netid, netid,
@ -394,36 +426,36 @@ namespace llarp
} }
// IWP config // IWP config
m_OutboundPort = conf->links.m_OutboundLink.port; m_OutboundPort = conf.links.m_OutboundLink.port;
// Router config // Router config
_rc.SetNick(conf->router.m_nickname); _rc.SetNick(conf.router.m_nickname);
_outboundSessionMaker.maxConnectedRouters = conf->router.m_maxConnectedRouters; _outboundSessionMaker.maxConnectedRouters = conf.router.m_maxConnectedRouters;
_outboundSessionMaker.minConnectedRouters = conf->router.m_minConnectedRouters; _outboundSessionMaker.minConnectedRouters = conf.router.m_minConnectedRouters;
encryption_keyfile = m_keyManager->m_encKeyPath; encryption_keyfile = m_keyManager->m_encKeyPath;
our_rc_file = m_keyManager->m_rcPath; our_rc_file = m_keyManager->m_rcPath;
transport_keyfile = m_keyManager->m_transportKeyPath; transport_keyfile = m_keyManager->m_transportKeyPath;
ident_keyfile = m_keyManager->m_idKeyPath; ident_keyfile = m_keyManager->m_idKeyPath;
_ourAddress = conf->router.m_publicAddress; _ourAddress = conf.router.m_publicAddress;
RouterContact::BlockBogons = conf->router.m_blockBogons; RouterContact::BlockBogons = conf.router.m_blockBogons;
// Lokid Config // Lokid Config
usingSNSeed = conf->lokid.usingSNSeed; usingSNSeed = conf.lokid.usingSNSeed;
whitelistRouters = conf->lokid.whitelistRouters; whitelistRouters = conf.lokid.whitelistRouters;
lokidRPCAddr = lokimq::address(conf->lokid.lokidRPCAddr); lokidRPCAddr = lokimq::address(conf.lokid.lokidRPCAddr);
if (usingSNSeed) if (usingSNSeed)
ident_keyfile = conf->lokid.ident_keyfile; ident_keyfile = conf.lokid.ident_keyfile;
// TODO: add config flag for "is service node" // TODO: add config flag for "is service node"
if (conf->links.m_InboundLinks.size()) if (conf.links.m_InboundLinks.size())
{ {
m_isServiceNode = true; m_isServiceNode = true;
} }
networkConfig = conf->network; networkConfig = conf.network;
/// build a set of strictConnectPubkeys ( /// build a set of strictConnectPubkeys (
/// TODO: make this consistent with config -- do we support multiple strict connections /// TODO: make this consistent with config -- do we support multiple strict connections
@ -446,21 +478,21 @@ namespace llarp
throw std::invalid_argument(stringify("invalid key for strict-connect: ", val)); throw std::invalid_argument(stringify("invalid key for strict-connect: ", val));
} }
std::vector<fs::path> configRouters = conf->connect.routers; std::vector<fs::path> configRouters = conf.connect.routers;
configRouters.insert( configRouters.insert(
configRouters.end(), conf->bootstrap.routers.begin(), conf->bootstrap.routers.end()); configRouters.end(), conf.bootstrap.routers.begin(), conf.bootstrap.routers.end());
// if our conf had no bootstrap files specified, try the default location of // if our conf had no bootstrap files specified, try the default location of
// <DATA_DIR>/bootstrap.signed. If this isn't present, leave a useful error message // <DATA_DIR>/bootstrap.signed. If this isn't present, leave a useful error message
if (configRouters.size() == 0 and not m_isServiceNode) if (configRouters.size() == 0 and not m_isServiceNode)
{ {
// TODO: use constant // TODO: use constant
fs::path defaultBootstrapFile = conf->router.m_dataDir / "bootstrap.signed"; fs::path defaultBootstrapFile = conf.router.m_dataDir / "bootstrap.signed";
if (fs::exists(defaultBootstrapFile)) if (fs::exists(defaultBootstrapFile))
{ {
configRouters.push_back(defaultBootstrapFile); configRouters.push_back(defaultBootstrapFile);
} }
else if (not conf->bootstrap.skipBootstrap) else if (not conf.bootstrap.skipBootstrap)
{ {
LogError("No bootstrap files specified in config file, and the default"); LogError("No bootstrap files specified in config file, and the default");
LogError("bootstrap file ", defaultBootstrapFile, " does not exist."); LogError("bootstrap file ", defaultBootstrapFile, " does not exist.");
@ -515,6 +547,7 @@ namespace llarp
// Init components after relevant config settings loaded // Init components after relevant config settings loaded
_outboundMessageHandler.Init(&_linkManager, _logic); _outboundMessageHandler.Init(&_linkManager, _logic);
_outboundSessionMaker.Init( _outboundSessionMaker.Init(
this,
&_linkManager, &_linkManager,
&_rcLookupHandler, &_rcLookupHandler,
&_routerProfiling, &_routerProfiling,
@ -534,16 +567,16 @@ namespace llarp
m_isServiceNode); m_isServiceNode);
// create inbound links, if we are a service node // create inbound links, if we are a service node
for (const LinksConfig::LinkInfo& serverConfig : conf->links.m_InboundLinks) for (const LinksConfig::LinkInfo& serverConfig : conf.links.m_InboundLinks)
{ {
auto server = iwp::NewInboundLink( auto server = iwp::NewInboundLink(
m_keyManager, m_keyManager,
util::memFn(&AbstractRouter::rc, this), util::memFn(&AbstractRouter::rc, this),
util::memFn(&AbstractRouter::HandleRecvLinkMessageBuffer, this), util::memFn(&AbstractRouter::HandleRecvLinkMessageBuffer, this),
util::memFn(&AbstractRouter::Sign, this), util::memFn(&AbstractRouter::Sign, this),
util::memFn(&IOutboundSessionMaker::OnSessionEstablished, &_outboundSessionMaker), util::memFn(&Router::ConnectionEstablished, this),
util::memFn(&AbstractRouter::CheckRenegotiateValid, this), util::memFn(&AbstractRouter::CheckRenegotiateValid, this),
util::memFn(&IOutboundSessionMaker::OnConnectTimeout, &_outboundSessionMaker), util::memFn(&Router::ConnectionTimedOut, this),
util::memFn(&AbstractRouter::SessionClosed, this), util::memFn(&AbstractRouter::SessionClosed, this),
util::memFn(&AbstractRouter::PumpLL, this), util::memFn(&AbstractRouter::PumpLL, this),
util::memFn(&AbstractRouter::QueueWork, this)); util::memFn(&AbstractRouter::QueueWork, this));
@ -559,15 +592,15 @@ namespace llarp
} }
// Network config // Network config
if (conf->network.m_enableProfiling.has_value() and not*conf->network.m_enableProfiling) if (conf.network.m_enableProfiling.has_value() and not*conf.network.m_enableProfiling)
{ {
routerProfiling().Disable(); routerProfiling().Disable();
LogWarn("router profiling explicitly disabled"); LogWarn("router profiling explicitly disabled");
} }
if (!conf->network.m_routerProfilesFile.empty()) if (!conf.network.m_routerProfilesFile.empty())
{ {
routerProfilesFile = conf->network.m_routerProfilesFile; routerProfilesFile = conf.network.m_routerProfilesFile;
routerProfiling().Load(routerProfilesFile.c_str()); routerProfiling().Load(routerProfilesFile.c_str());
llarp::LogInfo("setting profiles to ", routerProfilesFile); llarp::LogInfo("setting profiles to ", routerProfilesFile);
} }
@ -575,15 +608,27 @@ namespace llarp
// API config // API config
if (not IsServiceNode()) if (not IsServiceNode())
{ {
hiddenServiceContext().AddEndpoint(*conf); hiddenServiceContext().AddEndpoint(conf);
}
// peer stats
if (conf.router.m_enablePeerStats)
{
LogInfo("Initializing peerdb...");
m_peerDb = std::make_shared<PeerDb>();
m_peerDb->configure(conf.router);
}
else
{
assert(not IsServiceNode()); // enable peer stats must be enabled for service nodes
} }
// Logging config // Logging config
LogContext::Instance().Initialize( LogContext::Instance().Initialize(
conf->logging.m_logLevel, conf.logging.m_logLevel,
conf->logging.m_logType, conf.logging.m_logType,
conf->logging.m_logFile, conf.logging.m_logFile,
conf->router.m_nickname, conf.router.m_nickname,
util::memFn(&AbstractRouter::QueueDiskIO, this)); util::memFn(&AbstractRouter::QueueDiskIO, this));
return true; return true;
@ -754,6 +799,20 @@ namespace llarp
{ {
nodedb()->AsyncFlushToDisk(); nodedb()->AsyncFlushToDisk();
} }
if (m_peerDb)
{
// TODO: throttle this?
// TODO: need to capture session stats when session terminates / is removed from link manager
_linkManager.updatePeerDb(m_peerDb);
if (m_peerDb->shouldFlush(now))
{
LogWarn("Queing database flush...");
QueueDiskIO([this]() { m_peerDb->flushDatabase(); });
}
}
// get connected peers // get connected peers
std::set<dht::Key_t> peersWeHave; std::set<dht::Key_t> peersWeHave;
_linkManager.ForEachPeer([&peersWeHave](ILinkSession* s) { _linkManager.ForEachPeer([&peersWeHave](ILinkSession* s) {
@ -791,6 +850,31 @@ namespace llarp
LogInfo("Session to ", remote, " fully closed"); LogInfo("Session to ", remote, " fully closed");
} }
void
Router::ConnectionTimedOut(ILinkSession* session)
{
if (m_peerDb)
{
RouterID id{session->GetPubKey()};
// TODO: make sure this is a public router (on whitelist)?
m_peerDb->modifyPeerStats(id, [&](PeerStats& stats) { stats.numConnectionTimeouts++; });
}
_outboundSessionMaker.OnConnectTimeout(session);
}
bool
Router::ConnectionEstablished(ILinkSession* session, bool inbound)
{
RouterID id{session->GetPubKey()};
if (m_peerDb)
{
// TODO: make sure this is a public router (on whitelist)?
m_peerDb->modifyPeerStats(id, [&](PeerStats& stats) { stats.numConnectionSuccesses++; });
}
NotifyRouterEvent<tooling::LinkSessionEstablishedEvent>(pubkey(), id, inbound);
return _outboundSessionMaker.OnSessionEstablished(session);
}
bool bool
Router::GetRandomConnectedRouter(RouterContact& result) const Router::GetRandomConnectedRouter(RouterContact& result) const
{ {
@ -1101,19 +1185,6 @@ namespace llarp
return true; return true;
} }
bool
Router::ValidateConfig(Config* /*conf*/) const
{
return true;
}
bool
Router::Reconfigure(Config*)
{
// TODO: implement me
return true;
}
bool bool
Router::TryConnectAsync(RouterContact rc, uint16_t tries) Router::TryConnectAsync(RouterContact rc, uint16_t tries)
{ {
@ -1142,9 +1213,9 @@ namespace llarp
util::memFn(&AbstractRouter::rc, this), util::memFn(&AbstractRouter::rc, this),
util::memFn(&AbstractRouter::HandleRecvLinkMessageBuffer, this), util::memFn(&AbstractRouter::HandleRecvLinkMessageBuffer, this),
util::memFn(&AbstractRouter::Sign, this), util::memFn(&AbstractRouter::Sign, this),
util::memFn(&IOutboundSessionMaker::OnSessionEstablished, &_outboundSessionMaker), util::memFn(&Router::ConnectionEstablished, this),
util::memFn(&AbstractRouter::CheckRenegotiateValid, this), util::memFn(&AbstractRouter::CheckRenegotiateValid, this),
util::memFn(&IOutboundSessionMaker::OnConnectTimeout, &_outboundSessionMaker), util::memFn(&Router::ConnectionTimedOut, this),
util::memFn(&AbstractRouter::SessionClosed, this), util::memFn(&AbstractRouter::SessionClosed, this),
util::memFn(&AbstractRouter::PumpLL, this), util::memFn(&AbstractRouter::PumpLL, this),
util::memFn(&AbstractRouter::QueueWork, this)); util::memFn(&AbstractRouter::QueueWork, this));
@ -1178,4 +1249,11 @@ namespace llarp
LogDebug("Message failed sending to ", remote); LogDebug("Message failed sending to ", remote);
} }
} }
void
Router::HandleRouterEvent(tooling::RouterEventPtr event) const
{
LogDebug(event->ToString());
}
} // namespace llarp } // namespace llarp

View file

@ -16,6 +16,7 @@
#include <messages/link_message_parser.hpp> #include <messages/link_message_parser.hpp>
#include <nodedb.hpp> #include <nodedb.hpp>
#include <path/path_context.hpp> #include <path/path_context.hpp>
#include <peerstats/peer_db.hpp>
#include <profiling.hpp> #include <profiling.hpp>
#include <router_contact.hpp> #include <router_contact.hpp>
#include <router/outbound_message_handler.hpp> #include <router/outbound_message_handler.hpp>
@ -48,7 +49,7 @@
namespace llarp namespace llarp
{ {
struct Router final : public AbstractRouter struct Router : public AbstractRouter
{ {
llarp_time_t _lastPump = 0s; llarp_time_t _lastPump = 0s;
bool ready; bool ready;
@ -306,12 +307,18 @@ namespace llarp
return _rcLookupHandler; return _rcLookupHandler;
} }
std::shared_ptr<PeerDb>
peerDb() override
{
return m_peerDb;
}
void void
GossipRCIfNeeded(const RouterContact rc) override; GossipRCIfNeeded(const RouterContact rc) override;
explicit Router(llarp_ev_loop_ptr __netloop, std::shared_ptr<Logic> logic); explicit Router(llarp_ev_loop_ptr __netloop, std::shared_ptr<Logic> logic);
~Router() override; virtual ~Router() override;
bool bool
HandleRecvLinkMessageBuffer(ILinkSession* from, const llarp_buffer_t& msg) override; HandleRecvLinkMessageBuffer(ILinkSession* from, const llarp_buffer_t& msg) override;
@ -338,7 +345,7 @@ namespace llarp
Close(); Close();
bool bool
Configure(Config* conf, bool isRouter, llarp_nodedb* nodedb = nullptr) override; Configure(const Config& conf, bool isRouter, llarp_nodedb* nodedb = nullptr) override;
bool bool
StartRpcServer() override; StartRpcServer() override;
@ -385,19 +392,9 @@ namespace llarp
void void
try_connect(fs::path rcfile); try_connect(fs::path rcfile);
/// inject configuration and reconfigure router
bool
Reconfigure(Config* conf) override;
bool bool
TryConnectAsync(RouterContact rc, uint16_t tries) override; TryConnectAsync(RouterContact rc, uint16_t tries) override;
/// validate new configuration against old one
/// return true on 100% valid
/// return false if not 100% valid
bool
ValidateConfig(Config* conf) const override;
/// send to remote router or queue for sending /// send to remote router or queue for sending
/// returns false on overflow /// returns false on overflow
/// returns true on successful queue /// returns true on successful queue
@ -427,6 +424,14 @@ namespace llarp
void void
SessionClosed(RouterID remote) override; SessionClosed(RouterID remote) override;
/// called by link when an unestablished connection times out
void
ConnectionTimedOut(ILinkSession* session);
/// called by link when session is fully established
bool
ConnectionEstablished(ILinkSession* session, bool inbound);
/// call internal router ticker /// call internal router ticker
void void
Tick(); Tick();
@ -495,6 +500,7 @@ namespace llarp
llarp_time_t m_LastStatsReport = 0s; llarp_time_t m_LastStatsReport = 0s;
std::shared_ptr<llarp::KeyManager> m_keyManager; std::shared_ptr<llarp::KeyManager> m_keyManager;
std::shared_ptr<PeerDb> m_peerDb;
uint32_t path_build_count = 0; uint32_t path_build_count = 0;
@ -508,10 +514,20 @@ namespace llarp
UpdateOurRC(bool rotateKeys = false); UpdateOurRC(bool rotateKeys = false);
bool bool
FromConfig(Config* conf); FromConfig(const Config& conf);
void void
MessageSent(const RouterID& remote, SendStatus status); MessageSent(const RouterID& remote, SendStatus status);
protected:
virtual void
HandleRouterEvent(tooling::RouterEventPtr event) const override;
virtual bool
disableGossipingRC_TestingOnly()
{
return false;
};
}; };
} // namespace llarp } // namespace llarp

View file

@ -1,5 +1,6 @@
#include <rpc/lokid_rpc_client.hpp> #include <rpc/lokid_rpc_client.hpp>
#include <stdexcept>
#include <util/logging/logger.hpp> #include <util/logging/logger.hpp>
#include <router/abstractrouter.hpp> #include <router/abstractrouter.hpp>
@ -36,18 +37,20 @@ namespace llarp
: m_lokiMQ(std::move(lmq)), m_Router(r) : m_lokiMQ(std::move(lmq)), m_Router(r)
{ {
// m_lokiMQ->log_level(toLokiMQLogLevel(LogLevel::Instance().curLevel)); // m_lokiMQ->log_level(toLokiMQLogLevel(LogLevel::Instance().curLevel));
// TODO: proper auth here
auto lokidCategory = m_lokiMQ->add_category("lokid", lokimq::Access{lokimq::AuthLevel::none});
lokidCategory.add_request_command(
"get_peer_stats", [this](lokimq::Message& m) { HandleGetPeerStats(m); });
} }
void void
LokidRpcClient::ConnectAsync(lokimq::address url) LokidRpcClient::ConnectAsync(lokimq::address url)
{ {
LogInfo("connecting to lokid via LMQ at ", url); LogInfo("connecting to lokid via LMQ at ", url);
m_lokiMQ->connect_remote( m_Connection = m_lokiMQ->connect_remote(
url, url,
[self = shared_from_this()](lokimq::ConnectionID c) { [self = shared_from_this()](lokimq::ConnectionID) { self->Connected(); },
self->m_Connection = std::move(c);
self->Connected();
},
[self = shared_from_this(), url](lokimq::ConnectionID, std::string_view f) { [self = shared_from_this(), url](lokimq::ConnectionID, std::string_view f) {
llarp::LogWarn("Failed to connect to lokid: ", f); llarp::LogWarn("Failed to connect to lokid: ", f);
LogicCall(self->m_Router->logic(), [self, url]() { self->ConnectAsync(url); }); LogicCall(self->m_Router->logic(), [self, url]() { self->ConnectAsync(url); });
@ -100,10 +103,18 @@ namespace llarp
constexpr auto PingInterval = 1min; constexpr auto PingInterval = 1min;
constexpr auto NodeListUpdateInterval = 30s; constexpr auto NodeListUpdateInterval = 30s;
LogInfo("we connected to lokid [", *m_Connection, "]"); auto makePingRequest = [self = shared_from_this()]() {
Command("admin.lokinet_ping"); nlohmann::json payload = {{"version", {VERSION[0], VERSION[1], VERSION[2]}}};
m_lokiMQ->add_timer( self->Request(
[self = shared_from_this()]() { self->Command("admin.lokinet_ping"); }, PingInterval); "admin.lokinet_ping",
[](bool success, std::vector<std::string> data) {
(void)data;
LogDebug("Received response for ping. Successful: ", success);
},
payload.dump());
};
makePingRequest();
m_lokiMQ->add_timer(makePingRequest, PingInterval);
m_lokiMQ->add_timer( m_lokiMQ->add_timer(
[self = shared_from_this()]() { self->UpdateServiceNodeList(); }, NodeListUpdateInterval); [self = shared_from_this()]() { self->UpdateServiceNodeList(); }, NodeListUpdateInterval);
UpdateServiceNodeList(); UpdateServiceNodeList();
@ -175,13 +186,13 @@ namespace llarp
"failed to get private key request " "failed to get private key request "
"failed"); "failed");
} }
if (data.empty()) if (data.empty() or data.size() < 2)
{ {
throw std::runtime_error( throw std::runtime_error(
"failed to get private key request " "failed to get private key request "
"data empty"); "data empty");
} }
const auto j = nlohmann::json::parse(data[0]); const auto j = nlohmann::json::parse(data[1]);
SecretKey k; SecretKey k;
if (not k.FromHex(j.at("service_node_ed25519_privkey").get<std::string>())) if (not k.FromHex(j.at("service_node_ed25519_privkey").get<std::string>()))
{ {
@ -189,8 +200,14 @@ namespace llarp
} }
promise.set_value(k); promise.set_value(k);
} }
catch (const std::exception& e)
{
LogWarn("Caught exception while trying to request admin keys: ", e.what());
promise.set_exception(std::current_exception());
}
catch (...) catch (...)
{ {
LogWarn("Caught non-standard exception while trying to request admin keys");
promise.set_exception(std::current_exception()); promise.set_exception(std::current_exception());
} }
}); });
@ -198,5 +215,74 @@ namespace llarp
return ftr.get(); return ftr.get();
} }
void
LokidRpcClient::HandleGetPeerStats(lokimq::Message& msg)
{
LogInfo("Got request for peer stats (size: ", msg.data.size(), ")");
for (auto str : msg.data)
{
LogInfo(" :", str);
}
assert(m_Router != nullptr);
if (not m_Router->peerDb())
{
LogWarn("HandleGetPeerStats called when router has no peerDb set up.");
// TODO: this can sometimes occur if lokid hits our API before we're done configuring
// (mostly an issue in a loopback testnet)
msg.send_reply("EAGAIN");
return;
}
try
{
// msg.data[0] is expected to contain a bt list of router ids (in our preferred string
// format)
if (msg.data.empty())
{
LogWarn("lokid requested peer stats with no request body");
msg.send_reply("peer stats request requires list of router IDs");
return;
}
std::vector<std::string> routerIdStrings;
lokimq::bt_deserialize(msg.data[0], routerIdStrings);
std::vector<RouterID> routerIds;
routerIds.reserve(routerIdStrings.size());
for (const auto& routerIdString : routerIdStrings)
{
RouterID id;
if (not id.FromString(routerIdString))
{
LogWarn("lokid sent us an invalid router id: ", routerIdString);
msg.send_reply("Invalid router id");
return;
}
routerIds.push_back(std::move(id));
}
auto statsList = m_Router->peerDb()->listPeerStats(routerIds);
int32_t bufSize =
256 + (statsList.size() * 1024); // TODO: tune this or allow to grow dynamically
auto buf = std::unique_ptr<uint8_t[]>(new uint8_t[bufSize]);
llarp_buffer_t llarpBuf(buf.get(), bufSize);
PeerStats::BEncodeList(statsList, &llarpBuf);
msg.send_reply(std::string_view((const char*)llarpBuf.base, llarpBuf.cur - llarpBuf.base));
}
catch (const std::exception& e)
{
LogError("Failed to handle get_peer_stats request: ", e.what());
msg.send_reply("server error");
}
}
} // namespace rpc } // namespace rpc
} // namespace llarp } // namespace llarp

View file

@ -59,6 +59,10 @@ namespace llarp
void void
HandleGotServiceNodeList(std::string json); HandleGotServiceNodeList(std::string json);
// Handles request from lokid for peer stats on a specific peer
void
HandleGetPeerStats(lokimq::Message& msg);
std::optional<lokimq::ConnectionID> m_Connection; std::optional<lokimq::ConnectionID> m_Connection;
LMQ_ptr m_lokiMQ; LMQ_ptr m_lokiMQ;
std::string m_CurrentBlockHash; std::string m_CurrentBlockHash;

View file

@ -0,0 +1,33 @@
#include <tooling/hive_context.hpp>
#include <tooling/hive_router.hpp>
namespace tooling
{
HiveContext::HiveContext(RouterHive* hive) : m_hive(hive)
{
}
std::unique_ptr<llarp::AbstractRouter>
HiveContext::makeRouter(
llarp_ev_loop_ptr netloop,
std::shared_ptr<llarp::Logic> logic)
{
return std::make_unique<HiveRouter>(netloop, logic, m_hive);
}
HiveRouter*
HiveContext::getRouterAsHiveRouter()
{
if (not router)
return nullptr;
HiveRouter* hiveRouter = dynamic_cast<HiveRouter*>(router.get());
if (hiveRouter == nullptr)
throw std::runtime_error("HiveContext has a router not of type HiveRouter");
return hiveRouter;
}
} // namespace tooling

View file

@ -0,0 +1,30 @@
#pragma once
#include <llarp.hpp>
#include <tooling/hive_router.hpp>
namespace tooling
{
/// HiveContext is a subclass of llarp::Context which allows RouterHive to
/// perform custom behavior which might be undesirable in production code.
struct HiveContext : public llarp::Context
{
HiveContext(RouterHive* hive);
std::unique_ptr<llarp::AbstractRouter>
makeRouter(
llarp_ev_loop_ptr netloop,
std::shared_ptr<llarp::Logic> logic) override;
/// Get this context's router as a HiveRouter.
///
/// Returns nullptr if there is no router or throws an exception if the
/// router is somehow not an instance of HiveRouter.
HiveRouter*
getRouterAsHiveRouter();
protected:
RouterHive* m_hive = nullptr;
};
} // namespace tooling

View file

@ -0,0 +1,37 @@
#include <tooling/hive_router.hpp>
#include <tooling/router_hive.hpp>
namespace tooling
{
HiveRouter::HiveRouter(
llarp_ev_loop_ptr netloop, std::shared_ptr<llarp::Logic> logic, RouterHive* hive)
: Router(netloop, logic), m_hive(hive)
{
}
bool
HiveRouter::disableGossipingRC_TestingOnly()
{
return m_disableGossiping;
}
void
HiveRouter::disableGossiping()
{
m_disableGossiping = true;
}
void
HiveRouter::enableGossiping()
{
m_disableGossiping = false;
}
void
HiveRouter::HandleRouterEvent(RouterEventPtr event) const
{
m_hive->NotifyEvent(std::move(event));
}
} // namespace tooling

View file

@ -0,0 +1,38 @@
#pragma once
#include <router/router.hpp>
namespace tooling
{
/// HiveRouter is a subclass of Router which overrides specific behavior in
/// order to perform testing-related functions. It exists largely to prevent
/// this behavior (which may often be "dangerous") from leaking into release
/// code.
struct HiveRouter : public llarp::Router
{
HiveRouter(
llarp_ev_loop_ptr netloop,
std::shared_ptr<llarp::Logic> logic,
RouterHive* hive);
virtual ~HiveRouter() = default;
/// Override logic to prevent base Router class from gossiping its RC.
virtual bool
disableGossipingRC_TestingOnly() override;
void
disableGossiping();
void
enableGossiping();
protected:
bool m_disableGossiping = false;
RouterHive* m_hive = nullptr;
virtual void
HandleRouterEvent(RouterEventPtr event) const override;
};
} // namespace tooling

View file

@ -0,0 +1,44 @@
#pragma once
#include "router_event.hpp"
namespace tooling
{
struct LinkSessionEstablishedEvent : public RouterEvent
{
llarp::RouterID remoteId;
bool inbound = false;
LinkSessionEstablishedEvent(
const llarp::RouterID& ourRouterId, const llarp::RouterID& remoteId_, bool inbound_)
: RouterEvent("Link: LinkSessionEstablishedEvent", ourRouterId, false)
, remoteId(remoteId_)
, inbound(inbound_)
{
}
std::string
ToString() const
{
return RouterEvent::ToString() + (inbound ? "inbound" : "outbound")
+ " : LinkSessionEstablished with " + remoteId.ToString();
}
};
struct ConnectionAttemptEvent : public RouterEvent
{
llarp::RouterID remoteId;
ConnectionAttemptEvent(const llarp::RouterID& ourRouterId, const llarp::RouterID& remoteId_)
: RouterEvent("Link: ConnectionAttemptEvent", ourRouterId, false), remoteId(remoteId_)
{
}
std::string
ToString() const
{
return RouterEvent::ToString() + " : LinkSessionEstablished with " + remoteId.ToString();
}
};
} // namespace tooling

View file

@ -14,45 +14,43 @@ using namespace std::chrono_literals;
namespace tooling namespace tooling
{ {
void void
RouterHive::AddRouter( RouterHive::AddRouter(const std::shared_ptr<llarp::Config>& config, bool isRouter)
const std::shared_ptr<llarp::Config>& config, std::vector<llarp_main*>* routers, bool isRelay)
{ {
llarp_main* ctx = llarp_main_init_from_config(config->Copy(), isRelay); auto& container = (isRouter ? relays : clients);
auto result = llarp_main_setup(ctx, isRelay);
if (result == 0) llarp::RuntimeOptions opts;
{ opts.isRouter = isRouter;
llarp::Context::Get(ctx)->InjectHive(this);
routers->push_back(ctx); Context_ptr context = std::make_shared<HiveContext>(this);
} context->Configure(*config);
else context->Setup(opts);
{
throw std::runtime_error(llarp::stringify( auto routerId = llarp::RouterID(context->router->pubkey());
"Failed to add RouterHive ", container[routerId] = context;
(isRelay ? "relay" : "client"), std::cout << "Generated router with ID " << routerId << std::endl;
", llarp_main_setup() returned ",
result));
}
} }
void void
RouterHive::AddRelay(const std::shared_ptr<llarp::Config>& config) RouterHive::AddRelay(const std::shared_ptr<llarp::Config>& config)
{ {
AddRouter(config, &relays, true); AddRouter(config, true);
} }
void void
RouterHive::AddClient(const std::shared_ptr<llarp::Config>& config) RouterHive::AddClient(const std::shared_ptr<llarp::Config>& config)
{ {
AddRouter(config, &clients, false); AddRouter(config, false);
} }
void void
RouterHive::StartRouters(std::vector<llarp_main*>* routers, bool isRelay) RouterHive::StartRouters(bool isRelay)
{ {
for (llarp_main* ctx : *routers) auto& container = (isRelay ? relays : clients);
for (auto [routerId, ctx] : container)
{ {
routerMainThreads.emplace_back([=]() { routerMainThreads.emplace_back([=]() {
llarp_main_run(ctx, llarp_main_runtime_opts{false, false, false, isRelay}); ctx->Run(llarp::RuntimeOptions{false, false, isRelay});
}); });
std::this_thread::sleep_for(2ms); std::this_thread::sleep_for(2ms);
} }
@ -61,39 +59,39 @@ namespace tooling
void void
RouterHive::StartRelays() RouterHive::StartRelays()
{ {
StartRouters(&relays, true); StartRouters(true);
} }
void void
RouterHive::StartClients() RouterHive::StartClients()
{ {
StartRouters(&clients, false); StartRouters(false);
} }
void void
RouterHive::StopRouters() RouterHive::StopRouters()
{ {
llarp::LogInfo("Signalling all routers to stop"); llarp::LogInfo("Signalling all routers to stop");
for (llarp_main* ctx : relays) for (auto [routerId, ctx] : relays)
{ {
llarp_main_signal(ctx, 2 /* SIGINT */); LogicCall(ctx->logic, [ctx]() { ctx->HandleSignal(SIGINT); });
} }
for (llarp_main* ctx : clients) for (auto [routerId, ctx] : clients)
{ {
llarp_main_signal(ctx, 2 /* SIGINT */); LogicCall(ctx->logic, [ctx]() { ctx->HandleSignal(SIGINT); });
} }
llarp::LogInfo("Waiting on routers to be stopped"); llarp::LogInfo("Waiting on routers to be stopped");
for (llarp_main* ctx : relays) for (auto [routerId, ctx] : relays)
{ {
while (llarp_main_is_running(ctx)) while (ctx->IsUp())
{ {
std::this_thread::sleep_for(10ms); std::this_thread::sleep_for(10ms);
} }
} }
for (llarp_main* ctx : clients) for (auto [routerId, ctx] : clients)
{ {
while (llarp_main_is_running(ctx)) while (ctx->IsUp())
{ {
std::this_thread::sleep_for(10ms); std::this_thread::sleep_for(10ms);
} }
@ -148,46 +146,40 @@ namespace tooling
} }
void void
RouterHive::VisitRouter(llarp_main* router, std::function<void(Context_ptr)> visit) RouterHive::VisitRouter(Context_ptr ctx, std::function<void(Context_ptr)> visit)
{ {
auto ctx = llarp::Context::Get(router); // TODO: this should be called from each router's appropriate Logic thread, e.g.:
LogicCall(ctx->logic, [visit, ctx]() { visit(ctx); }); // LogicCall(ctx->logic, [visit, ctx]() { visit(ctx); });
// however, this causes visit calls to be deferred
visit(ctx);
} }
void HiveRouter*
RouterHive::VisitRelay(size_t index, std::function<void(Context_ptr)> visit) RouterHive::GetRelay(const llarp::RouterID& id, bool needMutexLock)
{ {
if (index >= relays.size()) auto guard =
{ needMutexLock ? std::make_optional<std::lock_guard<std::mutex>>(routerMutex) : std::nullopt;
visit(nullptr);
return;
}
VisitRouter(relays[index], visit);
}
void auto itr = relays.find(id);
RouterHive::VisitClient(size_t index, std::function<void(Context_ptr)> visit) if (itr == relays.end())
{ return nullptr;
if (index >= clients.size())
{ auto ctx = itr->second;
visit(nullptr); return ctx->getRouterAsHiveRouter();
return;
}
VisitRouter(clients[index], visit);
} }
std::vector<size_t> std::vector<size_t>
RouterHive::RelayConnectedRelays() RouterHive::RelayConnectedRelays()
{ {
std::lock_guard<std::mutex> guard{routerMutex};
std::vector<size_t> results; std::vector<size_t> results;
results.resize(relays.size()); results.resize(relays.size());
std::mutex results_lock; std::mutex results_lock;
size_t i = 0; size_t i = 0;
size_t done_count = 0; size_t done_count = 0;
for (auto relay : relays) for (auto [routerId, ctx] : relays)
{ {
auto ctx = llarp::Context::Get(relay);
LogicCall(ctx->logic, [&, i, ctx]() { LogicCall(ctx->logic, [&, i, ctx]() {
size_t count = ctx->router->NumberOfConnectedRouters(); size_t count = ctx->router->NumberOfConnectedRouters();
std::lock_guard<std::mutex> guard{results_lock}; std::lock_guard<std::mutex> guard{results_lock};
@ -216,17 +208,43 @@ namespace tooling
std::vector<llarp::RouterContact> std::vector<llarp::RouterContact>
RouterHive::GetRelayRCs() RouterHive::GetRelayRCs()
{ {
std::lock_guard<std::mutex> guard{routerMutex};
std::vector<llarp::RouterContact> results; std::vector<llarp::RouterContact> results;
results.resize(relays.size()); results.resize(relays.size());
size_t i = 0; size_t i = 0;
for (auto relay : relays) for (auto [routerId, ctx] : relays)
{ {
auto ctx = llarp::Context::Get(relay);
results[i] = ctx->router->rc(); results[i] = ctx->router->rc();
i++; i++;
} }
return results; return results;
} }
void
RouterHive::ForEachRelay(std::function<void(Context_ptr)> visit)
{
for (auto [routerId, ctx] : relays)
{
VisitRouter(ctx, visit);
}
}
void
RouterHive::ForEachClient(std::function<void(Context_ptr)> visit)
{
for (auto [routerId, ctx] : clients)
{
VisitRouter(ctx, visit);
}
}
/// safely visit every router context
void
RouterHive::ForEachRouter(std::function<void(Context_ptr)> visit)
{
ForEachRelay(visit);
ForEachClient(visit);
}
} // namespace tooling } // namespace tooling

View file

@ -4,6 +4,7 @@
#include <llarp.h> #include <llarp.h>
#include <config/config.hpp> #include <config/config.hpp>
#include <tooling/hive_context.hpp>
#include <vector> #include <vector>
#include <deque> #include <deque>
@ -16,35 +17,26 @@ struct llarp_main;
namespace llarp namespace llarp
{ {
struct Context; struct Context;
} } // namespace llarp
namespace tooling namespace tooling
{ {
struct HiveRouter; // Hive's version of Router
struct RouterHive struct RouterHive
{ {
using Context_ptr = std::shared_ptr<llarp::Context>; using Context_ptr = std::shared_ptr<HiveContext>;
private: private:
void void
StartRouters(std::vector<llarp_main*>* routers, bool isRelay); StartRouters(bool isRelay);
void void
AddRouter( AddRouter(const std::shared_ptr<llarp::Config>& config, bool isRelay);
const std::shared_ptr<llarp::Config>& config,
std::vector<llarp_main*>* routers,
bool isRelay);
/// safely visit router /// safely visit router (asynchronously)
void void
VisitRouter(llarp_main* router, std::function<void(Context_ptr)> visit); VisitRouter(Context_ptr ctx, std::function<void(Context_ptr)> visit);
/// safely visit relay at index N
void
VisitRelay(size_t index, std::function<void(Context_ptr)> visit);
/// safely visit client at index N
void
VisitClient(size_t index, std::function<void(Context_ptr)> visit);
public: public:
RouterHive() = default; RouterHive() = default;
@ -73,31 +65,16 @@ namespace tooling
std::deque<RouterEventPtr> std::deque<RouterEventPtr>
GetAllEvents(); GetAllEvents();
// functions to safely visit each relay and/or client's HiveContext
void void
ForEachRelay(std::function<void(Context_ptr)> visit) ForEachRelay(std::function<void(Context_ptr)> visit);
{ void
for (size_t idx = 0; idx < relays.size(); ++idx) ForEachClient(std::function<void(Context_ptr)> visit);
{ void
VisitRelay(idx, visit); ForEachRouter(std::function<void(Context_ptr)> visit);
}
}
void HiveRouter*
ForEachClient(std::function<void(Context_ptr)> visit) GetRelay(const llarp::RouterID& id, bool needMutexLock = true);
{
for (size_t idx = 0; idx < clients.size(); ++idx)
{
VisitClient(idx, visit);
}
}
/// safely visit every router context
void
ForEachRouter(std::function<void(Context_ptr)> visit)
{
ForEachRelay(visit);
ForEachClient(visit);
}
std::vector<size_t> std::vector<size_t>
RelayConnectedRelays(); RelayConnectedRelays();
@ -105,8 +82,9 @@ namespace tooling
std::vector<llarp::RouterContact> std::vector<llarp::RouterContact>
GetRelayRCs(); GetRelayRCs();
std::vector<llarp_main*> relays; std::mutex routerMutex;
std::vector<llarp_main*> clients; std::unordered_map<llarp::RouterID, Context_ptr, llarp::RouterID::Hash> relays;
std::unordered_map<llarp::RouterID, Context_ptr, llarp::RouterID::Hash> clients;
std::vector<std::thread> routerMainThreads; std::vector<std::thread> routerMainThreads;

View file

@ -1,11 +1,13 @@
pybind11_add_module(pyllarp MODULE pybind11_add_module(pyllarp MODULE
module.cpp module.cpp
llarp/context.cpp llarp/context.cpp
llarp/router.cpp
llarp/router_id.cpp llarp/router_id.cpp
llarp/router_contact.cpp llarp/router_contact.cpp
llarp/crypto/types.cpp llarp/crypto/types.cpp
llarp/config.cpp llarp/config.cpp
llarp/logger.cpp llarp/logger.cpp
llarp/peerstats.cpp
llarp/dht/dht_types.cpp llarp/dht/dht_types.cpp
llarp/path/path_types.cpp llarp/path/path_types.cpp
llarp/path/path_hop_config.cpp llarp/path/path_hop_config.cpp

View file

@ -20,6 +20,9 @@ namespace llarp
void void
CryptoTypes_Init(py::module& mod); CryptoTypes_Init(py::module& mod);
void
AbstractRouter_Init(py::module& mod);
void void
RouterID_Init(py::module& mod); RouterID_Init(py::module& mod);
@ -32,6 +35,12 @@ namespace llarp
void void
PathTypes_Init(py::module& mod); PathTypes_Init(py::module& mod);
void
PeerDb_Init(py::module& mod);
void
PeerStats_Init(py::module& mod);
namespace dht namespace dht
{ {
void void
@ -65,4 +74,10 @@ namespace tooling
void void
RouterEvent_Init(py::module& mod); RouterEvent_Init(py::module& mod);
void
HiveContext_Init(py::module& mod);
void
HiveRouter_Init(py::module& mod);
} // namespace tooling } // namespace tooling

View file

@ -48,7 +48,8 @@ namespace llarp
}) })
.def_readwrite("workerThreads", &RouterConfig::m_workerThreads) .def_readwrite("workerThreads", &RouterConfig::m_workerThreads)
.def_readwrite("numNetThreads", &RouterConfig::m_numNetThreads) .def_readwrite("numNetThreads", &RouterConfig::m_numNetThreads)
.def_readwrite("JobQueueSize", &RouterConfig::m_JobQueueSize); .def_readwrite("JobQueueSize", &RouterConfig::m_JobQueueSize)
.def_readwrite("enablePeerStats", &RouterConfig::m_enablePeerStats);
py::class_<NetworkConfig>(mod, "NetworkConfig") py::class_<NetworkConfig>(mod, "NetworkConfig")
.def(py::init<>()) .def(py::init<>())
@ -101,7 +102,10 @@ namespace llarp
.def_readwrite("usingSNSeed", &LokidConfig::usingSNSeed) .def_readwrite("usingSNSeed", &LokidConfig::usingSNSeed)
.def_readwrite("whitelistRouters", &LokidConfig::whitelistRouters) .def_readwrite("whitelistRouters", &LokidConfig::whitelistRouters)
.def_readwrite("ident_keyfile", &LokidConfig::ident_keyfile) .def_readwrite("ident_keyfile", &LokidConfig::ident_keyfile)
.def_readwrite("lokidRPCAddr", &LokidConfig::lokidRPCAddr); .def_property(
"lokidRPCAddr",
[](LokidConfig& self) { return self.lokidRPCAddr.full_address().c_str(); },
[](LokidConfig& self, std::string arg) { self.lokidRPCAddr = lokimq::address(arg); });
py::class_<BootstrapConfig>(mod, "BootstrapConfig") py::class_<BootstrapConfig>(mod, "BootstrapConfig")
.def(py::init<>()) .def(py::init<>())

View file

@ -1,5 +1,6 @@
#include "common.hpp" #include "common.hpp"
#include <llarp.hpp> #include <llarp.hpp>
#include <tooling/hive_context.hpp>
#include <router/router.cpp> #include <router/router.cpp>
#include "llarp/handlers/pyhandler.hpp" #include "llarp/handlers/pyhandler.hpp"
namespace llarp namespace llarp
@ -11,8 +12,10 @@ namespace llarp
py::class_<Context, Context_ptr>(mod, "Context") py::class_<Context, Context_ptr>(mod, "Context")
.def( .def(
"Setup", "Setup",
[](Context_ptr self, bool isRelay) -> bool { return self->Setup(isRelay) == 0; }) [](Context_ptr self, bool isRouter) {
.def("Run", [](Context_ptr self) -> int { return self->Run(llarp_main_runtime_opts{}); }) self->Setup({false, false, isRouter});
})
.def("Run", [](Context_ptr self) -> int { return self->Run(RuntimeOptions{}); })
.def("Stop", [](Context_ptr self) { self->CloseAsync(); }) .def("Stop", [](Context_ptr self) { self->CloseAsync(); })
.def("IsUp", &Context::IsUp) .def("IsUp", &Context::IsUp)
.def("IsRelay", [](Context_ptr self) -> bool { return self->router->IsServiceNode(); }) .def("IsRelay", [](Context_ptr self) -> bool { return self->router->IsServiceNode(); })
@ -34,4 +37,19 @@ namespace llarp
}) })
.def("CallSafe", &Context::CallSafe); .def("CallSafe", &Context::CallSafe);
} }
} // namespace llarp } // namespace llarp
namespace tooling
{
void
HiveContext_Init(py::module& mod)
{
using HiveContext_ptr = std::shared_ptr<HiveContext>;
py::class_<tooling::HiveContext, HiveContext_ptr, llarp::Context>(mod, "HiveContext")
.def(
"getRouterAsHiveRouter",
&tooling::HiveContext::getRouterAsHiveRouter,
py::return_value_policy::reference);
}
} // namespace tooling

View file

@ -0,0 +1,40 @@
#include "common.hpp"
#include "config/config.hpp"
#include "peerstats/peer_db.hpp"
#include "peerstats/types.hpp"
#include <netinet/in.h>
namespace llarp
{
void
PeerDb_Init(py::module& mod)
{
using PeerDb_ptr = std::shared_ptr<PeerDb>;
py::class_<PeerDb, PeerDb_ptr>(mod, "PeerDb")
.def("getCurrentPeerStats", &PeerDb::getCurrentPeerStats);
}
void
PeerStats_Init(py::module& mod)
{
py::class_<PeerStats>(mod, "PeerStats")
.def_readwrite("routerId", &PeerStats::routerId)
.def_readwrite("numConnectionAttempts", &PeerStats::numConnectionAttempts)
.def_readwrite("numConnectionSuccesses", &PeerStats::numConnectionSuccesses)
.def_readwrite("numConnectionRejections", &PeerStats::numConnectionRejections)
.def_readwrite("numConnectionTimeouts", &PeerStats::numConnectionTimeouts)
.def_readwrite("numPathBuilds", &PeerStats::numPathBuilds)
.def_readwrite("numPacketsAttempted", &PeerStats::numPacketsAttempted)
.def_readwrite("numPacketsSent", &PeerStats::numPacketsSent)
.def_readwrite("numPacketsDropped", &PeerStats::numPacketsDropped)
.def_readwrite("numPacketsResent", &PeerStats::numPacketsResent)
.def_readwrite("numDistinctRCsReceived", &PeerStats::numDistinctRCsReceived)
.def_readwrite("numLateRCs", &PeerStats::numLateRCs)
.def_readwrite("peakBandwidthBytesPerSec", &PeerStats::peakBandwidthBytesPerSec)
.def_readwrite("longestRCReceiveInterval", &PeerStats::longestRCReceiveInterval)
.def_readwrite("leastRCRemainingLifetime", &PeerStats::leastRCRemainingLifetime)
.def_readwrite("lastRCUpdated", &PeerStats::lastRCUpdated)
.def_readwrite("stale", &PeerStats::stale);
}
} // namespace llarp

28
pybind/llarp/router.cpp Normal file
View file

@ -0,0 +1,28 @@
#include "common.hpp"
#include "router/abstractrouter.hpp"
#include "tooling/hive_router.hpp"
namespace llarp
{
void
AbstractRouter_Init(py::module& mod)
{
py::class_<AbstractRouter>(mod, "AbstractRouter")
.def("rc", &AbstractRouter::rc)
.def("Stop", &AbstractRouter::Stop)
.def("peerDb", &AbstractRouter::peerDb);
}
} // namespace llarp
namespace tooling
{
void
HiveRouter_Init(py::module& mod)
{
py::class_<HiveRouter, llarp::AbstractRouter>(mod, "HiveRouter")
.def("disableGossiping", &HiveRouter::disableGossiping)
.def("enableGossiping", &HiveRouter::enableGossiping);
}
} // namespace tooling

View file

@ -18,8 +18,6 @@ namespace llarp
.def("__repr__", &RouterID::ToString) .def("__repr__", &RouterID::ToString)
.def("__str__", &RouterID::ToString) .def("__str__", &RouterID::ToString)
.def("ShortString", &RouterID::ShortString) .def("ShortString", &RouterID::ShortString)
.def("__eq__", [](const RouterID* const lhs, const RouterID* const rhs) { .def("__eq__", [](const RouterID& lhs, const RouterID& rhs) { return lhs == rhs; });
return *lhs == *rhs;
});
} }
} // namespace llarp } // namespace llarp

View file

@ -0,0 +1,28 @@
#pragma once
#include "router_event.hpp"
namespace tooling
{
struct LinkSessionEstablishedEvent : public RouterEvent
{
llarp::RouterID remoteId;
bool inbound = false;
LinkSessionEstablishedEvent(
const llarp::RouterID& ourRouterId, const llarp::RouterID& remoteId_, bool inbound_)
: RouterEvent("Link: LinkSessionEstablishedEvent", ourRouterId, false)
, remoteId(remoteId_)
, inbound(inbound_)
{
}
std::string
ToString() const
{
return RouterEvent::ToString() + (inbound ? "inbound" : "outbound")
+ " : LinkSessionEstablished with " + remoteId.ToString();
}
};
} // namespace tooling

View file

@ -5,6 +5,7 @@
#include "tooling/dht_event.hpp" #include "tooling/dht_event.hpp"
#include "tooling/path_event.hpp" #include "tooling/path_event.hpp"
#include "tooling/rc_event.hpp" #include "tooling/rc_event.hpp"
#include "tooling/peer_stats_event.hpp"
#include <messages/relay_status.hpp> #include <messages/relay_status.hpp>
#include <path/path.hpp> #include <path/path.hpp>
@ -73,6 +74,13 @@ namespace tooling
mod, "FindRouterReceivedEvent"); mod, "FindRouterReceivedEvent");
py::class_<FindRouterSentEvent, FindRouterEvent, RouterEvent>(mod, "FindRouterSentEvent"); py::class_<FindRouterSentEvent, FindRouterEvent, RouterEvent>(mod, "FindRouterSentEvent");
py::class_<LinkSessionEstablishedEvent, RouterEvent>(mod, "LinkSessionEstablishedEvent")
.def_readonly("remoteId", &LinkSessionEstablishedEvent::remoteId)
.def_readonly("inbound", &LinkSessionEstablishedEvent::inbound);
py::class_<ConnectionAttemptEvent, RouterEvent>(mod, "ConnectionAttemptEvent")
.def_readonly("remoteId", &ConnectionAttemptEvent::remoteId);
} }
} // namespace tooling } // namespace tooling

View file

@ -3,13 +3,18 @@
#include "pybind11/iostream.h" #include "pybind11/iostream.h"
#include <tooling/router_hive.hpp> #include <tooling/router_hive.hpp>
#include "router/abstractrouter.hpp"
#include "llarp.hpp" #include "llarp.hpp"
namespace tooling namespace tooling
{ {
void void
RouterHive_Init(py::module& mod) RouterHive_Init(py::module& mod)
{ {
using RouterHive_ptr = std::shared_ptr<RouterHive>; using RouterHive_ptr = std::shared_ptr<RouterHive>;
using Context_ptr = RouterHive::Context_ptr;
using ContextVisitor = std::function<void(Context_ptr)>;
py::class_<RouterHive, RouterHive_ptr>(mod, "RouterHive") py::class_<RouterHive, RouterHive_ptr>(mod, "RouterHive")
.def(py::init<>()) .def(py::init<>())
.def("AddRelay", &RouterHive::AddRelay) .def("AddRelay", &RouterHive::AddRelay)
@ -17,12 +22,34 @@ namespace tooling
.def("StartRelays", &RouterHive::StartRelays) .def("StartRelays", &RouterHive::StartRelays)
.def("StartClients", &RouterHive::StartClients) .def("StartClients", &RouterHive::StartClients)
.def("StopAll", &RouterHive::StopRouters) .def("StopAll", &RouterHive::StopRouters)
.def("ForEachRelay", &RouterHive::ForEachRelay) .def(
.def("ForEachClient", &RouterHive::ForEachClient) "ForEachRelay",
.def("ForEachRouter", &RouterHive::ForEachRouter) [](RouterHive& hive, ContextVisitor visit) {
hive.ForEachRelay([visit](Context_ptr ctx) {
py::gil_scoped_acquire acquire;
visit(std::move(ctx));
});
})
.def(
"ForEachClient",
[](RouterHive& hive, ContextVisitor visit) {
hive.ForEachClient([visit](Context_ptr ctx) {
py::gil_scoped_acquire acquire;
visit(std::move(ctx));
});
})
.def(
"ForEachRouter",
[](RouterHive& hive, ContextVisitor visit) {
hive.ForEachRouter([visit](Context_ptr ctx) {
py::gil_scoped_acquire acquire;
visit(std::move(ctx));
});
})
.def("GetNextEvent", &RouterHive::GetNextEvent) .def("GetNextEvent", &RouterHive::GetNextEvent)
.def("GetAllEvents", &RouterHive::GetAllEvents) .def("GetAllEvents", &RouterHive::GetAllEvents)
.def("RelayConnectedRelays", &RouterHive::RelayConnectedRelays) .def("RelayConnectedRelays", &RouterHive::RelayConnectedRelays)
.def("GetRelayRCs", &RouterHive::GetRelayRCs); .def("GetRelayRCs", &RouterHive::GetRelayRCs)
.def("GetRelay", &RouterHive::GetRelay);
} }
} // namespace tooling } // namespace tooling

View file

@ -5,10 +5,15 @@ PYBIND11_MODULE(pyllarp, m)
{ {
tooling::RouterHive_Init(m); tooling::RouterHive_Init(m);
tooling::RouterEvent_Init(m); tooling::RouterEvent_Init(m);
llarp::AbstractRouter_Init(m);
tooling::HiveRouter_Init(m);
llarp::PeerDb_Init(m);
llarp::PeerStats_Init(m);
llarp::RouterID_Init(m); llarp::RouterID_Init(m);
llarp::RouterContact_Init(m); llarp::RouterContact_Init(m);
llarp::CryptoTypes_Init(m); llarp::CryptoTypes_Init(m);
llarp::Context_Init(m); llarp::Context_Init(m);
tooling::HiveContext_Init(m);
llarp::Config_Init(m); llarp::Config_Init(m);
llarp::dht::DHTTypes_Init(m); llarp::dht::DHTTypes_Init(m);
llarp::PathTypes_Init(m); llarp::PathTypes_Init(m);

View file

@ -68,6 +68,8 @@ add_executable(catchAll
util/test_llarp_util_printer.cpp util/test_llarp_util_printer.cpp
util/test_llarp_util_str.cpp util/test_llarp_util_str.cpp
util/test_llarp_util_decaying_hashset.cpp util/test_llarp_util_decaying_hashset.cpp
peerstats/test_peer_db.cpp
peerstats/test_peer_types.cpp
config/test_llarp_config_definition.cpp config/test_llarp_config_definition.cpp
config/test_llarp_config_output.cpp config/test_llarp_config_output.cpp
net/test_ip_address.cpp net/test_ip_address.cpp

View file

@ -6,39 +6,40 @@
#include <llarp.hpp> #include <llarp.hpp>
#include <catch2/catch.hpp> #include <catch2/catch.hpp>
llarp_main* static const llarp::RuntimeOptions opts = {.background = false, .debug = false, .isRouter = true};
std::shared_ptr<llarp::Context>
make_context() make_context()
{ {
// make config llarp::Config conf;
auto config = new llarp_config(); conf.LoadDefault(true, fs::current_path());
REQUIRE(config != nullptr);
REQUIRE(config->impl.LoadDefault(true, fs::current_path()));
// set testing defaults // set testing defaults
config->impl.network.m_endpointType = "null"; conf.network.m_endpointType = "null";
config->impl.bootstrap.skipBootstrap = true; conf.bootstrap.skipBootstrap = true;
config->impl.api.m_enableRPCServer = false; conf.api.m_enableRPCServer = false;
// make a fake inbound link // make a fake inbound link
config->impl.links.m_InboundLinks.emplace_back(); conf.links.m_InboundLinks.emplace_back();
auto& link = config->impl.links.m_InboundLinks.back(); auto& link = conf.links.m_InboundLinks.back();
link.interface = llarp::net::LoopbackInterfaceName(); link.interface = llarp::net::LoopbackInterfaceName();
link.addressFamily = AF_INET; link.addressFamily = AF_INET;
link.port = 0; link.port = 0;
// configure // configure
auto ptr = llarp_main_init_from_config(config, true);
REQUIRE(ptr != nullptr); auto context = std::make_shared<llarp::Context>();
llarp_config_free(config); REQUIRE_NOTHROW(context->Configure(std::move(conf)));
return ptr;
return context;
} }
TEST_CASE("ensure snode address allocation", "[snode]") TEST_CASE("ensure snode address allocation", "[snode]")
{ {
llarp::LogSilencer shutup; llarp::LogSilencer shutup;
auto ctx = make_context(); auto ctx = make_context();
REQUIRE(llarp_main_setup(ctx, true) == 0); REQUIRE_NOTHROW(ctx->Setup(opts));
auto ctx_pp = llarp::Context::Get(ctx); ctx->CallSafe([ctx]() {
ctx_pp->CallSafe([ctx_pp]() { REQUIRE(ctx->router->IsServiceNode());
REQUIRE(ctx_pp->router->IsServiceNode()); auto& context = ctx->router->exitContext();
auto& context = ctx_pp->router->exitContext();
llarp::PubKey pk; llarp::PubKey pk;
pk.Randomize(); pk.Randomize();
@ -51,8 +52,9 @@ TEST_CASE("ensure snode address allocation", "[snode]")
REQUIRE( REQUIRE(
context.FindEndpointForPath(firstPath)->LocalIP() context.FindEndpointForPath(firstPath)->LocalIP()
== context.FindEndpointForPath(secondPath)->LocalIP()); == context.FindEndpointForPath(secondPath)->LocalIP());
ctx_pp->CloseAsync(); ctx->CloseAsync();
}); });
REQUIRE(llarp_main_run(ctx, llarp_main_runtime_opts{.isRelay = true}) == 0); REQUIRE(ctx->Run(opts) == 0);
llarp_main_free(ctx);
ctx.reset();
} }

View file

@ -33,3 +33,15 @@ def HiveArbitrary():
router_hive.Stop() router_hive.Stop()
@pytest.fixture()
def HiveForPeerStats():
router_hive = None
def _make(n_relays, n_clients, netid):
nonlocal router_hive
router_hive = hive.RouterHive(n_relays, n_clients, netid)
router_hive.Start()
return router_hive
yield _make
router_hive.Stop()

View file

@ -65,6 +65,7 @@ class RouterHive(object):
config.router.nickname = "Router%d" % index config.router.nickname = "Router%d" % index
config.router.overrideAddress('127.0.0.1:{}'.format(port)) config.router.overrideAddress('127.0.0.1:{}'.format(port))
config.router.blockBogons = False config.router.blockBogons = False
config.router.enablePeerStats = True
config.network.enableProfiling = False config.network.enableProfiling = False
config.network.routerProfilesFile = "%s/profiles.dat" % dirname config.network.routerProfilesFile = "%s/profiles.dat" % dirname
@ -80,6 +81,8 @@ class RouterHive(object):
config.api.enableRPCServer = False config.api.enableRPCServer = False
config.lokid.whitelistRouters = False
print("adding relay at index %d" % port); print("adding relay at index %d" % port);
self.hive.AddRelay(config) self.hive.AddRelay(config)
@ -110,6 +113,8 @@ class RouterHive(object):
config.api.enableRPCServer = False config.api.enableRPCServer = False
config.lokid.whitelistRouters = False
self.hive.AddClient(config) self.hive.AddClient(config)
def InitFirstRC(self): def InitFirstRC(self):

View file

@ -0,0 +1,96 @@
import pyllarp
from time import time
def test_peer_stats(HiveForPeerStats):
numRelays = 12
hive = HiveForPeerStats(n_relays=numRelays, n_clients=0, netid="hive")
someRouterId = None
def collectStatsForAWhile(duration):
print("collecting router hive stats for {} seconds...", duration)
start_time = time()
cur_time = start_time
# we track the number of attempted sessions and inbound/outbound established sessions
numInbound = 0
numOutbound = 0
numAttempts = 0
nonlocal someRouterId
while cur_time < start_time + duration:
hive.CollectAllEvents()
for event in hive.events:
event_name = event.__class__.__name__
if event_name == "LinkSessionEstablishedEvent":
if event.inbound:
numInbound += 1
else:
numOutbound += 1
if event_name == "ConnectionAttemptEvent":
numAttempts += 1
# we pick an arbitrary router out of our routers
if someRouterId is None:
someRouterId = event.remoteId;
hive.events = []
cur_time = time()
# these should be strictly equal, although there is variation because of
# the time we sample
print("test duration exceeded")
print("in: {} out: {} attempts: {}", numInbound, numOutbound, numAttempts);
totalReceived = tally_rc_received_for_peer(hive.hive, someRouterId)
# every router should have received this relay's RC exactly once
print("total times RC received: {} numRelays: {}", totalReceived, numRelays)
return {
"numInbound": numInbound,
"numOutbound": numOutbound,
"numAttempts": numAttempts,
"totalTargetRCsReceived": totalReceived,
};
results1 = collectStatsForAWhile(30);
assert(results1["totalTargetRCsReceived"] == numRelays)
# stop our router from gossiping
router = hive.hive.GetRelay(someRouterId, True)
router.disableGossiping();
ignore = collectStatsForAWhile(30);
# ensure that no one hears a fresh RC from this router again
print("Starting second (longer) stats collection...")
results2 = collectStatsForAWhile(3600);
assert(results2["totalTargetRCsReceived"] == numRelays) # should not have increased
def tally_rc_received_for_peer(hive, routerId):
numFound = 0
def visit(context):
nonlocal numFound
peerDb = context.getRouterAsHiveRouter().peerDb()
stats = peerDb.getCurrentPeerStats(routerId);
assert(stats.routerId == routerId)
numFound += stats.numDistinctRCsReceived
hive.ForEachRelay(visit)
return numFound;
if __name__ == "__main__":
main()

View file

@ -86,8 +86,9 @@ struct IWPLinkContext
return true; return true;
}, },
// established handler // established handler
[established](llarp::ILinkSession* s) { [established](llarp::ILinkSession* s, bool linkIsInbound) {
REQUIRE(s != nullptr); REQUIRE(s != nullptr);
REQUIRE(inbound == linkIsInbound);
established(s); established(s);
return true; return true;
}, },

View file

@ -0,0 +1,217 @@
#include <peerstats/peer_db.hpp>
#include <test_util.hpp>
#include <numeric>
#include <catch2/catch.hpp>
#include "peerstats/types.hpp"
#include "router_contact.hpp"
#include "util/time.hpp"
TEST_CASE("Test PeerDb PeerStats memory storage", "[PeerDb]")
{
const llarp::RouterID id = llarp::test::makeBuf<llarp::RouterID>(0x01);
const llarp::PeerStats empty(id);
llarp::PeerDb db;
CHECK(db.getCurrentPeerStats(id).has_value() == false);
llarp::PeerStats delta(id);
delta.numConnectionAttempts = 4;
delta.peakBandwidthBytesPerSec = 5;
db.accumulatePeerStats(id, delta);
CHECK(* db.getCurrentPeerStats(id) == delta);
delta = llarp::PeerStats(id);
delta.numConnectionAttempts = 5;
delta.peakBandwidthBytesPerSec = 6;
db.accumulatePeerStats(id, delta);
llarp::PeerStats expected(id);
expected.numConnectionAttempts = 9;
expected.peakBandwidthBytesPerSec = 6;
CHECK(* db.getCurrentPeerStats(id) == expected);
}
TEST_CASE("Test PeerDb flush before load", "[PeerDb]")
{
llarp::PeerDb db;
CHECK_THROWS_WITH(db.flushDatabase(), "Cannot flush database before it has been loaded");
}
TEST_CASE("Test PeerDb load twice", "[PeerDb]")
{
llarp::PeerDb db;
CHECK_NOTHROW(db.loadDatabase(std::nullopt));
CHECK_THROWS_WITH(db.loadDatabase(std::nullopt), "Reloading database not supported");
}
TEST_CASE("Test PeerDb nukes stats on load", "[PeerDb]")
{
const llarp::RouterID id = llarp::test::makeBuf<llarp::RouterID>(0x01);
llarp::PeerDb db;
llarp::PeerStats stats(id);
stats.numConnectionAttempts = 1;
db.accumulatePeerStats(id, stats);
CHECK(* db.getCurrentPeerStats(id) == stats);
db.loadDatabase(std::nullopt);
CHECK(db.getCurrentPeerStats(id).has_value() == false);
}
TEST_CASE("Test PeerDb file-backed database reloads properly", "[PeerDb]")
{
const std::string filename = "/tmp/peerdb_test_tmp2.db.sqlite";
const llarp::RouterID id = llarp::test::makeBuf<llarp::RouterID>(0x02);
{
llarp::PeerDb db;
db.loadDatabase(filename);
llarp::PeerStats stats(id);
stats.numConnectionAttempts = 43;
db.accumulatePeerStats(id, stats);
db.flushDatabase();
}
{
llarp::PeerDb db;
db.loadDatabase(filename);
auto stats = db.getCurrentPeerStats(id);
CHECK(stats.has_value() == true);
CHECK(stats->numConnectionAttempts == 43);
}
fs::remove(filename);
}
TEST_CASE("Test PeerDb modifyPeerStats", "[PeerDb]")
{
const llarp::RouterID id = llarp::test::makeBuf<llarp::RouterID>(0xF2);
int numTimesCalled = 0;
llarp::PeerDb db;
db.loadDatabase(std::nullopt);
db.modifyPeerStats(id, [&](llarp::PeerStats& stats) {
numTimesCalled++;
stats.numPathBuilds += 42;
});
db.flushDatabase();
CHECK(numTimesCalled == 1);
auto stats = db.getCurrentPeerStats(id);
CHECK(stats.has_value());
CHECK(stats->numPathBuilds == 42);
}
TEST_CASE("Test PeerDb handleGossipedRC", "[PeerDb]")
{
const llarp::RouterID id = llarp::test::makeBuf<llarp::RouterID>(0xCA);
auto rcLifetime = llarp::RouterContact::Lifetime;
llarp_time_t now = 0s;
llarp::RouterContact rc;
rc.pubkey = llarp::PubKey(id);
rc.last_updated = 10s;
llarp::PeerDb db;
db.handleGossipedRC(rc, now);
auto stats = db.getCurrentPeerStats(id);
CHECK(stats.has_value());
CHECK(stats->leastRCRemainingLifetime == 0ms); // not calculated on first received RC
CHECK(stats->numDistinctRCsReceived == 1);
CHECK(stats->lastRCUpdated == 10000ms);
now = 9s;
db.handleGossipedRC(rc, now);
stats = db.getCurrentPeerStats(id);
CHECK(stats.has_value());
// these values should remain unchanged, this is not a new RC
CHECK(stats->leastRCRemainingLifetime == 0ms);
CHECK(stats->numDistinctRCsReceived == 1);
CHECK(stats->lastRCUpdated == 10000ms);
rc.last_updated = 11s;
db.handleGossipedRC(rc, now);
stats = db.getCurrentPeerStats(id);
// should be (previous expiration time - new received time)
CHECK(stats->leastRCRemainingLifetime == ((10s + rcLifetime) - now));
CHECK(stats->numDistinctRCsReceived == 2);
CHECK(stats->lastRCUpdated == 11000ms);
}
TEST_CASE("Test PeerDb handleGossipedRC expiry calcs", "[PeerDb]")
{
const llarp::RouterID id = llarp::test::makeBuf<llarp::RouterID>(0xF9);
// see comments in peer_db.cpp above PeerDb::handleGossipedRC() for some context around these
// tests and esp. these numbers
const llarp_time_t ref = 48h;
const llarp_time_t rcLifetime = llarp::RouterContact::Lifetime;
// rc1, first rc received
const llarp_time_t s1 = ref;
const llarp_time_t r1 = s1 + 30s;
const llarp_time_t e1 = s1 + rcLifetime;
llarp::RouterContact rc1;
rc1.pubkey = llarp::PubKey(id);
rc1.last_updated = s1;
// rc2, second rc received
// received "healthily", with lots of room to spare before rc1 expires
const llarp_time_t s2 = s1 + 8h;
const llarp_time_t r2 = s2 + 30s; // healthy recv time
const llarp_time_t e2 = s2 + rcLifetime;
llarp::RouterContact rc2;
rc2.pubkey = llarp::PubKey(id);
rc2.last_updated = s2;
// rc3, third rc received
// received "unhealthily" (after rc2 expires)
const llarp_time_t s3 = s2 + 8h;
const llarp_time_t r3 = e2 + 1h; // received after e2
llarp::RouterContact rc3;
rc3.pubkey = llarp::PubKey(id);
rc3.last_updated = s3;
llarp::PeerDb db;
db.handleGossipedRC(rc1, r1);
auto stats1 = db.getCurrentPeerStats(id);
CHECK(stats1.has_value());
CHECK(stats1->leastRCRemainingLifetime == 0ms);
CHECK(stats1->numDistinctRCsReceived == 1);
CHECK(stats1->lastRCUpdated == s1);
db.handleGossipedRC(rc2, r2);
auto stats2 = db.getCurrentPeerStats(id);
CHECK(stats2.has_value());
CHECK(stats2->leastRCRemainingLifetime == (e1 - r2));
CHECK(stats2->leastRCRemainingLifetime > 0ms); // ensure positive indicates healthy
CHECK(stats2->numDistinctRCsReceived == 2);
CHECK(stats2->lastRCUpdated == s2);
db.handleGossipedRC(rc3, r3);
auto stats3 = db.getCurrentPeerStats(id);
CHECK(stats3.has_value());
CHECK(stats3->leastRCRemainingLifetime == (e2 - r3));
CHECK(
stats3->leastRCRemainingLifetime
< 0ms); // ensure negative indicates unhealthy and we use min()
CHECK(stats3->numDistinctRCsReceived == 3);
CHECK(stats3->lastRCUpdated == s3);
}

View file

@ -0,0 +1,77 @@
#include <numeric>
#include <peerstats/types.hpp>
#include <test_util.hpp>
#include <catch2/catch.hpp>
TEST_CASE("Test PeerStats operator+=", "[PeerStats]")
{
llarp::RouterID id = {};
// TODO: test all members
llarp::PeerStats stats(id);
stats.numConnectionAttempts = 1;
stats.peakBandwidthBytesPerSec = 12;
llarp::PeerStats delta(id);
delta.numConnectionAttempts = 2;
delta.peakBandwidthBytesPerSec = 4;
stats += delta;
CHECK(stats.numConnectionAttempts == 3);
CHECK(stats.peakBandwidthBytesPerSec == 12); // should take max(), not add
}
TEST_CASE("Test PeerStats BEncode", "[PeerStats]")
{
llarp::RouterID id = llarp::test::makeBuf<llarp::RouterID>(0x01);
llarp::PeerStats stats(id);
stats.numConnectionAttempts = 1;
stats.numConnectionSuccesses = 2;
stats.numConnectionRejections = 3;
stats.numConnectionTimeouts = 4;
stats.numPathBuilds = 5;
stats.numPacketsAttempted = 6;
stats.numPacketsSent = 7;
stats.numPacketsDropped = 8;
stats.numPacketsResent = 9;
stats.numDistinctRCsReceived = 10;
stats.numLateRCs = 11;
stats.peakBandwidthBytesPerSec = 12.1; // should truncate to 12
stats.longestRCReceiveInterval = 13ms;
stats.leastRCRemainingLifetime = 14ms;
stats.lastRCUpdated = 15ms;
constexpr int bufSize = 4096;
byte_t* raw = new byte_t[bufSize];
llarp_buffer_t buf(raw, bufSize);
CHECK_NOTHROW(stats.BEncode(&buf));
std::string asString = (const char*)raw;
constexpr std::string_view expected =
"d"
"21:numConnectionAttempts" "i1e"
"22:numConnectionSuccesses" "i2e"
"23:numConnectionRejections" "i3e"
"21:numConnectionTimeouts" "i4e"
"13:numPathBuilds" "i5e"
"19:numPacketsAttempted" "i6e"
"14:numPacketsSent" "i7e"
"17:numPacketsDropped" "i8e"
"16:numPacketsResent" "i9e"
"22:numDistinctRCsReceived" "i10e"
"10:numLateRCs" "i11e"
"24:peakBandwidthBytesPerSec" "i12e"
"24:longestRCReceiveInterval" "i13e"
"24:leastRCRemainingLifetime" "i14e"
"13:lastRCUpdated" "i15e"
"e";
CHECK(asString == expected);
delete [] raw;
}

View file

@ -11,13 +11,15 @@ llarp::RuntimeOptions opts = {false, false, false};
static std::shared_ptr<llarp::Context> static std::shared_ptr<llarp::Context>
make_context(std::optional<fs::path> keyfile) make_context(std::optional<fs::path> keyfile)
{ {
auto context = std::make_shared<llarp::Context>(); llarp::Config conf;
context->Configure(opts, {}); conf.LoadDefault(opts.isRouter, {});
conf.network.m_endpointType = "null";
conf.network.m_keyfile = keyfile;
conf.bootstrap.skipBootstrap = true;
conf.api.m_enableRPCServer = false;
context->config->network.m_endpointType = "null"; auto context = std::make_shared<llarp::Context>();
context->config->network.m_keyfile = keyfile; REQUIRE_NOTHROW(context->Configure(std::move(conf)));
context->config->bootstrap.skipBootstrap = true;
context->config->api.m_enableRPCServer = false;
return context; return context;
} }