pull yourself up by your bootstraps sonny

- initial/subsequent fetching combined for RouterContacts and RouterIDs
- bootstraps fallback implemented and looped into fetch logic
pull/2227/head
dr7ana 7 months ago
parent f6e651caea
commit 91121ea22b

@ -178,7 +178,7 @@ lokinet_add_library(lokinet-nodedb
set(BOOTSTRAP_FALLBACKS)
foreach(bs IN ITEMS MAINNET TESTNET)
if(BOOTSTRAP_FALLBACK_${bs})
message(STATUS "Building with ${bs} fallback boostrap path \"${BOOTSTRAP_FALLBACK_${bs}}\"")
message(STATUS "Building with ${bs} fallback bootstrap path \"${BOOTSTRAP_FALLBACK_${bs}}\"")
file(READ "${BOOTSTRAP_FALLBACK_${bs}}" bs_data HEX)
if(bs STREQUAL TESTNET)
set(network "gamma")

@ -25,6 +25,24 @@ namespace llarp
return true;
}
bool
BootstrapList::contains(const RouterID& rid)
{
for (const auto& it : *this)
{
if (it.router_id() == rid)
return true;
}
return false;
}
bool
BootstrapList::contains(const RemoteRC& rc)
{
return count(rc);
}
std::string_view
BootstrapList::bt_encode() const
{

@ -11,6 +11,8 @@ namespace llarp
{
struct BootstrapList final : public std::set<RemoteRC>
{
size_t index;
bool
bt_decode(std::string_view buf);
@ -20,6 +22,21 @@ namespace llarp
void
read_from_file(const fs::path& fpath);
bool
contains(const RouterID& rid);
// returns a reference to the next index and a boolean that equals true if
// this is the front of the set
std::pair<const RemoteRC&, bool>
next()
{
++index %= this->size();
return std::make_pair(*std::next(this->begin(), index), index == 0);
}
bool
contains(const RemoteRC& rc);
void
clear_list()
{

@ -5,9 +5,8 @@
#include <llarp/messages/dht.hpp>
#include <llarp/messages/exit.hpp>
#include <llarp/messages/fetch.hpp>
#include <llarp/messages/path.hpp>
#include <llarp/messages/rc.hpp>
#include <llarp/messages/router_id.hpp>
#include <llarp/nodedb.hpp>
#include <llarp/path/path.hpp>
#include <llarp/router/router.hpp>
@ -537,22 +536,68 @@ namespace llarp
void
LinkManager::handle_gossip_rc(oxen::quic::message m)
{
try
// RemoteRC constructor wraps deserialization in a try/catch
RemoteRC rc{m.body()};
if (node_db->put_rc_if_newer(rc))
{
RemoteRC rc{m.body()};
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.");
}
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.");
void
LinkManager::fetch_bootstrap_rcs(
const RouterID& source, std::string payload, std::function<void(oxen::quic::message m)> func)
{
send_control_message(source, "bfetch_rcs", std::move(payload), std::move(func));
}
void
LinkManager::handle_fetch_bootstrap_rcs(oxen::quic::message m)
{
// this handler should not be registered for clients
assert(_router.is_service_node());
const auto& rcs = node_db->get_rcs();
size_t quantity;
try
{
oxenc::bt_dict_consumer btdc{m.body()};
quantity = btdc.require<size_t>("quantity");
}
catch (const std::exception& e)
{
log::info(link_cat, "Recieved invalid RC, dropping on the floor.");
log::info(link_cat, "Exception handling RC Fetch request: {}", e.what());
m.respond(messages::ERROR_RESPONSE, true);
return;
}
auto rc_size = rcs.size();
auto now = llarp::time_now_ms();
size_t i = 0;
oxenc::bt_dict_producer btdp;
{
auto sublist = btdp.append_list("rcs");
while (i < quantity)
{
auto& next_rc = std::next(rcs.begin(), csrng() % rc_size)->second;
if (next_rc.is_expired(now))
continue;
sublist.append_encoded(next_rc.view());
++i;
}
}
m.respond(std::move(btdp).str());
}
void
@ -571,69 +616,64 @@ namespace llarp
const auto& rcs = node_db->get_rcs();
const auto now = time_point_now();
std::vector<std::string> explicit_ids;
rc_time since_time;
try
{
oxenc::bt_dict_consumer btdc{m.body()};
btdc.required("explicit_ids");
auto explicit_ids = btdc.consume_list<std::vector<std::string>>();
explicit_ids = btdc.consume_list<std::vector<std::string>>();
auto since_time = rc_time{std::chrono::seconds{btdc.require<int64_t>("since")}};
since_time = rc_time{std::chrono::seconds{btdc.require<int64_t>("since")}};
}
catch (const std::exception& e)
{
log::info(link_cat, "Exception handling RC Fetch request: {}", e.what());
m.respond(messages::ERROR_RESPONSE, true);
return;
}
std::unordered_set<RouterID> explicit_relays;
// Initial fetch: give me all the RC's
if (explicit_ids.empty())
{
// TODO: this
}
// Initial fetch: give me all the RC's
if (explicit_ids.empty())
{
// TODO: this
}
std::unordered_set<RouterID> explicit_relays;
if (explicit_ids.size() > (rcs.size() / 4))
for (auto& sv : explicit_ids)
{
if (sv.size() != RouterID::SIZE)
{
log::info(
link_cat, "Remote requested too many relay IDs (greater than 1/4 of what we have).");
m.respond(RCFetchMessage::INVALID_REQUEST, true);
return;
}
for (auto& sv : explicit_ids)
{
if (sv.size() != RouterID::SIZE)
{
m.respond(RCFetchMessage::INVALID_REQUEST, true);
return;
}
explicit_relays.emplace(reinterpret_cast<const byte_t*>(sv.data()));
}
oxenc::bt_dict_producer btdp;
explicit_relays.emplace(reinterpret_cast<const byte_t*>(sv.data()));
}
{
auto rc_sublist = btdp.append_list("rcs");
oxenc::bt_dict_producer btdp;
const auto& last_time = node_db->get_last_rc_update_times();
const auto& last_time = node_db->get_last_rc_update_times();
{
auto sublist = btdp.append_list("rcs");
// if since_time isn't epoch start, subtract a bit for buffer
if (since_time != decltype(since_time)::min())
since_time -= 5s;
// if since_time isn't epoch start, subtract a bit for buffer
if (since_time != decltype(since_time)::min())
since_time -= 5s;
for (const auto& [_, rc] : rcs)
{
if (last_time.at(rc.router_id()) > since_time or explicit_relays.count(rc.router_id()))
rc_sublist.append_encoded(rc.view());
}
for (const auto& [_, rc] : rcs)
{
if (last_time.at(rc.router_id()) > since_time or explicit_relays.count(rc.router_id()))
sublist.append_encoded(rc.view());
}
}
btdp.append("time", now.time_since_epoch().count());
btdp.append("time", now.time_since_epoch().count());
m.respond(std::move(btdp).str());
}
catch (const std::exception& e)
{
log::info(link_cat, "Exception handling RC Fetch request: {}", e.what());
m.respond(messages::ERROR_RESPONSE, true);
}
m.respond(std::move(btdp).str());
}
void
@ -713,6 +753,7 @@ namespace llarp
{
log::warning(link_cat, "Exception: {}", e.what());
respond(messages::ERROR_RESPONSE);
return;
}
_router.rpc_client()->lookup_ons_hash(
@ -1305,33 +1346,20 @@ namespace llarp
void
LinkManager::handle_obtain_exit(oxen::quic::message m)
{
uint64_t flag;
ustring_view pubkey, sig;
std::string_view tx_id, dict_data;
try
{
uint64_t flag;
ustring_view pubkey, sig;
std::string_view tx_id;
oxenc::bt_list_consumer btlc{m.body()};
auto dict_data = btlc.consume_dict_data();
dict_data = btlc.consume_dict_data();
oxenc::bt_dict_consumer btdc{dict_data};
sig = to_usv(btlc.consume_string_view());
flag = btdc.require<uint64_t>("E");
pubkey = btdc.require<ustring_view>("I");
tx_id = btdc.require<std::string_view>("T");
RouterID target{pubkey.data()};
auto transit_hop =
_router.path_context().GetTransitHop(target, PathID_t{to_usv(tx_id).data()});
const auto rx_id = transit_hop->info.rxID;
auto success =
(crypto::verify(pubkey, to_usv(dict_data), sig)
and _router.exitContext().ObtainNewExit(PubKey{pubkey.data()}, rx_id, flag != 0));
m.respond(
ObtainExitMessage::sign_and_serialize_response(_router.identity(), tx_id), not success);
}
catch (const std::exception& e)
{
@ -1339,6 +1367,18 @@ namespace llarp
m.respond(messages::ERROR_RESPONSE, true);
throw;
}
RouterID target{pubkey.data()};
auto transit_hop = _router.path_context().GetTransitHop(target, PathID_t{to_usv(tx_id).data()});
const auto rx_id = transit_hop->info.rxID;
auto success =
(crypto::verify(pubkey, to_usv(dict_data), sig)
and _router.exitContext().ObtainNewExit(PubKey{pubkey.data()}, rx_id, flag != 0));
m.respond(
ObtainExitMessage::sign_and_serialize_response(_router.identity(), tx_id), not success);
}
void
@ -1354,62 +1394,45 @@ namespace llarp
// TODO: what to do here
}
std::string_view tx_id, dict_data;
ustring_view sig;
try
{
std::string_view tx_id;
ustring_view sig;
oxenc::bt_list_consumer btlc{m.body()};
auto dict_data = btlc.consume_dict_data();
dict_data = btlc.consume_dict_data();
oxenc::bt_dict_consumer btdc{dict_data};
sig = to_usv(btlc.consume_string_view());
tx_id = btdc.require<std::string_view>("T");
auto path_ptr = _router.path_context().GetPath(PathID_t{to_usv(tx_id).data()});
if (crypto::verify(_router.pubkey(), to_usv(dict_data), sig))
path_ptr->enable_exit_traffic();
}
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
throw;
}
auto path_ptr = _router.path_context().GetPath(PathID_t{to_usv(tx_id).data()});
if (crypto::verify(_router.pubkey(), to_usv(dict_data), sig))
path_ptr->enable_exit_traffic();
}
void
LinkManager::handle_update_exit(oxen::quic::message m)
{
std::string_view path_id, tx_id, dict_data;
ustring_view sig;
try
{
std::string_view path_id, tx_id;
ustring_view sig;
oxenc::bt_list_consumer btlc{m.body()};
auto dict_data = btlc.consume_dict_data();
dict_data = btlc.consume_dict_data();
oxenc::bt_dict_consumer btdc{dict_data};
sig = to_usv(btlc.consume_string_view());
path_id = btdc.require<std::string_view>("P");
tx_id = btdc.require<std::string_view>("T");
auto transit_hop =
_router.path_context().GetTransitHop(_router.pubkey(), PathID_t{to_usv(tx_id).data()});
if (auto exit_ep =
_router.exitContext().FindEndpointForPath(PathID_t{to_usv(path_id).data()}))
{
if (crypto::verify(exit_ep->PubKey().data(), to_usv(dict_data), sig))
{
(exit_ep->UpdateLocalPath(transit_hop->info.rxID))
? m.respond(UpdateExitMessage::sign_and_serialize_response(_router.identity(), tx_id))
: m.respond(
serialize_response({{messages::STATUS_KEY, UpdateExitMessage::UPDATE_FAILED}}),
true);
}
// If we fail to verify the message, no-op
}
}
catch (const std::exception& e)
{
@ -1417,6 +1440,22 @@ namespace llarp
m.respond(messages::ERROR_RESPONSE, true);
return;
}
auto transit_hop =
_router.path_context().GetTransitHop(_router.pubkey(), PathID_t{to_usv(tx_id).data()});
if (auto exit_ep = _router.exitContext().FindEndpointForPath(PathID_t{to_usv(path_id).data()}))
{
if (crypto::verify(exit_ep->PubKey().data(), to_usv(dict_data), sig))
{
(exit_ep->UpdateLocalPath(transit_hop->info.rxID))
? m.respond(UpdateExitMessage::sign_and_serialize_response(_router.identity(), tx_id))
: m.respond(
serialize_response({{messages::STATUS_KEY, UpdateExitMessage::UPDATE_FAILED}}),
true);
}
// If we fail to verify the message, no-op
}
}
void
@ -1432,69 +1471,53 @@ namespace llarp
// TODO: what to do here
}
std::string tx_id;
std::string_view dict_data;
ustring_view sig;
try
{
std::string tx_id;
ustring_view sig;
oxenc::bt_list_consumer btlc{m.body()};
auto dict_data = btlc.consume_dict_data();
dict_data = btlc.consume_dict_data();
oxenc::bt_dict_consumer btdc{dict_data};
sig = to_usv(btlc.consume_string_view());
tx_id = btdc.require<std::string_view>("T");
auto path_ptr = _router.path_context().GetPath(PathID_t{to_usv(tx_id).data()});
if (crypto::verify(_router.pubkey(), to_usv(dict_data), sig))
{
if (path_ptr->update_exit(std::stoul(tx_id)))
{
// TODO: talk to tom and Jason about how this stupid shit was a no-op originally
// see Path::HandleUpdateExitVerifyMessage
}
else
{}
}
}
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
return;
}
auto path_ptr = _router.path_context().GetPath(PathID_t{to_usv(tx_id).data()});
if (crypto::verify(_router.pubkey(), to_usv(dict_data), sig))
{
if (path_ptr->update_exit(std::stoul(tx_id)))
{
// TODO: talk to tom and Jason about how this stupid shit was a no-op originally
// see Path::HandleUpdateExitVerifyMessage
}
else
{}
}
}
void
LinkManager::handle_close_exit(oxen::quic::message m)
{
std::string_view tx_id, dict_data;
ustring_view sig;
try
{
std::string_view tx_id;
ustring_view sig;
oxenc::bt_list_consumer btlc{m.body()};
auto dict_data = btlc.consume_dict_data();
dict_data = btlc.consume_dict_data();
oxenc::bt_dict_consumer btdc{dict_data};
sig = to_usv(btlc.consume_string_view());
tx_id = btdc.require<std::string_view>("T");
auto transit_hop =
_router.path_context().GetTransitHop(_router.pubkey(), PathID_t{to_usv(tx_id).data()});
const auto rx_id = transit_hop->info.rxID;
if (auto exit_ep = router().exitContext().FindEndpointForPath(rx_id))
{
if (crypto::verify(exit_ep->PubKey().data(), to_usv(dict_data), sig))
{
exit_ep->Close();
m.respond(CloseExitMessage::sign_and_serialize_response(_router.identity(), tx_id));
}
}
m.respond(
serialize_response({{messages::STATUS_KEY, CloseExitMessage::UPDATE_FAILED}}), true);
}
catch (const std::exception& e)
{
@ -1502,6 +1525,22 @@ namespace llarp
m.respond(messages::ERROR_RESPONSE, true);
return;
}
auto transit_hop =
_router.path_context().GetTransitHop(_router.pubkey(), PathID_t{to_usv(tx_id).data()});
const auto rx_id = transit_hop->info.rxID;
if (auto exit_ep = router().exitContext().FindEndpointForPath(rx_id))
{
if (crypto::verify(exit_ep->PubKey().data(), to_usv(dict_data), sig))
{
exit_ep->Close();
m.respond(CloseExitMessage::sign_and_serialize_response(_router.identity(), tx_id));
}
}
m.respond(serialize_response({{messages::STATUS_KEY, CloseExitMessage::UPDATE_FAILED}}), true);
}
void
@ -1517,95 +1556,127 @@ namespace llarp
// TODO: what to do here
}
std::string_view nonce, tx_id, dict_data;
ustring_view sig;
try
{
std::string_view nonce, tx_id;
ustring_view sig;
oxenc::bt_list_consumer btlc{m.body()};
auto dict_data = btlc.consume_dict_data();
dict_data = btlc.consume_dict_data();
oxenc::bt_dict_consumer btdc{dict_data};
sig = to_usv(btlc.consume_string_view());
tx_id = btdc.require<std::string_view>("T");
nonce = btdc.require<std::string_view>("Y");
auto path_ptr = _router.path_context().GetPath(PathID_t{to_usv(tx_id).data()});
if (path_ptr->SupportsAnyRoles(path::ePathRoleExit | path::ePathRoleSVC)
and crypto::verify(_router.pubkey(), to_usv(dict_data), sig))
path_ptr->mark_exit_closed();
}
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
return;
}
auto path_ptr = _router.path_context().GetPath(PathID_t{to_usv(tx_id).data()});
if (path_ptr->SupportsAnyRoles(path::ePathRoleExit | path::ePathRoleSVC)
and crypto::verify(_router.pubkey(), to_usv(dict_data), sig))
path_ptr->mark_exit_closed();
}
void
LinkManager::handle_path_control(oxen::quic::message m, const RouterID& from)
{
ustring_view nonce, path_id_str;
std::string payload;
try
{
oxenc::bt_dict_consumer btdc{m.body()};
auto nonce = SymmNonce{btdc.require<ustring_view>("NONCE").data()};
auto path_id_str = btdc.require<ustring_view>("PATHID");
auto payload = btdc.require<std::string>("PAYLOAD");
auto path_id = PathID_t{path_id_str.data()};
auto hop = _router.path_context().GetTransitHop(from, path_id);
// TODO: use "path_control" for both directions? If not, drop message on
// floor if we don't have the path_id in question; if we decide to make this
// bidirectional, will need to check if we have a Path with path_id.
if (not hop)
return;
// if terminal hop, payload should contain a request (e.g. "find_name"); handle and respond.
if (hop->terminal_hop)
{
hop->onion(payload, nonce, false);
handle_inner_request(std::move(m), std::move(payload), std::move(hop));
return;
}
auto next_id = path_id == hop->info.rxID ? hop->info.txID : hop->info.rxID;
auto next_router = path_id == hop->info.rxID ? hop->info.upstream : hop->info.downstream;
auto new_payload = hop->onion_and_payload(payload, next_id, nonce);
send_control_message(
next_router,
"path_control"s,
std::move(new_payload),
[hop_weak = hop->weak_from_this(), path_id, prev_message = std::move(m)](
oxen::quic::message response) mutable {
auto hop = hop_weak.lock();
if (not hop)
return;
oxenc::bt_dict_consumer resp_btdc{response.body()};
auto nonce = SymmNonce{resp_btdc.require<ustring_view>("NONCE").data()};
auto payload = resp_btdc.require<std::string>("PAYLOAD");
auto resp_payload = hop->onion_and_payload(payload, path_id, nonce);
prev_message.respond(std::move(resp_payload), false);
});
nonce = btdc.require<ustring_view>("NONCE");
path_id_str = btdc.require<ustring_view>("PATHID");
payload = btdc.require<std::string>("PAYLOAD");
}
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
return;
}
auto symnonce = SymmNonce{nonce.data()};
auto path_id = PathID_t{path_id_str.data()};
auto hop = _router.path_context().GetTransitHop(from, path_id);
// TODO: use "path_control" for both directions? If not, drop message on
// floor if we don't have the path_id in question; if we decide to make this
// bidirectional, will need to check if we have a Path with path_id.
if (not hop)
return;
// if terminal hop, payload should contain a request (e.g. "find_name"); handle and respond.
if (hop->terminal_hop)
{
hop->onion(payload, symnonce, false);
handle_inner_request(std::move(m), std::move(payload), std::move(hop));
return;
}
auto& next_id = path_id == hop->info.rxID ? hop->info.txID : hop->info.rxID;
auto& next_router = path_id == hop->info.rxID ? hop->info.upstream : hop->info.downstream;
std::string new_payload = hop->onion_and_payload(payload, next_id, symnonce);
send_control_message(
next_router,
"path_control"s,
std::move(new_payload),
[hop_weak = hop->weak_from_this(), path_id, prev_message = std::move(m)](
oxen::quic::message response) mutable {
auto hop = hop_weak.lock();
if (not hop)
return;
ustring_view nonce;
std::string payload, response_body;
try
{
oxenc::bt_dict_consumer btdc{response.body()};
nonce = btdc.require<ustring_view>("NONCE");
payload = btdc.require<std::string>("PAYLOAD");
}
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
return;
}
auto symnonce = SymmNonce{nonce.data()};
auto resp_payload = hop->onion_and_payload(payload, path_id, symnonce);
prev_message.respond(std::move(resp_payload), false);
});
}
void
LinkManager::handle_inner_request(
oxen::quic::message m, std::string payload, std::shared_ptr<path::TransitHop> hop)
{
oxenc::bt_dict_consumer btdc{payload};
auto body = btdc.require<std::string_view>("BODY");
auto method = btdc.require<std::string_view>("METHOD");
std::string_view body, method;
try
{
oxenc::bt_dict_consumer btdc{payload};
body = btdc.require<std::string_view>("BODY");
method = btdc.require<std::string_view>("METHOD");
}
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
return;
}
// If a handler exists for "method", call it; else drop request on the floor.
auto itr = path_requests.find(method);
if (itr == path_requests.end())
{
log::info(link_cat, "Received path control request \"{}\", which has no handler.", method);
@ -1634,7 +1705,9 @@ namespace llarp
}
try
{}
{
//
}
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());

