Merge pull request #2216 from tewinget/path-messages

Path build and onioned messages
This commit is contained in:
dr7ana 2023-11-27 09:23:16 -08:00 committed by GitHub
commit e58e8473f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 950 additions and 1838 deletions

View File

@ -12,7 +12,6 @@ endfunction()
lokinet_add_library(lokinet-cryptography
crypto/crypto.cpp
crypto/encrypted_frame.cpp
crypto/types.cpp
)
@ -207,7 +206,6 @@ lokinet_add_library(lokinet-config
# All path objects; link directly to lokinet-core
lokinet_add_library(lokinet-path
messages/relay.cpp
path/abstracthophandler.cpp
path/path.cpp
path/path_context.cpp

View File

@ -65,7 +65,7 @@ namespace llarp
static bool
dh_client_priv(
llarp::SharedSecret& shared, const PubKey& pk, const SecretKey& sk, const TunnelNonce& n)
llarp::SharedSecret& shared, const PubKey& pk, const SecretKey& sk, const SymmNonce& n)
{
llarp::SharedSecret dh_result;
@ -81,13 +81,13 @@ namespace llarp
static bool
dh_server_priv(
llarp::SharedSecret& shared, const PubKey& pk, const SecretKey& sk, const TunnelNonce& n)
llarp::SharedSecret& shared, const PubKey& pk, const SecretKey& sk, const SymmNonce& n)
{
llarp::SharedSecret dh_result;
if (dh(dh_result, pk, sk.toPublic(), pk.data(), sk))
{
return crypto_generichash_blake2b(shared.data(), 32, n.data(), 32, dh_result.data(), 32)
return crypto_generichash_blake2b(shared.data(), 32, n.data(), n.size(), dh_result.data(), 32)
!= -1;
}
@ -102,7 +102,7 @@ namespace llarp
if (dh(dh_result.data(), pk, sk, pk, sk))
{
return crypto_generichash_blake2b(shared, 32, nonce, 32, dh_result.data(), 32) != -1;
return crypto_generichash_blake2b(shared, 32, nonce, 24, dh_result.data(), 32) != -1;
}
llarp::LogWarn("crypto::dh_server - dh failed");
@ -143,7 +143,7 @@ namespace llarp
}
bool
crypto::xchacha20(uint8_t* buf, size_t size, const SharedSecret& k, const TunnelNonce& n)
crypto::xchacha20(uint8_t* buf, size_t size, const SharedSecret& k, const SymmNonce& n)
{
return xchacha20(buf, size, n.data(), k.data());
}
@ -154,16 +154,31 @@ 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
SymmNonce
crypto::onion(
unsigned char* buf,
size_t size,
const SharedSecret& k,
const SymmNonce& nonce,
const SymmNonce& 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)
llarp::SharedSecret& shared, const PubKey& pk, const SecretKey& sk, const SymmNonce& n)
{
return dh_client_priv(shared, pk, sk, n);
}
/// path dh relay side
bool
crypto::dh_server(
llarp::SharedSecret& shared, const PubKey& pk, const SecretKey& sk, const TunnelNonce& n)
llarp::SharedSecret& shared, const PubKey& pk, const SecretKey& sk, const SymmNonce& n)
{
return dh_server_priv(shared, pk, sk, n);
}
@ -177,20 +192,6 @@ namespace llarp
{
return dh_server_priv(shared_secret, other_pk, local_pk, nonce);
}
/// transport dh client side
bool
crypto::transport_dh_client(
llarp::SharedSecret& shared, const PubKey& pk, const SecretKey& sk, const TunnelNonce& n)
{
return dh_client_priv(shared, pk, sk, n);
}
/// transport dh server side
bool
crypto::transport_dh_server(
llarp::SharedSecret& shared, const PubKey& pk, const SecretKey& sk, const TunnelNonce& n)
{
return dh_server_priv(shared, pk, sk, n);
}
bool
crypto::shorthash(ShortHash& result, uint8_t* buf, size_t size)

View File

@ -23,28 +23,30 @@ namespace llarp
/// xchacha symmetric cipher
bool
xchacha20(uint8_t*, size_t size, const SharedSecret&, const TunnelNonce&);
xchacha20(uint8_t*, size_t size, const SharedSecret&, const SymmNonce&);
bool
xchacha20(uint8_t*, size_t size, const uint8_t*, const uint8_t*);
SymmNonce
onion(
unsigned char* buf,
size_t size,
const SharedSecret& k,
const SymmNonce& nonce,
const SymmNonce& xor_factor);
/// path dh creator's side
bool
dh_client(SharedSecret&, const PubKey&, const SecretKey&, const TunnelNonce&);
dh_client(SharedSecret&, const PubKey&, const SecretKey&, const SymmNonce&);
/// path dh relay side
bool
dh_server(SharedSecret&, const PubKey&, const SecretKey&, const TunnelNonce&);
dh_server(SharedSecret&, const PubKey&, const SecretKey&, const SymmNonce&);
bool
dh_server(
uint8_t* shared_secret,
const uint8_t* other_pk,
const uint8_t* local_pk,
const uint8_t* nonce);
/// transport dh client side
bool
transport_dh_client(SharedSecret&, const PubKey&, const SecretKey&, const TunnelNonce&);
/// transport dh server side
bool
transport_dh_server(SharedSecret&, const PubKey&, const SecretKey&, const TunnelNonce&);
/// blake2b 256 bit
bool
shorthash(ShortHash&, uint8_t*, size_t size);

View File

@ -1,127 +0,0 @@
#include "encrypted_frame.hpp"
#include "crypto.hpp"
#include <llarp/util/logging.hpp>
namespace llarp
{
bool
EncryptedFrame::DoEncrypt(const SharedSecret& shared, bool noDH)
{
uint8_t* hash_ptr = data();
uint8_t* nonce_ptr = hash_ptr + SHORTHASHSIZE;
uint8_t* pubkey_ptr = nonce_ptr + TUNNONCESIZE;
uint8_t* body_ptr = pubkey_ptr + PUBKEYSIZE;
if (noDH)
{
crypto::randbytes(nonce_ptr, TUNNONCESIZE);
crypto::randbytes(pubkey_ptr, PUBKEYSIZE);
}
TunnelNonce nonce(nonce_ptr);
// encrypt body
if (!crypto::xchacha20(body_ptr, size() - EncryptedFrameOverheadSize, shared, nonce))
{
llarp::LogError("encrypt failed");
return false;
}
if (!crypto::hmac(hash_ptr, nonce_ptr, size() - SHORTHASHSIZE, shared))
{
llarp::LogError("Failed to generate message auth");
return false;
}
return true;
}
bool
EncryptedFrame::EncryptInPlace(const SecretKey& ourSecretKey, const PubKey& otherPubkey)
{
// format of frame is
// <32 bytes keyed hash of following data>
// <32 bytes nonce>
// <32 bytes pubkey>
// <N bytes encrypted payload>
//
byte_t* hash = data();
byte_t* noncePtr = hash + SHORTHASHSIZE;
byte_t* pubkey = noncePtr + TUNNONCESIZE;
SharedSecret shared;
// set our pubkey
memcpy(pubkey, ourSecretKey.toPublic().data(), PUBKEYSIZE);
// randomize nonce
crypto::randbytes(noncePtr, TUNNONCESIZE);
TunnelNonce nonce(noncePtr);
// derive shared key
if (!crypto::dh_client(shared, otherPubkey, ourSecretKey, nonce))
{
llarp::LogError("DH failed");
return false;
}
return DoEncrypt(shared, false);
}
bool
EncryptedFrame::DoDecrypt(const SharedSecret& shared)
{
uint8_t* hash_ptr = data();
uint8_t* nonce_ptr = hash_ptr + SHORTHASHSIZE;
uint8_t* body_ptr = hash_ptr + EncryptedFrameOverheadSize;
TunnelNonce nonce(nonce_ptr);
ShortHash digest;
if (!crypto::hmac(digest.data(), nonce_ptr, size() - SHORTHASHSIZE, shared))
{
llarp::LogError("Digest failed");
return false;
}
if (!std::equal(digest.begin(), digest.end(), hash_ptr))
{
llarp::LogError("message authentication failed");
return false;
}
if (!crypto::xchacha20(body_ptr, size() - EncryptedFrameOverheadSize, shared, nonce))
{
llarp::LogError("decrypt failed");
return false;
}
return true;
}
bool
EncryptedFrame::DecryptInPlace(const SecretKey& ourSecretKey)
{
// format of frame is
// <32 bytes keyed hash of following data>
// <32 bytes nonce>
// <32 bytes pubkey>
// <N bytes encrypted payload>
//
byte_t* noncePtr = data() + SHORTHASHSIZE;
TunnelNonce nonce(noncePtr);
PubKey otherPubkey(noncePtr + TUNNONCESIZE);
SharedSecret shared;
// use dh_server because we are not the creator of this message
if (!crypto::dh_server(shared, otherPubkey, ourSecretKey, nonce))
{
llarp::LogError("DH failed");
return false;
}
return DoDecrypt(shared);
}
} // namespace llarp

View File

@ -1,87 +0,0 @@
#pragma once
#include "encrypted.hpp"
#include "types.hpp"
#include <llarp/util/buffer.hpp>
#include <llarp/util/mem.h>
#include <utility>
namespace llarp
{
static constexpr size_t EncryptedFrameOverheadSize = PUBKEYSIZE + TUNNONCESIZE + SHORTHASHSIZE;
static constexpr size_t EncryptedFrameBodySize = 128 * 6;
static constexpr size_t EncryptedFrameSize = EncryptedFrameOverheadSize + EncryptedFrameBodySize;
struct EncryptedFrame : public Encrypted<EncryptedFrameSize>
{
EncryptedFrame() : EncryptedFrame(EncryptedFrameBodySize)
{}
EncryptedFrame(size_t sz)
: Encrypted<EncryptedFrameSize>(
std::min(sz, EncryptedFrameBodySize) + EncryptedFrameOverheadSize)
{}
void
Resize(size_t sz)
{
if (sz <= EncryptedFrameSize)
{
_sz = sz;
UpdateBuffer();
}
}
bool
DoEncrypt(const SharedSecret& shared, bool noDH = false);
bool
DecryptInPlace(const SecretKey& seckey);
bool
DoDecrypt(const SharedSecret& shared);
bool
EncryptInPlace(const SecretKey& seckey, const PubKey& other);
};
template <typename User>
struct AsyncFrameDecrypter
{
using User_ptr = std::shared_ptr<User>;
using DecryptHandler = std::function<void(llarp_buffer_t*, User_ptr)>;
void
Decrypt(User_ptr user)
{
if (target.DecryptInPlace(seckey))
{
auto buf = target.Buffer();
buf->cur = buf->base + EncryptedFrameOverheadSize;
result(buf, user);
}
else
result(nullptr, user);
}
AsyncFrameDecrypter(const SecretKey& secretkey, DecryptHandler h)
: result(std::move(h)), seckey(secretkey)
{}
DecryptHandler result;
const SecretKey& seckey;
EncryptedFrame target;
using WorkFunc_t = std::function<void(void)>;
using WorkerFunction_t = std::function<void(WorkFunc_t)>;
void
AsyncDecrypt(const EncryptedFrame& frame, User_ptr u, WorkerFunction_t worker)
{
target = frame;
worker([this, u = std::move(u)]() mutable { Decrypt(std::move(u)); });
}
};
} // namespace llarp

