diff --git a/llarp/CMakeLists.txt b/llarp/CMakeLists.txt index 6653a5f08c..a34a69123b 100644 --- a/llarp/CMakeLists.txt +++ b/llarp/CMakeLists.txt @@ -145,6 +145,7 @@ add_library(lokinet-config # lokinet-consensus is for deriving and tracking network consensus state for both service nodes and clients add_library(lokinet-consensus STATIC + consensus/edge_selector.cpp consensus/reachability_testing.cpp ) diff --git a/llarp/consensus/edge_selector.cpp b/llarp/consensus/edge_selector.cpp new file mode 100644 index 0000000000..20386e0e4f --- /dev/null +++ b/llarp/consensus/edge_selector.cpp @@ -0,0 +1,71 @@ +#include "edge_selector.hpp" + +#include +#include +#include +#include + +namespace llarp::consensus +{ + + EdgeSelector::EdgeSelector(AbstractRouter& r) : _router{r} + {} + + std::optional + EdgeSelector::select_path_edge(const std::unordered_set& connected_now) const + { + auto conf = _router.GetConfig(); + auto nodedb = _router.nodedb(); + + const auto& _keys = conf->network.m_strictConnect; + // no strict hops. select a random good snode. + if (_keys.empty()) + { + return nodedb->GetRandom([&connected_now, this](const auto& rc) { + return connected_now.count(rc.pubkey) == 0 + and not _router.routerProfiling().IsBadForConnect(rc.pubkey) + and not _router.IsBootstrapNode(rc.pubkey); + }); + } + + // select random from strict connections. + std::vector keys{_keys.begin(), _keys.end()}; + + std::shuffle(keys.begin(), keys.end(), llarp::CSRNG{}); + + for (const auto& pk : keys) + { + if (_router.routerProfiling().IsBadForConnect(pk)) + continue; + if (connected_now.count(pk)) + continue; + if (auto maybe = nodedb->Get(pk)) + return maybe; + } + return std::nullopt; + } + + std::optional + EdgeSelector::select_bootstrap(const std::unordered_set& connected_now) const + { + auto conf = _router.GetConfig(); + auto nodedb = _router.nodedb(); + if (const auto& _keys = conf->network.m_strictConnect; not _keys.empty()) + { + // try bootstrapping off strict connections first if we have any. + std::vector keys{_keys.begin(), _keys.end()}; + std::shuffle(keys.begin(), keys.end(), llarp::CSRNG{}); + for (const auto& pk : keys) + { + if (connected_now.count(pk)) + continue; + if (auto maybe = nodedb->Get(pk)) + return maybe; + } + } + // if we cant use a strict connection we'll just use one of our bootstrap nodes. + return nodedb->GetRandom([&connected_now, this](const auto& rc) { + return connected_now.count(rc.pubkey) == 0 and _router.IsBootstrapNode(rc.pubkey); + }); + } +} // namespace llarp::consensus diff --git a/llarp/consensus/edge_selector.hpp b/llarp/consensus/edge_selector.hpp new file mode 100644 index 0000000000..499e72aa51 --- /dev/null +++ b/llarp/consensus/edge_selector.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include + +namespace llarp +{ + struct AbstractRouter; + struct RouterContact; +} // namespace llarp + +namespace llarp::consensus +{ + /// when we want to connect to and edge when we are a client, an instance of EdgeSelector acts as + /// a stateless selection algorith for router contacts for each attempt at connecting out we make. + class EdgeSelector + { + AbstractRouter& _router; + + public: + explicit EdgeSelector(AbstractRouter& router); + + /// select a candidate for connecting out to the network when we need more connections to do a + /// path build. pass in an unordered set of the snode pubkeys we are currently connected to. + std::optional + select_path_edge(const std::unordered_set& connected_now = {}) const; + + /// get a router contact of a bootstrap snode to bootstrap with if we are in need of + /// bootstrapping more peers. pass in an unodereed set of bootstrap router pubkeys we are + /// currently connected to. + std::optional + select_bootstrap(const std::unordered_set& connected_now = {}) const; + }; +} // namespace llarp::consensus diff --git a/llarp/router/abstractrouter.hpp b/llarp/router/abstractrouter.hpp index 162b506167..9ef886d46d 100644 --- a/llarp/router/abstractrouter.hpp +++ b/llarp/router/abstractrouter.hpp @@ -45,6 +45,11 @@ namespace llarp struct I_RCLookupHandler; struct RoutePoker; + namespace consensus + { + class EdgeSelector; + } + namespace dns { class I_SystemSettings; @@ -130,6 +135,9 @@ namespace llarp virtual const RouterContact& rc() const = 0; + virtual const consensus::EdgeSelector& + edge_selector() const = 0; + /// modify our rc /// modify returns nullopt if unmodified otherwise it returns the new rc to be sigend and /// published out diff --git a/llarp/router/outbound_session_maker.cpp b/llarp/router/outbound_session_maker.cpp index 4eb264487f..80e99d680f 100644 --- a/llarp/router/outbound_session_maker.cpp +++ b/llarp/router/outbound_session_maker.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -139,30 +140,23 @@ namespace llarp OutboundSessionMaker::ConnectToRandomRouters(int numDesired) { int remainingDesired = numDesired; - std::set exclude; + std::unordered_set exclude; + for (const auto& item : pendingSessions) + exclude.emplace(item.first); + _linkManager->ForEachPeer([&exclude](auto* session) { + if (session and session->IsEstablished()) + exclude.emplace(session->GetPubKey()); + }); do { - auto filter = [exclude](const auto& rc) -> bool { return exclude.count(rc.pubkey) == 0; }; - - RouterContact other; - if (const auto maybe = _nodedb->GetRandom(filter)) - { - other = *maybe; - } - else + auto maybe_rc = _router->edge_selector().select_path_edge(exclude); + if (not maybe_rc) break; - exclude.insert(other.pubkey); - if (not _rcLookup->SessionIsAllowed(other.pubkey)) - { - continue; - } - if (not(_linkManager->HasSessionTo(other.pubkey) || HavePendingSessionTo(other.pubkey))) - { - CreateSessionTo(other, nullptr); - --remainingDesired; - } - + const auto& rc = *maybe_rc; + exclude.insert(rc.pubkey); + CreateSessionTo(rc, nullptr); + --remainingDesired; } while (remainingDesired > 0); LogDebug( "connecting to ", numDesired - remainingDesired, " out of ", numDesired, " random routers"); diff --git a/llarp/router/router.cpp b/llarp/router/router.cpp index 6a4db8c5ae..cfaa581185 100644 --- a/llarp/router/router.cpp +++ b/llarp/router/router.cpp @@ -50,6 +50,7 @@ namespace llarp Router::Router(EventLoop_ptr loop, std::shared_ptr vpnPlatform) : ready{false} , m_lmq{std::make_shared()} + , _edge_selector{*this} , _loop{std::move(loop)} , _vpnPlatform{std::move(vpnPlatform)} , paths{this} @@ -869,6 +870,12 @@ namespace llarp m_LastStatsReport = now; } + const consensus::EdgeSelector& + Router::edge_selector() const + { + return _edge_selector; + } + std::string Router::status_line() { @@ -1059,12 +1066,8 @@ namespace llarp _rcLookupHandler.ExploreNetwork(); m_NextExploreAt = timepoint_now + std::chrono::seconds(interval); } - size_t connectToNum = _outboundSessionMaker.minConnectedRouters; - const auto strictConnect = _rcLookupHandler.NumberOfStrictConnectRouters(); - if (strictConnect > 0 && connectToNum > strictConnect) - { - connectToNum = strictConnect; - } + + size_t connectToNum = _outboundSessionMaker.maxConnectedRouters; if (isSvcNode and now >= m_NextDecommissionWarn) { diff --git a/llarp/router/router.hpp b/llarp/router/router.hpp index 16efb1fccc..1423cff304 100644 --- a/llarp/router/router.hpp +++ b/llarp/router/router.hpp @@ -3,6 +3,7 @@ #include "abstractrouter.hpp" #include +#include #include #include #include @@ -79,6 +80,8 @@ namespace llarp std::shared_ptr m_Pump; + consensus::EdgeSelector _edge_selector; + path::BuildLimiter& pathBuildLimiter() override { @@ -148,6 +151,9 @@ namespace llarp const std::vector& greylist, const std::vector& unfunded) override; + const consensus::EdgeSelector& + edge_selector() const override; + std::unordered_set GetRouterWhitelist() const override {