Merge pull request #2224 from tewinget/rc-gossip

RC gossip
pull/2212/head
dr7ana 6 months ago committed by GitHub
commit d520e1d2c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -24,7 +24,6 @@ lokinet_add_library(lokinet-core-utils
exit/policy.cpp # net/
handlers/exit.cpp # link/ exit/
handlers/tun.cpp
router/rc_gossiper.cpp
service/auth.cpp # config/
service/context.cpp
service/identity.cpp
@ -109,7 +108,6 @@ lokinet_add_library(lokinet-time-place
# lokinet-platform holds all platform specific code
lokinet_add_library(lokinet-platform
net/interface_info.cpp
router/rc_lookup_handler.cpp
vpn/packet_router.cpp
vpn/platform.cpp
)

@ -104,7 +104,7 @@ namespace llarp::consensus
// We exhausted the queue so repopulate it and try again
testing_queue.clear();
const auto all = router->router_whitelist();
const auto all = router->get_whitelist();
testing_queue.insert(testing_queue.begin(), all.begin(), all.end());
std::shuffle(testing_queue.begin(), testing_queue.end(), llarp::csrng);

@ -272,35 +272,8 @@ namespace llarp::exit
if (numHops == 1)
{
auto r = router;
if (const auto maybe = r->node_db()->get_rc(exit_router); maybe.has_value())
r->connect_to(*maybe);
else
r->lookup_router(exit_router, [r](oxen::quic::message m) mutable {
if (m)
{
std::string payload;
try
{
oxenc::bt_dict_consumer btdc{m.body()};
payload = btdc.require<std::string>("RC");
}
catch (...)
{
log::warning(link_cat, "Failed to parse Find Router response!");
throw;
}
RemoteRC result{std::move(payload)};
r->node_db()->put_rc_if_newer(result);
r->connect_to(result);
}
else
{
r->link_manager().handle_find_router_error(std::move(m));
}
});
if (const auto maybe = router->node_db()->get_rc(exit_router); maybe.has_value())
router->connect_to(*maybe);
}
else if (UrgentBuild(now))
BuildOneAlignedTo(exit_router);

@ -2,8 +2,8 @@
#include <llarp/dns/dns.hpp>
#include <llarp/net/net.hpp>
#include <llarp/nodedb.hpp>
#include <llarp/path/path_context.hpp>
#include <llarp/router/rc_lookup_handler.hpp>
#include <llarp/router/router.hpp>
#include <llarp/service/protocol_type.hpp>
@ -243,9 +243,8 @@ namespace llarp::handlers
{
if (msg.questions[0].IsName("random.snode"))
{
RouterID random;
if (GetRouter()->GetRandomGoodRouter(random))
msg.AddCNAMEReply(random.ToString(), 1);
if (auto random = GetRouter()->GetRandomGoodRouter())
msg.AddCNAMEReply(random->ToString(), 1);
else
msg.AddNXReply();
}
@ -263,11 +262,10 @@ namespace llarp::handlers
const bool isV4 = msg.questions[0].qtype == dns::qTypeA;
if (msg.questions[0].IsName("random.snode"))
{
RouterID random;
if (GetRouter()->GetRandomGoodRouter(random))
if (auto random = GetRouter()->GetRandomGoodRouter())
{
msg.AddCNAMEReply(random.ToString(), 1);
auto ip = ObtainServiceNodeIP(random);
msg.AddCNAMEReply(random->ToString(), 1);
auto ip = ObtainServiceNodeIP(*random);
msg.AddINReply(ip, false);
}
else
@ -333,7 +331,7 @@ namespace llarp::handlers
void
ExitEndpoint::ObtainSNodeSession(const RouterID& rid, exit::SessionReadyFunc obtain_cb)
{
if (not router->rc_lookup_handler().is_session_allowed(rid))
if (not router->node_db()->is_connection_allowed(rid))
{
obtain_cb(nullptr);
return;

@ -606,34 +606,11 @@ namespace llarp::handlers
RouterID snode;
if (snode.FromString(qname))
{
router()->lookup_router(
snode, [r = router(), msg = std::move(msg), reply](oxen::quic::message m) mutable {
if (m)
{
std::string payload;
try
{
oxenc::bt_dict_consumer btdc{m.body()};
payload = btdc.require<std::string>("RC");
}
catch (...)
{
log::warning(link_cat, "Failed to parse Find Router response!");
throw;
}
r->node_db()->put_rc_if_newer(RemoteRC{payload});
msg.AddTXTReply(payload);
}
else
{
msg.AddNXReply();
r->link_manager().handle_find_router_error(std::move(m));
}
reply(msg);
});
if (auto rc = router()->node_db()->get_rc(snode))
msg.AddTXTReply(std::string{rc->view()});
else
msg.AddNXReply();
reply(msg);
return true;
}
@ -705,10 +682,9 @@ namespace llarp::handlers
{
if (is_random_snode(msg))
{
RouterID random;
if (router()->GetRandomGoodRouter(random))
if (auto random = router()->GetRandomGoodRouter())
{
msg.AddCNAMEReply(random.ToString(), 1);
msg.AddCNAMEReply(random->ToString(), 1);
}
else
msg.AddNXReply();
@ -755,11 +731,10 @@ namespace llarp::handlers
// on MacOS this is a typeA query
else if (is_random_snode(msg))
{
RouterID random;
if (router()->GetRandomGoodRouter(random))
if (auto random = router()->GetRandomGoodRouter())
{
msg.AddCNAMEReply(random.ToString(), 1);
return ReplyToSNodeDNSWhenReady(random, std::make_shared<dns::Message>(msg), isV6);
msg.AddCNAMEReply(random->ToString(), 1);
return ReplyToSNodeDNSWhenReady(*random, std::make_shared<dns::Message>(msg), isV6);
}
msg.AddNXReply();

@ -71,13 +71,6 @@ namespace llarp
return obj;
}
bool
Contacts::lookup_router(const RouterID& rid, std::function<void(oxen::quic::message)> func)
{
return _router.send_control_message(
rid, "find_router", FindRouterMessage::serialize(rid, false, false), std::move(func));
}
void
Contacts::put_rc_node_async(const dht::RCNode& val)
{

@ -45,9 +45,6 @@ namespace llarp
util::StatusObject
ExtractStatus() const;
bool
lookup_router(const RouterID&, std::function<void(oxen::quic::message)> = nullptr);
void
put_rc_node_async(const dht::RCNode& val);

@ -8,7 +8,6 @@
#include <llarp/messages/path.hpp>
#include <llarp/nodedb.hpp>
#include <llarp/path/path.hpp>
#include <llarp/router/rc_lookup_handler.hpp>
#include <llarp/router/router.hpp>
#include <algorithm>
@ -151,6 +150,11 @@ namespace llarp
[this, &rid, msg = std::move(m)]() mutable { handle_path_control(std::move(msg), rid); });
});
s->register_command("gossip_rc"s, [this, rid](oxen::quic::message m) {
_router.loop()->call(
[this, msg = std::move(m)]() mutable { handle_gossip_rc(std::move(msg)); });
});
for (auto& method : direct_requests)
{
s->register_command(
@ -243,21 +247,17 @@ namespace llarp
return true;
}
_router.loop()->call([this, remote, endpoint, body, f = std::move(func)]() {
auto pending = PendingControlMessage(body, endpoint, f);
_router.loop()->call([this,
remote,
endpoint = std::move(endpoint),
body = std::move(body),
f = std::move(func)]() {
auto pending = PendingControlMessage(std::move(body), std::move(endpoint), f);
auto [itr, b] = pending_conn_msg_queue.emplace(remote, MessageQueue());
itr->second.push_back(std::move(pending));
rc_lookup->get_rc(remote, [this]([[maybe_unused]] auto rid, auto rc, auto success) {
if (success)
{
_router.node_db()->put_rc_if_newer(*rc);
connect_to(*rc);
}
else
log::warning(quic_cat, "Do something intelligent here for error handling");
});
connect_to(remote);
});
return false;
@ -275,21 +275,13 @@ namespace llarp
return true;
}
_router.loop()->call([&]() {
_router.loop()->call([this, body = std::move(body), remote]() {
auto pending = PendingDataMessage(body);
auto [itr, b] = pending_conn_msg_queue.emplace(remote, MessageQueue());
itr->second.push_back(std::move(pending));
rc_lookup->get_rc(remote, [this]([[maybe_unused]] auto rid, auto rc, auto success) {
if (success)
{
_router.node_db()->put_rc_if_newer(*rc);
connect_to(*rc);
}
else
log::warning(quic_cat, "Do something intelligent here for error handling");
});
connect_to(remote);
});
return false;
@ -304,15 +296,13 @@ namespace llarp
void
LinkManager::connect_to(const RouterID& rid)
{
rc_lookup->get_rc(rid, [this]([[maybe_unused]] auto rid, auto rc, auto success) {
if (success)
{
_router.node_db()->put_rc_if_newer(*rc);
connect_to(*rc);
}
else
log::warning(quic_cat, "Do something intelligent here for error handling");
});
auto rc = node_db->get_rc(rid);
if (rc)
{
connect_to(*rc);
}
else
log::warning(quic_cat, "Do something intelligent here for error handling");
}
// This function assumes the RC has already had its signature verified and connection is allowed.
@ -400,6 +390,43 @@ namespace llarp
});
}
void
LinkManager::gossip_rc(const RouterID& rc_rid, std::string serialized_rc)
{
for (auto& [rid, conn] : ep.conns)
{
// don't send back to the owner...
if (rid == rc_rid)
continue;
// don't gossip RCs to clients
if (not conn->remote_is_relay)
continue;
send_control_message(rid, "gossip_rc", serialized_rc);
}
}
void
LinkManager::handle_gossip_rc(oxen::quic::message m)
{
try
{
RemoteRC rc{m.body()};
if (node_db->put_rc_if_newer(rc))
{
log::info(link_cat, "Received updated RC, forwarding to relay peers.");
gossip_rc(rc.router_id(), m.body_str());
}
else
log::debug(link_cat, "Received known or old RC, not storing or forwarding.");
}
catch (const std::exception& e)
{
log::info(link_cat, "Recieved invalid RC, dropping on the floor.");
}
}
bool
LinkManager::have_connection_to(const RouterID& remote, bool client_only) const
{
@ -486,10 +513,9 @@ namespace llarp
}
void
LinkManager::init(RCLookupHandler* rcLookup)
LinkManager::init()
{
is_stopping = false;
rc_lookup = rcLookup;
node_db = _router.node_db();
}
@ -509,7 +535,7 @@ namespace llarp
{
exclude.insert(maybe_other->router_id());
if (not rc_lookup->is_session_allowed(maybe_other->router_id()))
if (not node_db->is_connection_allowed(maybe_other->router_id()))
continue;
connect_to(*maybe_other);
@ -596,225 +622,6 @@ namespace llarp
}
}
void
LinkManager::handle_find_router(std::string_view body, std::function<void(std::string)> respond)
{
std::string target_key;
bool is_exploratory, is_iterative;
try
{
oxenc::bt_dict_consumer btdc{body};
is_exploratory = btdc.require<bool>("E");
is_iterative = btdc.require<bool>("I");
target_key = btdc.require<std::string>("K");
}
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
respond(messages::ERROR_RESPONSE);
return;
}
// TODO: do we need a replacement for dht.AllowTransit() etc here?
RouterID target_rid;
target_rid.FromString(target_key);
const auto target_addr = dht::Key_t{reinterpret_cast<uint8_t*>(target_key.data())};
const auto& local_rid = _router.rc().router_id();
const auto local_key = dht::Key_t{local_rid};
if (is_exploratory)
{
std::string neighbors{};
const auto closest_rcs =
_router.node_db()->find_many_closest_to(target_addr, RC_LOOKUP_STORAGE_REDUNDANCY);
for (const auto& rc : closest_rcs)
{
const auto& rid = rc.router_id();
if (_router.router_profiling().IsBadForConnect(rid) || target_rid == rid
|| local_rid == rid)
continue;
neighbors += rid.bt_encode();
}
respond(serialize_response(
{{messages::STATUS_KEY, FindRouterMessage::RETRY_EXP}, {"TARGET", neighbors}}));
}
else
{
const auto closest_rc = _router.node_db()->find_closest_to(target_addr);
const auto& closest_rid = closest_rc.router_id();
const auto closest_key = dht::Key_t{closest_rid};
if (target_addr == closest_key)
{
if (closest_rc.expires_within_delta(llarp::time_now_ms()))
{
send_control_message(
target_rid,
"find_router",
FindRouterMessage::serialize(target_rid, false, false),
[respond = std::move(respond)](oxen::quic::message msg) mutable {
respond(msg.body_str());
});
}
else
{
respond(serialize_response({{"RC", closest_rc.view()}}));
}
}
else if (not is_iterative)
{
if ((closest_key ^ target_addr) < (local_key ^ target_addr))
{
send_control_message(
closest_rid,
"find_router",
FindRouterMessage::serialize(closest_rid, false, false),
[respond = std::move(respond)](oxen::quic::message msg) mutable {
respond(msg.body_str());
});
}
else
{
respond(serialize_response(
{{messages::STATUS_KEY, FindRouterMessage::RETRY_ITER},
{"TARGET", reinterpret_cast<const char*>(target_addr.data())}}));
}
}
else
{
respond(serialize_response(
{{messages::STATUS_KEY, FindRouterMessage::RETRY_NEW},
{"TARGET", reinterpret_cast<const char*>(closest_rid.data())}}));
}
}
}
void
LinkManager::handle_find_router_response(oxen::quic::message m)
{
if (m.timed_out)
{
log::info(link_cat, "FindRouterMessage timed out!");
return;
}
std::string status, payload;
try
{
oxenc::bt_dict_consumer btdc{m.body()};
if (m)
payload = btdc.require<std::string>("RC");
else
{
payload = btdc.require<std::string>("RECIPIENT");
status = btdc.require<std::string>("TARGET");
}
}
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
return;
}
if (m)
{
_router.node_db()->put_rc_if_newer(RemoteRC{payload});
}
else
{
if (status == "ERROR")
{
log::info(link_cat, "FindRouterMessage failed with remote exception!");
// Do something smart here probably
return;
}
RouterID target{reinterpret_cast<uint8_t*>(payload.data())};
if (status == FindRouterMessage::RETRY_EXP)
{
log::info(link_cat, "FindRouterMessage failed, retrying as exploratory!");
send_control_message(
target, "find_router", FindRouterMessage::serialize(target, false, true));
}
else if (status == FindRouterMessage::RETRY_ITER)
{
log::info(link_cat, "FindRouterMessage failed, retrying as iterative!");
send_control_message(
target, "find_router", FindRouterMessage::serialize(target, true, false));
}
else if (status == FindRouterMessage::RETRY_NEW)
{
log::info(link_cat, "FindRouterMessage failed, retrying with new recipient!");
send_control_message(
target, "find_router", FindRouterMessage::serialize(target, false, false));
}
}
}
void
LinkManager::handle_find_router_error(oxen::quic::message&& m)
{
if (m.timed_out)
{
log::info(link_cat, "FindRouterMessage timed out!");
return;
}
std::string status, payload;
try
{
oxenc::bt_dict_consumer btdc{m.body()};
payload = btdc.require<std::string>("RECIPIENT");
status = btdc.require<std::string>("TARGET");
}
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
return;
}
if (status == "ERROR")
{
log::info(link_cat, "FindRouterMessage failed with remote exception!");
// Do something smart here probably
return;
}
RouterID target{reinterpret_cast<uint8_t*>(payload.data())};
if (status == FindRouterMessage::RETRY_EXP)
{
log::info(link_cat, "FindRouterMessage failed, retrying as exploratory!");
send_control_message(
target, "find_router", FindRouterMessage::serialize(target, false, true));
}
else if (status == FindRouterMessage::RETRY_ITER)
{
log::info(link_cat, "FindRouterMessage failed, retrying as iterative!");
send_control_message(
target, "find_router", FindRouterMessage::serialize(target, true, false));
}
else if (status == FindRouterMessage::RETRY_NEW)
{
log::info(link_cat, "FindRouterMessage failed, retrying with new recipient!");
send_control_message(
target, "find_router", FindRouterMessage::serialize(target, false, false));
}
}
void
LinkManager::handle_publish_intro(std::string_view body, std::function<void(std::string)> respond)
{
@ -1606,7 +1413,7 @@ namespace llarp
if (not hop)
return;
// if terminal hop, payload should contain a request (e.g. "find_router"); handle and respond.
// if terminal hop, payload should contain a request (e.g. "find_name"); handle and respond.
if (hop->terminal_hop)
{
hop->onion(payload, nonce, false);

@ -6,7 +6,6 @@
#include <llarp/crypto/crypto.hpp>
#include <llarp/messages/common.hpp>
#include <llarp/path/transit_hop.hpp>
#include <llarp/router/rc_lookup_handler.hpp>
#include <llarp/router_contact.hpp>
#include <llarp/util/compare_ptr.hpp>
#include <llarp/util/decaying_hashset.hpp>
@ -27,6 +26,7 @@ namespace
namespace llarp
{
struct LinkManager;
class NodeDB;
namespace link
{
@ -176,7 +176,6 @@ namespace llarp
util::DecayingHashSet<RouterID> clients{path::DEFAULT_LIFETIME};
RCLookupHandler* rc_lookup;
std::shared_ptr<NodeDB> node_db;
oxen::quic::Address addr;
@ -221,6 +220,12 @@ namespace llarp
return addr;
}
void
gossip_rc(const RouterID& rc_rid, std::string serialized_rc);
void
handle_gossip_rc(oxen::quic::message m);
bool
have_connection_to(const RouterID& remote, bool client_only = false) const;
@ -261,7 +266,7 @@ namespace llarp
extract_status() const;
void
init(RCLookupHandler* rcLookup);
init();
void
for_each_connection(std::function<void(link::Connection&)> func);
@ -288,9 +293,6 @@ namespace llarp
handle_find_intro(std::string_view body, std::function<void(std::string)> respond); // relay
void
handle_publish_intro(std::string_view body, std::function<void(std::string)> respond); // relay
void
handle_find_router(
std::string_view body, std::function<void(std::string)> respond); // relay + path
// Path messages
void
@ -314,7 +316,6 @@ namespace llarp
void (LinkManager::*)(std::string_view body, std::function<void(std::string)> respond)>
path_requests = {
{"find_name"sv, &LinkManager::handle_find_name},
{"find_router"sv, &LinkManager::handle_find_router},
{"publish_intro"sv, &LinkManager::handle_publish_intro},
{"find_intro"sv, &LinkManager::handle_find_intro}};
/*
@ -327,14 +328,12 @@ namespace llarp
*/
// these requests are direct, i.e. not over a path;
// only "find_router" makes sense client->relay,
// the rest are relay->relay
// TODO: new RC fetch endpoint (which will be both client->relay and relay->relay)
std::unordered_map<
std::string_view,
void (LinkManager::*)(std::string_view body, std::function<void(std::string)> respond)>
direct_requests = {
{"find_router"sv, &LinkManager::handle_find_router},
{"publish_intro"sv, &LinkManager::handle_publish_intro},
{"find_intro"sv, &LinkManager::handle_find_intro}};
@ -350,7 +349,6 @@ namespace llarp
void handle_find_name_response(oxen::quic::message);
void handle_find_intro_response(oxen::quic::message);
void handle_publish_intro_response(oxen::quic::message);
void handle_find_router_response(oxen::quic::message);
// Path responses
void handle_path_latency_response(oxen::quic::message);
@ -363,18 +361,11 @@ namespace llarp
std::unordered_map<std::string, void (LinkManager::*)(oxen::quic::message)> rpc_responses = {
{"find_name", &LinkManager::handle_find_name_response},
{"find_router", &LinkManager::handle_find_router_response},
{"publish_intro", &LinkManager::handle_publish_intro_response},
{"find_intro", &LinkManager::handle_find_intro_response},
{"update_exit", &LinkManager::handle_update_exit_response},
{"obtain_exit", &LinkManager::handle_obtain_exit_response},
{"close_exit", &LinkManager::handle_close_exit_response}};
public:
// Public response functions and error handling functions invoked elsehwere. These take
// r-value references s.t. that message is taken out of calling scope
void
handle_find_router_error(oxen::quic::message&& m);
};
namespace link
@ -438,7 +429,6 @@ namespace llarp
std::unordered_map<std::string, void (llarp::link::LinkManager::*)(oxen::quic::message)>
rpc_commands = {
{"find_name", &handle_find_name},
{"find_router", &handle_find_router},
// ...
};

@ -14,9 +14,6 @@ static const std::string RC_FILE_EXT = ".signed";
namespace llarp
{
NodeDB::Entry::Entry(RemoteRC value) : rc(std::move(value)), insertedAt(llarp::time_now_ms())
{}
static void
EnsureSkiplist(fs::path nodedbDir)
{
@ -72,8 +69,8 @@ namespace llarp
// make copy of all rcs
std::vector<RemoteRC> copy;
for (const auto& item : entries)
copy.push_back(item.second.rc);
for (const auto& item : known_rcs)
copy.push_back(item.second);
// flush them to disk in one big job
// TODO: split this up? idk maybe some day...
@ -99,6 +96,81 @@ namespace llarp
return m_Root / skiplistDir / fname;
}
bool
NodeDB::want_rc(const RouterID& rid) const
{
if (not router.is_service_node())
return true;
return registered_routers.count(rid);
}
void
NodeDB::set_bootstrap_routers(const std::set<RemoteRC>& rcs)
{
bootstraps.clear(); // this function really shouldn't be called more than once, but...
for (const auto& rc : rcs)
bootstraps.emplace(rc.router_id(), rc);
}
void
NodeDB::set_router_whitelist(
const std::vector<RouterID>& whitelist,
const std::vector<RouterID>& greylist,
const std::vector<RouterID>& greenlist)
{
if (whitelist.empty())
return;
registered_routers.clear();
registered_routers.insert(whitelist.begin(), whitelist.end());
registered_routers.insert(greylist.begin(), greylist.end());
registered_routers.insert(greenlist.begin(), greenlist.end());
router_whitelist.clear();
router_whitelist.insert(whitelist.begin(), whitelist.end());
router_greylist.clear();
router_greylist.insert(greylist.begin(), greylist.end());
router_greenlist.clear();
router_greenlist.insert(greenlist.begin(), greenlist.end());
log::info(
logcat, "lokinet service node list now has ", router_whitelist.size(), " active routers");
}
std::optional<RouterID>
NodeDB::get_random_whitelist_router() const
{
const auto sz = router_whitelist.size();
if (sz == 0)
return std::nullopt;
auto itr = router_whitelist.begin();
if (sz > 1)
std::advance(itr, randint() % sz);
return *itr;
}
bool
NodeDB::is_connection_allowed(const RouterID& remote) const
{
if (pinned_edges.size() && pinned_edges.count(remote) == 0 && bootstraps.count(remote) == 0)
{
return false;
}
if (not router.is_service_node())
return true;
return router_whitelist.count(remote) or router_greylist.count(remote);
}
bool
NodeDB::is_first_hop_allowed(const RouterID& remote) const
{
if (pinned_edges.size() && pinned_edges.count(remote) == 0)
return false;
return true;
}
void
NodeDB::load_from_disk()
{
@ -137,10 +209,10 @@ namespace llarp
return true;
}
// validate signature and purge entries with invalid signatures
// validate signature and purge known_rcs with invalid signatures
// load ones with valid signatures
if (rc.verify())
entries.emplace(rc.router_id(), rc);
known_rcs.emplace(rc.router_id(), rc);
else
purge.emplace(f);
@ -165,36 +237,33 @@ namespace llarp
return;
router.loop()->call([this]() {
for (const auto& item : entries)
item.second.rc.write(get_path_by_pubkey(item.first));
for (const auto& item : known_rcs)
item.second.write(get_path_by_pubkey(item.first));
});
}
bool
NodeDB::has_router(RouterID pk) const
NodeDB::has_rc(RouterID pk) const
{
return router.loop()->call_get(
[this, pk]() -> bool { return entries.find(pk) != entries.end(); });
return known_rcs.count(pk);
}
std::optional<RemoteRC>
NodeDB::get_rc(RouterID pk) const
{
return router.loop()->call_get([this, pk]() -> std::optional<RemoteRC> {
const auto itr = entries.find(pk);
const auto itr = known_rcs.find(pk);
if (itr == entries.end())
return std::nullopt;
if (itr == known_rcs.end())
return std::nullopt;
return itr->second.rc;
});
return itr->second;
}
void
NodeDB::remove_router(RouterID pk)
{
router.loop()->call([this, pk]() {
entries.erase(pk);
known_rcs.erase(pk);
remove_many_from_disk_async({pk});
});
}
@ -202,54 +271,37 @@ namespace llarp
void
NodeDB::remove_stale_rcs(std::unordered_set<RouterID> keep, llarp_time_t cutoff)
{
router.loop()->call([this, keep, cutoff]() {
std::unordered_set<RouterID> removed;
auto itr = entries.begin();
while (itr != entries.end())
{
if (itr->second.insertedAt < cutoff and keep.count(itr->second.rc.router_id()) == 0)
{
removed.insert(itr->second.rc.router_id());
itr = entries.erase(itr);
}
else
++itr;
}
if (not removed.empty())
remove_many_from_disk_async(std::move(removed));
});
(void)keep;
(void)cutoff;
// TODO: handling of "stale" is pending change, removing here for now.
}
void
bool
NodeDB::put_rc(RemoteRC rc)
{
router.loop()->call([this, rc]() {
const auto& rid = rc.router_id();
entries.erase(rid);
entries.emplace(rid, rc);
});
const auto& rid = rc.router_id();
if (not want_rc(rid))
return false;
known_rcs.erase(rid);
known_rcs.emplace(rid, std::move(rc));
return true;
}
size_t
NodeDB::num_loaded() const
{
return router.loop()->call_get([this]() { return entries.size(); });
return router.loop()->call_get([this]() { return known_rcs.size(); });
}
void
bool
NodeDB::put_rc_if_newer(RemoteRC rc)
{
router.loop()->call([this, rc]() {
auto itr = entries.find(rc.router_id());
if (itr == entries.end() or itr->second.rc.other_is_newer(rc))
{
// delete if existing
if (itr != entries.end())
entries.erase(itr);
// add new entry
entries.emplace(rc.router_id(), rc);
}
});
auto itr = known_rcs.find(rc.router_id());
if (itr == known_rcs.end() or itr->second.other_is_newer(rc))
{
return put_rc(std::move(rc));
}
return false;
}
void
@ -296,10 +348,10 @@ namespace llarp
return router.loop()->call_get([this, location, numRouters]() -> std::vector<RemoteRC> {
std::vector<const RemoteRC*> all;
all.reserve(entries.size());
for (auto& entry : entries)
all.reserve(known_rcs.size());
for (auto& entry : known_rcs)
{
all.push_back(&entry.second.rc);
all.push_back(&entry.second);
}
auto it_mid = numRouters < all.size() ? all.begin() + numRouters : all.end();

@ -24,16 +24,7 @@ namespace llarp
class NodeDB
{
struct Entry
{
const RemoteRC rc;
llarp_time_t insertedAt;
explicit Entry(RemoteRC rc);
};
using NodeMap = std::unordered_map<RouterID, Entry>;
NodeMap entries;
std::unordered_map<RouterID, RemoteRC> known_rcs;
const Router& router;
const fs::path m_Root;
@ -49,14 +40,95 @@ namespace llarp
fs::path
get_path_by_pubkey(RouterID pk) const;
std::unordered_map<RouterID, RemoteRC> bootstraps;
// whitelist = active routers
std::unordered_set<RouterID> router_whitelist;
// greylist = fully funded, but decommissioned routers
std::unordered_set<RouterID> router_greylist;
// greenlist = registered but not fully-staked routers
std::unordered_set<RouterID> router_greenlist;
// all registered relays (snodes)
std::unordered_set<RouterID> registered_routers;
// only ever use to specific edges as path first-hops
std::unordered_set<RouterID> pinned_edges;
bool
want_rc(const RouterID& rid) const;
public:
void
set_bootstrap_routers(const std::set<RemoteRC>& rcs);
const std::unordered_set<RouterID>&
whitelist() const
{
return router_whitelist;
}
const std::unordered_set<RouterID>&
greylist() const
{
return router_greylist;
}
const std::unordered_set<RouterID>&
get_registered_routers() const
{
return registered_routers;
}
void
set_router_whitelist(
const std::vector<RouterID>& whitelist,
const std::vector<RouterID>& greylist,
const std::vector<RouterID>& greenlist);
std::optional<RouterID>
get_random_whitelist_router() const;
// client:
// if pinned edges were specified, connections are allowed only to those and
// to the configured bootstrap nodes. otherwise, always allow.
//
// relay:
// outgoing connections are allowed only to other registered, funded relays
// (whitelist and greylist, respectively).
bool
is_connection_allowed(const RouterID& remote) const;
// client:
// same as is_connection_allowed
//
// server:
// we only build new paths through registered, not decommissioned relays
// (i.e. whitelist)
bool
is_path_allowed(const RouterID& remote) const
{
return router_whitelist.count(remote);
}
// if pinned edges were specified, the remote must be in that set, else any remote
// is allowed as first hop.
bool
is_first_hop_allowed(const RouterID& remote) const;
const std::unordered_set<RouterID>&
get_pinned_edges() const
{
return pinned_edges;
}
explicit NodeDB(
fs::path rootdir, std::function<void(std::function<void()>)> diskCaller, Router* r);
/// in memory nodedb
NodeDB();
/// load all entries from disk syncrhonously
/// load all known_rcs from disk syncrhonously
void
load_from_disk();
@ -82,7 +154,7 @@ namespace llarp
/// return true if we have an rc by its ident pubkey
bool
has_router(RouterID pk) const;
has_rc(RouterID pk) const;
/// maybe get an rc by its ident pubkey
std::optional<RemoteRC>
@ -93,44 +165,30 @@ namespace llarp
GetRandom(Filter visit) const
{
return router.loop()->call_get([visit]() -> std::optional<RemoteRC> {
std::vector<const decltype(entries)::value_type*> entries;
for (const auto& entry : entries)
entries.push_back(entry);
std::vector<const decltype(known_rcs)::value_type*> known_rcs;
for (const auto& entry : known_rcs)
known_rcs.push_back(entry);
std::shuffle(entries.begin(), entries.end(), llarp::csrng);
std::shuffle(known_rcs.begin(), known_rcs.end(), llarp::csrng);
for (const auto entry : entries)
for (const auto entry : known_rcs)
{
if (visit(entry->second.rc))
return entry->second.rc;
if (visit(entry->second))
return entry->second;
}
return std::nullopt;
});
}
/// visit all entries
/// visit all known_rcs
template <typename Visit>
void
VisitAll(Visit visit) const
{
router.loop()->call([this, visit]() {
for (const auto& item : entries)
visit(item.second.rc);
});
}
/// visit all entries inserted before a timestamp
template <typename Visit>
void
VisitInsertedBefore(Visit visit, llarp_time_t insertedBefore)
{
router.loop()->call([this, visit, insertedBefore]() {
for (const auto& item : entries)
{
if (item.second.insertedAt < insertedBefore)
visit(item.second.rc);
}
for (const auto& item : known_rcs)
visit(item.second);
});
}
@ -145,13 +203,13 @@ namespace llarp
{
router.loop()->call([this, visit]() {
std::unordered_set<RouterID> removed;
auto itr = entries.begin();
while (itr != entries.end())
auto itr = known_rcs.begin();
while (itr != known_rcs.end())
{
if (visit(itr->second.rc))
if (visit(itr->second))
{
removed.insert(itr->second.rc.router_id());
itr = entries.erase(itr);
removed.insert(itr->second.router_id());
itr = known_rcs.erase(itr);
}
else
++itr;
@ -165,12 +223,14 @@ namespace llarp
void
remove_stale_rcs(std::unordered_set<RouterID> keep, llarp_time_t cutoff);
/// put this rc into the cache if it is not there or newer than the one there already
void
put_rc_if_newer(RemoteRC rc);
/// unconditional put of rc into cache
void
/// put (or replace) the RC if we consider it valid (want_rc). returns true if put.
bool
put_rc(RemoteRC rc);
/// if we consider it valid (want_rc),
/// put this rc into the cache if it is not there or is newer than the one there already
/// returns true if the rc was inserted
bool
put_rc_if_newer(RemoteRC rc);
};
} // namespace llarp

@ -82,13 +82,6 @@ namespace llarp::path
"find_name", FindNameMessage::serialize(std::move(name)), std::move(func));
}
bool
Path::find_router(std::string rid, std::function<void(std::string)> func)
{
return send_path_control_message(
"find_router", FindRouterMessage::serialize(std::move(rid), false, false), std::move(func));
}
bool
Path::send_path_control_message(
std::string method, std::string body, std::function<void(std::string)> func)

@ -176,9 +176,6 @@ namespace llarp
bool
find_name(std::string name, std::function<void(std::string)> func = nullptr);
bool
find_router(std::string rid, std::function<void(std::string)> func = nullptr);
bool
find_intro(
const dht::Key_t& location,

@ -9,7 +9,6 @@
#include <llarp/nodedb.hpp>
#include <llarp/path/pathset.hpp>
#include <llarp/profiling.hpp>
#include <llarp/router/rc_lookup_handler.hpp>
#include <llarp/router/router.hpp>
#include <llarp/util/logging.hpp>
@ -575,30 +574,6 @@ namespace llarp
router->router_profiling().MarkPathTimeout(p.get());
PathSet::HandlePathBuildTimeout(p);
DoPathBuildBackoff();
for (const auto& hop : p->hops)
{
const auto& target = hop.rc.router_id();
// look up router and see if it's still on the network
log::info(path_cat, "Looking up RouterID {} due to path build timeout", target);
router->rc_lookup_handler().get_rc(
target,
[this](auto rid, auto rc, auto success) {
if (success && rc)
{
log::info(path_cat, "Refreshed RouterContact for {}", rid);
router->node_db()->put_rc_if_newer(*rc);
}
else
{
// remove all connections to this router as it's probably not registered anymore
log::warning(path_cat, "Removing router {} due to path build timeout", rid);
router->link_manager().deregister_peer(rid);
router->node_db()->remove_router(rid);
}
},
true);
}
}
void

@ -1,151 +0,0 @@
#include "rc_gossiper.hpp"
#include <llarp/router_contact.hpp>
#include <llarp/util/time.hpp>
namespace llarp
{
// 30 minutes
static constexpr auto RCGossipFilterDecayInterval = 30min;
// (30 minutes * 2) - 5 minutes
static constexpr auto GossipOurRCInterval = (RCGossipFilterDecayInterval * 2) - (5min);
RCGossiper::RCGossiper() : filter(std::chrono::duration_cast<Time_t>(RCGossipFilterDecayInterval))
{}
void
RCGossiper::Init(LinkManager* l, const RouterID& ourID, Router* r)
{
rid = ourID;
link_manager = l;
router = r;
}
bool
RCGossiper::ShouldGossipOurRC(Time_t now) const
{
return now >= (last_rc_gossip + GossipOurRCInterval);
}
bool
RCGossiper::IsOurRC(const LocalRC& rc) const
{
return rc.router_id() == rid;
}
void
RCGossiper::Decay(Time_t now)
{
filter.Decay(now);
}
void
RCGossiper::Forget(const RouterID& pk)
{
filter.Remove(pk);
if (rid == pk)
last_rc_gossip = 0s;
}
TimePoint_t
RCGossiper::NextGossipAt() const
{
if (auto maybe = LastGossipAt())
return *maybe + GossipOurRCInterval;
return DateClock_t::now();
}
std::optional<TimePoint_t>
RCGossiper::LastGossipAt() const
{
if (last_rc_gossip == 0s)
return std::nullopt;
return DateClock_t::time_point{last_rc_gossip};
}
bool
RCGossiper::GossipRC(const LocalRC& rc)
{
// only distribute public routers
if (not rc.is_public_router())
return false;
if (link_manager == nullptr)
return false;
const RouterID pubkey(rc.router_id());
// filter check
if (filter.Contains(pubkey))
return false;
filter.Insert(pubkey);
const auto now = time_now_ms();
// is this our rc?
if (IsOurRC(rc))
{
// should we gossip our rc?
if (not ShouldGossipOurRC(now))
{
// nah drop it
return false;
}
// ya pop it
last_rc_gossip = now;
}
// send a GRCM as gossip method
// DHTImmediateMessage gossip;
// gossip.msgs.emplace_back(new dht::GotRouterMessage(dht::Key_t{}, 0, {rc}, false));
// std::vector<RouterID> gossipTo;
/*
* TODO: gossip RC via libquic
*
// select peers to gossip to
m_LinkManager->ForEachPeer(
[&](const AbstractLinkSession* peerSession, bool) {
// ensure connected session
if (not(peerSession && peerSession->IsEstablished()))
return;
// check if public router
const auto other_rc = peerSession->GetRemoteRC();
if (not other_rc.IsPublicRouter())
return;
gossipTo.emplace_back(other_rc.pubkey);
},
true);
std::unordered_set<RouterID> keys;
// grab the keys we want to use
std::sample(
gossipTo.begin(), gossipTo.end(), std::inserter(keys, keys.end()), MaxGossipPeers,
llarp::csrng);
m_LinkManager->ForEachPeer([&](AbstractLinkSession* peerSession) {
if (not(peerSession && peerSession->IsEstablished()))
return;
// exclude from gossip as we have not selected to use it
if (keys.count(peerSession->GetPubKey()) == 0)
return;
// encode message
AbstractLinkSession::Message_t msg{};
msg.resize(MAX_LINK_MSG_SIZE / 2);
llarp_buffer_t buf(msg);
if (not gossip.BEncode(&buf))
return;
msg.resize(buf.cur - buf.base);
m_router->NotifyRouterEvent<tooling::RCGossipSentEvent>(m_router->pubkey(), rc);
// send message
peerSession->SendMessageBuffer(std::move(msg), nullptr, gossip.Priority());
});
*
*
*/
return true;
}
} // namespace llarp

@ -1,57 +0,0 @@
#pragma once
#include <llarp/router_id.hpp>
#include <llarp/util/decaying_hashset.hpp>
#include <optional>
namespace llarp
{
struct Router;
/// The maximum number of peers we will flood a gossiped RC to when propagating an RC
constexpr size_t MaxGossipPeers = 20;
struct LinkManager;
struct LocalRC;
struct RCGossiper
{
using Time_t = Duration_t;
RCGossiper();
~RCGossiper() = default;
bool
GossipRC(const LocalRC& rc);
void
Decay(Time_t now);
bool
ShouldGossipOurRC(Time_t now) const;
bool
IsOurRC(const LocalRC& rc) const;
void
Init(LinkManager*, const RouterID&, Router*);
void
Forget(const RouterID& router);
TimePoint_t
NextGossipAt() const;
std::optional<TimePoint_t>
LastGossipAt() const;
private:
RouterID rid;
Time_t last_rc_gossip = 0s;
LinkManager* link_manager = nullptr;
util::DecayingHashSet<RouterID> filter;
Router* router;
};
} // namespace llarp

@ -1,357 +0,0 @@
#include "rc_lookup_handler.hpp"
#include "router.hpp"
#include <llarp/crypto/crypto.hpp>
#include <llarp/link/contacts.hpp>
#include <llarp/link/link_manager.hpp>
#include <llarp/nodedb.hpp>
#include <llarp/router_contact.hpp>
#include <llarp/service/context.hpp>
#include <llarp/util/types.hpp>
#include <functional>
#include <iterator>
namespace llarp
{
void
RCLookupHandler::add_valid_router(const RouterID& rid)
{
router->loop()->call([this, rid]() { router_whitelist.insert(rid); });
}
void
RCLookupHandler::remove_valid_router(const RouterID& rid)
{
router->loop()->call([this, rid]() { router_whitelist.erase(rid); });
}
static void
loadColourList(std::unordered_set<RouterID>& beigelist, const std::vector<RouterID>& new_beige)
{
beigelist.clear();
beigelist.insert(new_beige.begin(), new_beige.end());
}
void
RCLookupHandler::set_router_whitelist(
const std::vector<RouterID>& whitelist,
const std::vector<RouterID>& greylist,
const std::vector<RouterID>& greenlist)
{
if (whitelist.empty())
return;
router->loop()->call([this, whitelist, greylist, greenlist]() {
loadColourList(router_whitelist, whitelist);
loadColourList(router_greylist, greylist);
loadColourList(router_greenlist, greenlist);
LogInfo("lokinet service node list now has ", router_whitelist.size(), " active routers");
});
}
bool
RCLookupHandler::has_received_whitelist() const
{
return router->loop()->call_get([this]() { return not router_whitelist.empty(); });
}
std::unordered_set<RouterID>
RCLookupHandler::whitelist() const
{
return router->loop()->call_get([this]() { return router_whitelist; });
}
void
RCLookupHandler::get_rc(const RouterID& rid, RCRequestCallback callback, bool forceLookup)
{
(void)rid;
(void)callback;
(void)forceLookup;
/* RC refactor pending, this will likely go away entirely
*
*
RemoteRC remoteRC;
if (not forceLookup)
{
if (const auto maybe = node_db->get_rc(rid); maybe.has_value())
{
remoteRC = *maybe;
if (callback)
{
callback(rid, remoteRC, true);
}
return;
}
}
auto lookup_cb = [this, callback, rid](RemoteRC rc, bool success) mutable {
auto& r = link_manager->router();
if (not success)
{
if (callback)
callback(rid, std::nullopt, false);
return;
}
r.node_db()->put_rc_if_newer(rc);
if (callback)
callback(rc.router_id(), rc, true);
};
// TODO: RC fetching and gossiping in general is being refactored, and the old method
// of look it up over a path or directly but using the same method but called
// differently is going away. It's a mess. The below will do a lookup via a path,
// relays will need a different implementation TBD.
if (!isServiceNode)
hidden_service_context->GetDefault()->lookup_router(rid, std::move(lookup_cb));
*/
}
bool
RCLookupHandler::is_grey_listed(const RouterID& remote) const
{
if (strict_connect_pubkeys.size() && strict_connect_pubkeys.count(remote) == 0
&& !is_remote_in_bootstrap(remote))
{
return false;
}
if (not isServiceNode)
return false;
return router->loop()->call_get([this, remote]() { return router_greylist.count(remote); });
}
bool
RCLookupHandler::is_green_listed(const RouterID& remote) const
{
return router->loop()->call_get([this, remote]() { return router_greenlist.count(remote); });
}
bool
RCLookupHandler::is_registered(const RouterID& rid) const
{
return router->loop()->call_get([this, rid]() {
return router_whitelist.count(rid) || router_greylist.count(rid)
|| router_greenlist.count(rid);
});
}
bool
RCLookupHandler::is_path_allowed(const RouterID& rid) const
{
return router->loop()->call_get([this, rid]() {
if (strict_connect_pubkeys.size() && strict_connect_pubkeys.count(rid) == 0
&& !is_remote_in_bootstrap(rid))
{
return false;
}
if (not isServiceNode)
return true;
return router_whitelist.count(rid) != 0;
});
}
bool
RCLookupHandler::is_session_allowed(const RouterID& rid) const
{
return router->loop()->call_get([this, rid]() {
if (strict_connect_pubkeys.size() && strict_connect_pubkeys.count(rid) == 0
&& !is_remote_in_bootstrap(rid))
{
return false;
}
if (not isServiceNode)
return true;
return router_whitelist.count(rid) or router_greylist.count(rid);
});
}
bool
RCLookupHandler::check_rc(const RemoteRC& rc) const
{
if (not is_session_allowed(rc.router_id()))
{
contacts->delete_rc_node_async(dht::Key_t{rc.router_id()});
return false;
}
if (not rc.verify())
{
log::info(link_cat, "Invalid RC (rid: {})", rc.router_id());
return false;
}
// update nodedb if required
if (rc.is_public_router())
{
log::info(link_cat, "Adding or updating RC (rid: {}) to nodeDB and DHT", rc.router_id());
node_db->put_rc_if_newer(rc);
contacts->put_rc_node_async(rc);
}
return true;
}
size_t
RCLookupHandler::num_strict_connect_routers() const
{
return strict_connect_pubkeys.size();
}
bool
RCLookupHandler::get_random_whitelist_router(RouterID& rid) const
{
return router->loop()->call_get([this, rid]() mutable {
const auto sz = router_whitelist.size();
auto itr = router_whitelist.begin();
if (sz == 0)
return false;
if (sz > 1)
std::advance(itr, randint() % sz);
rid = *itr;
return true;
});
}
void
RCLookupHandler::periodic_update(llarp_time_t now)
{
// try looking up stale routers
std::unordered_set<RouterID> routersToLookUp;
node_db->VisitInsertedBefore(
[&](const RouterContact& rc) { routersToLookUp.insert(rc.router_id()); },
now - RouterContact::REPUBLISH);
for (const auto& router : routersToLookUp)
{
get_rc(router, nullptr, true);
}
node_db->remove_stale_rcs(boostrap_rid_list, now - RouterContact::STALE);
}
void
RCLookupHandler::explore_network()
{
const size_t known = node_db->num_loaded();
if (bootstrap_rc_list.empty() && known == 0)
{
LogError("we have no bootstrap nodes specified");
}
else if (known <= bootstrap_rc_list.size())
{
for (const auto& rc : bootstrap_rc_list)
{
const auto& rid = rc.router_id();
log::info(link_cat, "Doing explore via bootstrap node: {}", rid);
// TODO: replace this concept
// dht->ExploreNetworkVia(dht::Key_t{rc.pubkey});
}
}
if (isServiceNode)
{
static constexpr size_t LookupPerTick = 5;
std::vector<RouterID> lookup_routers = router->loop()->call_get([this]() {
std::vector<RouterID> lookups;
lookups.reserve(LookupPerTick);
for (const auto& r : router_whitelist)
{
if (not node_db->has_router(r))
lookups.emplace_back(r);
}
return lookups;
});
if (lookup_routers.size() > LookupPerTick)
{
std::shuffle(lookup_routers.begin(), lookup_routers.end(), llarp::csrng);
lookup_routers.resize(LookupPerTick);
}
for (const auto& r : lookup_routers)
get_rc(r, nullptr, true);
return;
}
// service nodes gossip, not explore
if (contacts->router()->is_service_node())
return;
// explore via every connected peer
/*
* TODO: DHT explore via libquic
*
_linkManager->ForEachPeer([&](ILinkSession* s) {
if (!s->IsEstablished())
return;
const RouterContact rc = s->GetRemoteRC();
if (rc.IsPublicRouter() && (_bootstrapRCList.find(rc) == _bootstrapRCList.end()))
{
LogDebug("Doing explore via public node: ", RouterID(rc.pubkey));
_dht->impl->ExploreNetworkVia(dht::Key_t{rc.pubkey});
}
});
*
*
*/
}
void
RCLookupHandler::init(
std::shared_ptr<Contacts> c,
std::shared_ptr<NodeDB> nodedb,
EventLoop_ptr l,
std::function<void(std::function<void()>)> dowork,
LinkManager* linkManager,
service::Context* hiddenServiceContext,
const std::unordered_set<RouterID>& strictConnectPubkeys,
const std::set<RemoteRC>& bootstrapRCList,
bool isServiceNode_arg)
{
contacts = c;
node_db = std::move(nodedb);
loop = std::move(l);
work_func = std::move(dowork);
hidden_service_context = hiddenServiceContext;
strict_connect_pubkeys = strictConnectPubkeys;
bootstrap_rc_list = bootstrapRCList;
link_manager = linkManager;
router = &link_manager->router();
isServiceNode = isServiceNode_arg;
for (const auto& rc : bootstrap_rc_list)
{
boostrap_rid_list.insert(rc.router_id());
}
}
bool
RCLookupHandler::is_remote_in_bootstrap(const RouterID& remote) const
{
for (const auto& rc : bootstrap_rc_list)
{
if (rc.router_id() == remote)
{
return true;
}
}
return false;
}
} // namespace llarp

@ -1,143 +0,0 @@
#pragma once
#include <llarp/router_contact.hpp>
#include <llarp/router_id.hpp>
#include <llarp/util/thread/threading.hpp>
#include <chrono>
#include <list>
#include <set>
#include <unordered_map>
#include <unordered_set>
struct llarp_dht_context;
namespace llarp
{
class NodeDB;
struct Router;
class EventLoop;
namespace service
{
struct Context;
} // namespace service
struct Contacts;
struct LinkManager;
enum class RCRequestResult
{
Success,
InvalidRouter,
RouterNotFound,
BadRC
};
using RCRequestCallback =
std::function<void(const RouterID&, std::optional<RemoteRC>, bool success)>;
struct RCLookupHandler
{
public:
~RCLookupHandler() = default;
void
add_valid_router(const RouterID& router);
void
remove_valid_router(const RouterID& router);
void
set_router_whitelist(
const std::vector<RouterID>& whitelist,
const std::vector<RouterID>& greylist,
const std::vector<RouterID>& greenlist);
bool
has_received_whitelist() const;
void
get_rc(const RouterID& router, RCRequestCallback callback, bool forceLookup = false);
bool
is_path_allowed(const RouterID& remote) const;
bool
is_session_allowed(const RouterID& remote) const;
bool
is_grey_listed(const RouterID& remote) const;
// "greenlist" = new routers (i.e. "green") that aren't fully funded yet
bool
is_green_listed(const RouterID& remote) const;
// registered just means that there is at least an operator stake, but doesn't require the node
// be fully funded, active, or not decommed. (In other words: it is any of the white, grey, or
// green list).
bool
is_registered(const RouterID& remote) const;
bool
check_rc(const RemoteRC& rc) const;
bool
get_random_whitelist_router(RouterID& router) const;
void
periodic_update(llarp_time_t now);
void
explore_network();
size_t
num_strict_connect_routers() const;
void
init(
std::shared_ptr<Contacts> contacts,
std::shared_ptr<NodeDB> nodedb,
std::shared_ptr<EventLoop> loop,
std::function<void(std::function<void()>)> dowork,
LinkManager* linkManager,
service::Context* hiddenServiceContext,
const std::unordered_set<RouterID>& strictConnectPubkeys,
const std::set<RemoteRC>& bootstrapRCList,
bool isServiceNode_arg);
std::unordered_set<RouterID>
whitelist() const;
private:
bool
is_remote_in_bootstrap(const RouterID& remote) const;
std::shared_ptr<Contacts> contacts = nullptr;
std::shared_ptr<NodeDB> node_db;
std::shared_ptr<EventLoop> loop;
std::function<void(std::function<void()>)> work_func = nullptr;
service::Context* hidden_service_context = nullptr;
LinkManager* link_manager = nullptr;
Router* router;
/// explicit whitelist of routers we will connect to directly (not for
/// service nodes)
std::unordered_set<RouterID> strict_connect_pubkeys;
std::set<RemoteRC> bootstrap_rc_list;
std::unordered_set<RouterID> boostrap_rid_list;
// Now that all calls are made through the event loop, any access to these
// booleans is not guarded by a mutex
std::atomic<bool> isServiceNode = false;
// whitelist = active routers
std::unordered_set<RouterID> router_whitelist;
// greylist = fully funded, but decommissioned routers
std::unordered_set<RouterID> router_greylist;
// greenlist = registered but not fully-staked routers
std::unordered_set<RouterID> router_greenlist;
};
} // namespace llarp

@ -227,32 +227,19 @@ namespace llarp
_link_manager.set_conn_persist(remote, until);
}
void
Router::GossipRCIfNeeded(const LocalRC rc)
{
/// if we are not a service node forget about gossip
if (not is_service_node())
return;
/// wait for random uptime
if (std::chrono::milliseconds{Uptime()} < _randomStartDelay)
return;
_rcGossiper.GossipRC(rc);
}
bool
Router::GetRandomGoodRouter(RouterID& router)
std::optional<RouterID>
Router::GetRandomGoodRouter()
{
if (is_service_node())
{
return _rc_lookup_handler.get_random_whitelist_router(router);
return node_db()->get_random_whitelist_router();
}
if (auto maybe = node_db()->GetRandom([](const auto&) -> bool { return true; }))
{
router = maybe->router_id();
return true;
return maybe->router_id();
}
return false;
return std::nullopt;
}
void
@ -273,16 +260,6 @@ namespace llarp
_link_manager.connect_to(rc);
}
void
Router::lookup_router(RouterID rid, std::function<void(oxen::quic::message)> func)
{
_link_manager.send_control_message(
rid,
"find_router",
FindRouterMessage::serialize(std::move(rid), false, false),
std::move(func));
}
bool
Router::send_data_message(const RouterID& remote, std::string payload)
{
@ -479,25 +456,25 @@ namespace llarp
bool
Router::have_snode_whitelist() const
{
return is_service_node() and _rc_lookup_handler.has_received_whitelist();
return whitelist_received;
}
bool
Router::appears_decommed() const
{
return have_snode_whitelist() and _rc_lookup_handler.is_grey_listed(pubkey());
return have_snode_whitelist() and node_db()->greylist().count(pubkey());
}
bool
Router::appears_funded() const
{
return have_snode_whitelist() and _rc_lookup_handler.is_session_allowed(pubkey());
return have_snode_whitelist() and node_db()->is_connection_allowed(pubkey());
}
bool
Router::appears_registered() const
{
return have_snode_whitelist() and _rc_lookup_handler.is_registered(pubkey());
return have_snode_whitelist() and node_db()->get_registered_routers().count(pubkey());
}
bool
@ -509,7 +486,7 @@ namespace llarp
bool
Router::SessionToRouterAllowed(const RouterID& router) const
{
return _rc_lookup_handler.is_session_allowed(router);
return node_db()->is_connection_allowed(router);
}
bool
@ -520,7 +497,7 @@ namespace llarp
// we are decom'd don't allow any paths outbound at all
return false;
}
return _rc_lookup_handler.is_path_allowed(router);
return node_db()->is_path_allowed(router);
}
size_t
@ -542,16 +519,6 @@ namespace llarp
queue_disk_io([&]() { router_contact.write(our_rc_file); });
}
bool
Router::update_rc()
{
router_contact.resign();
if (is_service_node())
save_rc();
return true;
}
bool
Router::from_config(const Config& conf)
{
@ -679,23 +646,15 @@ namespace llarp
it = bootstrap_rc_list.erase(it);
}
node_db()->set_bootstrap_routers(bootstrap_rc_list);
if (conf.bootstrap.seednode)
LogInfo("we are a seed node");
else
LogInfo("Loaded ", bootstrap_rc_list.size(), " bootstrap routers");
// Init components after relevant config settings loaded
_link_manager.init(&_rc_lookup_handler);
_rc_lookup_handler.init(
_contacts,
_node_db,
_loop,
[this](std::function<void(void)> work) { queue_work(std::move(work)); },
&_link_manager,
&_hidden_service_context,
strictConnectPubkeys,
bootstrap_rc_list,
_is_service_node);
_link_manager.init();
// FIXME: kludge for now, will be part of larger cleanup effort.
if (_is_service_node)
@ -796,12 +755,12 @@ namespace llarp
" | {} active paths | block {} ",
path_context().CurrentTransitPaths(),
(_rpc_client ? _rpc_client->BlockHeight() : 0));
auto maybe_last = _rcGossiper.LastGossipAt();
bool have_gossiped = last_rc_gossip == std::chrono::system_clock::time_point::min();
fmt::format_to(
out,
" | gossip: (next/last) {} / {}",
short_time_from_now(_rcGossiper.NextGossipAt()),
maybe_last ? short_time_from_now(*maybe_last) : "never");
short_time_from_now(next_rc_gossip),
have_gossiped ? short_time_from_now(last_rc_gossip) : "never");
}
else
{
@ -854,36 +813,28 @@ namespace llarp
report_stats();
}
_rcGossiper.Decay(now);
_rc_lookup_handler.periodic_update(now);
const bool has_whitelist = _rc_lookup_handler.has_received_whitelist();
const bool is_snode = is_service_node();
const bool is_decommed = appears_decommed();
bool should_gossip = appears_funded();
if (is_snode
and (router_contact.expires_within_delta(now, std::chrono::milliseconds(randint() % 10000))
or (now - router_contact.timestamp().time_since_epoch()) > rc_regen_interval))
{
LogInfo("regenerating RC");
if (update_rc())
{
// our rc changed so we should gossip it
should_gossip = true;
// remove our replay entry so it goes out
_rcGossiper.Forget(pubkey());
}
else
LogError("failed to update our RC");
}
if (should_gossip)
// (relay-only) if we have fetched the relay list from oxend and
// we are registered and funded, we want to gossip our RC periodically
auto now_timepoint = std::chrono::system_clock::time_point(now);
if (is_snode and appears_funded() and (now_timepoint > next_rc_gossip))
{
// if we have the whitelist enabled, we have fetched the list and we are in either
// the white or grey list, we want to gossip our RC
GossipRCIfNeeded(router_contact);
log::info(logcat, "regenerating and gossiping RC");
router_contact.resign();
save_rc();
auto view = router_contact.view();
_link_manager.gossip_rc(
pubkey(), std::string{reinterpret_cast<const char*>(view.data()), view.size()});
last_rc_gossip = now_timepoint;
// 1min to 5min before "stale time" is next gossip time
auto random_delta =
std::chrono::seconds{std::uniform_int_distribution<size_t>{60, 300}(llarp::csrng)};
next_rc_gossip = now_timepoint + RouterContact::STALE_AGE - random_delta;
}
// remove RCs for nodes that are no longer allowed by network policy
node_db()->RemoveIf([&](const RemoteRC& rc) -> bool {
// don't purge bootstrap nodes from nodedb
@ -915,7 +866,7 @@ namespace llarp
}
// if we don't have the whitelist yet don't remove the entry
if (not has_whitelist)
if (not whitelist_received)
{
log::debug(logcat, "Skipping check on {}: don't have whitelist yet", rc.router_id());
return false;
@ -924,7 +875,7 @@ namespace llarp
// the whitelist enabled and we got the whitelist
// check against the whitelist and remove if it's not
// in the whitelist OR if there is no whitelist don't remove
if (has_whitelist and not _rc_lookup_handler.is_session_allowed(rc.router_id()))
if (not node_db()->is_connection_allowed(rc.router_id()))
{
log::debug(logcat, "Removing {}: not a valid router", rc.router_id());
return true;
@ -932,7 +883,9 @@ namespace llarp
return false;
});
if (not is_snode or not has_whitelist)
/* TODO: this behavior seems incorrect, but fixing it will require discussion
*
if (not is_snode or not whitelist_received)
{
// find all deregistered relays
std::unordered_set<RouterID> close_peers;
@ -948,23 +901,18 @@ namespace llarp
for (auto& peer : close_peers)
_link_manager.deregister_peer(peer);
}
*/
_link_manager.check_persisting_conns(now);
size_t connected = NumberOfConnectedRouters();
const int interval = is_snode ? 5 : 2;
const auto timepoint_now = std::chrono::steady_clock::now();
if (timepoint_now >= _next_explore_at and not is_decommed)
{
_rc_lookup_handler.explore_network();
_next_explore_at = timepoint_now + std::chrono::seconds(interval);
}
size_t connectToNum = _link_manager.min_connected_routers;
const auto strictConnect = _rc_lookup_handler.num_strict_connect_routers();
if (strictConnect > 0 && connectToNum > strictConnect)
const auto& pinned_edges = _node_db->get_pinned_edges();
const auto pinned_count = pinned_edges.size();
if (pinned_count > 0 && connectToNum > pinned_count)
{
connectToNum = strictConnect;
connectToNum = pinned_count;
}
if (is_snode and now >= _next_decomm_warning)
@ -1012,14 +960,6 @@ namespace llarp
_node_db->Tick(now);
std::set<dht::Key_t> peer_keys;
for_each_connection(
[&peer_keys](link::Connection& conn) { peer_keys.emplace(conn.remote_rc.router_id()); });
_contacts->rc_nodes()->RemoveIf(
[&peer_keys](const dht::Key_t& k) -> bool { return peer_keys.count(k) == 0; });
paths.ExpirePaths(now);
// update tick timestamp
@ -1032,13 +972,20 @@ namespace llarp
return _link_manager.get_random_connected(result);
}
const std::unordered_set<RouterID>&
Router::get_whitelist() const
{
return _node_db->whitelist();
}
void
Router::set_router_whitelist(
const std::vector<RouterID>& whitelist,
const std::vector<RouterID>& greylist,
const std::vector<RouterID>& unfundedlist)
{
_rc_lookup_handler.set_router_whitelist(whitelist, greylist, unfundedlist);
node_db()->set_router_whitelist(whitelist, greylist, unfundedlist);
whitelist_received = true;
}
bool
@ -1076,7 +1023,6 @@ namespace llarp
log::info(logcat, "Router initialized as service node!");
const RouterID us = pubkey();
_rcGossiper.Init(&_link_manager, us, this);
// relays do not use profiling
router_profiling().Disable();
}