View File

@ -164,10 +164,6 @@ namespace llarp
/// PKE(result, publickey, secretkey, nonce)
using path_dh_func = bool (*)(SharedSecret&, const PubKey&, const SecretKey&, const TunnelNonce&);
/// TKE(result, publickey, secretkey, nonce)
using transport_dh_func =
bool (*)(SharedSecret&, const PubKey&, const SecretKey&, const TunnelNonce&);
/// SH(result, body)
using shorthash_func = bool (*)(ShortHash&, const llarp_buffer_t&);
} // namespace llarp

View File

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

View File

@ -2,7 +2,6 @@
#include "ev.hpp"
#include "udp_handle.hpp"
#include <llarp/util/meta/memfn.hpp>
#include <llarp/util/thread/queue.hpp>
// #include <uvw.hpp>

View File

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

View File

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

View File

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

View File

@ -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;
}
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();
msg.AddMXReply(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;
}

View File

@ -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>
@ -123,6 +124,8 @@ namespace llarp
} // namespace link
using messages::serialize_response;
void
LinkManager::for_each_connection(std::function<void(link::Connection&)> func)
{
@ -135,13 +138,31 @@ namespace llarp
void
LinkManager::register_commands(std::shared_ptr<oxen::quic::BTRequestStream>& s)
{
for (const auto& [name, func] : rpc_commands)
assert(ep.connid_map.count(s->conn_id()));
const RouterID& rid = ep.connid_map[s->conn_id()];
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(name, [this, f = func](oxen::quic::message m) {
_router.loop()->call([this, func = f, msg = std::move(m)]() mutable {
std::invoke(func, this, std::move(msg));
});
});
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));
});
});
}
}
@ -194,12 +215,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)
{
@ -416,8 +432,6 @@ namespace llarp
return;
}
util::Lock l(m);
LogInfo("stopping links");
is_stopping = true;
@ -430,8 +444,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))
{
@ -513,29 +525,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(messages::ERROR_RESPONSE);
}
_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({{messages::STATUS_KEY, FindNameMessage::NOT_FOUND}}));
});
}
@ -553,7 +566,7 @@ namespace llarp
try
{
oxenc::bt_dict_consumer btdc{m.body()};
payload = btdc.require<std::string>(m ? "NAME" : "STATUS");
payload = btdc.require<std::string>(m ? "NAME" : messages::STATUS_KEY);
}
catch (const std::exception& e)
{
@ -567,7 +580,7 @@ namespace llarp
}
else
{
if (payload == FindNameMessage::EXCEPTION)
if (payload == "ERROR")
{
log::info(link_cat, "FindNameMessage failed with unkown error!");
@ -583,17 +596,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");
@ -602,8 +613,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(messages::ERROR_RESPONSE);
return;
}
@ -633,9 +643,8 @@ namespace llarp
neighbors += rid.bt_encode();
}
m.respond(
serialize_response({{"STATUS", FindRouterMessage::RETRY_EXP}, {"TARGET", neighbors}}),
true);
respond(serialize_response(
{{messages::STATUS_KEY, FindRouterMessage::RETRY_EXP}, {"TARGET", neighbors}}));
}
else
{
@ -651,13 +660,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)
@ -668,26 +677,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(
{{messages::STATUS_KEY, 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(
{{messages::STATUS_KEY, FindRouterMessage::RETRY_NEW},
{"TARGET", reinterpret_cast<const char*>(closest_rid.data())}}));
}
}
}
@ -727,7 +732,7 @@ namespace llarp
}
else
{
if (status == FindRouterMessage::EXCEPTION)
if (status == "ERROR")
{
log::info(link_cat, "FindRouterMessage failed with remote exception!");
// Do something smart here probably
@ -781,7 +786,7 @@ namespace llarp
return;
}
if (status == FindRouterMessage::EXCEPTION)
if (status == "ERROR")
{
log::info(link_cat, "FindRouterMessage failed with remote exception!");
// Do something smart here probably
@ -811,7 +816,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;
@ -819,7 +824,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");
@ -836,7 +841,7 @@ namespace llarp
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
m.respond(serialize_response({{"STATUS", PublishIntroMessage::EXCEPTION}}), true);
respond(messages::ERROR_RESPONSE);
return;
}
@ -847,14 +852,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({{messages::STATUS_KEY, 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({{messages::STATUS_KEY, PublishIntroMessage::EXPIRED}}));
return;
}
@ -864,7 +869,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({{messages::STATUS_KEY, PublishIntroMessage::INSUFFICIENT}}));
return;
}
@ -876,7 +881,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({{messages::STATUS_KEY, PublishIntroMessage::INVALID_ORDER}}));
return;
}
@ -893,7 +898,7 @@ namespace llarp
relay_order);
_router.contacts()->services()->PutNode(dht::ISNode{std::move(enc)});
m.respond(serialize_response({{"STATUS", ""}}));
respond(serialize_response({{messages::STATUS_KEY, ""}}));
}
else
{
@ -903,7 +908,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;
@ -926,7 +936,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({{messages::STATUS_KEY, ""}}));
}
else
log::warning(
@ -949,7 +959,7 @@ namespace llarp
try
{
oxenc::bt_dict_consumer btdc{m.body()};
payload = btdc.require<std::string>("STATUS");
payload = btdc.require<std::string>(messages::STATUS_KEY);
}
catch (const std::exception& e)
{
@ -963,7 +973,7 @@ namespace llarp
}
else
{
if (payload == PublishIntroMessage::EXCEPTION)
if (payload == "ERROR")
{
log::info(link_cat, "PublishIntroMessage failed with remote exception!");
// Do something smart here probably
@ -984,14 +994,14 @@ namespace llarp
}
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");
@ -1000,7 +1010,7 @@ namespace llarp
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
m.respond(serialize_response({{"STATUS", FindIntroMessage::EXCEPTION}}), true);
respond(messages::ERROR_RESPONSE);
return;
}
@ -1012,7 +1022,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({{messages::STATUS_KEY, FindIntroMessage::INVALID_ORDER}}));
return;
}
@ -1022,7 +1032,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({{messages::STATUS_KEY, FindIntroMessage::INSUFFICIENT_NODES}}));
return;
}
@ -1035,7 +1045,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,
@ -1048,19 +1058,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({{messages::STATUS_KEY, FindIntroMessage::NOT_FOUND}}));
}
}
}
@ -1079,7 +1089,7 @@ namespace llarp
try
{
oxenc::bt_dict_consumer btdc{m.body()};
payload = btdc.require<std::string>((m) ? "INTROSET" : "STATUS");
payload = btdc.require<std::string>((m) ? "INTROSET" : messages::STATUS_KEY);
}
catch (const std::exception& e)
{
@ -1101,12 +1111,12 @@ namespace llarp
}
void
LinkManager::handle_path_build(oxen::quic::message m)
LinkManager::handle_path_build(oxen::quic::message m, const RouterID& from)
{
if (!_router.path_context().AllowingTransit())
{
log::warning(link_cat, "got path build request when not permitting transit");
m.respond(serialize_response({{"STATUS", PathBuildMessage::NO_TRANSIT}}), true);
m.respond(serialize_response({{messages::STATUS_KEY, PathBuildMessage::NO_TRANSIT}}), true);
return;
}
try
@ -1136,7 +1146,8 @@ namespace llarp
shared.data(), other_pubkey.data(), _router.pubkey(), inner_nonce.data()))
{
log::info(link_cat, "DH server initialization failed during path build");
m.respond(serialize_response({{"STATUS", PathBuildMessage::BAD_CRYPTO}}), true);
m.respond(
serialize_response({{messages::STATUS_KEY, PathBuildMessage::BAD_CRYPTO}}), true);
return;
}
@ -1149,14 +1160,16 @@ namespace llarp
shared))
{
log::error(link_cat, "HMAC failed on path build request");
m.respond(serialize_response({{"STATUS", PathBuildMessage::BAD_CRYPTO}}), true);
m.respond(
serialize_response({{messages::STATUS_KEY, PathBuildMessage::BAD_CRYPTO}}), true);
return;
}
if (!std::equal(
digest.begin(), digest.end(), reinterpret_cast<const unsigned char*>(hash.data())))
{
log::info(link_cat, "HMAC mismatch on path build request");
m.respond(serialize_response({{"STATUS", PathBuildMessage::BAD_CRYPTO}}), true);
m.respond(
serialize_response({{messages::STATUS_KEY, PathBuildMessage::BAD_CRYPTO}}), true);
return;
}
@ -1168,7 +1181,8 @@ namespace llarp
outer_nonce.data()))
{
log::info(link_cat, "Decrypt failed on path build request");
m.respond(serialize_response({{"STATUS", PathBuildMessage::BAD_CRYPTO}}), true);
m.respond(
serialize_response({{messages::STATUS_KEY, PathBuildMessage::BAD_CRYPTO}}), true);
return;
}
@ -1189,17 +1203,14 @@ namespace llarp
if (frame.empty())
{
log::info(link_cat, "Path build request received invalid frame");
m.respond(serialize_response({{"STATUS", PathBuildMessage::BAD_FRAMES}}), true);
m.respond(serialize_response({{messages::STATUS_KEY, PathBuildMessage::BAD_FRAMES}}), true);
return;
}
// populate transit hop object with hop info
// TODO: how to get downstream hop RouterID from here (all we have is oxen::quic::message)
// could do message->btstream->stream->connection_interface->connectionid
// and check our mapping, but that feels ugly as sin (and message->stream is private)
// TODO: also need downstream for IP / path build limiting clients
// TODO: IP / path build limiting clients
auto hop = std::make_shared<path::TransitHop>();
// hop->info.downstream = m.from(); // TODO: RouterID m.from() or similar
hop->info.downstream = from;
// extract pathIDs and check if zero or used
auto& hop_info = hop->info;
@ -1209,22 +1220,16 @@ namespace llarp
if (hop_info.txID.IsZero() || hop_info.rxID.IsZero())
{
log::warning(link_cat, "Invalid PathID; PathIDs must be non-zero");
m.respond(serialize_response({{"STATUS", PathBuildMessage::BAD_PATHID}}), true);
m.respond(serialize_response({{messages::STATUS_KEY, PathBuildMessage::BAD_PATHID}}), true);
return;
}
hop_info.upstream.from_string(upstream);
// TODO: need downstream (above), and also 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 (nevermind that messages don't currently know the RouterID
// they came from).
if (_router.path_context().HasTransitHop(hop_info))
{
log::warning(link_cat, "Invalid PathID; PathIDs must be unique");
m.respond(serialize_response({{"STATUS", PathBuildMessage::BAD_PATHID}}), true);
m.respond(serialize_response({{messages::STATUS_KEY, PathBuildMessage::BAD_PATHID}}), true);
return;
}
@ -1232,11 +1237,13 @@ namespace llarp
hop->pathKey.data(), other_pubkey.data(), _router.pubkey(), inner_nonce.data()))
{
log::warning(link_cat, "DH failed during path build.");
m.respond(serialize_response({{"STATUS", PathBuildMessage::BAD_CRYPTO}}), true);
m.respond(serialize_response({{messages::STATUS_KEY, PathBuildMessage::BAD_CRYPTO}}), true);
return;
}
// generate hash of hop key for nonce mutation
crypto::shorthash(hop->nonceXOR, hop->pathKey.data(), hop->pathKey.size());
ShortHash xor_hash;
crypto::shorthash(xor_hash, hop->pathKey.data(), hop->pathKey.size());
hop->nonceXOR = xor_hash.data(); // nonceXOR is 24 bytes, ShortHash is 32; this will truncate
// set and check path lifetime
hop->lifetime = 1ms * lifetime;
@ -1244,7 +1251,8 @@ namespace llarp
if (hop->lifetime >= path::DEFAULT_LIFETIME)
{
log::warning(link_cat, "Path build attempt with too long of a lifetime.");
m.respond(serialize_response({{"STATUS", PathBuildMessage::BAD_LIFETIME}}), true);
m.respond(
serialize_response({{messages::STATUS_KEY, PathBuildMessage::BAD_LIFETIME}}), true);
return;
}
@ -1253,9 +1261,10 @@ 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);
m.respond(messages::OK_RESPONSE, false);
return;
}
@ -1287,52 +1296,7 @@ namespace llarp
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
m.respond(serialize_response({{"STATUS", PathBuildMessage::EXCEPTION}}), true);
return;
}
}
void
LinkManager::handle_path_build_response(oxen::quic::message m)
{
try
{
oxenc::bt_dict_consumer btdc{m.body()};
}
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
// m.respond(serialize_response({{"STATUS", "EXCEPTION"}}), true);
return;
}
}
void
LinkManager::handle_path_confirm(oxen::quic::message m)
{
try
{
oxenc::bt_dict_consumer btdc{m.body()};
}
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
m.respond(serialize_response({{"STATUS", "EXCEPTION"}}), true);
return;
}
}
void
LinkManager::handle_path_confirm_response(oxen::quic::message m)
{
try
{
oxenc::bt_dict_consumer btdc{m.body()};
}
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
// m.respond(serialize_response({{"STATUS", "EXCEPTION"}}), true);
m.respond(messages::ERROR_RESPONSE, true);
return;
}
}
@ -1347,7 +1311,7 @@ namespace llarp
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
m.respond(serialize_response({{"STATUS", "EXCEPTION"}}), true);
m.respond(messages::ERROR_RESPONSE, true);
return;
}
}
@ -1362,7 +1326,7 @@ namespace llarp
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
// m.respond(serialize_response({{"STATUS", "EXCEPTION"}}), true);
// m.respond(serialize_response({{messages::STATUS_KEY, "EXCEPTION"}}), true);
return;
}
}
@ -1377,7 +1341,7 @@ namespace llarp
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
m.respond(serialize_response({{"STATUS", "EXCEPTION"}}), true);
m.respond(messages::ERROR_RESPONSE, true);
return;
}
}
@ -1392,7 +1356,7 @@ namespace llarp
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
m.respond(serialize_response({{"STATUS", "EXCEPTION"}}), true);
m.respond(messages::ERROR_RESPONSE, true);
return;
}
}
@ -1416,8 +1380,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;
@ -1431,7 +1395,7 @@ namespace llarp
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
m.respond(serialize_response({{"STATUS", ObtainExitMessage::EXCEPTION}}), true);
m.respond(messages::ERROR_RESPONSE, true);
throw;
}
}
@ -1461,8 +1425,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();
@ -1490,8 +1453,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()}))
@ -1500,7 +1463,9 @@ namespace llarp
{
(exit_ep->UpdateLocalPath(transit_hop->info.rxID))
? m.respond(UpdateExitMessage::sign_and_serialize_response(_router.identity(), tx_id))
: m.respond(serialize_response({{"STATUS", UpdateExitMessage::UPDATE_FAILED}}), true);
: m.respond(
serialize_response({{messages::STATUS_KEY, UpdateExitMessage::UPDATE_FAILED}}),
true);
}
// If we fail to verify the message, no-op
}
@ -1508,7 +1473,7 @@ namespace llarp
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
m.respond(serialize_response({{"STATUS", UpdateExitMessage::EXCEPTION}}), true);
m.respond(messages::ERROR_RESPONSE, true);
return;
}
}
@ -1538,8 +1503,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))
{
@ -1574,8 +1538,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;
@ -1588,12 +1552,13 @@ namespace llarp
}
}
m.respond(serialize_response({{"STATUS", CloseExitMessage::UPDATE_FAILED}}), true);
m.respond(
serialize_response({{messages::STATUS_KEY, CloseExitMessage::UPDATE_FAILED}}), true);
}
catch (const std::exception& e)
{
log::warning(link_cat, "Exception: {}", e.what());
m.respond(serialize_response({{"STATUS", CloseExitMessage::EXCEPTION}}), true);
m.respond(messages::ERROR_RESPONSE, true);
return;
}
}
@ -1624,8 +1589,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))
@ -1639,16 +1603,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)
{
log::info(link_cat, "Path control message timed out!");
return;
}
try
{}
{
oxenc::bt_dict_consumer btdc{m.body()};
auto nonce = SymmNonce{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;
// 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 = SymmNonce{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());
@ -1656,6 +1655,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)
{

View File

@ -4,6 +4,8 @@
#include <llarp/constants/path.hpp>
#include <llarp/crypto/crypto.hpp>
#include <llarp/messages/common.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>
@ -26,12 +28,6 @@ namespace llarp
{
struct LinkManager;
inline std::string
serialize_response(oxenc::bt_dict supplement = {})
{
return oxenc::bt_serialize(supplement);
}
namespace link
{
struct Connection;
@ -171,8 +167,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;
@ -288,18 +282,21 @@ 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); // 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_relay_commit(oxen::quic::message); // relay
void handle_relay_status(oxen::quic::message); // relay
void
handle_path_build(oxen::quic::message, const RouterID& from); // 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
@ -309,21 +306,45 @@ namespace llarp
// Misc
void handle_convo_intro(oxen::quic::message);
std::unordered_map<std::string, void (LinkManager::*)(oxen::quic::message)> rpc_commands = {
{"find_name", &LinkManager::handle_find_name},
{"find_router", &LinkManager::handle_find_router},
{"publish_intro", &LinkManager::handle_publish_intro},
{"find_intro", &LinkManager::handle_find_intro},
{"path_build", &LinkManager::handle_path_build},
{"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 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);
@ -332,10 +353,6 @@ namespace llarp
void handle_find_router_response(oxen::quic::message);
// Path responses
void handle_path_build_response(oxen::quic::message);
void handle_relay_commit_response(oxen::quic::message);
void handle_relay_status_response(oxen::quic::message);
void handle_path_confirm_response(oxen::quic::message);
void handle_path_latency_response(oxen::quic::message);
void handle_path_transfer_response(oxen::quic::message);

View File

@ -18,6 +18,22 @@ namespace
namespace llarp
{
namespace messages
{
inline std::string
serialize_response(oxenc::bt_dict supplement = {})
{
return oxenc::bt_serialize(supplement);
}
// ideally STATUS is the first key in a bt-dict, so use a single, early ascii char
inline const auto STATUS_KEY = "!"s;
inline const auto TIMEOUT_RESPONSE = serialize_response({{STATUS_KEY, "TIMEOUT"}});
inline const auto ERROR_RESPONSE = serialize_response({{STATUS_KEY, "ERROR"}});
inline const auto OK_RESPONSE = serialize_response({{STATUS_KEY, "OK"}});
} // namespace messages
/// abstract base class for serialized messages
struct AbstractSerializable
{

View File

@ -6,7 +6,6 @@ namespace llarp
{
namespace FindRouterMessage
{
inline auto EXCEPTION = "EXCEPTION"sv;
inline auto RETRY_EXP = "RETRY AS EXPLORATORY"sv;
inline auto RETRY_ITER = "RETRY AS ITERATIVE"sv;
inline auto RETRY_NEW = "RETRY WITH NEW RECIPIENT"sv;
@ -52,9 +51,7 @@ namespace llarp
namespace FindIntroMessage
{
inline auto EXCEPTION = "EXCEPTION"sv;
inline auto NOT_FOUND = "NOT FOUND"sv;
inline auto TIMED_OUT = "TIMED OUT"sv;
inline auto INVALID_ORDER = "INVALID ORDER"sv;
inline auto INSUFFICIENT_NODES = "INSUFFICIENT NODES"sv;
@ -80,7 +77,6 @@ namespace llarp
namespace FindNameMessage
{
inline auto EXCEPTION = "EXCEPTION"sv;
inline auto NOT_FOUND = "NOT FOUND"sv;
inline static std::string
@ -120,7 +116,6 @@ namespace llarp
namespace PublishIntroMessage
{
inline auto EXCEPTION = "EXCEPTION"sv;
inline auto INVALID_INTROSET = "INVALID INTROSET"sv;
inline auto EXPIRED = "EXPIRED INTROSET"sv;
inline auto INSUFFICIENT = "INSUFFICIENT NODES"sv;

View File

@ -12,7 +12,6 @@ namespace llarp
namespace ObtainExitMessage
{
inline auto EXCEPTION = "EXCEPTION"sv;
// flag: 0 = Exit, 1 = Snode
inline std::string
@ -63,7 +62,6 @@ namespace llarp
namespace UpdateExitMessage
{
inline auto EXCEPTION = "EXCEPTION"sv;
inline auto UPDATE_FAILED = "EXIT UPDATE FAILED"sv;
inline std::string
@ -113,7 +111,6 @@ namespace llarp
namespace CloseExitMessage
{
inline auto EXCEPTION = "EXCEPTION"sv;
inline auto UPDATE_FAILED = "CLOSE EXIT FAILED"sv;
inline std::string

View File

@ -6,8 +6,6 @@ namespace llarp
{
namespace PathBuildMessage
{
inline auto OK = "OK"sv;
inline auto EXCEPTION = "EXCEPTION"sv;
inline auto BAD_FRAMES = "BAD_FRAMES"sv;
inline auto BAD_CRYPTO = "BAD_CRYPTO"sv;
inline auto NO_TRANSIT = "NOT ALLOWING TRANSIT"sv;
@ -29,7 +27,9 @@ namespace llarp
throw std::runtime_error{std::move(err)};
}
// generate nonceXOR value self->hop->pathKey
crypto::shorthash(hop.nonceXOR, hop.shared.data(), hop.shared.size());
ShortHash hash;
crypto::shorthash(hash, hop.shared.data(), hop.shared.size());
hop.nonceXOR = hash.data(); // nonceXOR is 24 bytes, ShortHash is 32; this will truncate
hop.upstream = nextHop;
}
@ -56,7 +56,7 @@ namespace llarp
crypto::encryption_keygen(framekey);
SharedSecret shared;
TunnelNonce outer_nonce;
SymmNonce outer_nonce;
outer_nonce.Randomize();
// derive (outer) shared key

View File

@ -1,121 +0,0 @@
#include "relay.hpp"
#include <llarp/path/path_context.hpp>
#include <llarp/router/router.hpp>
#include <llarp/util/bencode.hpp>
namespace llarp
{
void
RelayUpstreamMessage::clear()
{
pathid.Zero();
enc.Clear();
nonce.Zero();
version = 0;
}
std::string
RelayUpstreamMessage::bt_encode() const
{
oxenc::bt_dict_producer btdp;
try
{
btdp.append("a", "u");
btdp.append("p", pathid.ToView());
btdp.append("v", llarp::constants::proto_version);
btdp.append("x", std::string_view{reinterpret_cast<const char*>(enc.data()), enc.size()});
btdp.append("y", std::string_view{reinterpret_cast<const char*>(nonce.data()), nonce.size()});
}
catch (...)
{
log::critical(link_cat, "Error: RelayUpstreamMessage failed to bt encode contents!");
}
return std::move(btdp).str();
}
bool
RelayUpstreamMessage::decode_key(const llarp_buffer_t& key, llarp_buffer_t* buf)
{
bool read = false;
if (!BEncodeMaybeReadDictEntry("p", pathid, read, key, buf))
return false;
if (!BEncodeMaybeVerifyVersion("v", version, llarp::constants::proto_version, read, key, buf))
return false;
if (!BEncodeMaybeReadDictEntry("x", enc, read, key, buf))
return false;
if (!BEncodeMaybeReadDictEntry("y", nonce, read, key, buf))
return false;
return read;
}
bool
RelayUpstreamMessage::handle_message(Router* r) const
{
auto path = r->path_context().GetByDownstream(conn->remote_rc.router_id(), pathid);
if (path)
{
return path->HandleUpstream(llarp_buffer_t(enc), nonce, r);
}
return false;
}
void
RelayDownstreamMessage::clear()
{
pathid.Zero();
enc.Clear();
nonce.Zero();
version = 0;
}
std::string
RelayDownstreamMessage::bt_encode() const
{
oxenc::bt_dict_producer btdp;
try
{
btdp.append("a", "d");
btdp.append("p", pathid.ToView());
btdp.append("v", llarp::constants::proto_version);
btdp.append("x", std::string_view{reinterpret_cast<const char*>(enc.data()), enc.size()});
btdp.append("y", std::string_view{reinterpret_cast<const char*>(nonce.data()), nonce.size()});
}
catch (...)
{
log::critical(link_cat, "Error: RelayDownstreamMessage failed to bt encode contents!");
}
return std::move(btdp).str();
}
bool
RelayDownstreamMessage::decode_key(const llarp_buffer_t& key, llarp_buffer_t* buf)
{
bool read = false;
if (!BEncodeMaybeReadDictEntry("p", pathid, read, key, buf))
return false;
if (!BEncodeMaybeVerifyVersion("v", version, llarp::constants::proto_version, read, key, buf))
return false;
if (!BEncodeMaybeReadDictEntry("x", enc, read, key, buf))
return false;
if (!BEncodeMaybeReadDictEntry("y", nonce, read, key, buf))
return false;
return read;
}
bool
RelayDownstreamMessage::handle_message(Router* r) const
{
auto path = r->path_context().GetByUpstream(conn->remote_rc.router_id(), pathid);
if (path)
{
return path->HandleDownstream(llarp_buffer_t(enc), nonce, r);
}
llarp::LogWarn("no path for downstream message id=", pathid);
return false;
}
} // namespace llarp

View File

@ -1,75 +0,0 @@
#pragma once
#include "link_message.hpp"
#include <llarp/crypto/encrypted.hpp>
#include <llarp/crypto/types.hpp>
#include <llarp/path/path_types.hpp>
#include <vector>
namespace llarp
{
/*
Data messages to be sent via quic datagrams
*/
struct RelayUpstreamMessage final : public AbstractLinkMessage
{
Encrypted<MAX_LINK_MSG_SIZE - 128> enc;
TunnelNonce nonce;
bool
decode_key(const llarp_buffer_t& key, llarp_buffer_t* buf) override;
std::string
bt_encode() const override;
bool
handle_message(Router* router) const override;
void
clear() override;
const char*
name() const override
{
return "RelayUpstream";
}
uint16_t
priority() const override
{
return 0;
}
};
struct RelayDownstreamMessage final : public AbstractLinkMessage
{
Encrypted<MAX_LINK_MSG_SIZE - 128> enc;
TunnelNonce nonce;
bool
decode_key(const llarp_buffer_t& key, llarp_buffer_t* buf) override;
std::string
bt_encode() const override;
bool
handle_message(Router* router) const override;
void
clear() override;
const char*
name() const override
{
return "RelayDownstream";
}
uint16_t
priority() const override
{
return 0;
}
};
} // namespace llarp

View File

@ -4,34 +4,26 @@
namespace llarp::path
{
// handle data in upstream direction
bool
AbstractHopHandler::HandleUpstream(const llarp_buffer_t& X, const TunnelNonce& Y, Router* r)
std::string
make_onion_payload(
const SymmNonce& nonce, const PathID_t& path_id, const std::string_view& inner_payload)
{
auto& pkt = m_UpstreamQueue.emplace_back();
pkt.first.resize(X.sz);
std::copy_n(X.base, X.sz, pkt.first.begin());
pkt.second = Y;
r->TriggerPump();
return true;
return make_onion_payload(
nonce,
path_id,
ustring_view{
reinterpret_cast<const unsigned char*>(inner_payload.data()), inner_payload.size()});
}
// handle data in downstream direction
bool
AbstractHopHandler::HandleDownstream(const llarp_buffer_t& X, const TunnelNonce& Y, Router* r)
std::string
make_onion_payload(
const SymmNonce& nonce, const PathID_t& path_id, const ustring_view& inner_payload)
{
auto& pkt = m_DownstreamQueue.emplace_back();
pkt.first.resize(X.sz);
std::copy_n(X.base, X.sz, pkt.first.begin());
pkt.second = Y;
r->TriggerPump();
return true;
}
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);
void
AbstractHopHandler::DecayFilters(llarp_time_t now)
{
m_UpstreamReplayFilter.Decay(now);
m_DownstreamReplayFilter.Decay(now);
return std::move(next_dict).str();
}
} // namespace llarp::path

View File

@ -1,8 +1,8 @@
#pragma once
#include <llarp/crypto/encrypted_frame.hpp>
#include "path_types.hpp"
#include <llarp/crypto/types.hpp>
#include <llarp/messages/relay.hpp>
#include <llarp/util/decaying_hashset.hpp>
#include <llarp/util/types.hpp>
@ -22,41 +22,38 @@ namespace llarp
namespace path
{
std::string
make_onion_payload(
const SymmNonce& nonce, const PathID_t& path_id, const std::string_view& inner_payload);
std::string
make_onion_payload(
const SymmNonce& nonce, const PathID_t& path_id, const ustring_view& inner_payload);
struct AbstractHopHandler
{
using TrafficEvent_t = std::pair<std::vector<byte_t>, TunnelNonce>;
using TrafficQueue_t = std::list<TrafficEvent_t>;
virtual ~AbstractHopHandler() = default;
virtual PathID_t
RXID() const = 0;
void
DecayFilters(llarp_time_t now);
virtual bool
Expired(llarp_time_t now) const = 0;
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;
/// send routing message and increment sequence number
virtual bool
SendRoutingMessage(std::string payload, Router* r) = 0;
// handle data in upstream direction
virtual bool
HandleUpstream(const llarp_buffer_t& X, const TunnelNonce& Y, Router*);
// handle data in downstream direction
virtual bool
HandleDownstream(const llarp_buffer_t& X, const TunnelNonce& Y, Router*);
std::string method, std::string body, std::function<void(std::string)> func) = 0;
/// return timestamp last remote activity happened at
virtual llarp_time_t
@ -68,29 +65,8 @@ namespace llarp
return m_SequenceNum++;
}
virtual void
FlushUpstream(Router* r) = 0;
virtual void
FlushDownstream(Router* r) = 0;
protected:
uint64_t m_SequenceNum = 0;
TrafficQueue_t m_UpstreamQueue;
TrafficQueue_t m_DownstreamQueue;
util::DecayingHashSet<TunnelNonce> m_UpstreamReplayFilter;
util::DecayingHashSet<TunnelNonce> m_DownstreamReplayFilter;
virtual void
UpstreamWork(TrafficQueue_t queue, Router* r) = 0;
virtual void
DownstreamWork(TrafficQueue_t queue, Router* r) = 0;
virtual void
HandleAllUpstream(std::vector<RelayUpstreamMessage> msgs, Router* r) = 0;
virtual void
HandleAllDownstream(std::vector<RelayDownstreamMessage> msgs, Router* r) = 0;
};
using HopHandler_ptr = std::shared_ptr<AbstractHopHandler>;

View File

@ -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)> 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)> 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)> 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)> 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)> func)
{
return send_path_control_message(
"find_router", FindRouterMessage::serialize(std::move(rid), false, false), std::move(func));
@ -93,62 +91,79 @@ 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)> func)
{
std::string payload;
oxenc::bt_dict_producer btdp;
btdp.append("BODY", body);
btdp.append("METHOD", method);
auto payload = std::move(btdp).str();
{
oxenc::bt_dict_producer btdp;
btdp.append("BODY", body);
btdp.append("METHOD", method);
payload = std::move(btdp).str();
}
TunnelNonce nonce;
// TODO: old impl padded messages if smaller than a certain size; do we still want to?
SymmNonce 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();
// TODO: do we want to allow empty callback here?
if ((not self) or (not response_cb))
return;
if (m.timed_out)
{
// do path hop logic here
response_cb(messages::TIMEOUT_RESPONSE);
return;
}
SymmNonce nonce{};
std::string payload;
try
{
oxenc::bt_dict_consumer btdc{m.body()};
auto nonce = SymmNonce{btdc.require<ustring_view>("NONCE").data()};
auto payload = btdc.require<std::string>("PAYLOAD");
}
catch (const std::exception& e)
{
log::warning(path_cat, "Error parsing path control message response: {}", e.what());
response_cb(messages::ERROR_RESPONSE);
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);
});
}
bool
Path::HandleUpstream(const llarp_buffer_t& X, const TunnelNonce& Y, Router* r)
{
if (not m_UpstreamReplayFilter.Insert(Y))
return false;
return AbstractHopHandler::HandleUpstream(X, Y, r);
}
bool
Path::HandleDownstream(const llarp_buffer_t& X, const TunnelNonce& Y, Router* r)
{
if (not m_DownstreamReplayFilter.Insert(Y))
return false;
return AbstractHopHandler::HandleDownstream(X, Y, r);
}
RouterID
Path::Endpoint() const
{
@ -216,6 +231,9 @@ namespace llarp::path
void
Path::EnterState(PathStatus st, llarp_time_t now)
{
if (now == 0s)
now = router.now();
if (st == ePathFailed)
{
_status = st;
@ -286,8 +304,6 @@ namespace llarp::path
{"ready", IsReady()},
{"txRateCurrent", m_LastTXRate},
{"rxRateCurrent", m_LastRXRate},
{"replayTX", m_UpstreamReplayFilter.Size()},
{"replayRX", m_DownstreamReplayFilter.Size()},
{"hasExit", SupportsAnyRoles(ePathRoleExit)}};
std::vector<util::StatusObject> hopsObj;
@ -421,73 +437,6 @@ namespace llarp::path
}
}
void
Path::HandleAllUpstream(std::vector<RelayUpstreamMessage> msgs, Router* r)
{
for (const auto& msg : msgs)
{
if (r->send_data_message(upstream(), msg.bt_encode()))
{
m_TXRate += msg.enc.size();
}
else
{
LogDebug("failed to send upstream to ", upstream());
}
}
r->TriggerPump();
}
void
Path::UpstreamWork(TrafficQueue_t msgs, Router* r)
{
std::vector<RelayUpstreamMessage> sendmsgs(msgs.size());
size_t idx = 0;
for (auto& ev : msgs)
{
TunnelNonce n = ev.second;
uint8_t* buf = ev.first.data();
size_t sz = ev.first.size();
for (const auto& hop : hops)
{
crypto::xchacha20(buf, sz, hop.shared, n);
n ^= hop.nonceXOR;
}
auto& msg = sendmsgs[idx];
std::memcpy(msg.enc.data(), buf, sz);
msg.nonce = ev.second;
msg.pathid = TXID();
++idx;
}
r->loop()->call([self = shared_from_this(), data = std::move(sendmsgs), r]() mutable {
self->HandleAllUpstream(std::move(data), r);
});
}
void
Path::FlushUpstream(Router* r)
{
if (not m_UpstreamQueue.empty())
{
r->queue_work([self = shared_from_this(),
data = std::exchange(m_UpstreamQueue, {}),
r]() mutable { self->UpstreamWork(std::move(data), r); });
}
}
void
Path::FlushDownstream(Router* r)
{
if (not m_DownstreamQueue.empty())
{
r->queue_work([self = shared_from_this(),
data = std::exchange(m_DownstreamQueue, {}),
r]() mutable { self->DownstreamWork(std::move(data), r); });
}
}
/// how long we wait for a path to become active again after it times out
constexpr auto PathReanimationTimeout = 45s;
@ -515,47 +464,6 @@ namespace llarp::path
return fmt::format("TX={} RX={}", TXID(), RXID());
}
void
Path::DownstreamWork(TrafficQueue_t msgs, Router* r)
{
std::vector<RelayDownstreamMessage> sendMsgs(msgs.size());
size_t idx = 0;
for (auto& ev : msgs)
{
sendMsgs[idx].nonce = ev.second;
uint8_t* buf = ev.first.data();
size_t sz = ev.first.size();
for (const auto& hop : hops)
{
sendMsgs[idx].nonce ^= hop.nonceXOR;
crypto::xchacha20(buf, sz, hop.shared, sendMsgs[idx].nonce);
}
std::memcpy(sendMsgs[idx].enc.data(), buf, sz);
++idx;
}
r->loop()->call([self = shared_from_this(), msgs = std::move(sendMsgs), r]() mutable {
self->HandleAllDownstream(std::move(msgs), r);
});
}
void
Path::HandleAllDownstream(std::vector<RelayDownstreamMessage> msgs, Router* /* r */)
{
for (const auto& msg : msgs)
{
const llarp_buffer_t buf{msg.enc};
m_RXRate += buf.sz;
// if (HandleRoutingMessage(buf, r))
// {
// r->TriggerPump();
// m_LastRecvMessage = r->now();
// }
}
}
/** Note: this is one of two places where AbstractRoutingMessage::bt_encode() is called, the
other of which is llarp/path/transit_hop.cpp in TransitHop::SendRoutingMessage(). For now,
we will default to the override of ::bt_encode() that returns an std::string. The role that
@ -571,6 +479,7 @@ namespace llarp::path
functions it calls and so on) will need to be modified to take an std::string that we can
std::move around.
*/
/* TODO: replace this with sending an onion-ed data message
bool
Path::SendRoutingMessage(std::string payload, Router*)
{
@ -594,6 +503,7 @@ namespace llarp::path
return true;
}
*/
template <typename Samples_t>
static llarp_time_t

View File

@ -5,9 +5,7 @@
#include "pathset.hpp"
#include <llarp/constants/path.hpp>
#include <llarp/crypto/encrypted_frame.hpp>
#include <llarp/crypto/types.hpp>
#include <llarp/messages/relay.hpp>
#include <llarp/router_id.hpp>
#include <llarp/service/intro.hpp>
#include <llarp/util/aligned.hpp>
@ -33,8 +31,6 @@ namespace llarp
struct TransitHopInfo;
struct PathHopConfig;
using TransitHop_ptr = std::shared_ptr<TransitHop>;
struct Ptr_hash;
struct Endpoint_Hash;
@ -122,14 +118,6 @@ namespace llarp
return _status;
}
// handle data in upstream direction
bool
HandleUpstream(const llarp_buffer_t& X, const TunnelNonce& Y, Router*) override;
// handle data in downstream direction
bool
HandleDownstream(const llarp_buffer_t& X, const TunnelNonce& Y, Router*) override;
const std::string&
ShortName() const;
@ -143,7 +131,7 @@ namespace llarp
}
void
EnterState(PathStatus st, llarp_time_t now);
EnterState(PathStatus st, llarp_time_t now = 0s);
llarp_time_t
ExpireTime() const
@ -186,39 +174,41 @@ 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)> 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)> 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)> func = nullptr);
bool
close_exit(
SecretKey sk,
std::string tx_id,
std::function<void(oxen::quic::message m)> func = nullptr);
close_exit(SecretKey sk, std::string tx_id, std::function<void(std::string)> 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)> 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;
bool
SendRoutingMessage(std::string payload, Router* r) override;
std::function<void(std::string)> func = nullptr) override;
bool
IsReady() const;
@ -246,25 +236,6 @@ namespace llarp
std::string
name() const;
void
FlushUpstream(Router* r) override;
void
FlushDownstream(Router* r) override;
protected:
void
UpstreamWork(TrafficQueue_t queue, Router* r) override;
void
DownstreamWork(TrafficQueue_t queue, Router* r) override;
void
HandleAllUpstream(std::vector<RelayUpstreamMessage> msgs, Router* r) override;
void
HandleAllDownstream(std::vector<RelayDownstreamMessage> msgs, Router* r) override;
private:
bool
SendLatencyMessage(Router* r);

