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.
pull/2225/head
Thomas Winget 6 months ago
parent 6952e8f705
commit c30a4dd44a

@ -7,6 +7,7 @@
#include <llarp/messages/exit.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>
@ -14,6 +15,7 @@
#include <oxenc/bt_producer.h>
#include <algorithm>
#include <exception>
#include <set>
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<std::vector<ustring>>();
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<RouterID> 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<std::string_view>("source");
// if bad request, silently fail
if (source.size() != RouterID::SIZE)
return;
const auto source_rid = RouterID{reinterpret_cast<const byte_t*>(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<unsigned char, 64> sig;
if (!crypto::sign(const_cast<unsigned char*>(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
{

@ -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;

@ -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

@ -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<RouterID> 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<RouterID>& whitelist,

@ -54,7 +54,7 @@ namespace llarp
std::unordered_map<RouterID, rc_time> last_rc_update_times;
// Router list for clients
std::unordered_set<RouterID> client_known_rcs;
std::unordered_set<RouterID> client_known_routers;
// only ever use to specific edges as path first-hops
std::unordered_set<RouterID> 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<RouterID> router_id_fetch_sources;
std::unordered_map<RouterID, std::vector<RouterID>> 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<RouterID> excluded);
void
set_router_whitelist(
const std::vector<RouterID>& whitelist,

Loading…
Cancel
Save