@ -1,7 +1,5 @@
#pragma once
#include "rc_gossiper.hpp"
#include "rc_lookup_handler.hpp"
#include "route_poker.hpp"
#include <llarp/bootstrap.hpp>
@ -59,8 +57,6 @@ namespace llarp
static constexpr size_t INTROSET_STORAGE_REDUNDANCY =
(INTROSET_RELAY_REDUNDANCY * INTROSET_REQS_PER_RELAY);
static constexpr size_t RC_LOOKUP_STORAGE_REDUNDANCY{4};
struct Contacts;
struct Router : std::enable_shared_from_this<Router>
@ -122,17 +118,16 @@ namespace llarp
const llarp_time_t _randomStartDelay;
std::shared_ptr<rpc::LokidRpcClient> _rpc_client;
bool whitelist_received{false};
oxenmq::address rpc_addr;
Profiling _router_profiling;
fs::path _profile_file;
LinkManager _link_manager{*this};
RCLookupHandler _rc_lookup_handler;
RCGossiper _rcGossiper;
/// how often do we resign our RC? milliseconds.
// TODO: make configurable
llarp_time_t rc_regen_interval = 1h;
std::chrono::system_clock::time_point last_rc_gossip{
std::chrono::system_clock::time_point::min()};
std::chrono::system_clock::time_point next_rc_gossip{
std::chrono::system_clock::time_point::min()};
// should we be sending padded messages every interval?
bool send_padding = false;
@ -150,9 +145,6 @@ namespace llarp
void
save_rc();
bool
update_rc();
bool
from_config(const Config& conf);
@ -163,9 +155,6 @@ namespace llarp
void
for_each_connection(std::function<void(link::Connection&)> func);
void
lookup_router(RouterID rid, std::function<void(oxen::quic::message)> = nullptr);
void
connect_to(const RouterID& rid);
@ -211,12 +200,6 @@ namespace llarp
return _link_manager;
}
RCLookupHandler&
rc_lookup_handler()
{
return _rc_lookup_handler;
}
inline int
outbound_udp_socket() const
{
@ -292,11 +275,8 @@ namespace llarp
util::StatusObject
ExtractSummaryStatus() const;
std::unordered_set<RouterID>
router_whitelist() const
{
return _rc_lookup_handler.whitelist();
}
const std::unordered_set<RouterID>&
get_whitelist() const;
void
set_router_whitelist(
@ -380,17 +360,14 @@ namespace llarp
std::string
status_line();
void
GossipRCIfNeeded(const LocalRC rc);
void
InitInboundLinks();
void
InitOutboundLinks();
bool
GetRandomGoodRouter(RouterID& r);
std::optional<RouterID>
GetRandomGoodRouter();
/// initialize us as a service node
/// return true on success

@ -56,19 +56,18 @@ namespace llarp
/// Timespans for RCs:
/// How long (relative to its timestamp) before an RC becomes stale. Stale records are used
/// (e.g. for path building) only if there are no non-stale records available, such as might be
/// How long (from its signing time) before an RC is considered "stale". Relays republish
/// their RCs slightly more frequently than this so that ideally this won't happen.
static constexpr auto STALE_AGE = 6h;
/// How long (from its signing time) before an RC becomes "outdated". Outdated records are used
/// (e.g. for path building) only if there are no newer records available, such as might be
/// the case when a client has been turned off for a while.
static constexpr auto STALE = 12h;
static constexpr auto OUTDATED_AGE = 12h;
/// How long before an RC becomes invalid (and thus deleted).
static constexpr auto LIFETIME = 30 * 24h;
/// How long before a relay updates and re-publish its RC to the network. (Relays can
/// re-publish more frequently than this if needed; this is meant to apply only if there are no
/// changes i.e. just to push out a new confirmation of the details).
static constexpr auto REPUBLISH = STALE / 2 - 5min;
ustring_view
view() const
{

@ -846,56 +846,6 @@ namespace llarp::service
}
}
void
Endpoint::EnsureRouterIsKnown(const RouterID& rid)
{
if (rid.IsZero())
return;
if (!router()->node_db()->has_router(rid))
{
lookup_router(rid);
}
}
bool
Endpoint::lookup_router(RouterID rid, std::function<void(RouterContact rc, bool success)> func)
{
(void)rid;
(void)func;
return false;
/* RC refactor pending, this will likely go away entirely
*
*
auto path = GetEstablishedPathClosestTo(rid);
auto response_cb = [func = std::move(func)](std::string resp, bool timeout) {
if (timeout)
func(RouterContact{}, false);
std::string payload;
try
{
oxenc::bt_dict_consumer btdc{resp};
payload = btdc.require<std::string>("RC");
}
catch (...)
{
log::warning(link_cat, "Failed to parse Find Router response!");
func(RouterContact{}, false);
return;
}
RouterContact result{std::move(payload)};
func(result, true);
};
path->find_router("find_router", std::move(response_cb));
return true;
*/
}
void
Endpoint::HandlePathBuilt(path::Path_ptr p)
{
@ -1279,7 +1229,8 @@ namespace llarp::service
this);
_state->snode_sessions[snode] = session;
}
EnsureRouterIsKnown(snode);
if (not router()->node_db()->has_rc(snode))
return false;
auto range = nodeSessions.equal_range(snode);
auto itr = range.first;
while (itr != range.second)