View File

@ -69,195 +69,80 @@ 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
std::vector<std::shared_ptr<Path>>
PathContext::FindOwnedPathsWithEndpoint(const RouterID& r)
{
EndpointPathPtrSet found;
m_OurPaths.ForEach([&](const Path_ptr& p) {
if (p->Endpoint() == r && p->IsReady())
found.insert(p);
});
std::vector<std::shared_ptr<Path>> found;
for (const auto& [pathid, path] : own_paths)
{
// each path is stored in this map twice, once for each pathid at the first hop
// This will make the output deduplicated without needing a std::set
// TODO: we should only need to map one pathid; as the path owner we only send/receive
// packets with the first hop's RXID; its TXID is for packets between it and hop 2.
// TODO: Also, perhaps we want a bit of data duplication here, e.g. a map from
// RouterID (terminal hop) to shared_ptr<Path>.
if (path->TXID() == pathid)
continue;
if (path->Endpoint() == r && path->IsReady())
found.push_back(path);
}
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))
i = map.second.erase(i);
else
++i;
}
}
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;
TransitHopID upstream{info.upstream, info.txID};
if (transit_hops.count(upstream))
return true;
return false;
}
std::optional<std::weak_ptr<TransitHop>>
PathContext::TransitHopByInfo(const TransitHopInfo& info)
std::shared_ptr<TransitHop>
PathContext::GetTransitHop(const RouterID& rid, const PathID_t& path_id)
{
// 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;
if (auto itr = transit_hops.find({rid, path_id}); itr != transit_hops.end())
return itr->second;
return nullptr;
}
std::optional<std::weak_ptr<TransitHop>>
PathContext::TransitHopByUpstream(const RouterID& upstream, const PathID_t& id)
Path_ptr
PathContext::GetPath(const PathID_t& path_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;
}
if (auto itr = own_paths.find(path_id); itr != own_paths.end())
return itr->second;
HopHandler_ptr
PathContext::GetByUpstream(const RouterID& remote, const PathID_t& 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;
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; });
}
HopHandler_ptr
PathContext::GetByDownstream(const RouterID& remote, const PathID_t& 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; });
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;
@ -271,54 +156,30 @@ namespace llarp::path
return _router->pubkey();
}
TransitHop_ptr
std::shared_ptr<TransitHop>
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); });
}
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 +188,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,37 +201,30 @@ 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
{
itr->second->DecayFilters(now);
++itr;
}
}
}
{
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
{
itr->second->DecayFilters(now);
++itr;
}
}