@ -242,6 +242,15 @@ namespace llarp
void
handle_fetch_router_ids(oxen::quic::message m);
void
fetch_bootstrap_rcs(
const RouterID& source,
std::string payload,
std::function<void(oxen::quic::message m)> func);
void
handle_fetch_bootstrap_rcs(oxen::quic::message m);
bool
have_connection_to(const RouterID& remote, bool client_only = false) const;

@ -20,7 +20,6 @@ namespace llarp
{
namespace messages
{
inline std::string
serialize_response(oxenc::bt_dict supplement = {})
{

@ -0,0 +1,82 @@
#pragma once
#include "common.hpp"
namespace llarp
{
namespace RCFetchMessage
{
inline const auto INVALID_REQUEST =
messages::serialize_response({{messages::STATUS_KEY, "Invalid relay ID requested"}});
inline static std::string
serialize(
std::chrono::system_clock::time_point since, const std::vector<RouterID>& explicit_ids)
{
oxenc::bt_dict_producer btdp;
try
{
{
auto sublist = btdp.append_list("explicit_ids");
for (const auto& rid : explicit_ids)
sublist.append(rid.ToView());
}
btdp.append("since", since.time_since_epoch() / 1s);
}
catch (...)
{
log::error(link_cat, "Error: RCFetchMessage failed to bt encode contents!");
}
return std::move(btdp).str();
}
} // namespace RCFetchMessage
namespace BootstrapFetchMessage
{
inline static std::string
serialize(size_t quantity)
{
oxenc::bt_dict_producer btdp;
btdp.append("quantity", quantity);
return std::move(btdp).str();
}
inline static std::string
serialize_response(const std::vector<RouterID>& explicit_ids)
{
oxenc::bt_dict_producer btdp;
try
{
auto sublist = btdp.append_list("explicit_ids");
for (const auto& rid : explicit_ids)
sublist.append(rid.ToView());
}
catch (...)
{
log::error(link_cat, "Error: BootstrapFetchMessage failed to bt encode contents!");
}
return std::move(btdp).str();
}
} // namespace BootstrapFetchMessage
namespace FetchRIDMessage
{
inline constexpr auto INVALID_REQUEST = "Invalid relay ID requested to relay response from."sv;
inline static std::string
serialize(const RouterID& source)
{
// serialize_response is a bit weird here, and perhaps could have a sister function
// with the same purpose but as a request, but...it works.
return messages::serialize_response({{"source", source.ToView()}});
}
} // namespace FetchRIDMessage
} // namespace llarp

@ -1,31 +0,0 @@
#pragma once
#include "common.hpp"
namespace llarp::RCFetchMessage
{
inline const auto INVALID_REQUEST =
messages::serialize_response({{messages::STATUS_KEY, "Invalid relay ID requested"}});
inline static std::string
serialize(std::chrono::system_clock::time_point since, const std::vector<RouterID>& explicit_ids)
{
oxenc::bt_dict_producer btdp;
try
{
btdp.append("since", since.time_since_epoch() / 1s);
{
auto id_list = btdp.append_list("explicit_ids");
for (const auto& rid : explicit_ids)
id_list.append(rid.ToView());
}
}
catch (...)
{
log::error(link_cat, "Error: RCFetchMessage failed to bt encode contents!");
}
return std::move(btdp).str();
}
} // namespace llarp::RCFetchMessage

@ -1,17 +0,0 @@
#pragma once
#include "common.hpp"
namespace llarp::RouterIDFetch
{
inline constexpr auto INVALID_REQUEST = "Invalid relay ID requested to relay response from."sv;
inline static std::string
serialize(const RouterID& source)
{
// serialize_response is a bit weird here, and perhaps could have a sister function
// with the same purpose but as a request, but...it works.
return messages::serialize_response({{"source", source.ToView()}});
}
} // namespace llarp::RouterIDFetch

@ -2,8 +2,7 @@
#include "crypto/types.hpp"
#include "dht/kademlia.hpp"
#include "messages/rc.hpp"
#include "messages/router_id.hpp"
#include "messages/fetch.hpp"
#include "router_contact.hpp"
#include "util/time.hpp"
@ -105,86 +104,13 @@ namespace llarp
}
void
NodeDB::set_bootstrap_routers(const std::set<RemoteRC>& rcs)
NodeDB::set_bootstrap_routers(std::unique_ptr<BootstrapList> from_router)
{
bootstraps.clear(); // this function really shouldn't be called more than once, but...
for (const auto& rc : rcs)
{
bootstraps.emplace(rc.router_id(), rc);
}
}
bool
NodeDB::rotate_startup_rc_source()
{
if (active_client_routers.size() < 13)
{
// do something here
return false;
}
// TODO: if this needs to be called more than once (ex: drastic failures), then
// change this assert to a bootstraps.clear() call
assert(_bootstraps->empty());
RouterID temp = fetch_source;
while (temp == fetch_source)
std::sample(active_client_routers.begin(), active_client_routers.end(), &temp, 1, csrng);
fetch_source = std::move(temp);
return true;
}
/// Called in normal operation when the relay we fetched RCs from gives either a "bad"
/// response or a timeout. Attempts to switch to a new relay as our RC source, using
/// existing connections if possible, and respecting pinned edges.
void
NodeDB::rotate_rc_source()
{
auto conn_count = _router.link_manager().get_num_connected();
// This function makes no sense to be called if we have no connections...
if (conn_count == 0)
throw std::runtime_error{"Called rotate_rc_source with no connections, does not make sense!"};
// We should not be in this function if client_known_routers isn't populated
if (active_client_routers.size() <= 1)
throw std::runtime_error{"Cannot rotate RC source without RC source(s) to rotate to!"};
RemoteRC new_source{};
_router.link_manager().get_random_connected(new_source);
if (conn_count == 1)
{
// if we only have one connection, it must be current rc fetch source
assert(new_source.router_id() == fetch_source);
if (pinned_edges.size() == 1)
{
// only one pinned edge set, use it even though it gave unsatisfactory RCs
assert(fetch_source == *(pinned_edges.begin()));
log::warning(
logcat,
"Single pinned edge {} gave bad RC response; still using it despite this.",
fetch_source);
return;
}
// only one connection, choose a new relay to connect to for rc fetching
RouterID r = fetch_source;
while (r == fetch_source)
{
std::sample(active_client_routers.begin(), active_client_routers.end(), &r, 1, csrng);
}
fetch_source = std::move(r);
return;
}
// choose one of our other existing connections to use as the RC fetch source
while (new_source.router_id() == fetch_source)
{
_router.link_manager().get_random_connected(new_source);
}
fetch_source = new_source.router_id();
_bootstraps = std::move(from_router);
}
bool
@ -207,12 +133,33 @@ namespace llarp
void
NodeDB::ingest_rid_fetch_responses(const RouterID& source, std::unordered_set<RouterID> ids)
{
if (ids.empty())
fail_sources.insert(source);
fetch_rid_responses[source] = std::move(ids);
}
/** We only call into this function after ensuring two conditions:
1) We have received at least 8 of 12 responses from the queried RouterID sources
2) Of those reponses, less than 4 were errors of any sorts
Logically, this function performs the following basic analysis of the returned RIDs.
*/
bool
NodeDB::process_fetched_rids()
{
std::unordered_set<RouterID> union_set;
for (const auto& [rid, responses] : fetch_rid_responses)
{
std::merge(
union_set.begin(),
union_set.end(),
responses.begin(),
responses.end(),
std::inserter(union_set, union_set.begin()));
}
for (const auto& [rid, responses] : fetch_rid_responses)
{
// TODO: empty == failure, handle that case
@ -228,256 +175,26 @@ namespace llarp
void
NodeDB::fetch_initial()
{
int num_fails = 0;
// fetch_initial_{rcs,router_ids} return false when num_fails == 0
if (fetch_initial_rcs(num_fails))
{
_router.last_rc_fetch = llarp::time_point_now();
if (fetch_initial_router_ids(num_fails))
{
_router.last_rid_fetch = llarp::time_point_now();
return;
}
}
// failure case
// TODO: use bootstrap here!
}
bool
NodeDB::fetch_initial_rcs(int n_fails)
{
int num_failures = n_fails;
is_fetching_rcs = true;
std::vector<RouterID> needed;
const auto now = time_point_now();
for (const auto& [rid, rc] : known_rcs)
{
if (now - rc.timestamp() > RouterContact::OUTDATED_AGE)
needed.push_back(rid);
}
RouterID src =
*std::next(active_client_routers.begin(), csrng() % active_client_routers.size());
while (num_failures < MAX_FETCH_ATTEMPTS)
{
auto success = std::make_shared<std::promise<bool>>();
auto f = success->get_future();
_router.link_manager().fetch_rcs(
src,
RCFetchMessage::serialize(last_rc_update_relay_timestamp, needed),
[this, src, p = std::move(success)](oxen::quic::message m) mutable {
if (m.timed_out)
{
log::info(logcat, "RC fetch to {} timed out", src);
p->set_value(false);
return;
}
try
{
oxenc::bt_dict_consumer btdc{m.body()};
if (not m)
{
auto reason = btdc.require<std::string_view>(messages::STATUS_KEY);
log::info(logcat, "RC fetch to {} returned error: {}", src, reason);
p->set_value(false);
return;
}
auto btlc = btdc.require<oxenc::bt_list_consumer>("rcs"sv);
auto timestamp = rc_time{std::chrono::seconds{btdc.require<int64_t>("time"sv)}};
std::vector<RemoteRC> rcs;
while (not btlc.is_finished())
{
rcs.emplace_back(btlc.consume_dict_consumer());
}
process_fetched_rcs(src, std::move(rcs), timestamp);
p->set_value(true);
}
catch (const std::exception& e)
{
log::info(logcat, "Failed to parse RC fetch response from {}: {}", src, e.what());
p->set_value(false);
return;
}
});
if (f.get())
{
log::debug(logcat, "Successfully fetched RC's from {}", src);
fetch_source = src;
return true;
}
++num_failures;
log::debug(
logcat,
"Unable to fetch RC's from {}; rotating RC source ({}/{} attempts)",
src,
num_failures,
MAX_FETCH_ATTEMPTS);
src = *std::next(active_client_routers.begin(), csrng() % active_client_routers.size());
}
return false;
}
bool
NodeDB::fetch_initial_router_ids(int n_fails)
{
assert(not is_fetching_rids);
int num_failures = n_fails;
select_router_id_sources();
is_fetching_rids = true;
fetch_rid_responses.clear();
RouterID src =
// Set fetch source as random selection of known active client routers
fetch_source =
*std::next(active_client_routers.begin(), csrng() % active_client_routers.size());
std::unordered_set<RouterID> fails;
while (num_failures < MAX_FETCH_ATTEMPTS)
{
auto success = std::make_shared<std::promise<int>>();
auto f = success->get_future();
fails.clear();
for (const auto& target : rid_sources)
{
_router.link_manager().fetch_router_ids(
src,
RouterIDFetch::serialize(target),
[this, src, target, p = std::move(success)](oxen::quic::message m) mutable {
if (not m)
{
log::info(link_cat, "RID fetch from {} via {} timed out", src, target);
ingest_rid_fetch_responses(src);
p->set_value(-1);
return;
}
try
{
oxenc::bt_dict_consumer btdc{m.body()};
btdc.required("routers");
auto router_id_strings = btdc.consume_list<std::vector<ustring>>();
btdc.require_signature("signature", [&src](ustring_view msg, ustring_view sig) {
if (sig.size() != 64)
throw std::runtime_error{"Invalid signature: not 64 bytes"};
if (not crypto::verify(src, msg, sig))
throw std::runtime_error{
"Failed to verify signature for fetch RouterIDs response."};
});
std::unordered_set<RouterID> router_ids;
for (const auto& s : router_id_strings)
{
if (s.size() != RouterID::SIZE)
{
log::warning(
link_cat, "RID fetch from {} via {} returned bad RouterID", target, src);
p->set_value(0);
return;
}
router_ids.emplace(s.data());
}
ingest_rid_fetch_responses(src, std::move(router_ids));
return;
}
catch (const std::exception& e)
{
log::info(link_cat, "Error handling fetch RouterIDs response: {}", e.what());
p->set_value(0);
}
ingest_rid_fetch_responses(src); // empty response == failure
});
switch (f.get())
{
case 1:
log::debug(logcat, "Successfully fetched RID's from {} via {}", target, src);
continue;
case 0:
// RC node relayed our fetch routerID request, but the request failed at the target
log::debug(logcat, "Unsuccessfully fetched RID's from {} via {}", target, src);
fails.insert(target);
continue;
default:
// RC node failed to relay our routerID request; re-select RC node and continue
log::debug(logcat, "RC source {} failed to mediate RID fetching from {}", src, target);
src = *std::next(active_client_routers.begin(), csrng() % active_client_routers.size());
++num_failures;
fetch_rcs();
continue;
}
}
auto n_fails = fails.size();
if (n_fails <= MAX_RID_ERRORS)
{
log::debug(
logcat,
"RID fetching was successful ({}/{} acceptable errors)",
fails.size(),
MAX_RID_ERRORS);
fetch_source = src;
// this is where the trust model will do verification based on the similarity of the sets
if (process_fetched_rids())
{
log::debug(logcat, "Accumulated RID's accepted by trust model");
return true;
}
log::debug(
logcat, "Accumulated RID's rejected by trust model, reselecting all RID sources...");
select_router_id_sources(rid_sources);
++num_failures;
continue;
}
// we had 4 or more failed requests, so we will need to rotate our rid sources
log::debug(
logcat, "RID fetching found {} failures; reselecting failed RID sources...", n_fails);
++num_failures;
select_router_id_sources(fails);
}
return false;
fetch_rcs(true);
}
void
NodeDB::fetch_rcs()
NodeDB::fetch_rcs(bool initial)
{
auto& num_failures = fetch_failures;
// base case; this function is called recursively
if (num_failures > MAX_FETCH_ATTEMPTS)
{
fetch_rcs_result(true);
fetch_rcs_result(initial, true);
return;
}
is_fetching_rcs = true;
is_fetching_rcs = true; // TOTHINK: do these booleans do anything?
std::vector<RouterID> needed;
const auto now = time_point_now();
@ -493,21 +210,22 @@ namespace llarp
_router.link_manager().fetch_rcs(
src,
RCFetchMessage::serialize(last_rc_update_relay_timestamp, needed),
[this, src](oxen::quic::message m) mutable {
[this, src, initial](oxen::quic::message m) mutable {
if (m.timed_out)
{
log::info(logcat, "RC fetch to {} timed out", src);
fetch_rcs_result(true);
fetch_rcs_result(initial, true);
return;
}
try
{
oxenc::bt_dict_consumer btdc{m.body()};
if (not m)
{
auto reason = btdc.require<std::string_view>(messages::STATUS_KEY);
log::info(logcat, "RC fetch to {} returned error: {}", src, reason);
fetch_rcs_result(true);
fetch_rcs_result(initial, true);
return;
}
@ -517,75 +235,34 @@ namespace llarp
std::vector<RemoteRC> rcs;
while (not btlc.is_finished())
{
rcs.emplace_back(btlc.consume_dict_consumer());
}
// if process_fetched_rcs returns false, then the trust model rejected the fetched RC's
fetch_rcs_result(not process_fetched_rcs(src, std::move(rcs), timestamp));
fetch_rcs_result(initial, not process_fetched_rcs(src, std::move(rcs), timestamp));
}
catch (const std::exception& e)
{
log::info(logcat, "Failed to parse RC fetch response from {}: {}", src, e.what());
fetch_rcs_result(true);
fetch_rcs_result(initial, true);
return;
}
});
}
void
NodeDB::fetch_rcs_result(bool error)
{
if (error)
{
++fetch_failures;
if (fetch_failures > MAX_FETCH_ATTEMPTS)
{
log::info(
logcat,
"Failed {} attempts to fetch RC's from {}; reverting to bootstrap...",
MAX_FETCH_ATTEMPTS,
fetch_source);
// TODO: revert to bootstrap
// set rc_fetch_source to bootstrap and try again!
}
else
// find new non-bootstrap RC fetch source and try again buddy
fetch_source = std::next(known_rcs.begin(), csrng() % known_rcs.size())->first;
fetch_rcs();
}
else
{
log::debug(logcat, "Successfully fetched RC's from {}", fetch_source);
post_fetch_rcs();
}
}
void
NodeDB::post_fetch_rcs()
{
is_fetching_rcs = false;
_router.last_rc_fetch = llarp::time_point_now();
}
// TODO: differentiate between errors from the relay node vs errors from the target nodes
void
NodeDB::fetch_router_ids()
NodeDB::fetch_rids(bool initial)
{
// base case; this function is called recursively
if (fetch_failures > MAX_FETCH_ATTEMPTS)
{
fetch_rids_result();
fetch_rids_result(initial);
return;
}
if (rid_sources.empty())
select_router_id_sources();
// if we *still* don't have fetch sources, we can't exactly fetch...
if (rid_sources.empty())
if (not initial and rid_sources.empty())
{
log::error(logcat, "Attempting to fetch RouterIDs, but have no source from which to do so.");
return;
@ -600,15 +277,13 @@ namespace llarp
{
_router.link_manager().fetch_router_ids(
src,
RouterIDFetch::serialize(target),
[this, src, target](oxen::quic::message m) mutable {
if (m.timed_out)
FetchRIDMessage::serialize(target),
[this, src, target, initial](oxen::quic::message m) mutable {
if (not m)
{
log::info(link_cat, "RID fetch from {} via {} timed out", src, target);
++fetch_failures;
ingest_rid_fetch_responses(src);
fetch_rids_result();
ingest_rid_fetch_responses(target);
fetch_rids_result(initial);
return;
}
@ -636,8 +311,7 @@ namespace llarp
log::warning(
link_cat, "RID fetch from {} via {} returned bad RouterID", target, src);
ingest_rid_fetch_responses(target);
fail_sources.insert(target);
fetch_rids_result();
fetch_rids_result(initial);
return;
}
@ -645,22 +319,54 @@ namespace llarp
}
ingest_rid_fetch_responses(target, std::move(router_ids));
fetch_rids_result(); // success
fetch_rids_result(initial); // success
return;
}
catch (const std::exception& e)
{
log::info(link_cat, "Error handling fetch RouterIDs response: {}", e.what());
ingest_rid_fetch_responses(target);
fail_sources.insert(target);
fetch_rids_result();
fetch_rids_result(initial);
}
});
}
}
void
NodeDB::fetch_rids_result()
NodeDB::fetch_rcs_result(bool initial, bool error)
{
if (error)
{
++fetch_failures;
if (fetch_failures > MAX_FETCH_ATTEMPTS)
{
log::info(
logcat,
"Failed {} attempts to fetch RC's from {}; reverting to bootstrap...",
MAX_FETCH_ATTEMPTS,
fetch_source);
fallback_to_bootstrap();
return;
}
// find new non-bootstrap RC fetch source and try again buddy
fetch_source = (initial)
? *std::next(active_client_routers.begin(), csrng() % active_client_routers.size())
: std::next(known_rcs.begin(), csrng() % known_rcs.size())->first;
fetch_rcs(initial);
}
else
{
log::debug(logcat, "Successfully fetched RC's from {}", fetch_source);
post_fetch_rcs(initial);
}
}
void
NodeDB::fetch_rids_result(bool initial)
{
if (fetch_failures > MAX_FETCH_ATTEMPTS)
{
@ -670,17 +376,15 @@ namespace llarp
MAX_FETCH_ATTEMPTS,
fetch_source);
// TODO: revert rc_source to bootstrap, start over
fetch_router_ids();
fallback_to_bootstrap();
return;
}
auto n_responses = fetch_rid_responses.size();
if (n_responses < MIN_RID_FETCHES)
if (n_responses < ROUTER_ID_SOURCE_COUNT)
{
log::debug(logcat, "Received {}/{} fetch RID requests", n_responses, 12);
log::debug(logcat, "Received {}/{} fetch RID requests", n_responses, ROUTER_ID_SOURCE_COUNT);
return;
}
@ -695,7 +399,7 @@ namespace llarp
if (process_fetched_rids())
{
log::debug(logcat, "Accumulated RID's accepted by trust model");
post_fetch_rids();
post_fetch_rids(initial);
return;
}
@ -713,17 +417,100 @@ namespace llarp
select_router_id_sources(fail_sources);
}
fetch_router_ids();
fetch_rids(true);
}
void
NodeDB::post_fetch_rcs(bool initial)
{
is_fetching_rcs = false;
_router.last_rc_fetch = llarp::time_point_now();
if (initial)
fetch_rids(initial);
}
void
NodeDB::post_fetch_rids()
NodeDB::post_fetch_rids(bool initial)
{
is_fetching_rids = false;
fetch_rid_responses.clear();
fail_sources.clear();
fetch_failures = 0;
_router.last_rid_fetch = llarp::time_point_now();
if (initial)
_router.initial_fetch_completed();
}
void
NodeDB::fallback_to_bootstrap()
{
if (bootstrap_failures >= MAX_BOOTSTRAP_FETCH_ATTEMPTS)
{
log::info(logcat, "Current bootstrap failed... cycling to next bootstrap...");
bootstrap_failures = 0;
auto [rc, is_front] = _bootstraps->next();
// Base case: if we have returned to the front of the bootstrap list, we're in a
// bad spot
if (using_bootstrap_fallback && is_front)
{
auto err = fmt::format("ERROR: ALL BOOTSTRAPS ARE BAD");
log::error(logcat, err);
throw std::runtime_error{err};
}
using_bootstrap_fallback = true;
fetch_source = rc.router_id();
}
_router.link_manager().fetch_bootstrap_rcs(
fetch_source,
BootstrapFetchMessage::serialize(BOOTSTRAP_SOURCE_COUNT),
[this](oxen::quic::message m) mutable {
if (not m)
{
++bootstrap_failures;
fallback_to_bootstrap();
return;
}
std::unordered_set<RouterID> rids;
try
{
oxenc::bt_dict_consumer btdc{m.body()};
{
auto btlc = btdc.require<oxenc::bt_list_consumer>("rcs"sv);
while (not btlc.is_finished())
{
auto rc = RemoteRC{btlc.consume_dict_consumer()};
rids.emplace(rc.router_id());
}
}
}
catch (const std::exception& e)
{
log::info(
logcat,
"Failed to parse BootstrapRC fetch response from {}: {}",
fetch_source,
e.what());
++bootstrap_failures;
fallback_to_bootstrap();
return;
}
rid_sources.swap(rids);
// if this result is bad, we won't try this bootstrap again
bootstrap_failures = MAX_BOOTSTRAP_FETCH_ATTEMPTS;
using_bootstrap_fallback = false;
fetch_initial();
});
}
void
@ -809,7 +596,8 @@ namespace llarp
bool
NodeDB::is_connection_allowed(const RouterID& remote) const
{
if (pinned_edges.size() && pinned_edges.count(remote) == 0 && bootstraps.count(remote) == 0)
if (_pinned_edges.size() && _pinned_edges.count(remote) == 0
&& not _bootstraps->contains(remote))
{
return false;
}
@ -823,7 +611,7 @@ namespace llarp
bool
NodeDB::is_first_hop_allowed(const RouterID& remote) const
{
if (pinned_edges.size() && pinned_edges.count(remote) == 0)
if (_pinned_edges.size() && _pinned_edges.count(remote) == 0)
return false;
return true;
}

@ -27,6 +27,8 @@ namespace llarp
inline constexpr size_t MIN_ACTIVE_RIDS{24};
inline constexpr size_t MAX_RID_ERRORS{ROUTER_ID_SOURCE_COUNT - MIN_RID_FETCHES};
inline constexpr int MAX_FETCH_ATTEMPTS{10};
inline constexpr int MAX_BOOTSTRAP_FETCH_ATTEMPTS{3};
inline constexpr size_t BOOTSTRAP_SOURCE_COUNT{50};
inline constexpr auto FLUSH_INTERVAL{5min};
@ -66,21 +68,27 @@ namespace llarp
std::unordered_map<RouterID, rc_time> last_rc_update_times;
rc_time last_rc_update_relay_timestamp;
// only ever use to specific edges as path first-hops
std::unordered_set<RouterID> pinned_edges;
std::unordered_set<RouterID> _pinned_edges;
// source of "truth" for RC updating. This relay will also mediate requests to the
// 12 selected active RID's for RID fetching
RouterID fetch_source;
// set of 12 randomly selected RID's from the set of active client routers
std::unordered_set<RouterID> rid_sources;
// set of 12 randomly selected RID's from the client's set of routers
std::unordered_set<RouterID> rid_sources{};
// logs the RID's that resulted in an error during RID fetching
std::unordered_set<RouterID> fail_sources;
std::unordered_set<RouterID> fail_sources{};
// stores all RID fetch responses for greedy comprehensive processing
std::unordered_map<RouterID, std::unordered_set<RouterID>> fetch_rid_responses;
// tracks fetch failures from the RC node performing the initial RC fetch and mediating
// the 12 RID requests to the 12 sources, NOT failures from the 12 sources themselves
std::atomic<int> fetch_failures{0};
/** Failure counters:
- fetch_failures: tracks errors fetching RC's from the RC node and requesting RID's
from the 12 RID sources. Errors in the individual RID sets are NOT counted towards
this, their performance as a group is evaluated wholistically
- bootstrap_failures: tracks errors fetching both RC's from bootstrasps and RID requests
they mediate. This is a different counter as we only bootstrap in problematic cases
*/
std::atomic<int> fetch_failures{0}, bootstrap_failures{0};
std::atomic<bool> is_fetching_rids{false}, is_fetching_rcs{false};
std::atomic<bool> is_fetching_rids{false}, is_fetching_rcs{false},
using_bootstrap_fallback{false};
bool
want_rc(const RouterID& rid) const;
@ -93,63 +101,14 @@ namespace llarp
fs::path
get_path_by_pubkey(RouterID pk) const;
std::unordered_map<RouterID, RemoteRC> bootstraps;
std::unique_ptr<BootstrapList> _bootstraps;
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;
}
const std::unordered_map<RouterID, RemoteRC>&
get_rcs() const
{
return known_rcs;
}
const std::unordered_map<RouterID, rc_time>&
get_last_rc_update_times() const
{
return last_rc_update_times;
}
explicit NodeDB(
fs::path rootdir, std::function<void(std::function<void()>)> diskCaller, Router* r);
/// If we receive a bad set of RCs from our current RC source relay, we consider
/// that relay to be a bad source of RCs and we randomly choose a new one.
///
/// When using a new RC fetch relay, we first re-fetch the full RC list and, if
/// that aligns with our RouterID list, we go back to periodic updates from that relay.
///
/// This will respect edge-pinning and attempt to use a relay we already have
/// a connection with.
void
rotate_rc_source();
/// This function is called during startup and initial fetching. When a lokinet client
/// instance performs its initial RC/RID fetching, it may need to randomly select a
/// node from its list of stale RC's to relay its requests. If there is a failure in
/// mediating these request, the client will randomly select another RC source
///
/// Returns:
/// true - a new startup RC source was selected
/// false - a new startup RC source was NOT selected
bool
rotate_startup_rc_source();
/// in memory nodedb
NodeDB();
bool
process_fetched_rcs(RouterID source, std::vector<RemoteRC> rcs, rc_time timestamp);
@ -163,33 +122,51 @@ namespace llarp
void
fetch_initial();
bool
fetch_initial_rcs(int n_fails = 0);
bool
fetch_initial_router_ids(int n_fails = 0);
// RouterContact fetching
void
fetch_rcs();
fetch_rcs(bool initial = false);
void
fetch_rcs_result(bool error = false);
post_fetch_rcs(bool initial = false);
void
fetch_router_ids();
fetch_rcs_result(bool initial = false, bool error = false);
// RouterID fetching
void
post_fetch_rcs();
fetch_rids(bool initial = false);
void
post_fetch_rids();
post_fetch_rids(bool initial = false);
void
fetch_rids_result(bool initial = false);
// Bootstrap fallback
void
fetch_rids_result();
fallback_to_bootstrap();
void
select_router_id_sources(std::unordered_set<RouterID> excluded = {});
// /// If we receive a bad set of RCs from our current RC source relay, we consider
// /// that relay to be a bad source of RCs and we randomly choose a new one.
// ///
// /// When using a new RC fetch relay, we first re-fetch the full RC list and, if
// /// that aligns with our RouterID list, we go back to periodic updates from that relay.
// ///
// /// This will respect edge-pinning and attempt to use a relay we already have
// /// a connection with.
// void
// rotate_rc_source();
// /// This function is called during startup and initial fetching. When a lokinet client
// /// instance performs its initial RC/RID fetching, it may need to randomly select a
// /// node from its list of stale RC's to relay its requests. If there is a failure in
// /// mediating these request, the client will randomly select another RC source
// ///
// /// Returns:
// /// true - a new startup RC source was selected
// /// false - a new startup RC source was NOT selected
// bool
// rotate_startup_rc_source();
void
set_router_whitelist(
const std::vector<RouterID>& whitelist,
@ -226,17 +203,50 @@ namespace llarp
bool
is_first_hop_allowed(const RouterID& remote) const;
std::unordered_set<RouterID>&
pinned_edges()
{
return _pinned_edges;
}
std::unique_ptr<BootstrapList>&
bootstrap_list()
{
return _bootstraps;
}
void
set_bootstrap_routers(std::unique_ptr<BootstrapList> from_router);
const std::unordered_set<RouterID>&
get_pinned_edges() const
whitelist() const
{
return pinned_edges;
return router_whitelist;
}
explicit NodeDB(
fs::path rootdir, std::function<void(std::function<void()>)> diskCaller, Router* r);
const std::unordered_set<RouterID>&
greylist() const
{
return router_greylist;
}
/// in memory nodedb
NodeDB();
const std::unordered_set<RouterID>&
get_registered_routers() const
{
return registered_routers;
}
const std::unordered_map<RouterID, RemoteRC>&
get_rcs() const
{
return known_rcs;
}
const std::unordered_map<RouterID, rc_time>&
get_last_rc_update_times() const
{
return last_rc_update_times;
}
/// load all known_rcs from disk syncrhonously
void

@ -464,19 +464,21 @@ namespace llarp
bool
Router::appears_decommed() const
{
return have_snode_whitelist() and node_db()->greylist().count(pubkey());
return _is_service_node and have_snode_whitelist() and node_db()->greylist().count(pubkey());
}
bool
Router::appears_funded() const
{
return have_snode_whitelist() and node_db()->is_connection_allowed(pubkey());
return _is_service_node and have_snode_whitelist()
and node_db()->is_connection_allowed(pubkey());
}
bool
Router::appears_registered() const
{
return have_snode_whitelist() and node_db()->get_registered_routers().count(pubkey());
return _is_service_node and have_snode_whitelist()
and node_db()->get_registered_routers().count(pubkey());
}
bool
@ -573,8 +575,6 @@ namespace llarp
auto& networkConfig = conf.network;
/// build a set of strictConnectPubkeys
std::unordered_set<RouterID> strictConnectPubkeys;
if (not networkConfig.strict_connect.empty())
{
const auto& val = networkConfig.strict_connect;
@ -586,7 +586,7 @@ namespace llarp
throw std::runtime_error(
"Must specify more than one strict-connect router if using strict-connect");
strictConnectPubkeys.insert(val.begin(), val.end());
_node_db->pinned_edges().insert(val.begin(), val.end());
log::debug(logcat, "{} strict-connect routers configured", val.size());
}
@ -599,29 +599,31 @@ namespace llarp
// <DATA_DIR>/bootstrap.signed. If this isn't present, leave a useful error message
// TODO: use constant
fs::path defaultBootstrapFile = conf.router.data_dir / "bootstrap.signed";
if (configRouters.empty() and conf.bootstrap.routers.empty())
{
if (fs::exists(defaultBootstrapFile))
configRouters.push_back(defaultBootstrapFile);
}
bootstrap_rc_list.clear();
auto _bootstrap_rc_list = std::make_unique<BootstrapList>();
for (const auto& router : configRouters)
{
log::debug(logcat, "Loading bootstrap router list from {}", defaultBootstrapFile);
bootstrap_rc_list.read_from_file(router);
_bootstrap_rc_list->read_from_file(router);
}
for (const auto& rc : conf.bootstrap.routers)
{
bootstrap_rc_list.emplace(rc);
_bootstrap_rc_list->emplace(rc);
}
if (bootstrap_rc_list.empty() and not conf.bootstrap.seednode)
if (_bootstrap_rc_list->empty() and not conf.bootstrap.seednode)
{
auto fallbacks = llarp::load_bootstrap_fallbacks();
if (bootstrap_rc_list.empty() and not conf.bootstrap.seednode)
if (_bootstrap_rc_list->empty() and not conf.bootstrap.seednode)
{
// empty after trying fallback, if set
log::error(
@ -636,10 +638,10 @@ namespace llarp
// in case someone has an old bootstrap file and is trying to use a bootstrap
// that no longer exists
for (auto it = bootstrap_rc_list.begin(); it != bootstrap_rc_list.end();)
for (auto it = _bootstrap_rc_list->begin(); it != _bootstrap_rc_list->end();)
{
if (it->is_obsolete_bootstrap())
log::warning(logcat, "ignoring obsolete boostrap RC: {}", it->router_id());
log::warning(logcat, "ignoring obsolete bootstrap RC: {}", it->router_id());
else if (not it->verify())
log::warning(logcat, "ignoring invalid bootstrap RC: {}", it->router_id());
else
@ -649,15 +651,15 @@ namespace llarp
}
// we are in one of the above error cases that we warned about:
it = bootstrap_rc_list.erase(it);
it = _bootstrap_rc_list->erase(it);
}
node_db()->set_bootstrap_routers(bootstrap_rc_list);
node_db()->set_bootstrap_routers(std::move(_bootstrap_rc_list));
if (conf.bootstrap.seednode)
LogInfo("we are a seed node");
else
LogInfo("Loaded ", bootstrap_rc_list.size(), " bootstrap routers");
LogInfo("Loaded ", _bootstrap_rc_list->size(), " bootstrap routers");
// Init components after relevant config settings loaded
_link_manager.init();
@ -703,9 +705,10 @@ namespace llarp
bool
Router::IsBootstrapNode(const RouterID r) const
{
const auto& b = _node_db->bootstrap_list();
return std::count_if(
bootstrap_rc_list.begin(),
bootstrap_rc_list.end(),
b->begin(),
b->end(),
[r](const RemoteRC& rc) -> bool { return rc.router_id() == r; })
> 0;
}
@ -725,7 +728,7 @@ namespace llarp
logcat,
"{} RCs loaded with {} bootstrap peers and {} router connections!",
node_db()->num_loaded(),
bootstrap_rc_list.size(),
_node_db->bootstrap_list()->size(),
NumberOfConnectedRouters());
if (is_service_node())
@ -851,7 +854,7 @@ namespace llarp
}
else
{
if (needs_initial_fetch)
if (_needs_initial_fetch)
{
node_db()->fetch_initial();
}
@ -861,14 +864,12 @@ namespace llarp
if (now_timepoint - last_rc_fetch > RC_UPDATE_INTERVAL)
{
node_db()->fetch_rcs();
last_rc_fetch = now_timepoint;
}
// (client-only) periodically fetch updated RouterID list
if (now_timepoint - last_rid_fetch > ROUTERID_UPDATE_INTERVAL)
{
node_db()->fetch_router_ids();
last_rid_fetch = now_timepoint;
node_db()->fetch_rids();
}
}
}
@ -946,8 +947,9 @@ namespace llarp
size_t connected = NumberOfConnectedRouters();
size_t connectToNum = _link_manager.min_connected_routers;
const auto& pinned_edges = _node_db->get_pinned_edges();
const auto& pinned_edges = _node_db->pinned_edges();
const auto pinned_count = pinned_edges.size();
if (pinned_count > 0 && connectToNum > pinned_count)
{
connectToNum = pinned_count;
@ -1085,7 +1087,7 @@ namespace llarp
_contacts = std::make_shared<Contacts>(llarp::dht::Key_t(pubkey()), *this);
for (const auto& rc : bootstrap_rc_list)
for (const auto& rc : *_node_db->bootstrap_list())
{
node_db()->put_rc(rc);
_contacts->rc_nodes()->PutNode(rc);

@ -63,8 +63,6 @@ namespace llarp
private:
std::shared_ptr<RoutePoker> _route_poker;
/// bootstrap RCs
BootstrapList bootstrap_rc_list;
std::chrono::steady_clock::time_point _next_explore_at;
llarp_time_t last_pump = 0s;
// transient iwp encryption key
@ -121,8 +119,6 @@ namespace llarp
fs::path _profile_file;
LinkManager _link_manager{*this};
bool needs_initial_fetch{true};
// should we be sending padded messages every interval?
bool send_padding = false;
@ -146,6 +142,8 @@ namespace llarp
insufficient_peers() const;
protected:
bool _needs_initial_fetch{true};
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{last_rc_gossip};
@ -153,6 +151,18 @@ namespace llarp
std::chrono::system_clock::time_point last_rid_fetch{last_rc_gossip};
public:
bool
needs_initial_fetch() const
{
return _needs_initial_fetch;
}
void
initial_fetch_completed()
{
_needs_initial_fetch = false;
}
void
for_each_connection(std::function<void(link::Connection&)> func);

Loading…
Cancel
Save