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/2232/head
Thomas Winget 9 months ago committed by dr7ana
parent 3bc8c30b22
commit bf2665bbe7

@ -154,6 +154,21 @@ namespace llarp
return crypto_stream_xchacha20_xor(buf, buf, size, nonce, secret) == 0; 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 bool
crypto::dh_client( crypto::dh_client(
llarp::SharedSecret& shared, const PubKey& pk, const SecretKey& sk, const TunnelNonce& n) llarp::SharedSecret& shared, const PubKey& pk, const SecretKey& sk, const TunnelNonce& n)

@ -27,6 +27,14 @@ namespace llarp
bool bool
xchacha20(uint8_t*, size_t size, const uint8_t*, const uint8_t*); 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 /// path dh creator's side
bool bool
dh_client(SharedSecret&, const PubKey&, const SecretKey&, const TunnelNonce&); dh_client(SharedSecret&, const PubKey&, const SecretKey&, const TunnelNonce&);

@ -131,7 +131,7 @@ namespace llarp
llarp_time_t timeout) = 0; llarp_time_t timeout) = 0;
virtual void 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& virtual const EventLoop_ptr&
Loop() = 0; Loop() = 0;

@ -57,7 +57,8 @@ namespace llarp::exit
if (!parent->UpdateEndpointPath(remote_signkey, nextPath)) if (!parent->UpdateEndpointPath(remote_signkey, nextPath))
return false; return false;
const RouterID us{parent->GetRouter()->pubkey()}; 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; return true;
} }

@ -20,9 +20,9 @@ namespace llarp::handlers
ExitEndpoint::~ExitEndpoint() = default; ExitEndpoint::~ExitEndpoint() = default;
void 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 void
@ -766,7 +766,9 @@ namespace llarp::handlers
{ {
if (wantInternet && !permit_exit) if (wantInternet && !permit_exit)
return false; 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) if (handler == nullptr)
return false; return false;
auto ip = GetIPForIdent(pk); auto ip = GetIPForIdent(pk);

@ -49,7 +49,7 @@ namespace llarp
llarp_time_t timeout) override; llarp_time_t timeout) override;
void 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& const EventLoop_ptr&
Loop() override; Loop() override;

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