View File

@ -5,7 +5,6 @@
#include "pathset.hpp"
#include "transit_hop.hpp"
#include <llarp/crypto/encrypted_frame.hpp>
#include <llarp/ev/ev.hpp>
#include <llarp/net/ip_address.hpp>
#include <llarp/util/compare_ptr.hpp>
@ -25,149 +24,121 @@ namespace llarp
struct TransitHop;
struct TransitHopInfo;
using TransitHop_ptr = std::shared_ptr<TransitHop>;
struct PathContext
struct TransitHopID
{
explicit PathContext(Router* router);
/// called from router tick function
void
ExpirePaths(llarp_time_t now);
void
PumpUpstream();
void
PumpDownstream();
void
AllowTransit();
void
RejectTransit();
RouterID rid;
PathID_t path_id;
bool
CheckPathLimitHitByIP(const IpAddress& ip);
bool
CheckPathLimitHitByIP(const std::string& ip);
bool
AllowingTransit() const;
bool
HasTransitHop(const TransitHopInfo& info);
void
PutTransitHop(std::shared_ptr<TransitHop> hop);
HopHandler_ptr
GetByUpstream(const RouterID& id, const PathID_t& path);
bool
TransitHopPreviousIsRouter(const PathID_t& path, const RouterID& r);
TransitHop_ptr
GetPathForTransfer(const PathID_t& topath);
HopHandler_ptr
GetByDownstream(const RouterID& id, const PathID_t& path);
std::optional<std::weak_ptr<TransitHop>>
TransitHopByInfo(const TransitHopInfo&);
std::optional<std::weak_ptr<TransitHop>>
TransitHopByUpstream(const RouterID&, const PathID_t&);
PathSet_ptr
GetLocalPathSet(const PathID_t& id);
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);
bool
HopIsUs(const RouterID& k) const;
void
AddOwnPath(PathSet_ptr set, Path_ptr p);
void
RemovePathSet(PathSet_ptr set);
using TransitHopsMap_t = std::unordered_multimap<PathID_t, TransitHop_ptr>;
struct SyncTransitMap_t
operator==(const TransitHopID& other) const
{
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;
return rid == other.rid && path_id == other.path_id;
}
private:
Router* _router;
SyncTransitMap_t m_TransitPaths;
SyncOwnedPathsMap_t m_OurPaths;
bool m_AllowTransit;
util::DecayingHashSet<IpAddress> path_limits;
};
} // namespace path
} // namespace llarp
namespace std
{
template <>
struct hash<llarp::path::TransitHopID>
{
size_t
operator()(const llarp::path::TransitHopID& obj) const noexcept
{
return std::hash<llarp::PathID_t>{}(obj.path_id);
}
};
} // namespace std
namespace llarp::path
{
struct PathContext
{
explicit PathContext(Router* router);
/// called from router tick function
void
ExpirePaths(llarp_time_t now);
void
AllowTransit();
void
RejectTransit();
bool
CheckPathLimitHitByIP(const IpAddress& ip);
bool
CheckPathLimitHitByIP(const std::string& ip);
bool
AllowingTransit() const;
bool
HasTransitHop(const TransitHopInfo& info);
void
PutTransitHop(std::shared_ptr<TransitHop> hop);
Path_ptr
GetPath(const PathID_t& path_id);
bool
TransitHopPreviousIsRouter(const PathID_t& path, const RouterID& r);
std::shared_ptr<TransitHop>
GetPathForTransfer(const PathID_t& topath);
std::shared_ptr<TransitHop>
GetTransitHop(const RouterID&, const PathID_t&);
PathSet_ptr
GetLocalPathSet(const PathID_t& id);
/// get a set of all paths that we own who's endpoint is r
std::vector<std::shared_ptr<Path>>
FindOwnedPathsWithEndpoint(const RouterID& r);
bool
HopIsUs(const RouterID& k) const;
void
AddOwnPath(PathSet_ptr set, Path_ptr p);
void
RemovePathSet(PathSet_ptr set);
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:
Router* _router;
std::unordered_map<TransitHopID, std::shared_ptr<TransitHop>> transit_hops;
std::unordered_map<PathID_t, Path_ptr> own_paths;
bool m_AllowTransit;
util::DecayingHashSet<IpAddress> path_limits;
};
} // namespace llarp::path

