path control messages and onioning fleshed out

- control messages can be sent along a path
- the path owner onion-encrypts the "inner" message for each hop in the
  path
- relays on the path will onion the payload in both directions, such
  that the terminal relay will get the plaintext "inner" message and the
  client will get the plaintext "response" to that.
- control messages have (mostly, see below) been changed to be invokable
  either over a path or directly to a relay, as appropriate.

TODO:
  - exit messages need looked at, so they have not yet been changed for
    this
  - path transfer messages (traffic from client to client over 2 paths
    with a shared "pivot") are not yet implemented
pull/2216/head
Thomas Winget 8 months ago
parent c25ced50a3
commit b0fb194e2c

@ -154,6 +154,21 @@ namespace llarp
return crypto_stream_xchacha20_xor(buf, buf, size, nonce, secret) == 0;
}
// do a round of chacha for and return the nonce xor the given xor_factor
TunnelNonce
crypto::onion(
unsigned char* buf,
size_t size,
const SharedSecret& k,
const TunnelNonce& nonce,
const ShortHash& xor_factor)
{
if (!crypto::xchacha20(buf, size, k, nonce))
throw std::runtime_error{"chacha failed during onion step"};
return nonce ^ xor_factor;
}
bool
crypto::dh_client(
llarp::SharedSecret& shared, const PubKey& pk, const SecretKey& sk, const TunnelNonce& n)

@ -27,6 +27,14 @@ namespace llarp
bool
xchacha20(uint8_t*, size_t size, const uint8_t*, const uint8_t*);
TunnelNonce
onion(
unsigned char* buf,
size_t size,
const SharedSecret& k,
const TunnelNonce& nonce,
const ShortHash& xor_factor);
/// path dh creator's side
bool
dh_client(SharedSecret&, const PubKey&, const SecretKey&, const TunnelNonce&);

@ -131,7 +131,7 @@ namespace llarp
llarp_time_t timeout) = 0;
virtual void
lookup_name(std::string name, std::function<void(oxen::quic::message)> func) = 0;
lookup_name(std::string name, std::function<void(std::string, bool)> func) = 0;
virtual const EventLoop_ptr&
Loop() = 0;

@ -57,7 +57,8 @@ namespace llarp::exit
if (!parent->UpdateEndpointPath(remote_signkey, nextPath))
return false;
const RouterID us{parent->GetRouter()->pubkey()};
current_path = parent->GetRouter()->path_context().GetByUpstream(us, nextPath);
// TODO: is this getting a Path or a TransitHop?
// current_path = parent->GetRouter()->path_context().GetByUpstream(us, nextPath);
return true;
}

@ -20,9 +20,9 @@ namespace llarp::handlers
ExitEndpoint::~ExitEndpoint() = default;
void
ExitEndpoint::lookup_name(std::string, std::function<void(oxen::quic::message)>)
ExitEndpoint::lookup_name(std::string, std::function<void(std::string, bool)>)
{
// TODO: implement me
// TODO: implement me (or does EndpointBase having this method as virtual even make sense?)
}
void
@ -766,7 +766,9 @@ namespace llarp::handlers
{
if (wantInternet && !permit_exit)
return false;
path::HopHandler_ptr handler = router->path_context().GetByUpstream(router->pubkey(), path);
// TODO: is this getting a path or a transit hop or...somehow possibly either?
// path::HopHandler_ptr handler = router->path_context().GetByUpstream(router->pubkey(), path);
path::HopHandler_ptr handler{};
if (handler == nullptr)
return false;
auto ip = GetIPForIdent(pk);

@ -49,7 +49,7 @@ namespace llarp
llarp_time_t timeout) override;
void
lookup_name(std::string name, std::function<void(oxen::quic::message)> func) override;
lookup_name(std::string name, std::function<void(std::string, bool)> func) override;
const EventLoop_ptr&
Loop() override;

@ -683,28 +683,17 @@ namespace llarp::handlers
}
else if (service::is_valid_name(ons_name))
{
lookup_name(ons_name, [msg, ons_name, reply](oxen::quic::message m) mutable {
if (m)
{
std::string result;
try
{
oxenc::bt_dict_consumer btdc{m.body()};
result = btdc.require<std::string>("NAME");
}
catch (...)
{
log::warning(logcat, "Failed to parse find name response!");
throw;
}
msg.AddMXReply(result, 1);
}
else
msg.AddNXReply();
lookup_name(
ons_name, [msg, ons_name, reply](std::string name_result, bool success) mutable {
if (success)
{
msg.AddMXReply(name_result, 1);
}
else
msg.AddNXReply();
reply(msg);
});
reply(msg);
});
return true;
}
@ -837,29 +826,15 @@ namespace llarp::handlers
ons_name,
isV6,
reply,
ReplyToDNSWhenReady](oxen::quic::message m) mutable {
if (m)
{
std::string name;
try
{
oxenc::bt_dict_consumer btdc{m.body()};
name = btdc.require<std::string>("NAME");
}
catch (...)
{
log::warning(logcat, "Failed to parse find name response!");
throw;
}
ReplyToDNSWhenReady(name, msg, isV6);
}
else
ReplyToDNSWhenReady](std::string name_result, bool success) mutable {
if (not success)
{
log::warning(logcat, "{} (ONS name: {}) not resolved", name, ons_name);
msg->AddNXReply();
reply(*msg);
}
ReplyToDNSWhenReady(name_result, msg, isV6);
});
return true;
}

