From c30a4dd44aafdc1045faa0a8ac7c865d2da05f1a Mon Sep 17 00:00:00 2001 From: Thomas Winget Date: Fri, 24 Nov 2023 19:40:51 -0500 Subject: [PATCH] Implement fetch RouterIDs method and usage Periodically clients will fetch the set of RouterIDs for all relays on the network. It will request this list from a number (12, currently) of relays, but as we are likely to be requesting from more relays than we want to have edge connections, this request will itself be relayed to the target source via one of our edges. As we can't trust our edge to do this honestly, the responses are signed by the source relay. TODO: the responses from all (12) relays are collected, then processed together. The reconciliation of their responses is not yet implemented. TODO: the source selection for this method obviously requires sources to begin with, but this is the method by which we learn of those...bootstrapping is still a bit in-progress, and will need to be finished for this. TODO: make Router call this periodically, as with RC fetching. --- llarp/link/link_manager.cpp | 116 +++++++++++++++++++++++++++++++++++ llarp/link/link_manager.hpp | 6 ++ llarp/messages/router_id.hpp | 17 +++++ llarp/nodedb.cpp | 88 +++++++++++++++++++++++++- llarp/nodedb.hpp | 12 +++- 5 files changed, 235 insertions(+), 4 deletions(-) create mode 100644 llarp/messages/router_id.hpp diff --git a/llarp/link/link_manager.cpp b/llarp/link/link_manager.cpp index a42d98b92..71fd79e38 100644 --- a/llarp/link/link_manager.cpp +++ b/llarp/link/link_manager.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -14,6 +15,7 @@ #include #include +#include #include namespace llarp @@ -544,6 +546,120 @@ namespace llarp } } + void + LinkManager::fetch_router_ids(const RouterID& source) + { + if (ep.conns.empty()) + { + log::debug(link_cat, "Not attempting to fetch Router IDs: not connected to any relays."); + return; + } + // TODO: randomize? Also, keep track of successful responses and drop this edge + // if not many come back successfully. + RouterID edge = ep.conns.begin()->first; + send_control_message( + edge, + "fetch_router_ids"s, + RouterIDFetch::serialize(source), + [this, source = source, edge = std::move(edge)](oxen::quic::message m) { + if (not m) + { + log::info( + link_cat, + "Error fetching RouterIDs from source \"{}\" via edge \"{}\"", + source, + edge); + node_db->ingest_router_ids(edge, {}); // empty response == failure + return; + } + try + { + oxenc::bt_dict_consumer btdc{m.body()}; + btdc.required("routers"); + auto router_id_strings = btdc.consume_list>(); + btdc.require_signature("signature", [&edge](ustring_view msg, ustring_view sig) { + if (sig.size() != 64) + throw std::runtime_error{"Invalid signature: not 64 bytes"}; + if (not crypto::verify(edge, msg, sig)) + throw std::runtime_error{ + "Failed to verify signature for fetch RouterIDs response."}; + }); + std::vector router_ids; + for (const auto& s : router_id_strings) + { + if (s.size() != RouterID::SIZE) + { + log::warning(link_cat, "Got bad RouterID from edge \"{}\".", edge); + return; + } + router_ids.emplace_back(s.data()); + } + node_db->ingest_router_ids(edge, std::move(router_ids)); + return; + } + catch (const std::exception& e) + { + log::info(link_cat, "Error handling fetch RouterIDs response: {}", e.what()); + } + node_db->ingest_router_ids(edge, {}); // empty response == failure + }); + } + + void + LinkManager::handle_fetch_router_ids(oxen::quic::message m) + { + try + { + oxenc::bt_dict_consumer btdc{m.body()}; + + auto source = btdc.require("source"); + + // if bad request, silently fail + if (source.size() != RouterID::SIZE) + return; + + const auto source_rid = RouterID{reinterpret_cast(source.data())}; + const auto our_rid = RouterID{router().pubkey()}; + + if (source_rid == our_rid) + { + oxenc::bt_dict_producer btdp; + { + auto btlp = btdp.append_list("routers"); + for (const auto& relay : node_db->whitelist()) + { + btlp.append(relay.ToView()); + } + } + btdp.append_signature("signature", [this](ustring_view to_sign) { + std::array sig; + + if (!crypto::sign(const_cast(sig.data()), _router.identity(), to_sign)) + throw std::runtime_error{"Failed to sign fetch RouterIDs response"}; + + return sig; + }); + m.respond(std::move(btdp).str()); + return; + } + + send_control_message( + source_rid, + "fetch_router_ids"s, + m.body_str(), + [source_rid = std::move(source_rid), + orig_mess = std::move(m)](oxen::quic::message m) mutable { + if (not m.timed_out) + orig_mess.respond(m.body_str()); + // on timeout, just silently drop (as original requester will just time out anyway) + }); + } + catch (const std::exception& e) + { + log::info(link_cat, "Error fulfilling fetch RouterIDs request: {}", e.what()); + } + } + bool LinkManager::have_connection_to(const RouterID& remote, bool client_only) const { diff --git a/llarp/link/link_manager.hpp b/llarp/link/link_manager.hpp index 219cd7ca8..899b25af4 100644 --- a/llarp/link/link_manager.hpp +++ b/llarp/link/link_manager.hpp @@ -232,6 +232,12 @@ namespace llarp void handle_fetch_rcs(oxen::quic::message m); + void + fetch_router_ids(const RouterID& source); + + void + handle_fetch_router_ids(oxen::quic::message m); + bool have_connection_to(const RouterID& remote, bool client_only = false) const; diff --git a/llarp/messages/router_id.hpp b/llarp/messages/router_id.hpp new file mode 100644 index 000000000..dbdd897de --- /dev/null +++ b/llarp/messages/router_id.hpp @@ -0,0 +1,17 @@ +#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 9fbf8b4ad..3b5e6784c 100644 --- a/llarp/nodedb.cpp +++ b/llarp/nodedb.cpp @@ -114,7 +114,27 @@ namespace llarp void NodeDB::rotate_rc_source() - {} + { + auto conn_count = router.link_manager().get_num_connected(); + if (conn_count == 0) + { + // not connected to any nodes yet, so no sensible source + return; + } + RemoteRC new_source{}; + router.link_manager().get_random_connected(new_source); + if (conn_count == 1) + { + // only one connection, use it + rc_fetch_source = new_source.router_id(); + } + + while (new_source.router_id() == rc_fetch_source) + { + router.link_manager().get_random_connected(new_source); + } + rc_fetch_source = new_source.router_id(); + } // TODO: trust model void @@ -145,7 +165,16 @@ namespace llarp router_id_response_count++; if (router_id_response_count == router_id_fetch_sources.size()) { - // TODO: reconcile all the responses + // TODO: reconcile all the responses, for now just insert all + for (const auto& [rid, responses] : router_id_fetch_responses) + { + // TODO: empty == failure, handle that case + for (const auto& response : responses) + { + client_known_routers.insert(std::move(response)); + } + } + router_id_fetch_in_progress = false; } } @@ -166,6 +195,61 @@ namespace llarp rc_fetch_source, last_rc_update_relay_timestamp, std::move(needed)); } + void + NodeDB::fetch_router_ids() + { + if (router_id_fetch_in_progress) + return; + if (router_id_fetch_sources.empty()) + select_router_id_sources({}); + + // if we *still* don't have fetch sources, we can't exactly fetch... + if (router_id_fetch_sources.empty()) + { + log::info(logcat, "Attempting to fetch RouterIDs, but have no source from which to do so."); + return; + } + + router_id_fetch_in_progress = true; + router_id_response_count = 0; + router_id_fetch_responses.clear(); + for (const auto& rid : router_id_fetch_sources) + router.link_manager().fetch_router_ids(rid); + } + + void + NodeDB::select_router_id_sources(std::unordered_set excluded) + { + // TODO: bootstrapping should be finished before this is called, so this + // shouldn't happen; need to make sure that's the case. + if (client_known_routers.empty()) + return; + + // keep using any we've been using, but remove `excluded` ones + for (const auto& r : excluded) + router_id_fetch_sources.erase(r); + + // only know so many routers, so no need to randomize + if (client_known_routers.size() <= (ROUTER_ID_SOURCE_COUNT + excluded.size())) + { + for (const auto& r : client_known_routers) + { + if (excluded.count(r)) + continue; + router_id_fetch_sources.insert(r); + } + } + + // select at random until we have chosen enough + while (router_id_fetch_sources.size() < ROUTER_ID_SOURCE_COUNT) + { + RouterID r; + std::sample(client_known_routers.begin(), client_known_routers.end(), &r, 1, csrng); + if (excluded.count(r) == 0) + router_id_fetch_sources.insert(r); + } + } + void NodeDB::set_router_whitelist( const std::vector& whitelist, diff --git a/llarp/nodedb.hpp b/llarp/nodedb.hpp index 58eea3b33..8146c9c71 100644 --- a/llarp/nodedb.hpp +++ b/llarp/nodedb.hpp @@ -54,7 +54,7 @@ namespace llarp std::unordered_map last_rc_update_times; // Router list for clients - std::unordered_set client_known_rcs; + std::unordered_set client_known_routers; // only ever use to specific edges as path first-hops std::unordered_set pinned_edges; @@ -62,10 +62,12 @@ namespace llarp // rc update info RouterID rc_fetch_source; rc_time last_rc_update_relay_timestamp; + static constexpr auto ROUTER_ID_SOURCE_COUNT = 12; std::unordered_set router_id_fetch_sources; std::unordered_map> router_id_fetch_responses; // process responses once all are received (or failed/timed out) size_t router_id_response_count{0}; + bool router_id_fetch_in_progress{false}; bool want_rc(const RouterID& rid) const; @@ -104,7 +106,7 @@ namespace llarp return last_rc_update_times; } - // If we receive a set of RCs from our current RC source relay, we consider + // 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 @@ -124,6 +126,12 @@ namespace llarp void update_rcs(); + void + fetch_router_ids(); + + void + select_router_id_sources(std::unordered_set excluded); + void set_router_whitelist( const std::vector& whitelist,