View File

@ -32,11 +32,11 @@ namespace llarp
/// shared secret at this hop
SharedSecret shared;
/// hash of shared secret used for nonce mutation
ShortHash nonceXOR;
SymmNonce nonceXOR;
/// next hop's router id
RouterID upstream;
/// nonce for key exchange
TunnelNonce nonce;
SymmNonce nonce;
// lifetime
llarp_time_t lifetime = DEFAULT_LIFETIME;

View File

@ -7,11 +7,14 @@
#include <llarp/link/link_manager.hpp>
#include <llarp/messages/path.hpp>
#include <llarp/nodedb.hpp>
#include <llarp/path/pathset.hpp>
#include <llarp/profiling.hpp>
#include <llarp/router/rc_lookup_handler.hpp>
#include <llarp/router/router.hpp>
#include <llarp/util/logging.hpp>
#include <functional>
namespace llarp
{
namespace
@ -90,7 +93,9 @@ namespace llarp
throw std::runtime_error{std::move(err)};
}
// generate nonceXOR value self->hop->pathKey
crypto::shorthash(hop.nonceXOR, hop.shared.data(), hop.shared.size());
ShortHash hash;
crypto::shorthash(hash, hop.shared.data(), hop.shared.size());
hop.nonceXOR = hash.data(); // nonceXOR is 24 bytes, ShortHash is 32; this will truncate
hop.upstream = nextHop;
}
@ -117,7 +122,7 @@ namespace llarp
crypto::encryption_keygen(framekey);
SharedSecret shared;
TunnelNonce outer_nonce;
SymmNonce outer_nonce;
outer_nonce.Randomize();
// derive (outer) shared key
@ -460,8 +465,7 @@ namespace llarp
frames.append(dummy);
}
auto self = GetSelf();
router->path_context().AddOwnPath(self, path);
router->path_context().AddOwnPath(GetSelf(), path);
PathBuildStarted(path);
// TODO:
@ -469,30 +473,33 @@ namespace llarp
// handle these responses as well as how we store and use Paths as a whole might
// be worth doing sooner rather than later. Leaving some TODOs below where fail
// and success live.
auto response_cb = [self](oxen::quic::message m) {
if (m)
auto response_cb = [path](oxen::quic::message m) {
try
{
std::string status;
try
if (m)
{
oxenc::bt_dict_consumer btdc{m.body()};
status = btdc.require<std::string>("STATUS");
// TODO: inform success (what this means needs revisiting, badly)
path->EnterState(path::ePathEstablished);
return;
}
catch (...)
if (m.timed_out)
{
log::warning(path_cat, "Error: Failed to parse path build response!", status);
m.respond(serialize_response({{"STATUS", "EXCEPTION"}}), true);
throw;
log::warning(path_cat, "Path build timed out");
}
else
{
oxenc::bt_dict_consumer d{m.body()};
auto status = d.require<std::string_view>(messages::STATUS_KEY);
log::warning(path_cat, "Path build returned failure status: {}", status);
}
// TODO: success logic
}
else
catch (const std::exception& e)
{
log::warning(path_cat, "Path build request returned failure {}");
// TODO: failure logic
log::warning(path_cat, "Failed parsing path build response.");
}
// TODO: inform failure (what this means needs revisiting, badly)
path->EnterState(path::ePathFailed);
};
if (not router->send_control_message(

View File

@ -2,6 +2,8 @@
#include "path.hpp"
#include <llarp/crypto/crypto.hpp>
namespace llarp::path
{
PathSet::PathSet(size_t num) : numDesiredPaths(num)
@ -441,16 +443,4 @@ namespace llarp::path
return chosen;
}
void
PathSet::UpstreamFlush(Router* r)
{
ForEachPath([r](const Path_ptr& p) { p->FlushUpstream(r); });
}
void
PathSet::DownstreamFlush(Router* r)
{
ForEachPath([r](const Path_ptr& p) { p->FlushDownstream(r); });
}
} // namespace llarp::path

View File

@ -12,21 +12,43 @@ namespace llarp::path
"[TransitHopInfo tx={} rx={} upstream={} downstream={}]", txID, rxID, upstream, downstream);
}
TransitHop::TransitHop()
: AbstractHopHandler{}
, m_UpstreamGather{TRANSIT_HOP_QUEUE_SIZE}
, m_DownstreamGather{TRANSIT_HOP_QUEUE_SIZE}
TransitHop::TransitHop() : AbstractHopHandler{}
{}
void
TransitHop::onion(ustring& data, SymmNonce& nonce, bool randomize) const
{
m_UpstreamGather.enable();
m_DownstreamGather.enable();
m_UpstreamWorkCounter = 0;
m_DownstreamWorkCounter = 0;
if (randomize)
nonce.Randomize();
nonce = crypto::onion(data.data(), data.size(), pathKey, nonce, nonceXOR);
}
void
TransitHop::onion(std::string& data, SymmNonce& 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<SymmNonce> nonce) const
{
SymmNonce 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)>)
TransitHop::send_path_control_message(std::string, std::string, std::function<void(std::string)>)
{
// TODO: if we want terminal/pivot hops to be able to *initiate* a request rather than
// simply responding/reacting to the client end's requests, this will need
// an implementation.
return true;
}
@ -60,6 +82,7 @@ namespace llarp::path
functions it calls and so on) will need to be modified to take an std::string that we can
std::move around.
*/
/* TODO: replace this with layer of onion + send data message
bool
TransitHop::SendRoutingMessage(std::string payload, Router* r)
{
@ -82,157 +105,7 @@ namespace llarp::path
return true;
}
void
TransitHop::DownstreamWork(TrafficQueue_t msgs, Router* r)
{
auto flushIt = [self = shared_from_this(), r]() {
std::vector<RelayDownstreamMessage> msgs;
while (auto maybe = self->m_DownstreamGather.tryPopFront())
{
msgs.push_back(*maybe);
}
self->HandleAllDownstream(std::move(msgs), r);
};
for (auto& ev : msgs)
{
RelayDownstreamMessage msg;
// const llarp_buffer_t buf(ev.first);
uint8_t* buf = ev.first.data();
size_t sz = ev.first.size();
msg.pathid = info.rxID;
msg.nonce = ev.second ^ nonceXOR;
crypto::xchacha20(buf, sz, pathKey, ev.second);
std::memcpy(msg.enc.data(), buf, sz);
llarp::LogDebug(
"relay ",
msg.enc.size(),
" bytes downstream from ",
info.upstream,
" to ",
info.downstream);
if (m_DownstreamGather.full())
{
r->loop()->call(flushIt);
}
if (m_DownstreamGather.enabled())
m_DownstreamGather.pushBack(msg);
}
r->loop()->call(flushIt);
}
void
TransitHop::UpstreamWork(TrafficQueue_t msgs, Router* r)
{
for (auto& ev : msgs)
{
RelayUpstreamMessage msg;
uint8_t* buf = ev.first.data();
size_t sz = ev.first.size();
crypto::xchacha20(buf, sz, pathKey, ev.second);
msg.pathid = info.txID;
msg.nonce = ev.second ^ nonceXOR;
std::memcpy(msg.enc.data(), buf, sz);
if (m_UpstreamGather.tryPushBack(msg) != thread::QueueReturn::Success)
break;
}
// Flush it:
r->loop()->call([self = shared_from_this(), r] {
std::vector<RelayUpstreamMessage> msgs;
while (auto maybe = self->m_UpstreamGather.tryPopFront())
{
msgs.push_back(*maybe);
}
self->HandleAllUpstream(std::move(msgs), r);
});
}
void
TransitHop::HandleAllUpstream(std::vector<RelayUpstreamMessage> msgs, Router* r)
{
if (IsEndpoint(r->pubkey()))
{
for (const auto& msg : msgs)
{
const llarp_buffer_t buf(msg.enc);
if (!r->ParseRoutingMessageBuffer(buf, *this, info.rxID))
{
LogWarn("invalid upstream data on endpoint ", info);
}
m_LastActivity = r->now();
}
FlushDownstream(r);
for (const auto& other : m_FlushOthers)
{
other->FlushDownstream(r);
}
m_FlushOthers.clear();
}
else
{
for (const auto& msg : msgs)
{
llarp::LogDebug(
"relay ",
msg.enc.size(),
" bytes upstream from ",
info.downstream,
" to ",
info.upstream);
r->send_data_message(info.upstream, msg.bt_encode());
}
}
r->TriggerPump();
}
void
TransitHop::HandleAllDownstream(std::vector<RelayDownstreamMessage> msgs, Router* r)
{
for (const auto& msg : msgs)
{
log::debug(
path_cat,
"Relaying {} bytes downstream from {} to {}",
msg.enc.size(),
info.upstream,
info.downstream);
// TODO: is this right?
r->send_data_message(info.downstream, msg.bt_encode());
}
r->TriggerPump();
}
void
TransitHop::FlushUpstream(Router* r)
{
if (not m_UpstreamQueue.empty())
{
r->queue_work([self = shared_from_this(),
data = std::exchange(m_UpstreamQueue, {}),
r]() mutable { self->UpstreamWork(std::move(data), r); });
}
}
void
TransitHop::FlushDownstream(Router* r)
{
if (not m_DownstreamQueue.empty())
{
r->queue_work([self = shared_from_this(),
data = std::exchange(m_DownstreamQueue, {}),
r]() mutable { self->DownstreamWork(std::move(data), r); });
}
}
*/
std::string
TransitHop::ToString() const
@ -244,8 +117,7 @@ namespace llarp::path
void
TransitHop::Stop()
{
m_UpstreamGather.disable();
m_DownstreamGather.disable();
// TODO: still need this concept?
}
void