@ -7,6 +7,7 @@
#include <llarp/messages/exit.hpp>
#include <llarp/messages/path.hpp>
#include <llarp/nodedb.hpp>
#include <llarp/path/path.hpp>
#include <llarp/router/rc_lookup_handler.hpp>
#include <llarp/router/router.hpp>
@ -137,19 +138,30 @@ namespace llarp
{
assert(ep.connid_map.count(s->conn_id()));
RouterID rid = ep.connid_map[s->conn_id()];
for (const auto& [name, func] : rpc_commands)
{
s->register_command(name, [this, func=func](oxen::quic::message m) {
_router.loop()->call([this, func, msg = std::move(m)]() mutable {
std::invoke(func, this, std::move(msg));
});
});
}
s->register_command("path_build"s, [this, rid](oxen::quic::message m) {
_router.loop()->call(
[this, &rid, msg = std::move(m)]() mutable { handle_path_build(std::move(msg), rid); });
});
s->register_command("path_control"s, [this, rid](oxen::quic::message m) {
_router.loop()->call(
[this, &rid, msg = std::move(m)]() mutable { handle_path_control(std::move(msg), rid); });
});
for (auto& method : direct_requests)
{
s->register_command(
std::string{method.first}, [this, func = method.second](oxen::quic::message m) {
_router.loop()->call([this, msg = std::move(m), func = std::move(func)]() mutable {
auto body = msg.body_str();
auto respond = [m = std::move(msg)](std::string response) mutable {
m.respond(std::move(response));
};
std::invoke(func, this, body, std::move(respond));
});
});
}
}
std::shared_ptr<oxen::quic::Endpoint>
@ -201,12 +213,7 @@ namespace llarp
std::string body,
std::function<void(oxen::quic::message m)> func)
{
if (not func and rpc_responses.count(endpoint))
{
func = [&](oxen::quic::message m) {
std::invoke(rpc_responses[endpoint], this, std::move(m));
};
}
assert(func); // makes no sense to send control message and ignore response
if (func)
{
@ -421,8 +428,6 @@ namespace llarp
return;
}
util::Lock l(m);
LogInfo("stopping links");
is_stopping = true;
@ -435,8 +440,6 @@ namespace llarp
if (is_stopping)
return;
util::Lock l(m);
persisting_conns[remote] = std::max(until, persisting_conns[remote]);
if (have_client_connection_to(remote))
{
@ -518,29 +521,30 @@ namespace llarp
}
void
LinkManager::handle_find_name(oxen::quic::message m)
LinkManager::handle_find_name(std::string_view body, std::function<void(std::string)> respond)
{
std::string name_hash;
try
{
oxenc::bt_dict_consumer btdp{m.body()};
oxenc::bt_dict_consumer btdp{body};
name_hash = btdp.require<std::string>("H");
}
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
m.respond(serialize_response({{"STATUS", FindNameMessage::EXCEPTION}}), true);
respond(serialize_response({{"STATUS", FindNameMessage::EXCEPTION}}));
}
_router.rpc_client()->lookup_ons_hash(
name_hash,
[msg = std::move(m)]([[maybe_unused]] std::optional<service::EncryptedName> maybe) mutable {
[respond = std::move(respond)](
[[maybe_unused]] std::optional<service::EncryptedName> maybe) mutable {
if (maybe)
msg.respond(serialize_response({{"NAME", maybe->ciphertext}}));
respond(serialize_response({{"NAME", maybe->ciphertext}}));
else
msg.respond(serialize_response({{"STATUS", FindNameMessage::NOT_FOUND}}), true);
respond(serialize_response({{"STATUS", FindNameMessage::NOT_FOUND}}));
});
}
@ -588,17 +592,15 @@ namespace llarp
}
}
// TODO: add callback to relayed messages (calls to send_control_message so the
// response finds its way back)
void
LinkManager::handle_find_router(oxen::quic::message m)
LinkManager::handle_find_router(std::string_view body, std::function<void(std::string)> respond)
{
std::string target_key;
bool is_exploratory, is_iterative;
try
{
oxenc::bt_dict_consumer btdc{m.body()};
oxenc::bt_dict_consumer btdc{body};
is_exploratory = btdc.require<bool>("E");
is_iterative = btdc.require<bool>("I");
@ -607,8 +609,7 @@ namespace llarp
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
m.respond(
serialize_response({{"STATUS", FindRouterMessage::EXCEPTION}, {"TARGET", ""}}), true);
respond(serialize_response({{"STATUS", FindRouterMessage::EXCEPTION}, {"TARGET", ""}}));
return;
}
@ -638,9 +639,8 @@ namespace llarp
neighbors += rid.bt_encode();
}
m.respond(
serialize_response({{"STATUS", FindRouterMessage::RETRY_EXP}, {"TARGET", neighbors}}),
true);
respond(
serialize_response({{"STATUS", FindRouterMessage::RETRY_EXP}, {"TARGET", neighbors}}));
}
else
{
@ -656,13 +656,13 @@ namespace llarp
target_rid,
"find_router",
FindRouterMessage::serialize(target_rid, false, false),
[original = std::move(m)](oxen::quic::message msg) mutable {
original.respond(msg.body_str(), not msg);
[respond = std::move(respond)](oxen::quic::message msg) mutable {
respond(msg.body_str());
});
}
else
{
m.respond(serialize_response({{"RC", closest_rc.view()}}));
respond(serialize_response({{"RC", closest_rc.view()}}));
}
}
else if (not is_iterative)
@ -673,26 +673,22 @@ namespace llarp
closest_rid,
"find_router",
FindRouterMessage::serialize(closest_rid, false, false),
[original = std::move(m)](oxen::quic::message msg) mutable {
original.respond(msg.body_str(), not msg);
[respond = std::move(respond)](oxen::quic::message msg) mutable {
respond(msg.body_str());
});
}
else
{
m.respond(
serialize_response(
{{"STATUS", FindRouterMessage::RETRY_ITER},
{"TARGET", reinterpret_cast<const char*>(target_addr.data())}}),
true);
respond(serialize_response(
{{"STATUS", FindRouterMessage::RETRY_ITER},
{"TARGET", reinterpret_cast<const char*>(target_addr.data())}}));
}
}
else
{
m.respond(
serialize_response(
{{"STATUS", FindRouterMessage::RETRY_NEW},
{"TARGET", reinterpret_cast<const char*>(closest_rid.data())}}),
true);
respond(serialize_response(
{{"STATUS", FindRouterMessage::RETRY_NEW},
{"TARGET", reinterpret_cast<const char*>(closest_rid.data())}}));
}
}
}
@ -816,7 +812,7 @@ namespace llarp
}
void
LinkManager::handle_publish_intro(oxen::quic::message m)
LinkManager::handle_publish_intro(std::string_view body, std::function<void(std::string)> respond)
{
std::string introset, derived_signing_key, payload, sig, nonce;
uint64_t is_relayed, relay_order;
@ -824,7 +820,7 @@ namespace llarp
try
{
oxenc::bt_dict_consumer btdc_a{m.body()};
oxenc::bt_dict_consumer btdc_a{body};
introset = btdc_a.require<std::string>("I");
relay_order = btdc_a.require<uint64_t>("O");
@ -841,7 +837,7 @@ namespace llarp
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
m.respond(serialize_response({{"STATUS", PublishIntroMessage::EXCEPTION}}), true);
respond(serialize_response({{"STATUS", PublishIntroMessage::EXCEPTION}}));
return;
}
@ -852,14 +848,14 @@ namespace llarp
if (not service::EncryptedIntroSet::verify(introset, derived_signing_key, sig))
{
log::error(link_cat, "Received PublishIntroMessage with invalid introset: {}", introset);
m.respond(serialize_response({{"STATUS", PublishIntroMessage::INVALID_INTROSET}}), true);
respond(serialize_response({{"STATUS", PublishIntroMessage::INVALID_INTROSET}}));
return;
}
if (now + service::MAX_INTROSET_TIME_DELTA > signed_at + path::DEFAULT_LIFETIME)
{
log::error(link_cat, "Received PublishIntroMessage with expired introset: {}", introset);
m.respond(serialize_response({{"STATUS", PublishIntroMessage::EXPIRED}}), true);
respond(serialize_response({{"STATUS", PublishIntroMessage::EXPIRED}}));
return;
}
@ -869,7 +865,7 @@ namespace llarp
{
log::error(
link_cat, "Received PublishIntroMessage but only know {} nodes", closest_rcs.size());
m.respond(serialize_response({{"STATUS", PublishIntroMessage::INSUFFICIENT}}), true);
respond(serialize_response({{"STATUS", PublishIntroMessage::INSUFFICIENT}}));
return;
}
@ -881,7 +877,7 @@ namespace llarp
{
log::error(
link_cat, "Received PublishIntroMessage with invalide relay order: {}", relay_order);
m.respond(serialize_response({{"STATUS", PublishIntroMessage::INVALID_ORDER}}), true);
respond(serialize_response({{"STATUS", PublishIntroMessage::INVALID_ORDER}}));
return;
}
@ -898,7 +894,7 @@ namespace llarp
relay_order);
_router.contacts()->services()->PutNode(dht::ISNode{std::move(enc)});
m.respond(serialize_response({{"STATUS", ""}}));
respond(serialize_response({{"STATUS", ""}}));
}
else
{
@ -908,7 +904,12 @@ namespace llarp
send_control_message(
peer_key,
"publish_intro",
PublishIntroMessage::serialize(introset, relay_order, is_relayed));
PublishIntroMessage::serialize(introset, relay_order, is_relayed),
[respond = std::move(respond)](oxen::quic::message m) {
if (m.timed_out)
return; // drop if timed out; requester will have timed out as well
respond(m.body_str());
});
}
return;
@ -931,7 +932,7 @@ namespace llarp
log::info(link_cat, "Received PublishIntroMessage for {} (TXID: {}); we are candidate {}");
_router.contacts()->services()->PutNode(dht::ISNode{std::move(enc)});
m.respond(serialize_response());
respond(serialize_response({{"STATUS", ""}}));
}
else
log::warning(
@ -978,29 +979,25 @@ namespace llarp
log::info(link_cat, "PublishIntroMessage failed with error code: {}", payload);
if (payload == PublishIntroMessage::INVALID_INTROSET)
{
}
{}
else if (payload == PublishIntroMessage::EXPIRED)
{
}
{}
else if (payload == PublishIntroMessage::INSUFFICIENT)
{
}
{}
else if (payload == PublishIntroMessage::INVALID_ORDER)
{
}
{}
}
}
void
LinkManager::handle_find_intro(oxen::quic::message m)
LinkManager::handle_find_intro(std::string_view body, std::function<void(std::string)> respond)
{
ustring location;
uint64_t relay_order, is_relayed;
try
{
oxenc::bt_dict_consumer btdc{m.body()};
oxenc::bt_dict_consumer btdc{body};
relay_order = btdc.require<uint64_t>("O");
is_relayed = btdc.require<uint64_t>("R");
@ -1009,7 +1006,7 @@ namespace llarp
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
m.respond(serialize_response({{"STATUS", FindIntroMessage::EXCEPTION}}), true);
respond(serialize_response({{"STATUS", FindIntroMessage::EXCEPTION}}));
return;
}
@ -1021,7 +1018,7 @@ namespace llarp
{
log::warning(
link_cat, "Received FindIntroMessage with invalid relay order: {}", relay_order);
m.respond(serialize_response({{"STATUS", FindIntroMessage::INVALID_ORDER}}), true);
respond(serialize_response({{"STATUS", FindIntroMessage::INVALID_ORDER}}));
return;
}
@ -1031,7 +1028,7 @@ namespace llarp
{
log::error(
link_cat, "Received FindIntroMessage but only know {} nodes", closest_rcs.size());
m.respond(serialize_response({{"STATUS", FindIntroMessage::INSUFFICIENT_NODES}}), true);
respond(serialize_response({{"STATUS", FindIntroMessage::INSUFFICIENT_NODES}}));
return;
}
@ -1044,7 +1041,7 @@ namespace llarp
peer_key,
"find_intro",
FindIntroMessage::serialize(dht::Key_t{peer_key}, is_relayed, relay_order),
[original_msg = std::move(m)](oxen::quic::message relay_response) mutable {
[respond = std::move(respond)](oxen::quic::message relay_response) mutable {
if (relay_response)
log::info(
link_cat,
@ -1057,19 +1054,19 @@ namespace llarp
log::critical(
link_cat, "Relayed FindIntroMessage failed! Notifying initial requester");
original_msg.respond(relay_response.body_str(), not relay_response);
respond(relay_response.body_str());
});
}
else
{
if (auto maybe_intro = _router.contacts()->get_introset_by_location(addr))
m.respond(serialize_response({{"INTROSET", maybe_intro->bt_encode()}}));
respond(serialize_response({{"INTROSET", maybe_intro->bt_encode()}}));
else
{
log::warning(
link_cat,
"Received FindIntroMessage with relayed == false and no local introset entry");
m.respond(serialize_response({{"STATUS", FindIntroMessage::NOT_FOUND}}), true);
respond(serialize_response({{"STATUS", FindIntroMessage::NOT_FOUND}}));
}
}
}
@ -1221,11 +1218,6 @@ namespace llarp
hop_info.upstream.from_string(upstream);
// TODO: the whole transit hop container is garbage.
// namely the PathID uniqueness checking uses the PathIDs and upstream/downstream
// but if someone made a path with txid, rxid, and downstream the same but
// a different upstream, that would be "unique" but we wouldn't know where
// to route messages.
if (_router.path_context().HasTransitHop(hop_info))
{
log::warning(link_cat, "Invalid PathID; PathIDs must be unique");
@ -1258,6 +1250,7 @@ namespace llarp
if (hop_info.upstream == _router.pubkey())
{
hop->terminal_hop = true;
// we are terminal hop and everything is okay
_router.path_context().PutTransitHop(hop);
m.respond(serialize_response({{"STATUS", PathBuildMessage::OK}}), false);
@ -1421,8 +1414,8 @@ namespace llarp
tx_id = btdc.require<std::string_view>("T");
RouterID target{pubkey.data()};
auto transit_hop = std::static_pointer_cast<path::TransitHop>(
_router.path_context().GetByUpstream(target, PathID_t{to_usv(tx_id).data()}));
auto transit_hop =
_router.path_context().GetTransitHop(target, PathID_t{to_usv(tx_id).data()});
const auto rx_id = transit_hop->info.rxID;
@ -1466,8 +1459,7 @@ namespace llarp
sig = to_usv(btlc.consume_string_view());
tx_id = btdc.require<std::string_view>("T");
auto path_ptr = std::static_pointer_cast<path::Path>(
_router.path_context().GetByDownstream(_router.pubkey(), PathID_t{to_usv(tx_id).data()}));
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();
@ -1495,8 +1487,8 @@ namespace llarp
path_id = btdc.require<std::string_view>("P");
tx_id = btdc.require<std::string_view>("T");
auto transit_hop = std::static_pointer_cast<path::TransitHop>(
_router.path_context().GetByUpstream(_router.pubkey(), PathID_t{to_usv(tx_id).data()}));
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()}))
@ -1543,8 +1535,7 @@ namespace llarp
sig = to_usv(btlc.consume_string_view());
tx_id = btdc.require<std::string_view>("T");
auto path_ptr = std::static_pointer_cast<path::Path>(
_router.path_context().GetByDownstream(_router.pubkey(), PathID_t{to_usv(tx_id).data()}));
auto path_ptr = _router.path_context().GetPath(PathID_t{to_usv(tx_id).data()});
if (crypto::verify(_router.pubkey(), to_usv(dict_data), sig))
{
@ -1554,8 +1545,7 @@ namespace llarp
// see Path::HandleUpdateExitVerifyMessage
}
else
{
}
{}
}
}
catch (const std::exception& e)
@ -1580,8 +1570,8 @@ namespace llarp
sig = to_usv(btlc.consume_string_view());
tx_id = btdc.require<std::string_view>("T");
auto transit_hop = std::static_pointer_cast<path::TransitHop>(
_router.path_context().GetByUpstream(_router.pubkey(), PathID_t{to_usv(tx_id).data()}));
auto transit_hop =
_router.path_context().GetTransitHop(_router.pubkey(), PathID_t{to_usv(tx_id).data()});
const auto rx_id = transit_hop->info.rxID;
@ -1630,8 +1620,7 @@ namespace llarp
tx_id = btdc.require<std::string_view>("T");
nonce = btdc.require<std::string_view>("Y");
auto path_ptr = std::static_pointer_cast<path::Path>(
_router.path_context().GetByDownstream(_router.pubkey(), PathID_t{to_usv(tx_id).data()}));
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))
@ -1645,16 +1634,51 @@ namespace llarp
}
void
LinkManager::handle_path_control(oxen::quic::message m)
LinkManager::handle_path_control(oxen::quic::message m, const RouterID& from)
{
if (m.timed_out)
try
{
log::info(link_cat, "Path control message timed out!");
return;
}
oxenc::bt_dict_consumer btdc{m.body()};
auto nonce = TunnelNonce{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;
try
{}
// if terminal hop, payload should contain a request (e.g. "find_router"); 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 = TunnelNonce{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);
});
}
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
@ -1662,6 +1686,34 @@ namespace llarp
}
}
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");
// 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);
return;
}
auto respond = [m = std::move(m),
hop_weak = hop->weak_from_this()](std::string response) mutable {
auto hop = hop_weak.lock();
if (not hop)
return; // transit hop gone, drop response
m.respond(hop->onion_and_payload(response, hop->info.rxID), false);
};
std::invoke(itr->second, this, std::move(body), std::move(respond));
}
void
LinkManager::handle_convo_intro(oxen::quic::message m)
{

@ -4,6 +4,7 @@
#include <llarp/constants/path.hpp>
#include <llarp/crypto/crypto.hpp>
#include <llarp/path/transit_hop.hpp>
#include <llarp/router/rc_lookup_handler.hpp>
#include <llarp/router_contact.hpp>
#include <llarp/util/compare_ptr.hpp>
@ -170,8 +171,6 @@ namespace llarp
friend struct link::Endpoint;
std::atomic<bool> is_stopping;
// DISCUSS: is this necessary? can we reduce the amount of locking and nuke this
mutable util::Mutex m; // protects persisting_conns
// sessions to persist -> timestamp to end persist at
std::unordered_map<RouterID, llarp_time_t> persisting_conns;
@ -287,16 +286,22 @@ namespace llarp
private:
// DHT messages
void handle_find_name(oxen::quic::message); // relay
void handle_find_intro(oxen::quic::message); // relay
void handle_publish_intro(oxen::quic::message); // relay
void handle_find_router(oxen::quic::message); // relay + path
void
handle_find_name(std::string_view body, std::function<void(std::string)> respond); // relay
void
handle_find_intro(std::string_view body, std::function<void(std::string)> respond); // relay
void
handle_publish_intro(std::string_view body, std::function<void(std::string)> respond); // relay
void
handle_find_router(
std::string_view body, std::function<void(std::string)> respond); // relay + path
// Path messages
void handle_path_build(oxen::quic::message, const RouterID& from); // relay
void handle_path_confirm(oxen::quic::message); // relay
void handle_path_latency(oxen::quic::message); // relay
void handle_path_transfer(oxen::quic::message); // relay
void
handle_path_build(oxen::quic::message, const RouterID& from); // relay
void handle_path_confirm(oxen::quic::message); // relay
void handle_path_latency(oxen::quic::message); // relay
void handle_path_transfer(oxen::quic::message); // relay
// Exit messages
void handle_obtain_exit(oxen::quic::message); // relay
@ -306,11 +311,45 @@ namespace llarp
// Misc
void handle_convo_intro(oxen::quic::message);
std::unordered_map<std::string, void (LinkManager::*)(oxen::quic::message)> rpc_commands = {
{"path_control", &LinkManager::handle_path_control}};
// These requests come over a path (as a "path_control" request),
// may or may not need to make a request to another relay,
// then respond (onioned) back along the path.
std::unordered_map<
std::string_view,
void (LinkManager::*)(std::string_view body, std::function<void(std::string)> respond)>
path_requests = {
{"find_name"sv, &LinkManager::handle_find_name},
{"find_router"sv, &LinkManager::handle_find_router},
{"publish_intro"sv, &LinkManager::handle_publish_intro},
{"find_intro"sv, &LinkManager::handle_find_intro}};
/*
{"path_confirm", &LinkManager::handle_path_confirm},
{"path_latency", &LinkManager::handle_path_latency},
{"update_exit", &LinkManager::handle_update_exit},
{"obtain_exit", &LinkManager::handle_obtain_exit},
{"close_exit", &LinkManager::handle_close_exit},
{"convo_intro", &LinkManager::handle_convo_intro}};
*/
// these requests are direct, i.e. not over a path;
// only "find_router" makes sense client->relay,
// the rest are relay->relay
// TODO: new RC fetch endpoint (which will be both client->relay and relay->relay)
std::unordered_map<
std::string_view,
void (LinkManager::*)(std::string_view body, std::function<void(std::string)> respond)>
direct_requests = {
{"find_router"sv, &LinkManager::handle_find_router},
{"publish_intro"sv, &LinkManager::handle_publish_intro},
{"find_intro"sv, &LinkManager::handle_find_intro}};
// Path relaying
void handle_path_control(oxen::quic::message);
void
handle_path_control(oxen::quic::message, const RouterID& from);
void
handle_inner_request(
oxen::quic::message m, std::string payload, std::shared_ptr<path::TransitHop> hop);
// DHT responses
void handle_find_name_response(oxen::quic::message);

@ -54,7 +54,8 @@ namespace llarp
bool
RelayUpstreamMessage::handle_message(Router* r) const
{
auto path = r->path_context().GetByDownstream(conn->remote_rc.router_id(), pathid);
path::HopHandler_ptr path = r->path_context().GetPath(pathid);
path = path ? path : r->path_context().GetTransitHop(conn->remote_rc.router_id(), pathid);
if (path)
{
return path->HandleUpstream(llarp_buffer_t(enc), nonce, r);
@ -110,7 +111,8 @@ namespace llarp
bool
RelayDownstreamMessage::handle_message(Router* r) const
{
auto path = r->path_context().GetByUpstream(conn->remote_rc.router_id(), pathid);
path::HopHandler_ptr path = r->path_context().GetPath(pathid);
path = path ? path : r->path_context().GetTransitHop(conn->remote_rc.router_id(), pathid);
if (path)
{
return path->HandleDownstream(llarp_buffer_t(enc), nonce, r);

@ -4,6 +4,29 @@
namespace llarp::path
{
std::string
make_onion_payload(
const TunnelNonce& nonce, const PathID_t& path_id, const std::string_view& inner_payload)
{
return make_onion_payload(
nonce,
path_id,
ustring_view{
reinterpret_cast<const unsigned char*>(inner_payload.data()), inner_payload.size()});
}
std::string
make_onion_payload(
const TunnelNonce& nonce, const PathID_t& path_id, const ustring_view& inner_payload)
{
oxenc::bt_dict_producer next_dict;
next_dict.append("NONCE", nonce.ToView());
next_dict.append("PATHID", path_id.ToView());
next_dict.append("PAYLOAD", inner_payload);
return std::move(next_dict).str();
}
// handle data in upstream direction
bool
AbstractHopHandler::HandleUpstream(const llarp_buffer_t& X, const TunnelNonce& Y, Router* r)

@ -22,6 +22,14 @@ namespace llarp
namespace path
{
std::string
make_onion_payload(
const TunnelNonce& nonce, const PathID_t& path_id, const std::string_view& inner_payload);
std::string
make_onion_payload(
const TunnelNonce& nonce, const PathID_t& path_id, const ustring_view& inner_payload);
struct AbstractHopHandler
{
using TrafficEvent_t = std::pair<std::vector<byte_t>, TunnelNonce>;
@ -41,11 +49,17 @@ namespace llarp
virtual bool
ExpiresSoon(llarp_time_t now, llarp_time_t dlt) const = 0;
/// sends a control request along a path
///
/// performs the necessary onion encryption before sending.
/// func will be called when a timeout occurs or a response is received.
/// if a response is received, onion decryption is performed before func is called.
///
/// func is called with a bt-encoded response string (if applicable), and
/// a timeout flag (if set, response string will be empty)
virtual bool
send_path_control_message(
std::string method,
std::string body,
std::function<void(oxen::quic::message m)> func) = 0;
std::string method, std::string body, std::function<void(std::string, bool)> func) = 0;
/// send routing message and increment sequence number
virtual bool

@ -8,6 +8,7 @@
namespace llarp::path
{
Path::Path(
Router* rtr,
const std::vector<RemoteRC>& h,
@ -48,10 +49,7 @@ namespace llarp::path
bool
Path::obtain_exit(
SecretKey sk,
uint64_t flag,
std::string tx_id,
std::function<void(oxen::quic::message m)> func)
SecretKey sk, uint64_t flag, std::string tx_id, std::function<void(std::string, bool)> func)
{
return send_path_control_message(
"obtain_exit",
@ -60,7 +58,7 @@ namespace llarp::path
}
bool
Path::close_exit(SecretKey sk, std::string tx_id, std::function<void(oxen::quic::message m)> func)
Path::close_exit(SecretKey sk, std::string tx_id, std::function<void(std::string, bool)> func)
{
return send_path_control_message(
"close_exit", CloseExitMessage::sign_and_serialize(sk, std::move(tx_id)), std::move(func));
@ -71,21 +69,21 @@ namespace llarp::path
const dht::Key_t& location,
bool is_relayed,
uint64_t order,
std::function<void(oxen::quic::message m)> func)
std::function<void(std::string, bool)> func)
{
return send_path_control_message(
"find_intro", FindIntroMessage::serialize(location, is_relayed, order), std::move(func));
}
bool
Path::find_name(std::string name, std::function<void(oxen::quic::message m)> func)
Path::find_name(std::string name, std::function<void(std::string, bool)> func)
{
return send_path_control_message(
"find_name", FindNameMessage::serialize(std::move(name)), std::move(func));
}
bool
Path::find_router(std::string rid, std::function<void(oxen::quic::message m)> func)
Path::find_router(std::string rid, std::function<void(std::string, bool)> func)
{
return send_path_control_message(
"find_router", FindRouterMessage::serialize(std::move(rid), false, false), std::move(func));
@ -93,44 +91,71 @@ namespace llarp::path
bool
Path::send_path_control_message(
std::string method, std::string body, std::function<void(oxen::quic::message m)> func)
std::string method, std::string body, std::function<void(std::string, bool)> func)
{
std::string payload;
{
oxenc::bt_dict_producer btdp;
btdp.append("BODY", body);
btdp.append("METHOD", method);
payload = std::move(btdp).str();
}
oxenc::bt_dict_producer btdp;
btdp.append("BODY", body);
btdp.append("METHOD", method);
auto payload = std::move(btdp).str();
// TODO: old impl padded messages if smaller than a certain size; do we still want to?
TunnelNonce nonce;
nonce.Randomize();
// chacha and mutate nonce for each hop
for (const auto& hop : hops)
{
// do a round of chacha for each hop and mutate the nonce with that hop's nonce
crypto::xchacha20(
reinterpret_cast<unsigned char*>(payload.data()), payload.size(), hop.shared, nonce);
nonce ^= hop.nonceXOR;
nonce = crypto::onion(
reinterpret_cast<unsigned char*>(payload.data()),
payload.size(),
hop.shared,
nonce,
hop.nonceXOR);
}
oxenc::bt_dict_producer outer_dict;
outer_dict.append("NONCE", nonce.ToView());
outer_dict.append("PATHID", TXID().ToView());
outer_dict.append("PAYLOAD", payload);
auto outer_payload = make_onion_payload(nonce, TXID(), payload);
return router.send_control_message(
upstream(),
"path_control",
std::move(outer_dict).str(),
[response_cb = std::move(func)](oxen::quic::message m) {
if (m)
std::move(outer_payload),
[response_cb = std::move(func), weak = weak_from_this()](oxen::quic::message m) {
auto self = weak.lock();
if (not self)
return;
if (m.timed_out)
{
// do path hop logic here
response_cb(""s, true);
return;
}
TunnelNonce nonce{};
std::string payload;
try
{
oxenc::bt_dict_consumer btdc{m.body()};
auto nonce = TunnelNonce{btdc.require<ustring_view>("NONCE").data()};
auto payload = btdc.require<std::string>("PAYLOAD");
}
catch (const std::exception& e)
{
log::warning(path_cat, "Error parsing onion response: {}", e.what());
return;
}
for (const auto& hop : self->hops)
{
nonce = crypto::onion(
reinterpret_cast<unsigned char*>(payload.data()),
payload.size(),
hop.shared,
nonce,
hop.nonceXOR);
}
// TODO: should we do anything (even really simple) here to check if the decrypted
// response is sensible (e.g. is a bt dict)? Parsing and handling of the
// contents (errors or otherwise) is the currently responsibility of the callback.
response_cb(payload, false);
});
}

@ -186,36 +186,42 @@ namespace llarp
Tick(llarp_time_t now, Router* r);
bool
find_name(std::string name, std::function<void(oxen::quic::message m)> func = nullptr);
find_name(std::string name, std::function<void(std::string, bool)> func = nullptr);
bool
find_router(std::string rid, std::function<void(oxen::quic::message m)> func = nullptr);
find_router(std::string rid, std::function<void(std::string, bool)> func = nullptr);
bool
find_intro(
const dht::Key_t& location,
bool is_relayed = false,
uint64_t order = 0,
std::function<void(oxen::quic::message m)> func = nullptr);
std::function<void(std::string, bool)> func = nullptr);
bool
close_exit(
SecretKey sk,
std::string tx_id,
std::function<void(oxen::quic::message m)> func = nullptr);
SecretKey sk, std::string tx_id, std::function<void(std::string, bool)> func = nullptr);
bool
obtain_exit(
SecretKey sk,
uint64_t flag,
std::string tx_id,
std::function<void(oxen::quic::message m)> func = nullptr);
std::function<void(std::string, bool)> func = nullptr);
/// sends a control request along a path
///
/// performs the necessary onion encryption before sending.
/// func will be called when a timeout occurs or a response is received.
/// if a response is received, onion decryption is performed before func is called.
///
/// func is called with a bt-encoded response string (if applicable), and
/// a timeout flag (if set, response string will be empty)
bool
send_path_control_message(
std::string method,
std::string body,
std::function<void(oxen::quic::message m)> func = nullptr) override;
std::function<void(std::string, bool)> func = nullptr) override;
bool
SendRoutingMessage(std::string payload, Router* r) override;

@ -69,195 +69,71 @@ namespace llarp::path
bool
PathContext::HopIsUs(const RouterID& k) const
{
return std::equal(_router->pubkey(), _router->pubkey() + PUBKEYSIZE, k.begin());
return _router->pubkey() == k;
}
PathContext::EndpointPathPtrSet
PathContext::FindOwnedPathsWithEndpoint(const RouterID& r)
{
EndpointPathPtrSet found;
m_OurPaths.ForEach([&](const Path_ptr& p) {
if (p->Endpoint() == r && p->IsReady())
found.insert(p);
});
return found;
}
template <
typename Lock_t,
typename Map_t,
typename Key_t,
typename CheckValue_t,
typename GetFunc_t,
typename Return_ptr = HopHandler_ptr>
Return_ptr
MapGet(Map_t& map, const Key_t& k, CheckValue_t check, GetFunc_t get)
{
Lock_t lock(map.first);
auto range = map.second.equal_range(k);
for (auto i = range.first; i != range.second; ++i)
{
if (check(i->second))
return get(i->second);
}
return nullptr;
}
template <typename Lock_t, typename Map_t, typename Key_t, typename CheckValue_t>
bool
MapHas(Map_t& map, const Key_t& k, CheckValue_t check)
{
Lock_t lock(map.first);
auto range = map.second.equal_range(k);
for (auto i = range.first; i != range.second; ++i)
{
if (check(i->second))
return true;
}
return false;
}
template <typename Lock_t, typename Map_t, typename Key_t, typename Value_t>
void
MapPut(Map_t& map, const Key_t& k, const Value_t& v)
{
Lock_t lock(map.first);
map.second.emplace(k, v);
}
template <typename Lock_t, typename Map_t, typename Visit_t>
void
MapIter(Map_t& map, Visit_t v)
{
Lock_t lock(map.first);
for (const auto& item : map.second)
v(item);
}
template <typename Lock_t, typename Map_t, typename Key_t, typename Check_t>
void
MapDel(Map_t& map, const Key_t& k, Check_t check)
{
Lock_t lock(map.first);
auto range = map.second.equal_range(k);
for (auto i = range.first; i != range.second;)
for (const auto& [pathid, path] : own_paths)
{
if (check(i->second))
i = map.second.erase(i);
else
++i;
if (path->Endpoint() == r && path->IsReady())
found.insert(path);
}
return found;
}
void
PathContext::AddOwnPath(PathSet_ptr set, Path_ptr path)
{
set->AddPath(path);
MapPut<util::Lock>(m_OurPaths, path->TXID(), path);
MapPut<util::Lock>(m_OurPaths, path->RXID(), path);
own_paths[path->TXID()] = path;
own_paths[path->RXID()] = path;
}
bool
PathContext::HasTransitHop(const TransitHopInfo& info)
{
return MapHas<SyncTransitMap_t::Lock_t>(
m_TransitPaths, info.txID, [info](const std::shared_ptr<TransitHop>& hop) -> bool {
return info == hop->info;
});
}
TransitHopID downstream{info.downstream, info.rxID};
if (transit_hops.count(downstream))
return true;
std::optional<std::weak_ptr<TransitHop>>
PathContext::TransitHopByInfo(const TransitHopInfo& info)
{
// this is ugly as sin
auto own = MapGet<
SyncTransitMap_t::Lock_t,
decltype(m_TransitPaths),
PathID_t,
std::function<bool(const std::shared_ptr<TransitHop>&)>,
std::function<TransitHop*(const std::shared_ptr<TransitHop>&)>,
TransitHop*>(
m_TransitPaths,
info.txID,
[info](const auto& hop) -> bool { return hop->info == info; },
[](const auto& hop) -> TransitHop* { return hop.get(); });
if (own)
return own->weak_from_this();
return std::nullopt;
}
TransitHopID upstream{info.upstream, info.txID};
if (transit_hops.count(upstream))
return true;
std::optional<std::weak_ptr<TransitHop>>
PathContext::TransitHopByUpstream(const RouterID& upstream, const PathID_t& id)
{
// this is ugly as sin as well
auto own = MapGet<
SyncTransitMap_t::Lock_t,
decltype(m_TransitPaths),
PathID_t,
std::function<bool(const std::shared_ptr<TransitHop>&)>,
std::function<TransitHop*(const std::shared_ptr<TransitHop>&)>,
TransitHop*>(
m_TransitPaths,
id,
[upstream](const auto& hop) -> bool { return hop->info.upstream == upstream; },
[](const auto& hop) -> TransitHop* { return hop.get(); });
if (own)
return own->weak_from_this();
return std::nullopt;
return false;
}
HopHandler_ptr
PathContext::GetByUpstream(const RouterID& remote, const PathID_t& id)
std::shared_ptr<TransitHop>
PathContext::GetTransitHop(const RouterID& rid, const PathID_t& path_id)
{
auto own = MapGet<util::Lock>(
m_OurPaths,
id,
[](const Path_ptr) -> bool {
// TODO: is this right?
return true;
},
[](Path_ptr p) -> HopHandler_ptr { return p; });
if (own)
return own;
if (auto itr = transit_hops.find({rid, path_id}); itr != transit_hops.end())
return itr->second;
return MapGet<SyncTransitMap_t::Lock_t>(
m_TransitPaths,
id,
[remote](const std::shared_ptr<TransitHop>& hop) -> bool {
return hop->info.upstream == remote;
},
[](const std::shared_ptr<TransitHop>& h) -> HopHandler_ptr { return h; });
return nullptr;
}
HopHandler_ptr
PathContext::GetByDownstream(const RouterID& remote, const PathID_t& id)
Path_ptr
PathContext::GetPath(const PathID_t& path_id)
{
return MapGet<SyncTransitMap_t::Lock_t>(
m_TransitPaths,
id,
[remote](const std::shared_ptr<TransitHop>& hop) -> bool {
return hop->info.downstream == remote;
},
[](const std::shared_ptr<TransitHop>& h) -> HopHandler_ptr { return h; });
if (auto itr = own_paths.find(path_id); itr != own_paths.end())
return itr->second;
return nullptr;
}
bool
PathContext::TransitHopPreviousIsRouter(const PathID_t& path, const RouterID& otherRouter)
PathContext::TransitHopPreviousIsRouter(const PathID_t& path_id, const RouterID& otherRouter)
{
SyncTransitMap_t::Lock_t lock(m_TransitPaths.first);
auto itr = m_TransitPaths.second.find(path);
if (itr == m_TransitPaths.second.end())
return false;
return itr->second->info.downstream == otherRouter;
return transit_hops.count({otherRouter, path_id});
}
PathSet_ptr
PathContext::GetLocalPathSet(const PathID_t& id)
{
auto& map = m_OurPaths;
util::Lock lock(map.first);
auto itr = map.second.find(id);
if (itr != map.second.end())
if (auto itr = own_paths.find(id); itr != own_paths.end())
{
if (auto parent = itr->second->m_PathSet.lock())
return parent;
@ -274,51 +150,27 @@ namespace llarp::path
TransitHop_ptr
PathContext::GetPathForTransfer(const PathID_t& id)
{
const RouterID us(OurRouterID());
auto& map = m_TransitPaths;
if (auto itr = transit_hops.find({OurRouterID(), id}); itr != transit_hops.end())
{
SyncTransitMap_t::Lock_t lock(map.first);
auto range = map.second.equal_range(id);
for (auto i = range.first; i != range.second; ++i)
{
if (i->second->info.upstream == us)
return i->second;
}
return itr->second;
}
return nullptr;
}
void
PathContext::PumpUpstream()
{
m_TransitPaths.ForEach([&](auto& ptr) { ptr->FlushUpstream(_router); });
m_OurPaths.ForEach([&](auto& ptr) { ptr->FlushUpstream(_router); });
}
void
PathContext::PumpDownstream()
{
m_TransitPaths.ForEach([&](auto& ptr) { ptr->FlushDownstream(_router); });
m_OurPaths.ForEach([&](auto& ptr) { ptr->FlushDownstream(_router); });
return nullptr;
}
uint64_t
PathContext::CurrentTransitPaths()
{
SyncTransitMap_t::Lock_t lock(m_TransitPaths.first);
const auto& map = m_TransitPaths.second;
return map.size() / 2;
return transit_hops.size() / 2;
}
uint64_t
PathContext::CurrentOwnedPaths(path::PathStatus st)
{
uint64_t num{};
util::Lock lock{m_OurPaths.first};
auto& map = m_OurPaths.second;
for (auto itr = map.begin(); itr != map.end(); ++itr)
for (auto& own_path : own_paths)
{
if (itr->second->Status() == st)
if (own_path.second->Status() == st)
num++;
}
return num / 2;
@ -327,8 +179,10 @@ namespace llarp::path
void
PathContext::PutTransitHop(std::shared_ptr<TransitHop> hop)
{
MapPut<SyncTransitMap_t::Lock_t>(m_TransitPaths, hop->info.txID, hop);
MapPut<SyncTransitMap_t::Lock_t>(m_TransitPaths, hop->info.rxID, hop);
TransitHopID downstream{hop->info.downstream, hop->info.rxID};
TransitHopID upstream{hop->info.upstream, hop->info.txID};
transit_hops.emplace(std::move(downstream), hop);
transit_hops.emplace(std::move(upstream), hop);
}
void
@ -338,16 +192,14 @@ namespace llarp::path
path_limits.Decay(now);
{
SyncTransitMap_t::Lock_t lock(m_TransitPaths.first);
auto& map = m_TransitPaths.second;
auto itr = map.begin();
while (itr != map.end())
auto itr = transit_hops.begin();
while (itr != transit_hops.end())
{
if (itr->second->Expired(now))
{
// TODO: this
// _router->outboundMessageHandler().RemovePath(itr->first);
itr = map.erase(itr);
itr = transit_hops.erase(itr);
}
else
{
@ -357,14 +209,11 @@ namespace llarp::path
}
}
{
util::Lock lock(m_OurPaths.first);
auto& map = m_OurPaths.second;
auto itr = map.begin();
while (itr != map.end())
for (auto itr = own_paths.begin(); itr != own_paths.end();)
{
if (itr->second->Expired(now))
{
itr = map.erase(itr);
itr = own_paths.erase(itr);
}
else
{

@ -25,149 +25,130 @@ namespace llarp
struct TransitHop;
struct TransitHopInfo;
using TransitHop_ptr = std::shared_ptr<TransitHop>;
struct TransitHopID
{
RouterID rid;
PathID_t path_id;
bool
operator==(const TransitHopID& other) const
{
return rid == other.rid && path_id == other.path_id;
}
};
} // namespace path
} // namespace llarp
struct PathContext
namespace std
{
inline bool
operator==(const llarp::path::TransitHopID& lhs, const llarp::path::TransitHopID& rhs)
{
return lhs.operator==(rhs);
}
template <>
struct hash<llarp::path::TransitHopID>
{
size_t
operator()(const llarp::path::TransitHopID& obj) const noexcept
{
explicit PathContext(Router* router);
return std::hash<llarp::PathID_t>{}(obj.path_id);
}
};
} // namespace std
/// called from router tick function
void
ExpirePaths(llarp_time_t now);
namespace llarp::path
{
using TransitHop_ptr = std::shared_ptr<TransitHop>;
void
PumpUpstream();
struct PathContext
{
explicit PathContext(Router* router);
void
PumpDownstream();
/// called from router tick function
void
ExpirePaths(llarp_time_t now);
void
AllowTransit();
void
AllowTransit();
void
RejectTransit();
void
RejectTransit();
bool
CheckPathLimitHitByIP(const IpAddress& ip);
bool
CheckPathLimitHitByIP(const IpAddress& ip);
bool
CheckPathLimitHitByIP(const std::string& ip);
bool
CheckPathLimitHitByIP(const std::string& ip);
bool
AllowingTransit() const;
bool
AllowingTransit() const;
bool
HasTransitHop(const TransitHopInfo& info);
bool
HasTransitHop(const TransitHopInfo& info);
void
PutTransitHop(std::shared_ptr<TransitHop> hop);
void
PutTransitHop(std::shared_ptr<TransitHop> hop);
HopHandler_ptr
GetByUpstream(const RouterID& id, const PathID_t& path);
Path_ptr
GetPath(const PathID_t& path_id);
bool
TransitHopPreviousIsRouter(const PathID_t& path, const RouterID& r);
bool
TransitHopPreviousIsRouter(const PathID_t& path, const RouterID& r);
TransitHop_ptr
GetPathForTransfer(const PathID_t& topath);
TransitHop_ptr
GetPathForTransfer(const PathID_t& topath);
HopHandler_ptr
GetByDownstream(const RouterID& id, const PathID_t& path);
std::shared_ptr<TransitHop>
GetTransitHop(const RouterID&, const PathID_t&);
std::optional<std::weak_ptr<TransitHop>>
TransitHopByInfo(const TransitHopInfo&);
PathSet_ptr
GetLocalPathSet(const PathID_t& id);
std::optional<std::weak_ptr<TransitHop>>
TransitHopByUpstream(const RouterID&, const PathID_t&);
using EndpointPathPtrSet = std::set<Path_ptr, ComparePtr<Path_ptr>>;
/// get a set of all paths that we own who's endpoint is r
EndpointPathPtrSet
FindOwnedPathsWithEndpoint(const RouterID& r);
PathSet_ptr
GetLocalPathSet(const PathID_t& id);
bool
HopIsUs(const RouterID& k) const;
using EndpointPathPtrSet = std::set<Path_ptr, ComparePtr<Path_ptr>>;
/// get a set of all paths that we own who's endpoint is r
EndpointPathPtrSet
FindOwnedPathsWithEndpoint(const RouterID& r);
void
AddOwnPath(PathSet_ptr set, Path_ptr p);
bool
HopIsUs(const RouterID& k) const;
void
RemovePathSet(PathSet_ptr set);
void
AddOwnPath(PathSet_ptr set, Path_ptr p);
const EventLoop_ptr&
loop();
void
RemovePathSet(PathSet_ptr set);
const SecretKey&
EncryptionSecretKey();
using TransitHopsMap_t = std::unordered_multimap<PathID_t, TransitHop_ptr>;
const byte_t*
OurRouterID() const;
struct SyncTransitMap_t
{
using Mutex_t = util::NullMutex;
using Lock_t = util::NullLock;
Mutex_t first; // protects second
TransitHopsMap_t second;
/// Invokes a callback for each transit path; visit must be invokable with a `const
/// TransitHop_ptr&` argument.
template <typename TransitHopVisitor>
void
ForEach(TransitHopVisitor&& visit)
{
Lock_t lock(first);
for (const auto& item : second)
visit(item.second);
}
};
// maps path id -> pathset owner of path
using OwnedPathsMap_t = std::unordered_map<PathID_t, Path_ptr>;
struct SyncOwnedPathsMap_t
{
util::Mutex first; // protects second
OwnedPathsMap_t second;
/// Invokes a callback for each owned path; visit must be invokable with a `const Path_ptr&`
/// argument.
template <typename OwnedHopVisitor>
void
ForEach(OwnedHopVisitor&& visit)
{
util::Lock lock(first);
for (const auto& item : second)
visit(item.second);
}
};
const EventLoop_ptr&
loop();
const SecretKey&
EncryptionSecretKey();
const byte_t*
OurRouterID() const;
/// current number of transit paths we have
uint64_t
CurrentTransitPaths();
/// current number of paths we created in status
uint64_t
CurrentOwnedPaths(path::PathStatus status = path::PathStatus::ePathEstablished);
Router*
router() const
{
return _router;
}
/// current number of transit paths we have
uint64_t
CurrentTransitPaths();
private:
Router* _router;
SyncTransitMap_t m_TransitPaths;
SyncOwnedPathsMap_t m_OurPaths;
bool m_AllowTransit;
util::DecayingHashSet<IpAddress> path_limits;
};
} // namespace path
} // namespace llarp
/// current number of paths we created in status
uint64_t
CurrentOwnedPaths(path::PathStatus status = path::PathStatus::ePathEstablished);
Router*
router() const
{
return _router;
}
private:
Router* _router;
std::unordered_map<TransitHopID, TransitHop_ptr> transit_hops;
std::unordered_map<PathID_t, Path_ptr> own_paths;
bool m_AllowTransit;
util::DecayingHashSet<IpAddress> path_limits;
};
} // namespace llarp::path

@ -1,6 +1,4 @@
#include "pathbuilder.hpp"
#include "path.hpp"
#include "path_context.hpp"
#include "path.hpp"
#include "path_context.hpp"
@ -8,7 +6,6 @@
#include <llarp/crypto/crypto.hpp>
#include <llarp/link/link_manager.hpp>
#include <llarp/messages/path.hpp>
#include <llarp/nodedb.hpp>
#include <llarp/profiling.hpp>
#include <llarp/router/rc_lookup_handler.hpp>

@ -23,9 +23,37 @@ namespace llarp::path
m_DownstreamWorkCounter = 0;
}
void
TransitHop::onion(ustring& data, TunnelNonce& nonce, bool randomize) const
{
if (randomize)
nonce.Randomize();
nonce = crypto::onion(data.data(), data.size(), pathKey, nonce, nonceXOR);
}
void
TransitHop::onion(std::string& data, TunnelNonce& nonce, bool randomize) const
{
if (randomize)
nonce.Randomize();
nonce = crypto::onion(
reinterpret_cast<unsigned char*>(data.data()), data.size(), pathKey, nonce, nonceXOR);
}
std::string
TransitHop::onion_and_payload(
std::string& payload, PathID_t next_id, std::optional<TunnelNonce> nonce) const
{
TunnelNonce n;
auto& nref = nonce ? *nonce : n;
onion(payload, nref, not nonce);
return path::make_onion_payload(nref, next_id, payload);
}
bool
TransitHop::send_path_control_message(
std::string, std::string, std::function<void(oxen::quic::message m)>)
std::string, std::string, std::function<void(std::string, bool)>)
{
return true;
}

@ -61,6 +61,23 @@ namespace llarp
llarp_time_t lifetime = DEFAULT_LIFETIME;
llarp_proto_version_t version;
llarp_time_t m_LastActivity = 0s;
bool terminal_hop{false};
// If randomize is given, first randomizes `nonce`
//
// Does xchacha20 on `data` in-place with `nonce` and `pathKey`, then
// mutates `nonce` = `nonce` ^ `nonceXOR` in-place.
void
onion(ustring& data, TunnelNonce& nonce, bool randomize = false) const;
void
onion(std::string& data, TunnelNonce& nonce, bool randomize = false) const;
std::string
onion_and_payload(
std::string& payload,
PathID_t next_id,
std::optional<TunnelNonce> nonce = std::nullopt) const;
PathID_t
RXID() const override
@ -106,11 +123,24 @@ namespace llarp
return now >= ExpireTime() - dlt;
}
// TODO: should this be a separate method indicating directionality?
// Most control messages won't make sense to be sent to a client,
// so perhaps control messages from a terminal relay to a client (rather than
// the other way around) should be their own message type.
//
/// sends a control request along a path
///
/// performs the necessary onion encryption before sending.
/// func will be called when a timeout occurs or a response is received.
/// if a response is received, onion decryption is performed before func is called.
///
/// func is called with a bt-encoded response string (if applicable), and
/// a timeout flag (if set, response string will be empty)
bool
send_path_control_message(
std::string method,
std::string body,
std::function<void(oxen::quic::message m)> func) override;
std::function<void(std::string, bool)> func) override;
// send routing message when end of path
bool

@ -83,58 +83,27 @@ namespace llarp
}
}
auto lookup_cb = [this, callback, rid](oxen::quic::message m) mutable {
auto lookup_cb = [this, callback, rid](RemoteRC rc, bool success) mutable {
auto& r = link_manager->router();
if (m)
{
std::string payload;
try
{
oxenc::bt_dict_consumer btdc{m.body()};
payload = btdc.require<std::string>("RC");
}
catch (...)
{
log::warning(link_cat, "Failed to parse Find Router response!");
throw;
}
RemoteRC result{std::move(payload)};
if (callback)
callback(result.router_id(), result, true);
else
r.node_db()->put_rc_if_newer(result);
}
else
if (not success)
{
if (callback)
callback(rid, std::nullopt, false);
else
link_manager->handle_find_router_error(std::move(m));
return;
}
r.node_db()->put_rc_if_newer(rc);
if (callback)
callback(rc.router_id(), rc, true);
};
// if we are a client try using the hidden service endpoints
// TODO: RC fetching and gossiping in general is being refactored, and the old method
// of look it up over a path or directly but using the same method but called
// differently is going away. It's a mess. The below will do a lookup via a path,
// relays will need a different implementation TBD.
if (!isServiceNode)
{
bool sent = false;
LogInfo("Lookup ", rid, " anonymously");
hidden_service_context->ForEachService(
[&, cb = lookup_cb](
const std::string&, const std::shared_ptr<service::Endpoint>& ep) -> bool {
const bool success = ep->lookup_router(rid, cb);
sent = sent || success;
return !success;
});
if (sent)
return;
LogWarn("cannot lookup ", rid, " anonymously");
}
contacts->lookup_router(rid, lookup_cb);
hidden_service_context->GetDefault()->lookup_router(rid, std::move(lookup_cb));
}
bool

@ -75,8 +75,6 @@ namespace llarp
llarp::LogTrace("Router::PumpLL() start");
if (is_stopping.load())
return;
paths.PumpDownstream();
paths.PumpUpstream();
_hidden_service_context.Pump();
llarp::LogTrace("Router::PumpLL() end");
}
@ -1291,7 +1289,6 @@ namespace llarp
_exit_context.Stop();
llarp::sys::service_manager->stopping();
log::debug(logcat, "final upstream pump");
paths.PumpUpstream();
llarp::sys::service_manager->stopping();
log::debug(logcat, "final links pump");
_loop->call_later(200ms, [this] { AfterStopIssued(); });

