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,