View File

@ -55,12 +55,29 @@ namespace llarp
TransitHopInfo info;
SharedSecret pathKey;
ShortHash nonceXOR;
SymmNonce nonceXOR;
llarp_time_t started = 0s;
// 10 minutes default
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, SymmNonce& nonce, bool randomize = false) const;
void
onion(std::string& data, SymmNonce& nonce, bool randomize = false) const;
std::string
onion_and_payload(
std::string& payload,
PathID_t next_id,
std::optional<SymmNonce> nonce = std::nullopt) const;
PathID_t
RXID() const override
@ -106,47 +123,29 @@ 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;
// send routing message when end of path
bool
SendRoutingMessage(std::string payload, Router* r) override;
void
FlushUpstream(Router* r) override;
void
FlushDownstream(Router* r) override;
std::string method, std::string body, std::function<void(std::string)> func) override;
void
QueueDestroySelf(Router* r);
protected:
void
UpstreamWork(TrafficQueue_t queue, Router* r) override;
void
DownstreamWork(TrafficQueue_t queue, Router* r) override;
void
HandleAllUpstream(std::vector<RelayUpstreamMessage> msgs, Router* r) override;
void
HandleAllDownstream(std::vector<RelayDownstreamMessage> msgs, Router* r) override;
private:
void
SetSelfDestruct();
std::set<std::shared_ptr<TransitHop>, ComparePtr<std::shared_ptr<TransitHop>>> m_FlushOthers;
thread::Queue<RelayUpstreamMessage> m_UpstreamGather;
thread::Queue<RelayDownstreamMessage> m_DownstreamGather;
std::atomic<uint32_t> m_UpstreamWorkCounter;
std::atomic<uint32_t> m_DownstreamWorkCounter;
};
} // namespace path

