diff --git a/llarp/config/config.cpp b/llarp/config/config.cpp index b718de338..12bcee7ec 100644 --- a/llarp/config/config.cpp +++ b/llarp/config/config.cpp @@ -36,6 +36,7 @@ namespace llarp constexpr int DefaultWorkerThreads = 1; constexpr int DefaultNetThreads = 1; constexpr bool DefaultBlockBogons = true; + constexpr bool DefaultEnablePeerStats = false; conf.defineOption("router", "job-queue-size", false, DefaultJobQueueSize, [this](int arg) { if (arg < 1024) @@ -128,6 +129,13 @@ namespace llarp conf.defineOption( "router", "transport-privkey", false, "", AssignmentAcceptor(m_transportKeyFile)); + + conf.defineOption( + "router", + "enable-peer-stats", + false, + DefaultEnablePeerStats, + AssignmentAcceptor(m_enablePeerStats)); } void @@ -987,6 +995,40 @@ namespace llarp "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); } diff --git a/llarp/config/config.hpp b/llarp/config/config.hpp index 1a2733a95..77d3e5de0 100644 --- a/llarp/config/config.hpp +++ b/llarp/config/config.hpp @@ -64,6 +64,8 @@ namespace llarp std::string m_identityKeyFile; std::string m_transportKeyFile; + bool m_enablePeerStats = false; + void defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params); }; diff --git a/llarp/peerstats/peer_db.cpp b/llarp/peerstats/peer_db.cpp index a7751e22c..a814e65e6 100644 --- a/llarp/peerstats/peer_db.cpp +++ b/llarp/peerstats/peer_db.cpp @@ -5,6 +5,11 @@ namespace llarp { + PeerDb::PeerDb() + { + m_lastFlush.store({}); + } + void PeerDb::loadDatabase(std::optional file) { @@ -47,6 +52,12 @@ namespace llarp void PeerDb::flushDatabase() { + LogDebug("flushing PeerDb..."); + + auto start = time_now_ms(); + if (not shouldFlush(start)) + LogWarn("Double PeerDb flush?"); + if (not m_storage) throw std::runtime_error("Cannot flush database before it has been loaded"); @@ -65,6 +76,13 @@ namespace llarp m_storage->replace(entry.second); } + + auto end = time_now_ms(); + + auto elapsed = end - start; + LogDebug("PeerDb flush took about ", elapsed, " millis"); + + m_lastFlush.store(end); } void @@ -93,4 +111,21 @@ namespace llarp return itr->second; } + 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) + { + return (now - m_lastFlush.load() >= m_targetFlushInterval); + } + }; // namespace llarp diff --git a/llarp/peerstats/peer_db.hpp b/llarp/peerstats/peer_db.hpp index ea4ebd317..b8ba75efa 100644 --- a/llarp/peerstats/peer_db.hpp +++ b/llarp/peerstats/peer_db.hpp @@ -1,11 +1,11 @@ #pragma once -#include #include #include #include +#include #include #include #include @@ -19,6 +19,9 @@ namespace llarp /// 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). /// @@ -62,11 +65,27 @@ namespace llarp std::optional getCurrentPeerStats(const RouterID& routerId) const; + /// 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); + private: std::unordered_map m_peerStats; std::mutex m_statsLock; std::unique_ptr m_storage; + + llarp_time_t m_targetFlushInterval = 30s; + std::atomic m_lastFlush; }; } // namespace llarp diff --git a/llarp/router/router.cpp b/llarp/router/router.cpp index 7b8f0d336..1d77721d4 100644 --- a/llarp/router/router.cpp +++ b/llarp/router/router.cpp @@ -578,6 +578,14 @@ namespace llarp hiddenServiceContext().AddEndpoint(*conf); } + // peer stats + if (conf->router.m_enablePeerStats) + { + LogInfo("Initializing peerdb..."); + m_peerDb = std::make_unique(); + m_peerDb->configure(conf->router); + } + // Logging config LogContext::Instance().Initialize( conf->logging.m_logLevel, @@ -754,6 +762,10 @@ namespace llarp { nodedb()->AsyncFlushToDisk(); } + + if (m_peerDb and m_peerDb->shouldFlush(now)) + diskworker()->addJob([this]() { m_peerDb->flushDatabase(); }); + // get connected peers std::set peersWeHave; _linkManager.ForEachPeer([&peersWeHave](ILinkSession* s) { diff --git a/llarp/router/router.hpp b/llarp/router/router.hpp index 22dcb3514..3a3f16445 100644 --- a/llarp/router/router.hpp +++ b/llarp/router/router.hpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -495,6 +496,7 @@ namespace llarp llarp_time_t m_LastStatsReport = 0s; std::shared_ptr m_keyManager; + std::unique_ptr m_peerDb; uint32_t path_build_count = 0;