diff --git a/llarp/CMakeLists.txt b/llarp/CMakeLists.txt index 657c20cb2..3aff1a3f5 100644 --- a/llarp/CMakeLists.txt +++ b/llarp/CMakeLists.txt @@ -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") diff --git a/llarp/bootstrap.cpp b/llarp/bootstrap.cpp index 474052c30..65d53773c 100644 --- a/llarp/bootstrap.cpp +++ b/llarp/bootstrap.cpp @@ -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 { diff --git a/llarp/bootstrap.hpp b/llarp/bootstrap.hpp index 11e8286d3..bb1a30943 100644 --- a/llarp/bootstrap.hpp +++ b/llarp/bootstrap.hpp @@ -11,6 +11,8 @@ namespace llarp { struct BootstrapList final : public std::set { + 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 + next() + { + ++index %= this->size(); + return std::make_pair(*std::next(this->begin(), index), index == 0); + } + + bool + contains(const RemoteRC& rc); + void clear_list() { diff --git a/llarp/link/link_manager.cpp b/llarp/link/link_manager.cpp index c679b8a2e..d48cc9ba2 100644 --- a/llarp/link/link_manager.cpp +++ b/llarp/link/link_manager.cpp @@ -5,9 +5,8 @@ #include #include +#include #include -#include -#include #include #include #include @@ -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 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("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 explicit_ids; + rc_time since_time; + try { oxenc::bt_dict_consumer btdc{m.body()}; btdc.required("explicit_ids"); - auto explicit_ids = btdc.consume_list>(); + explicit_ids = btdc.consume_list>(); - auto since_time = rc_time{std::chrono::seconds{btdc.require("since")}}; + since_time = rc_time{std::chrono::seconds{btdc.require("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 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 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(sv.data())); - } - - oxenc::bt_dict_producer btdp; + explicit_relays.emplace(reinterpret_cast(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("E"); pubkey = btdc.require("I"); tx_id = btdc.require("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("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("P"); tx_id = btdc.require("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("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("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("T"); nonce = btdc.require("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("NONCE").data()}; - auto path_id_str = btdc.require("PATHID"); - auto payload = btdc.require("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("NONCE").data()}; - auto payload = resp_btdc.require("PAYLOAD"); - auto resp_payload = hop->onion_and_payload(payload, path_id, nonce); - prev_message.respond(std::move(resp_payload), false); - }); + nonce = btdc.require("NONCE"); + path_id_str = btdc.require("PATHID"); + payload = btdc.require("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("NONCE"); + payload = btdc.require("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 hop) { - oxenc::bt_dict_consumer btdc{payload}; - auto body = btdc.require("BODY"); - auto method = btdc.require("METHOD"); + std::string_view body, method; + + try + { + oxenc::bt_dict_consumer btdc{payload}; + body = btdc.require("BODY"); + method = btdc.require("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()); diff --git a/llarp/link/link_manager.hpp b/llarp/link/link_manager.hpp index e51f892dd..b305fa6ef 100644 --- a/llarp/link/link_manager.hpp +++ b/llarp/link/link_manager.hpp @@ -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 func); + + void + handle_fetch_bootstrap_rcs(oxen::quic::message m); + bool have_connection_to(const RouterID& remote, bool client_only = false) const; diff --git a/llarp/messages/common.hpp b/llarp/messages/common.hpp index f15a5184d..9d774aee2 100644 --- a/llarp/messages/common.hpp +++ b/llarp/messages/common.hpp @@ -20,7 +20,6 @@ namespace llarp { namespace messages { - inline std::string serialize_response(oxenc::bt_dict supplement = {}) { diff --git a/llarp/messages/fetch.hpp b/llarp/messages/fetch.hpp new file mode 100644 index 000000000..35e9d18ec --- /dev/null +++ b/llarp/messages/fetch.hpp @@ -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& 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& 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 diff --git a/llarp/messages/rc.hpp b/llarp/messages/rc.hpp deleted file mode 100644 index 633b540d2..000000000 --- a/llarp/messages/rc.hpp +++ /dev/null @@ -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& 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 diff --git a/llarp/messages/router_id.hpp b/llarp/messages/router_id.hpp deleted file mode 100644 index dbdd897de..000000000 --- a/llarp/messages/router_id.hpp +++ /dev/null @@ -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 diff --git a/llarp/nodedb.cpp b/llarp/nodedb.cpp index 5c898d051..dfea702bd 100644 --- a/llarp/nodedb.cpp +++ b/llarp/nodedb.cpp @@ -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& rcs) + NodeDB::set_bootstrap_routers(std::unique_ptr 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 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 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 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>(); - 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(messages::STATUS_KEY); - log::info(logcat, "RC fetch to {} returned error: {}", src, reason); - p->set_value(false); - return; - } - - auto btlc = btdc.require("rcs"sv); - auto timestamp = rc_time{std::chrono::seconds{btdc.require("time"sv)}}; - - std::vector 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 fails; - - while (num_failures < MAX_FETCH_ATTEMPTS) - { - auto success = std::make_shared>(); - 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>(); - - 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 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 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(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 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 rids; + + try + { + oxenc::bt_dict_consumer btdc{m.body()}; + + { + auto btlc = btdc.require("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; } diff --git a/llarp/nodedb.hpp b/llarp/nodedb.hpp index 9a089ddc5..a23d02904 100644 --- a/llarp/nodedb.hpp +++ b/llarp/nodedb.hpp @@ -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 last_rc_update_times; rc_time last_rc_update_relay_timestamp; // only ever use to specific edges as path first-hops - std::unordered_set pinned_edges; + std::unordered_set _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 rid_sources; + // set of 12 randomly selected RID's from the client's set of routers + std::unordered_set rid_sources{}; // logs the RID's that resulted in an error during RID fetching - std::unordered_set fail_sources; + std::unordered_set fail_sources{}; // stores all RID fetch responses for greedy comprehensive processing std::unordered_map> 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 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 fetch_failures{0}, bootstrap_failures{0}; - std::atomic is_fetching_rids{false}, is_fetching_rcs{false}; + std::atomic 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 bootstraps; + std::unique_ptr _bootstraps; public: - void - set_bootstrap_routers(const std::set& rcs); - - const std::unordered_set& - whitelist() const - { - return router_whitelist; - } - - const std::unordered_set& - greylist() const - { - return router_greylist; - } - - const std::unordered_set& - get_registered_routers() const - { - return registered_routers; - } - - const std::unordered_map& - get_rcs() const - { - return known_rcs; - } - - const std::unordered_map& - get_last_rc_update_times() const - { - return last_rc_update_times; - } + explicit NodeDB( + fs::path rootdir, std::function)> 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 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 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& whitelist, @@ -226,17 +203,50 @@ namespace llarp bool is_first_hop_allowed(const RouterID& remote) const; + std::unordered_set& + pinned_edges() + { + return _pinned_edges; + } + + std::unique_ptr& + bootstrap_list() + { + return _bootstraps; + } + + void + set_bootstrap_routers(std::unique_ptr from_router); + const std::unordered_set& - get_pinned_edges() const + whitelist() const { - return pinned_edges; + return router_whitelist; } - explicit NodeDB( - fs::path rootdir, std::function)> diskCaller, Router* r); + const std::unordered_set& + greylist() const + { + return router_greylist; + } - /// in memory nodedb - NodeDB(); + const std::unordered_set& + get_registered_routers() const + { + return registered_routers; + } + + const std::unordered_map& + get_rcs() const + { + return known_rcs; + } + + const std::unordered_map& + get_last_rc_update_times() const + { + return last_rc_update_times; + } /// load all known_rcs from disk syncrhonously void diff --git a/llarp/router/router.cpp b/llarp/router/router.cpp index e4f0cafaf..cc24a8dc9 100644 --- a/llarp/router/router.cpp +++ b/llarp/router/router.cpp @@ -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 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 // /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(); + 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(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); diff --git a/llarp/router/router.hpp b/llarp/router/router.hpp index 44e9f40a9..3a15cc85a 100644 --- a/llarp/router/router.hpp +++ b/llarp/router/router.hpp @@ -63,8 +63,6 @@ namespace llarp private: std::shared_ptr _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 func);