View File

@ -66,6 +66,12 @@ namespace llarp
void
RCLookupHandler::get_rc(const RouterID& rid, RCRequestCallback callback, bool forceLookup)
{
(void)rid;
(void)callback;
(void)forceLookup;
/* RC refactor pending, this will likely go away entirely
*
*
RemoteRC remoteRC;
if (not forceLookup)
@ -83,58 +89,28 @@ 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

View File

@ -11,7 +11,6 @@
#include <llarp/net/net.hpp>
#include <llarp/nodedb.hpp>
#include <llarp/util/logging.hpp>
#include <llarp/util/meta/memfn.hpp>
#include <llarp/util/status.hpp>
#include <cstdlib>
@ -75,8 +74,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");
}
@ -693,7 +690,7 @@ namespace llarp
_contacts,
_node_db,
_loop,
util::memFn(&Router::queue_work, this),
[this](std::function<void(void)> work) { queue_work(std::move(work)); },
&_link_manager,
&_hidden_service_context,
strictConnectPubkeys,
@ -1290,7 +1287,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(); });

View File

@ -4,7 +4,6 @@
#include <llarp/crypto/crypto.hpp>
#include <llarp/crypto/types.hpp>
#include <llarp/util/meta/memfn.hpp>
#include <utility>
@ -43,6 +42,9 @@ namespace llarp::service
AsyncKeyExchange::Encrypt(
std::shared_ptr<AsyncKeyExchange> self, std::shared_ptr<ProtocolFrameMessage> frame)
{
(void)self;
(void)frame;
/* TODO: client<->client session ("conversation"/"convo") key exchange
// derive ntru session key component
SharedSecret secret;
crypto::pqe_encrypt(frame->cipher, secret, self->introPubKey);
@ -73,5 +75,6 @@ namespace llarp::service
{
LogError("failed to encrypt and sign");
}
*/
}
} // namespace llarp::service

View File