@ -229,14 +229,6 @@ namespace llarp
bool
ProcessDataMessage(std::shared_ptr<ProtocolMessage> msg);
/// ensure that we know a router, looks up if it doesn't
void
EnsureRouterIsKnown(const RouterID& router);
// "find router" via closest path
bool
lookup_router(RouterID router, std::function<void(RouterContact, bool)> func = nullptr);
// "find name"
void
lookup_name(std::string name, std::function<void(std::string, bool)> func = nullptr) override;

@ -5,6 +5,7 @@
#include "endpoint_util.hpp"
#include "protocol_type.hpp"
#include <llarp/nodedb.hpp>
#include <llarp/router/router.hpp>
#include <algorithm>
@ -316,9 +317,6 @@ namespace llarp::service
}
}
}
// lookup router in intro if set and unknown
if (not next_intro.router.IsZero())
ep.EnsureRouterIsKnown(next_intro.router);
if (ReadyToSend())
{
@ -407,6 +405,18 @@ namespace llarp::service
std::vector<Introduction> intros = current_intro.intros;
// don't consider intros for which we don't have the RC for the pivot
auto itr = intros.begin();
while (itr != intros.end())
{
if (not ep.router()->node_db()->has_rc(itr->router))
{
itr = intros.erase(itr);
continue;
}
itr++;
}
if (intros.size() > 1)
{
std::shuffle(intros.begin(), intros.end(), llarp::csrng);
@ -435,7 +445,6 @@ namespace llarp::service
{
if (ep.SnodeBlacklist().count(intro.router))
continue;
ep.EnsureRouterIsKnown(intro.router);
if (intro.ExpiresSoon(now))
continue;
if (next_intro != intro)

Loading…
Cancel
Save