@ -139,63 +139,52 @@ namespace llarp::service
auth = AuthInfo{token},
ranges,
result_handler,
poker = router()->route_poker()](oxen::quic::message m) mutable {
if (m)
poker = router()->route_poker()](std::string name_result, bool success) mutable {
if (not success)
{
std::string name;
try
{
oxenc::bt_dict_consumer btdc{m.body()};
name = btdc.require<std::string>("NAME");
}
catch (...)
{
log::warning(link_cat, "Failed to parse find name response!");
throw;
}
if (auto saddr = service::Address(); saddr.FromString(name))
{
ptr->SetAuthInfoForEndpoint(saddr, auth);
ptr->MarkAddressOutbound(saddr);
result_handler(false, "Exit {} not found!"_format(name));
return;
}
auto result = ptr->EnsurePathToService(
saddr,
[ptr, name, ranges, result_handler, poker](auto addr, OutboundContext* ctx) {
if (ctx == nullptr)
if (auto saddr = service::Address(); saddr.FromString(name_result))
{
ptr->SetAuthInfoForEndpoint(saddr, auth);
ptr->MarkAddressOutbound(saddr);
auto result = ptr->EnsurePathToService(
saddr,
[ptr, name, name_result, ranges, result_handler, poker](
auto addr, OutboundContext* ctx) {
if (ctx == nullptr)
{
result_handler(
false, "could not establish flow to {} ({})"_format(name_result, name));
return;
}
// make a lambda that sends the reply after doing auth
auto apply_result = [ptr, poker, addr, result_handler, ranges](
std::string result, bool success) {
if (success)
{
result_handler(false, "could not establish flow to {}"_format(name));
return;
}
for (const auto& range : ranges)
ptr->MapExitRange(range, addr);
// make a lambda that sends the reply after doing auth
auto apply_result = [ptr, poker, addr, result_handler, ranges](
std::string result, bool success) {
if (success)
{
for (const auto& range : ranges)
ptr->MapExitRange(range, addr);
if (poker)
poker->put_up();
if (poker)
poker->put_up();
result_handler(true, result);
}
result_handler(true, result);
}
result_handler(false, result);
};
result_handler(false, result);
};
ctx->send_auth_async(apply_result);
},
ptr->PathAlignmentTimeout());
ctx->send_auth_async(apply_result);
},
ptr->PathAlignmentTimeout());
if (not result)
result_handler(false, "Could not build path to {}"_format(name));
}
}
else
{
result_handler(false, "Exit {} not found!"_format(name));
if (not result)
result_handler(false, "Could not build path to {} ({})"_format(name_result, name));
}
});
}
@ -316,35 +305,24 @@ namespace llarp::service
{
auto& name = item.first;
lookup_name(name, [this, name, info = item.second](oxen::quic::message m) mutable {
if (m)
{
std::string result;
try
{
oxenc::bt_dict_consumer btdc{m.body()};
result = btdc.require<std::string>("NAME");
}
catch (...)
{
log::warning(link_cat, "Failed to parse find name response!");
throw;
}
lookup_name(
name, [this, name, info = item.second](std::string name_result, bool success) mutable {
if (not success)
return;
const auto maybe_range = info.first;
const auto maybe_auth = info.second;
const auto maybe_range = info.first;
const auto maybe_auth = info.second;
_startup_ons_mappings.erase(name);
_startup_ons_mappings.erase(name);
if (auto saddr = service::Address(); saddr.FromString(result))
{
if (maybe_range.has_value())
_exit_map.Insert(*maybe_range, saddr);
if (maybe_auth.has_value())
SetAuthInfoForEndpoint(saddr, *maybe_auth);
}
}
});
if (auto saddr = service::Address(); saddr.FromString(name_result))
{
if (maybe_range.has_value())
_exit_map.Insert(*maybe_range, saddr);
if (maybe_auth.has_value())
SetAuthInfoForEndpoint(saddr, *maybe_auth);
}
});
}
}
}
@ -798,7 +776,7 @@ namespace llarp::service
}
void
Endpoint::lookup_name(std::string name, std::function<void(oxen::quic::message)> func)
Endpoint::lookup_name(std::string name, std::function<void(std::string, bool)> func)
{
// TODO: so fuck all this?
@ -838,10 +816,29 @@ namespace llarp::service
std::shuffle(chosenpaths.begin(), chosenpaths.end(), llarp::csrng);
chosenpaths.resize(std::min(paths.size(), MAX_ONS_LOOKUP_ENDPOINTS));
auto response_cb = [func = std::move(func)](std::string resp, bool timeout) {
if (timeout)
func(""s, false);
std::string name{};
try
{
oxenc::bt_dict_consumer btdc{resp};
name = btdc.require<std::string>("NAME");
}
catch (...)
{
log::warning(link_cat, "Failed to parse find name response!");
func(""s, false);
}
func(name, true);
};
for (const auto& path : chosenpaths)
{
log::info(link_cat, "{} lookup {} from {}", Name(), name, path->Endpoint());
path->find_name(name, func);
path->find_name(name, response_cb);
}
}
@ -857,18 +854,35 @@ namespace llarp::service
}
bool
Endpoint::lookup_router(RouterID rid, std::function<void(oxen::quic::message)> func)
Endpoint::lookup_router(RouterID rid, std::function<void(RouterContact rc, bool success)> func)
{
const auto& routers = _state->pending_routers;
auto path = GetEstablishedPathClosestTo(rid);
if (routers.find(rid) == routers.end())
{
auto path = GetEstablishedPathClosestTo(rid);
path->find_router("find_router", func);
return true;
}
auto response_cb = [func = std::move(func)](std::string resp, bool timeout) {
if (timeout)
func(RouterContact{}, false);
return false;
std::string payload;
try
{
oxenc::bt_dict_consumer btdc{resp};
payload = btdc.require<std::string>("RC");
}
catch (...)
{
log::warning(link_cat, "Failed to parse Find Router response!");
func(RouterContact{}, false);
return;
}
RouterContact result{std::move(payload)};
func(result, true);
};
path->find_router("find_router", std::move(response_cb));
return true;
}
void
@ -1327,30 +1341,39 @@ namespace llarp::service
// address once.
bool hookAdded = false;
auto got_it = std::make_shared<bool>(false);
// TODO: if all requests fail, call callback with failure?
for (const auto& path : paths)
{
path->find_intro(location, false, 0, [this, hook](oxen::quic::message m) mutable {
if (m)
{
std::string introset;
path->find_intro(
location, false, 0, [this, hook, got_it](std::string resp, bool timeout) mutable {
// asking many, use only first successful
if (timeout or *got_it)
return;
try
{
oxenc::bt_dict_consumer btdc{m.body()};
introset = btdc.require<std::string>("INTROSET");
}
catch (...)
{
log::warning(link_cat, "Failed to parse find name response!");
throw;
}
std::string introset;
try
{
oxenc::bt_dict_consumer btdc{resp};
introset = btdc.require<std::string>("INTROSET");
}
catch (...)
{
log::warning(link_cat, "Failed to parse find name response!");
throw;
}
service::EncryptedIntroSet enc{introset};
router()->contacts()->services()->PutNode(std::move(enc));
service::EncryptedIntroSet enc{introset};
router()->contacts()->services()->PutNode(std::move(enc));
// TODO: finish this
}
});
// TODO: finish this
/*
if (good)
*got_it = true;
*/
});
}
return hookAdded;
}