@ -139,63 +139,50 @@ namespace llarp::service
auth = AuthInfo{token},
ranges,
result_handler,
poker = router()->route_poker()](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(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(
saddr,
[ptr, name, ranges, result_handler, poker](auto addr, OutboundContext* ctx) {
if (ctx == nullptr)
{
result_handler(false, "could not establish flow to {}"_format(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)
{
for (const auto& range : ranges)
ptr->MapExitRange(range, addr);
if (poker)
poker->put_up();
result_handler(true, result);
}
result_handler(false, result);
};
ctx->send_auth_async(apply_result);
},
ptr->PathAlignmentTimeout());
if (not result)
result_handler(false, "Could not build path to {}"_format(name));
}
}
else
poker = router()->route_poker()](std::string name_result, bool success) mutable {
if (not success)
{
result_handler(false, "Exit {} not found!"_format(name));
return;
}
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)
{
for (const auto& range : ranges)
ptr->MapExitRange(range, addr);
if (poker)
poker->put_up();
}
result_handler(success, result);
};
ctx->send_auth_async(apply_result);
},
ptr->PathAlignmentTimeout());
if (not result)
result_handler(false, "Could not build path to {} ({})"_format(name_result, name));
}
});
}
@ -213,20 +200,22 @@ namespace llarp::service
// If we fail along the way (e.g. it's a .snode, we can't build a path, or whatever else) then
// we invoke the resultHandler with an empty vector.
lookup_name(
name, [this, resultHandler, service = std::move(service)](oxen::quic::message m) mutable {
if (!m)
name,
[this, resultHandler, service = std::move(service)](
std::string name_result, bool success) mutable {
if (!success)
return resultHandler({});
std::string name;
try
{
oxenc::bt_dict_consumer btdc{m.body()};
oxenc::bt_dict_consumer btdc{name_result};
name = btdc.require<std::string>("NAME");
}
catch (...)
{
log::warning(link_cat, "Failed to parse find name response!");
throw;
return resultHandler({});
}
auto saddr = service::Address();
@ -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,33 @@ namespace llarp::service
std::shuffle(chosenpaths.begin(), chosenpaths.end(), llarp::csrng);
chosenpaths.resize(std::min(paths.size(), MAX_ONS_LOOKUP_ENDPOINTS));
// TODO: only want one successful response to call the callback, or failed if all fail
auto response_cb = [func = std::move(func)](std::string resp) {
std::string name{};
try
{
oxenc::bt_dict_consumer btdc{resp};
auto status = btdc.require<std::string_view>(messages::STATUS_KEY);
if (status != "OK"sv)
{
log::info(link_cat, "Error on ONS lookup: {}", status);
func(std::string{status}, false);
}
name = btdc.require<std::string>("NAME");
}
catch (...)
{
log::warning(link_cat, "Failed to parse find name response!");
func("ERROR"s, false);
}
func(std::move(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 +858,42 @@ 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;
if (routers.find(rid) == routers.end())
{
auto path = GetEstablishedPathClosestTo(rid);
path->find_router("find_router", func);
return true;
}
(void)rid;
(void)func;
return false;
/* RC refactor pending, this will likely go away entirely
*
*
auto path = GetEstablishedPathClosestTo(rid);
auto response_cb = [func = std::move(func)](std::string resp, bool timeout) {
if (timeout)
func(RouterContact{}, 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,29 +1352,43 @@ 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)
path->find_intro(location, false, 0, [this, hook, got_it](std::string resp) mutable {
// asking many, use only first successful
if (*got_it)
return;
std::string introset;
try
{
std::string introset;
try
oxenc::bt_dict_consumer btdc{resp};
auto status = btdc.require<std::string_view>(messages::STATUS_KEY);
if (status != "OK"sv)
{
oxenc::bt_dict_consumer btdc{m.body()};
introset = btdc.require<std::string>("INTROSET");
log::info(link_cat, "Error in find intro set response: {}", status);
return;
}
catch (...)
{
log::warning(link_cat, "Failed to parse find name response!");
throw;
}
service::EncryptedIntroSet enc{introset};
router()->contacts()->services()->PutNode(std::move(enc));
// TODO: finish this
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));
// TODO: finish this
/*
if (good)
*got_it = true;
*/
});
}
return hookAdded;
@ -1432,7 +1471,7 @@ namespace llarp::service
queue.pop();
}
auto r = router();
// auto r = router();
// TODO: locking on this container
// for (const auto& [addr, outctx] : _state->remote_sessions)
@ -1452,8 +1491,6 @@ namespace llarp::service
// if (item.second->SendRoutingMessage(*item.first, r))
// ConvoTagTX(item.first->protocol_frame_msg.convo_tag);
// }
UpstreamFlush(r);
}
std::optional<ConvoTag>

View File

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

View File

@ -167,7 +167,7 @@ namespace llarp::service
return std::nullopt;
IntroSet i{other_i};
encrypted.nounce.Randomize();
encrypted.nonce.Randomize();
// set timestamp
// TODO: round to nearest 1000 ms
i.time_signed = now;
@ -180,7 +180,7 @@ namespace llarp::service
auto bte = i.bt_encode();
const SharedSecret k{i.address_keys.Addr()};
crypto::xchacha20(reinterpret_cast<uint8_t*>(bte.data()), bte.size(), k, encrypted.nounce);
crypto::xchacha20(reinterpret_cast<uint8_t*>(bte.data()), bte.size(), k, encrypted.nonce);
std::memcpy(encrypted.introsetPayload.data(), bte.data(), bte.size());

View File

@ -14,7 +14,7 @@ namespace llarp::service
std::string s)
: signedAt{signed_at}
, introsetPayload{reinterpret_cast<uint8_t*>(enc_payload.data()), enc_payload.size()}
, nounce{reinterpret_cast<uint8_t*>(nonce.data())}
, nonce{reinterpret_cast<uint8_t*>(nonce.data())}
{
derivedSigningKey = PubKey::from_string(signing_key);
sig.from_string(std::move(s));
@ -27,7 +27,7 @@ namespace llarp::service
oxenc::bt_dict_consumer btdc{bt_payload};
derivedSigningKey = PubKey::from_string(btdc.require<std::string>("d"));
nounce.from_string(btdc.require<std::string>("n"));
nonce.from_string(btdc.require<std::string>("n"));
signedAt = std::chrono::milliseconds{btdc.require<uint64_t>("s")};
introsetPayload = btdc.require<ustring>("x");
sig.from_string(btdc.require<std::string>("z"));
@ -54,7 +54,7 @@ namespace llarp::service
try
{
btdp.append("d", derivedSigningKey.ToView());
btdp.append("n", nounce.ToView());
btdp.append("n", nonce.ToView());
btdp.append("s", signedAt.count());
btdp.append(
"x",
@ -88,7 +88,7 @@ namespace llarp::service
if (not BEncodeMaybeReadDictEntry("d", derivedSigningKey, read, key, buf))
return false;
if (not BEncodeMaybeReadDictEntry("n", nounce, read, key, buf))
if (not BEncodeMaybeReadDictEntry("n", nonce, read, key, buf))
return false;
if (not BEncodeMaybeReadDictInt("s", signedAt, read, key, buf))
@ -111,7 +111,7 @@ namespace llarp::service
return fmt::format(
"[EncIntroSet d={} n={} s={} x=[{} bytes] z={}]",
derivedSigningKey,
nounce,
nonce,
signedAt.count(),
introsetPayload.size(),
sig);
@ -124,7 +124,7 @@ namespace llarp::service
std::string payload{
reinterpret_cast<const char*>(introsetPayload.data()), introsetPayload.size()};
crypto::xchacha20(reinterpret_cast<uint8_t*>(payload.data()), payload.size(), k, nounce);
crypto::xchacha20(reinterpret_cast<uint8_t*>(payload.data()), payload.size(), k, nonce);
return IntroSet{payload};
}

View File

@ -138,7 +138,7 @@ namespace llarp::service
PubKey derivedSigningKey;
llarp_time_t signedAt = 0s;
ustring introsetPayload;
TunnelNonce nounce;
SymmNonce nonce;
std::optional<Tag> topic;
Signature sig;
@ -203,8 +203,8 @@ namespace llarp::service
inline bool
operator==(const EncryptedIntroSet& lhs, const EncryptedIntroSet& rhs)
{
return std::tie(lhs.signedAt, lhs.derivedSigningKey, lhs.nounce, lhs.sig)
== std::tie(rhs.signedAt, rhs.derivedSigningKey, rhs.nounce, rhs.sig);
return std::tie(lhs.signedAt, lhs.derivedSigningKey, lhs.nonce, lhs.sig)
== std::tie(rhs.signedAt, rhs.derivedSigningKey, rhs.nonce, rhs.sig);
}
inline bool

View File

@ -157,6 +157,7 @@ namespace llarp::service
return "OBContext:" + current_intro.address_keys.Addr().ToString();
}
// TODO: it seems a lot of this logic is duplicated in service/endpoint
void
OutboundContext::UpdateIntroSet()
{
@ -173,7 +174,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) mutable {
if (marked_bad)
{
log::info(link_cat, "Outbound context has been marked bad (whatever that means)");
@ -182,43 +183,51 @@ namespace llarp::service
updatingIntroSet = false;
if (m)
// TODO: this parsing is probably elsewhere, may need DRYed
std::string introset;
try
{
std::string introset;
try
oxenc::bt_dict_consumer btdc{resp};
auto status = btdc.require<std::string_view>(messages::STATUS_KEY);
if (status != "OK"sv)
{
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()});
if (intro.time_signed == 0s)
{
log::warning(link_cat, "{} recieved introset with zero timestamp");
log::info(link_cat, "Error in find intro set response: {}", status);
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;
ShiftIntroRouter();
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()});
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;
}
// don't "shift" to the same intro we're already using...
if (current_intro == intro)
return;
if (intro.IsExpired(llarp::time_now_ms()))
{
log::warning(link_cat, "{} received expired introset", Name());
return;
}
current_intro = intro;
ShiftIntroRouter();
});
}
}
@ -534,8 +543,9 @@ 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) {
// TODO: revisit this
(void)resp;
ep.HandleHiddenServiceFrame(path, *frame.get());
};

View File

@ -302,8 +302,11 @@ namespace llarp::service
// PKE (A, B, N)
SharedSecret shared_secret;
if (!self->m_LocalIdentity.KeyExchange(
crypto::dh_server, shared_secret, self->msg->sender, self->frame.nonce))
if (!crypto::dh_server(
shared_secret,
self->msg->sender.EncryptionPublicKey(),
self->m_LocalIdentity.enckey,
self->frame.nonce))
{
LogError("x25519 key exchange failed");
Dump<MAX_PROTOCOL_MESSAGE_SIZE>(self->frame);

View File

@ -75,7 +75,7 @@ namespace llarp
PQCipherBlock cipher;
Encrypted<2048> enc;
uint64_t flag; // set to indicate in plaintext a nack, aka "dont try again"
KeyExchangeNonce nonce;
SymmNonce nonce;
Signature sig;
PathID_t path_id;
service::ConvoTag convo_tag;

View File

@ -1,72 +0,0 @@
#ifndef LLARP_UTIL_MEMFN
#define LLARP_UTIL_MEMFN
#include <memory>
#include <type_traits>
#include <utility>
namespace llarp::util
{
// Wraps a member function and instance into a callable object that invokes
// the method (non-const overload).
template <
typename Return,
typename Class,
typename Derived,
typename... Arg,
typename = std::enable_if_t<std::is_base_of<Class, Derived>::value>>
auto
memFn(Return (Class::*f)(Arg...), Derived* self)
{
return [f, self](Arg... args) -> Return { return (self->*f)(std::forward<Arg>(args)...); };
}
// Wraps a member function and instance into a lambda that invokes the
// method (const overload).
template <
typename Return,
typename Class,
typename Derived,
typename... Arg,
typename = std::enable_if_t<std::is_base_of<Class, Derived>::value>>
auto
memFn(Return (Class::*f)(Arg...) const, const Derived* self)
{
return [f, self](Arg... args) -> Return { return (self->*f)(std::forward<Arg>(args)...); };
}
// Wraps a member function and shared pointer to an instance into a lambda
// that invokes the method.
template <
typename Return,
typename Class,
typename Derived,
typename... Arg,
typename = std::enable_if_t<std::is_base_of<Class, Derived>::value>>
auto
memFn(Return (Class::*f)(Arg...), std::shared_ptr<Derived> self)
{
return [f, self = std::move(self)](Arg... args) -> Return {
return (self.get()->*f)(std::forward<Arg>(args)...);
};
}
// Wraps a member function and shared pointer to an instance into a lambda
// that invokes the method (const method overload).
template <
typename Return,
typename Class,
typename Derived,
typename... Arg,
typename = std::enable_if_t<std::is_base_of<Class, Derived>::value>>
auto
memFn(Return (Class::*f)(Arg...) const, std::shared_ptr<Derived> self)
{
return [f, self = std::move(self)](Arg... args) -> Return {
return (self.get()->*f)(std::forward<Arg>(args)...);
};
}
} // namespace llarp::util
#endif