@ -7,6 +7,7 @@
#include <llarp/messages/exit.hpp> #include <llarp/messages/exit.hpp>
#include <llarp/messages/path.hpp> #include <llarp/messages/path.hpp>
#include <llarp/nodedb.hpp> #include <llarp/nodedb.hpp>
#include <llarp/path/path.hpp>
#include <llarp/router/rc_lookup_handler.hpp> #include <llarp/router/rc_lookup_handler.hpp>
#include <llarp/router/router.hpp> #include <llarp/router/router.hpp>
@ -137,19 +138,30 @@ namespace llarp
{ {
assert(ep.connid_map.count(s->conn_id())); assert(ep.connid_map.count(s->conn_id()));
RouterID rid = ep.connid_map[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) { s->register_command("path_build"s, [this, rid](oxen::quic::message m) {
_router.loop()->call( _router.loop()->call(
[this, &rid, msg = std::move(m)]() mutable { handle_path_build(std::move(msg), rid); }); [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> std::shared_ptr<oxen::quic::Endpoint>
@ -201,12 +213,7 @@ namespace llarp
std::string body, std::string body,
std::function<void(oxen::quic::message m)> func) std::function<void(oxen::quic::message m)> func)
{ {
if (not func and rpc_responses.count(endpoint)) assert(func); // makes no sense to send control message and ignore response
{
func = [&](oxen::quic::message m) {
std::invoke(rpc_responses[endpoint], this, std::move(m));
};
}
if (func) if (func)
{ {
@ -423,8 +430,6 @@ namespace llarp
return; return;
} }
util::Lock l(m);
LogInfo("stopping links"); LogInfo("stopping links");
is_stopping = true; is_stopping = true;
@ -437,8 +442,6 @@ namespace llarp
if (is_stopping) if (is_stopping)
return; return;
util::Lock l(m);
persisting_conns[remote] = std::max(until, persisting_conns[remote]); persisting_conns[remote] = std::max(until, persisting_conns[remote]);
if (have_client_connection_to(remote)) if (have_client_connection_to(remote))
{ {
@ -520,29 +523,30 @@ namespace llarp
} }
void 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; std::string name_hash;
try try
{ {
oxenc::bt_dict_consumer btdp{m.body()}; oxenc::bt_dict_consumer btdp{body};
name_hash = btdp.require<std::string>("H"); name_hash = btdp.require<std::string>("H");
} }
catch (const std::exception& e) catch (const std::exception& e)
{ {
log::warning(link_cat, "Exception: {}", e.what()); 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( _router.rpc_client()->lookup_ons_hash(
name_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) if (maybe)
msg.respond(serialize_response({{"NAME", maybe->ciphertext}})); respond(serialize_response({{"NAME", maybe->ciphertext}}));
else else
msg.respond(serialize_response({{"STATUS", FindNameMessage::NOT_FOUND}}), true); respond(serialize_response({{"STATUS", FindNameMessage::NOT_FOUND}}));
}); });
} }
@ -590,17 +594,15 @@ namespace llarp
} }
} }
// TODO: add callback to relayed messages (calls to send_control_message so the
// response finds its way back)
void 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; std::string target_key;
bool is_exploratory, is_iterative; bool is_exploratory, is_iterative;
try try
{ {
oxenc::bt_dict_consumer btdc{m.body()}; oxenc::bt_dict_consumer btdc{body};
is_exploratory = btdc.require<bool>("E"); is_exploratory = btdc.require<bool>("E");
is_iterative = btdc.require<bool>("I"); is_iterative = btdc.require<bool>("I");
@ -609,8 +611,7 @@ namespace llarp
catch (const std::exception& e) catch (const std::exception& e)
{ {
log::warning(link_cat, "Exception: {}", e.what()); log::warning(link_cat, "Exception: {}", e.what());
m.respond( respond(serialize_response({{"STATUS", FindRouterMessage::EXCEPTION}, {"TARGET", ""}}));
serialize_response({{"STATUS", FindRouterMessage::EXCEPTION}, {"TARGET", ""}}), true);
return; return;
} }
@ -640,9 +641,8 @@ namespace llarp
neighbors += rid.bt_encode(); neighbors += rid.bt_encode();
} }
m.respond( respond(
serialize_response({{"STATUS", FindRouterMessage::RETRY_EXP}, {"TARGET", neighbors}}), serialize_response({{"STATUS", FindRouterMessage::RETRY_EXP}, {"TARGET", neighbors}}));
true);
} }
else else
{ {
@ -658,13 +658,13 @@ namespace llarp
target_rid, target_rid,
"find_router", "find_router",
FindRouterMessage::serialize(target_rid, false, false), FindRouterMessage::serialize(target_rid, false, false),
[original = std::move(m)](oxen::quic::message msg) mutable { [respond = std::move(respond)](oxen::quic::message msg) mutable {
original.respond(msg.body_str(), not msg); respond(msg.body_str());
}); });
} }
else else
{ {
m.respond(serialize_response({{"RC", closest_rc.view()}})); respond(serialize_response({{"RC", closest_rc.view()}}));
} }
} }
else if (not is_iterative) else if (not is_iterative)
@ -675,26 +675,22 @@ namespace llarp
closest_rid, closest_rid,
"find_router", "find_router",
FindRouterMessage::serialize(closest_rid, false, false), FindRouterMessage::serialize(closest_rid, false, false),
[original = std::move(m)](oxen::quic::message msg) mutable { [respond = std::move(respond)](oxen::quic::message msg) mutable {
original.respond(msg.body_str(), not msg); respond(msg.body_str());
}); });
} }
else else
{ {
m.respond( respond(serialize_response(
serialize_response( {{"STATUS", FindRouterMessage::RETRY_ITER},
{{"STATUS", FindRouterMessage::RETRY_ITER}, {"TARGET", reinterpret_cast<const char*>(target_addr.data())}}));
{"TARGET", reinterpret_cast<const char*>(target_addr.data())}}),
true);
} }
} }
else else
{ {
m.respond( respond(serialize_response(
serialize_response( {{"STATUS", FindRouterMessage::RETRY_NEW},
{{"STATUS", FindRouterMessage::RETRY_NEW}, {"TARGET", reinterpret_cast<const char*>(closest_rid.data())}}));
{"TARGET", reinterpret_cast<const char*>(closest_rid.data())}}),
true);
} }
} }
} }
@ -818,7 +814,7 @@ namespace llarp
} }
void 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; std::string introset, derived_signing_key, payload, sig, nonce;
uint64_t is_relayed, relay_order; uint64_t is_relayed, relay_order;
@ -826,7 +822,7 @@ namespace llarp
try try
{ {
oxenc::bt_dict_consumer btdc_a{m.body()}; oxenc::bt_dict_consumer btdc_a{body};
introset = btdc_a.require<std::string>("I"); introset = btdc_a.require<std::string>("I");
relay_order = btdc_a.require<uint64_t>("O"); relay_order = btdc_a.require<uint64_t>("O");
@ -843,7 +839,7 @@ namespace llarp
catch (const std::exception& e) catch (const std::exception& e)
{ {
log::warning(link_cat, "Exception: {}", e.what()); log::warning(link_cat, "Exception: {}", e.what());
m.respond(serialize_response({{"STATUS", PublishIntroMessage::EXCEPTION}}), true); respond(serialize_response({{"STATUS", PublishIntroMessage::EXCEPTION}}));
return; return;
} }
@ -854,14 +850,14 @@ namespace llarp
if (not service::EncryptedIntroSet::verify(introset, derived_signing_key, sig)) if (not service::EncryptedIntroSet::verify(introset, derived_signing_key, sig))
{ {
log::error(link_cat, "Received PublishIntroMessage with invalid introset: {}", introset); 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; return;
} }
if (now + service::MAX_INTROSET_TIME_DELTA > signed_at + path::DEFAULT_LIFETIME) if (now + service::MAX_INTROSET_TIME_DELTA > signed_at + path::DEFAULT_LIFETIME)
{ {
log::error(link_cat, "Received PublishIntroMessage with expired introset: {}", introset); 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; return;
} }
@ -871,7 +867,7 @@ namespace llarp
{ {
log::error( log::error(
link_cat, "Received PublishIntroMessage but only know {} nodes", closest_rcs.size()); 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; return;
} }
@ -883,7 +879,7 @@ namespace llarp
{ {
log::error( log::error(
link_cat, "Received PublishIntroMessage with invalide relay order: {}", relay_order); 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; return;
} }
@ -900,7 +896,7 @@ namespace llarp
relay_order); relay_order);
_router.contacts()->services()->PutNode(dht::ISNode{std::move(enc)}); _router.contacts()->services()->PutNode(dht::ISNode{std::move(enc)});
m.respond(serialize_response({{"STATUS", ""}})); respond(serialize_response({{"STATUS", ""}}));
} }
else else
{ {
@ -910,7 +906,12 @@ namespace llarp
send_control_message( send_control_message(
peer_key, peer_key,
"publish_intro", "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; return;
@ -933,7 +934,7 @@ namespace llarp
log::info(link_cat, "Received PublishIntroMessage for {} (TXID: {}); we are candidate {}"); log::info(link_cat, "Received PublishIntroMessage for {} (TXID: {}); we are candidate {}");
_router.contacts()->services()->PutNode(dht::ISNode{std::move(enc)}); _router.contacts()->services()->PutNode(dht::ISNode{std::move(enc)});
m.respond(serialize_response()); respond(serialize_response({{"STATUS", ""}}));
} }
else else
log::warning( log::warning(
@ -980,29 +981,25 @@ namespace llarp
log::info(link_cat, "PublishIntroMessage failed with error code: {}", payload); log::info(link_cat, "PublishIntroMessage failed with error code: {}", payload);
if (payload == PublishIntroMessage::INVALID_INTROSET) if (payload == PublishIntroMessage::INVALID_INTROSET)
{ {}
}
else if (payload == PublishIntroMessage::EXPIRED) else if (payload == PublishIntroMessage::EXPIRED)
{ {}
}
else if (payload == PublishIntroMessage::INSUFFICIENT) else if (payload == PublishIntroMessage::INSUFFICIENT)
{ {}
}
else if (payload == PublishIntroMessage::INVALID_ORDER) else if (payload == PublishIntroMessage::INVALID_ORDER)
{ {}
}
} }
} }
void 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; ustring location;
uint64_t relay_order, is_relayed; uint64_t relay_order, is_relayed;
try try
{ {
oxenc::bt_dict_consumer btdc{m.body()}; oxenc::bt_dict_consumer btdc{body};
relay_order = btdc.require<uint64_t>("O"); relay_order = btdc.require<uint64_t>("O");
is_relayed = btdc.require<uint64_t>("R"); is_relayed = btdc.require<uint64_t>("R");
@ -1011,7 +1008,7 @@ namespace llarp
catch (const std::exception& e) catch (const std::exception& e)
{ {
log::warning(link_cat, "Exception: {}", e.what()); log::warning(link_cat, "Exception: {}", e.what());
m.respond(serialize_response({{"STATUS", FindIntroMessage::EXCEPTION}}), true); respond(serialize_response({{"STATUS", FindIntroMessage::EXCEPTION}}));
return; return;
} }
@ -1023,7 +1020,7 @@ namespace llarp
{ {
log::warning( log::warning(
link_cat, "Received FindIntroMessage with invalid relay order: {}", relay_order); 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; return;
} }
@ -1033,7 +1030,7 @@ namespace llarp
{ {
log::error( log::error(
link_cat, "Received FindIntroMessage but only know {} nodes", closest_rcs.size()); 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; return;
} }
@ -1046,7 +1043,7 @@ namespace llarp
peer_key, peer_key,
"find_intro", "find_intro",
FindIntroMessage::serialize(dht::Key_t{peer_key}, is_relayed, relay_order), 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) if (relay_response)
log::info( log::info(
link_cat, link_cat,
@ -1059,19 +1056,19 @@ namespace llarp
log::critical( log::critical(
link_cat, "Relayed FindIntroMessage failed! Notifying initial requester"); link_cat, "Relayed FindIntroMessage failed! Notifying initial requester");
original_msg.respond(relay_response.body_str(), not relay_response); respond(relay_response.body_str());
}); });
} }
else else
{ {
if (auto maybe_intro = _router.contacts()->get_introset_by_location(addr)) 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 else
{ {
log::warning( log::warning(
link_cat, link_cat,
"Received FindIntroMessage with relayed == false and no local introset entry"); "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}}));
} }
} }
} }
@ -1223,11 +1220,6 @@ namespace llarp
hop_info.upstream.from_string(upstream); 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)) if (_router.path_context().HasTransitHop(hop_info))
{ {
log::warning(link_cat, "Invalid PathID; PathIDs must be unique"); log::warning(link_cat, "Invalid PathID; PathIDs must be unique");
@ -1260,6 +1252,7 @@ namespace llarp
if (hop_info.upstream == _router.pubkey()) if (hop_info.upstream == _router.pubkey())
{ {
hop->terminal_hop = true;
// we are terminal hop and everything is okay // we are terminal hop and everything is okay
_router.path_context().PutTransitHop(hop); _router.path_context().PutTransitHop(hop);
m.respond(serialize_response({{"STATUS", PathBuildMessage::OK}}), false); m.respond(serialize_response({{"STATUS", PathBuildMessage::OK}}), false);
@ -1423,8 +1416,8 @@ namespace llarp
tx_id = btdc.require<std::string_view>("T"); tx_id = btdc.require<std::string_view>("T");
RouterID target{pubkey.data()}; RouterID target{pubkey.data()};
auto transit_hop = std::static_pointer_cast<path::TransitHop>( auto transit_hop =
_router.path_context().GetByUpstream(target, PathID_t{to_usv(tx_id).data()})); _router.path_context().GetTransitHop(target, PathID_t{to_usv(tx_id).data()});
const auto rx_id = transit_hop->info.rxID; const auto rx_id = transit_hop->info.rxID;
@ -1468,8 +1461,7 @@ namespace llarp
sig = to_usv(btlc.consume_string_view()); sig = to_usv(btlc.consume_string_view());
tx_id = btdc.require<std::string_view>("T"); tx_id = btdc.require<std::string_view>("T");
auto path_ptr = std::static_pointer_cast<path::Path>( auto path_ptr = _router.path_context().GetPath(PathID_t{to_usv(tx_id).data()});
_router.path_context().GetByDownstream(_router.pubkey(), PathID_t{to_usv(tx_id).data()}));
if (crypto::verify(_router.pubkey(), to_usv(dict_data), sig)) if (crypto::verify(_router.pubkey(), to_usv(dict_data), sig))
path_ptr->enable_exit_traffic(); path_ptr->enable_exit_traffic();
@ -1497,8 +1489,8 @@ namespace llarp
path_id = btdc.require<std::string_view>("P"); path_id = btdc.require<std::string_view>("P");
tx_id = btdc.require<std::string_view>("T"); tx_id = btdc.require<std::string_view>("T");
auto transit_hop = std::static_pointer_cast<path::TransitHop>( auto transit_hop =
_router.path_context().GetByUpstream(_router.pubkey(), PathID_t{to_usv(tx_id).data()})); _router.path_context().GetTransitHop(_router.pubkey(), PathID_t{to_usv(tx_id).data()});
if (auto exit_ep = if (auto exit_ep =
_router.exitContext().FindEndpointForPath(PathID_t{to_usv(path_id).data()})) _router.exitContext().FindEndpointForPath(PathID_t{to_usv(path_id).data()}))
@ -1545,8 +1537,7 @@ namespace llarp
sig = to_usv(btlc.consume_string_view()); sig = to_usv(btlc.consume_string_view());
tx_id = btdc.require<std::string_view>("T"); tx_id = btdc.require<std::string_view>("T");
auto path_ptr = std::static_pointer_cast<path::Path>( auto path_ptr = _router.path_context().GetPath(PathID_t{to_usv(tx_id).data()});
_router.path_context().GetByDownstream(_router.pubkey(), PathID_t{to_usv(tx_id).data()}));
if (crypto::verify(_router.pubkey(), to_usv(dict_data), sig)) if (crypto::verify(_router.pubkey(), to_usv(dict_data), sig))
{ {
@ -1556,8 +1547,7 @@ namespace llarp
// see Path::HandleUpdateExitVerifyMessage // see Path::HandleUpdateExitVerifyMessage
} }
else else
{ {}
}
} }
} }
catch (const std::exception& e) catch (const std::exception& e)
@ -1582,8 +1572,8 @@ namespace llarp
sig = to_usv(btlc.consume_string_view()); sig = to_usv(btlc.consume_string_view());
tx_id = btdc.require<std::string_view>("T"); tx_id = btdc.require<std::string_view>("T");
auto transit_hop = std::static_pointer_cast<path::TransitHop>( auto transit_hop =
_router.path_context().GetByUpstream(_router.pubkey(), PathID_t{to_usv(tx_id).data()})); _router.path_context().GetTransitHop(_router.pubkey(), PathID_t{to_usv(tx_id).data()});
const auto rx_id = transit_hop->info.rxID; const auto rx_id = transit_hop->info.rxID;
@ -1632,8 +1622,7 @@ namespace llarp
tx_id = btdc.require<std::string_view>("T"); tx_id = btdc.require<std::string_view>("T");
nonce = btdc.require<std::string_view>("Y"); nonce = btdc.require<std::string_view>("Y");
auto path_ptr = std::static_pointer_cast<path::Path>( auto path_ptr = _router.path_context().GetPath(PathID_t{to_usv(tx_id).data()});
_router.path_context().GetByDownstream(_router.pubkey(), PathID_t{to_usv(tx_id).data()}));
if (path_ptr->SupportsAnyRoles(path::ePathRoleExit | path::ePathRoleSVC) if (path_ptr->SupportsAnyRoles(path::ePathRoleExit | path::ePathRoleSVC)
and crypto::verify(_router.pubkey(), to_usv(dict_data), sig)) and crypto::verify(_router.pubkey(), to_usv(dict_data), sig))
@ -1647,16 +1636,51 @@ namespace llarp
} }
void 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!"); oxenc::bt_dict_consumer btdc{m.body()};
return; 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) catch (const std::exception& e)
{ {
log::warning(link_cat, "Exception: {}", e.what()); log::warning(link_cat, "Exception: {}", e.what());
@ -1664,6 +1688,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 void
LinkManager::handle_convo_intro(oxen::quic::message m) LinkManager::handle_convo_intro(oxen::quic::message m)
{ {

@ -4,6 +4,7 @@
#include <llarp/constants/path.hpp> #include <llarp/constants/path.hpp>
#include <llarp/crypto/crypto.hpp> #include <llarp/crypto/crypto.hpp>
#include <llarp/path/transit_hop.hpp>
#include <llarp/router/rc_lookup_handler.hpp> #include <llarp/router/rc_lookup_handler.hpp>
#include <llarp/router_contact.hpp> #include <llarp/router_contact.hpp>
#include <llarp/util/compare_ptr.hpp> #include <llarp/util/compare_ptr.hpp>
@ -171,8 +172,6 @@ namespace llarp
friend struct link::Endpoint; friend struct link::Endpoint;
std::atomic<bool> is_stopping; 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 // sessions to persist -> timestamp to end persist at
std::unordered_map<RouterID, llarp_time_t> persisting_conns; std::unordered_map<RouterID, llarp_time_t> persisting_conns;
@ -288,16 +287,22 @@ namespace llarp
private: private:
// DHT messages // DHT messages
void handle_find_name(oxen::quic::message); // relay void
void handle_find_intro(oxen::quic::message); // relay handle_find_name(std::string_view body, std::function<void(std::string)> respond); // relay
void handle_publish_intro(oxen::quic::message); // relay void
void handle_find_router(oxen::quic::message); // relay + path 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 // Path messages
void handle_path_build(oxen::quic::message, const RouterID& from); // relay void
void handle_path_confirm(oxen::quic::message); // relay handle_path_build(oxen::quic::message, const RouterID& from); // relay
void handle_path_latency(oxen::quic::message); // relay void handle_path_confirm(oxen::quic::message); // relay
void handle_path_transfer(oxen::quic::message); // relay void handle_path_latency(oxen::quic::message); // relay
void handle_path_transfer(oxen::quic::message); // relay
// Exit messages // Exit messages
void handle_obtain_exit(oxen::quic::message); // relay void handle_obtain_exit(oxen::quic::message); // relay
@ -307,11 +312,45 @@ namespace llarp
// Misc // Misc
void handle_convo_intro(oxen::quic::message); void handle_convo_intro(oxen::quic::message);
std::unordered_map<std::string, void (LinkManager::*)(oxen::quic::message)> rpc_commands = { // These requests come over a path (as a "path_control" request),
{"path_control", &LinkManager::handle_path_control}}; // 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 // 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 // DHT responses
void handle_find_name_response(oxen::quic::message); void handle_find_name_response(oxen::quic::message);

@ -54,7 +54,8 @@ namespace llarp
bool bool
RelayUpstreamMessage::handle_message(Router* r) const 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) if (path)
{ {
return path->HandleUpstream(llarp_buffer_t(enc), nonce, r); return path->HandleUpstream(llarp_buffer_t(enc), nonce, r);
@ -110,7 +111,8 @@ namespace llarp
bool bool
RelayDownstreamMessage::handle_message(Router* r) const 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) if (path)
{ {
return path->HandleDownstream(llarp_buffer_t(enc), nonce, r); return path->HandleDownstream(llarp_buffer_t(enc), nonce, r);

@ -4,6 +4,29 @@
namespace llarp::path 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 // handle data in upstream direction
bool bool
AbstractHopHandler::HandleUpstream(const llarp_buffer_t& X, const TunnelNonce& Y, Router* r) AbstractHopHandler::HandleUpstream(const llarp_buffer_t& X, const TunnelNonce& Y, Router* r)

@ -22,6 +22,14 @@ namespace llarp
namespace path 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 struct AbstractHopHandler
{ {
using TrafficEvent_t = std::pair<std::vector<byte_t>, TunnelNonce>; using TrafficEvent_t = std::pair<std::vector<byte_t>, TunnelNonce>;
@ -41,11 +49,17 @@ namespace llarp
virtual bool virtual bool
ExpiresSoon(llarp_time_t now, llarp_time_t dlt) const = 0; 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 virtual bool
send_path_control_message( send_path_control_message(
std::string method, std::string method, std::string body, std::function<void(std::string, bool)> func) = 0;
std::string body,
std::function<void(oxen::quic::message m)> func) = 0;
/// send routing message and increment sequence number /// send routing message and increment sequence number
virtual bool virtual bool

@ -8,6 +8,7 @@
namespace llarp::path namespace llarp::path
{ {
Path::Path( Path::Path(
Router* rtr, Router* rtr,
const std::vector<RemoteRC>& h, const std::vector<RemoteRC>& h,
@ -48,10 +49,7 @@ namespace llarp::path
bool bool
Path::obtain_exit( Path::obtain_exit(
SecretKey sk, SecretKey sk, uint64_t flag, std::string tx_id, std::function<void(std::string, bool)> func)
uint64_t flag,
std::string tx_id,
std::function<void(oxen::quic::message m)> func)
{ {
return send_path_control_message( return send_path_control_message(
"obtain_exit", "obtain_exit",
@ -60,7 +58,7 @@ namespace llarp::path
} }
bool 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( return send_path_control_message(
"close_exit", CloseExitMessage::sign_and_serialize(sk, std::move(tx_id)), std::move(func)); "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, const dht::Key_t& location,
bool is_relayed, bool is_relayed,
uint64_t order, uint64_t order,
std::function<void(oxen::quic::message m)> func) std::function<void(std::string, bool)> func)
{ {
return send_path_control_message( return send_path_control_message(
"find_intro", FindIntroMessage::serialize(location, is_relayed, order), std::move(func)); "find_intro", FindIntroMessage::serialize(location, is_relayed, order), std::move(func));
} }
bool 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( return send_path_control_message(
"find_name", FindNameMessage::serialize(std::move(name)), std::move(func)); "find_name", FindNameMessage::serialize(std::move(name)), std::move(func));
} }
bool 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( return send_path_control_message(
"find_router", FindRouterMessage::serialize(std::move(rid), false, false), std::move(func)); "find_router", FindRouterMessage::serialize(std::move(rid), false, false), std::move(func));
@ -93,44 +91,71 @@ namespace llarp::path
bool bool
Path::send_path_control_message( 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);
oxenc::bt_dict_producer btdp; auto payload = std::move(btdp).str();
btdp.append("BODY", body);
btdp.append("METHOD", method);
payload = std::move(btdp).str();
}
// TODO: old impl padded messages if smaller than a certain size; do we still want to? // TODO: old impl padded messages if smaller than a certain size; do we still want to?
TunnelNonce nonce; TunnelNonce nonce;
nonce.Randomize(); nonce.Randomize();
// chacha and mutate nonce for each hop
for (const auto& hop : hops) for (const auto& hop : hops)
{ {
// do a round of chacha for each hop and mutate the nonce with that hop's nonce nonce = crypto::onion(
crypto::xchacha20( reinterpret_cast<unsigned char*>(payload.data()),
reinterpret_cast<unsigned char*>(payload.data()), payload.size(), hop.shared, nonce); payload.size(),
hop.shared,
nonce ^= hop.nonceXOR; nonce,
hop.nonceXOR);
} }
oxenc::bt_dict_producer outer_dict; auto outer_payload = make_onion_payload(nonce, TXID(), payload);
outer_dict.append("NONCE", nonce.ToView());
outer_dict.append("PATHID", TXID().ToView());
outer_dict.append("PAYLOAD", payload);
return router.send_control_message( return router.send_control_message(
upstream(), upstream(),
"path_control", "path_control",
std::move(outer_dict).str(), std::move(outer_payload),
[response_cb = std::move(func)](oxen::quic::message m) { [response_cb = std::move(func), weak = weak_from_this()](oxen::quic::message m) {
if (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); Tick(llarp_time_t now, Router* r);
bool 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 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 bool
find_intro( find_intro(
const dht::Key_t& location, const dht::Key_t& location,
bool is_relayed = false, bool is_relayed = false,
uint64_t order = 0, uint64_t order = 0,
std::function<void(oxen::quic::message m)> func = nullptr); std::function<void(std::string, bool)> func = nullptr);
bool bool
close_exit( close_exit(
SecretKey sk, SecretKey sk, std::string tx_id, std::function<void(std::string, bool)> func = nullptr);
std::string tx_id,
std::function<void(oxen::quic::message m)> func = nullptr);
bool bool
obtain_exit( obtain_exit(
SecretKey sk, SecretKey sk,
uint64_t flag, uint64_t flag,
std::string tx_id, 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 bool
send_path_control_message( send_path_control_message(
std::string method, std::string method,
std::string body, std::string body,
std::function<void(oxen::quic::message m)> func = nullptr) override; std::function<void(std::string, bool)> func = nullptr) override;
bool bool
SendRoutingMessage(std::string payload, Router* r) override; SendRoutingMessage(std::string payload, Router* r) override;

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

@ -25,149 +25,130 @@ namespace llarp
struct TransitHop; struct TransitHop;
struct TransitHopInfo; 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 namespace llarp::path
void {
ExpirePaths(llarp_time_t now); using TransitHop_ptr = std::shared_ptr<TransitHop>;
void struct PathContext
PumpUpstream(); {
explicit PathContext(Router* router);
void /// called from router tick function
PumpDownstream(); void
ExpirePaths(llarp_time_t now);
void void
AllowTransit(); AllowTransit();
void void
RejectTransit(); RejectTransit();
bool bool
CheckPathLimitHitByIP(const IpAddress& ip); CheckPathLimitHitByIP(const IpAddress& ip);
bool bool
CheckPathLimitHitByIP(const std::string& ip); CheckPathLimitHitByIP(const std::string& ip);
bool bool
AllowingTransit() const; AllowingTransit() const;
bool bool
HasTransitHop(const TransitHopInfo& info); HasTransitHop(const TransitHopInfo& info);
void void
PutTransitHop(std::shared_ptr<TransitHop> hop); PutTransitHop(std::shared_ptr<TransitHop> hop);
HopHandler_ptr Path_ptr
GetByUpstream(const RouterID& id, const PathID_t& path); GetPath(const PathID_t& path_id);
bool bool
TransitHopPreviousIsRouter(const PathID_t& path, const RouterID& r); TransitHopPreviousIsRouter(const PathID_t& path, const RouterID& r);
TransitHop_ptr TransitHop_ptr
GetPathForTransfer(const PathID_t& topath); GetPathForTransfer(const PathID_t& topath);
HopHandler_ptr std::shared_ptr<TransitHop>
GetByDownstream(const RouterID& id, const PathID_t& path); GetTransitHop(const RouterID&, const PathID_t&);
std::optional<std::weak_ptr<TransitHop>> PathSet_ptr
TransitHopByInfo(const TransitHopInfo&); GetLocalPathSet(const PathID_t& id);
std::optional<std::weak_ptr<TransitHop>> using EndpointPathPtrSet = std::set<Path_ptr, ComparePtr<Path_ptr>>;
TransitHopByUpstream(const RouterID&, const PathID_t&); /// get a set of all paths that we own who's endpoint is r
EndpointPathPtrSet
FindOwnedPathsWithEndpoint(const RouterID& r);
PathSet_ptr bool
GetLocalPathSet(const PathID_t& id); HopIsUs(const RouterID& k) const;
using EndpointPathPtrSet = std::set<Path_ptr, ComparePtr<Path_ptr>>; void
/// get a set of all paths that we own who's endpoint is r AddOwnPath(PathSet_ptr set, Path_ptr p);
EndpointPathPtrSet
FindOwnedPathsWithEndpoint(const RouterID& r);
bool void
HopIsUs(const RouterID& k) const; RemovePathSet(PathSet_ptr set);
void const EventLoop_ptr&
AddOwnPath(PathSet_ptr set, Path_ptr p); loop();
void const SecretKey&
RemovePathSet(PathSet_ptr set); EncryptionSecretKey();
using TransitHopsMap_t = std::unordered_multimap<PathID_t, TransitHop_ptr>; const byte_t*
OurRouterID() const;
struct SyncTransitMap_t /// current number of transit paths we have
{ uint64_t
using Mutex_t = util::NullMutex; CurrentTransitPaths();
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;
}
private: /// current number of paths we created in status
Router* _router; uint64_t
SyncTransitMap_t m_TransitPaths; CurrentOwnedPaths(path::PathStatus status = path::PathStatus::ePathEstablished);
SyncOwnedPathsMap_t m_OurPaths;
bool m_AllowTransit; Router*
util::DecayingHashSet<IpAddress> path_limits; router() const
}; {
} // namespace path return _router;
} // namespace llarp }
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 "pathbuilder.hpp"
#include "path.hpp"
#include "path_context.hpp"
#include "path.hpp" #include "path.hpp"
#include "path_context.hpp" #include "path_context.hpp"
@ -8,7 +6,6 @@
#include <llarp/crypto/crypto.hpp> #include <llarp/crypto/crypto.hpp>
#include <llarp/link/link_manager.hpp> #include <llarp/link/link_manager.hpp>
#include <llarp/messages/path.hpp> #include <llarp/messages/path.hpp>
#include <llarp/nodedb.hpp> #include <llarp/nodedb.hpp>
#include <llarp/profiling.hpp> #include <llarp/profiling.hpp>
#include <llarp/router/rc_lookup_handler.hpp> #include <llarp/router/rc_lookup_handler.hpp>

@ -23,9 +23,37 @@ namespace llarp::path
m_DownstreamWorkCounter = 0; 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 bool
TransitHop::send_path_control_message( 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; return true;
} }

@ -61,6 +61,23 @@ namespace llarp
llarp_time_t lifetime = DEFAULT_LIFETIME; llarp_time_t lifetime = DEFAULT_LIFETIME;
llarp_proto_version_t version; llarp_proto_version_t version;
llarp_time_t m_LastActivity = 0s; 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 PathID_t
RXID() const override RXID() const override
@ -106,11 +123,24 @@ namespace llarp
return now >= ExpireTime() - dlt; 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 bool
send_path_control_message( send_path_control_message(
std::string method, std::string method,
std::string body, 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 // send routing message when end of path
bool 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(); auto& r = link_manager->router();
if (m) if (not success)
{
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 (callback) if (callback)
callback(rid, std::nullopt, false); callback(rid, std::nullopt, false);
else return;
link_manager->handle_find_router_error(std::move(m));
} }
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) if (!isServiceNode)
{ hidden_service_context->GetDefault()->lookup_router(rid, std::move(lookup_cb));
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);
} }
bool bool

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

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

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

@ -173,7 +173,7 @@ namespace llarp::service
for (const auto& path : paths) 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) if (marked_bad)
{ {
log::info(link_cat, "Outbound context has been marked bad (whatever that means)"); log::info(link_cat, "Outbound context has been marked bad (whatever that means)");
@ -182,43 +182,48 @@ namespace llarp::service
updatingIntroSet = false; 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 service::EncryptedIntroSet enc{introset};
{ const auto intro = enc.decrypt(PubKey{addr.as_array()});
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}; if (intro.time_signed == 0s)
const auto intro = enc.decrypt(PubKey{addr.as_array()}); {
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) // don't "shift" to the same intro we're already using...
{ if (current_intro == intro)
log::warning(link_cat, "{} recieved introset with zero timestamp"); return;
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;
}
current_intro = intro; if (intro.IsExpired(llarp::time_now_ms()))
ShiftIntroRouter(); {
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->msg.proto = ProtocolType::Auth;
ex->hook = [this, path, cb = std::move(func)](auto frame) mutable { 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 // TODO: revisit this
(void)resp;
(void)timeout;
ep.HandleHiddenServiceFrame(path, *frame.get()); ep.HandleHiddenServiceFrame(path, *frame.get());
}; };

Loading…
Cancel
Save