@ -235,12 +235,11 @@ namespace llarp
// "find router" via closest path
bool
lookup_router(RouterID router, std::function<void(oxen::quic::message)> func = nullptr);
lookup_router(RouterID router, std::function<void(RouterContact, bool)> func = nullptr);
// "find name"
void
lookup_name(
std::string name, std::function<void(oxen::quic::message)> func = nullptr) override;
lookup_name(std::string name, std::function<void(std::string, bool)> func = nullptr) override;
// "find introset?"
void

@ -173,7 +173,7 @@ namespace llarp::service
for (const auto& path : paths)
{
path->find_intro(location, false, relayOrder, [this](oxen::quic::message m) mutable {
path->find_intro(location, false, relayOrder, [this](std::string resp, bool timeout) mutable {
if (marked_bad)
{
log::info(link_cat, "Outbound context has been marked bad (whatever that means)");
@ -182,43 +182,48 @@ namespace llarp::service
updatingIntroSet = false;
if (m)
if (timeout)
return;
// TODO: this parsing is probably elsewhere, may need DRYed
std::string introset;
try
{
oxenc::bt_dict_consumer btdc{resp};
introset = btdc.require<std::string>("INTROSET");
}
catch (...)
{
std::string introset;
log::warning(link_cat, "Failed to parse find name response!");
throw;
}
try
{
oxenc::bt_dict_consumer btdc{m.body()};
introset = btdc.require<std::string>("INTROSET");
}
catch (...)
{
log::warning(link_cat, "Failed to parse find name response!");
throw;
}
service::EncryptedIntroSet enc{introset};
const auto intro = enc.decrypt(PubKey{addr.as_array()});
service::EncryptedIntroSet enc{introset};
const auto intro = enc.decrypt(PubKey{addr.as_array()});
if (intro.time_signed == 0s)
{
log::warning(link_cat, "{} recieved introset with zero timestamp");
return;
}
if (current_intro.time_signed > intro.time_signed)
{
log::info(link_cat, "{} received outdated introset; dropping", Name());
return;
}
if (intro.time_signed == 0s)
{
log::warning(link_cat, "{} recieved introset with zero timestamp");
return;
}
if (current_intro.time_signed > intro.time_signed)
{
log::info(link_cat, "{} received outdated introset; dropping", Name());
return;
}
if (intro.IsExpired(llarp::time_now_ms()))
{
log::warning(link_cat, "{} received expired introset", Name());
return;
}
// don't "shift" to the same intro we're already using...
if (current_intro == intro)
return;
current_intro = intro;
ShiftIntroRouter();
if (intro.IsExpired(llarp::time_now_ms()))
{
log::warning(link_cat, "{} received expired introset", Name());
return;
}
current_intro = intro;
ShiftIntroRouter();
});
}
}
@ -534,8 +539,10 @@ namespace llarp::service
ex->msg.proto = ProtocolType::Auth;
ex->hook = [this, path, cb = std::move(func)](auto frame) mutable {
auto hook = [&, frame, path](oxen::quic::message) {
auto hook = [&, frame, path](std::string resp, bool timeout) {
// TODO: revisit this
(void)resp;
(void)timeout;
ep.HandleHiddenServiceFrame(path, *frame.get());
};

Loading…
Cancel
Save