Compare commits

..

No commits in common. 'd520e1d2c457c9f9ad7571a390edde9523e9d8a5' and '1ef77cccbd9ca9ec27e7d552e4ca1d0da40bbd1c' have entirely different histories.

@ -12,6 +12,7 @@ endfunction()
lokinet_add_library(lokinet-cryptography
crypto/crypto.cpp
crypto/encrypted_frame.cpp
crypto/types.cpp
)
@ -24,6 +25,7 @@ lokinet_add_library(lokinet-core-utils
exit/policy.cpp # net/
handlers/exit.cpp # link/ exit/
handlers/tun.cpp
router/rc_gossiper.cpp
service/auth.cpp # config/
service/context.cpp
service/identity.cpp
@ -108,6 +110,7 @@ lokinet_add_library(lokinet-time-place
# lokinet-platform holds all platform specific code
lokinet_add_library(lokinet-platform
net/interface_info.cpp
router/rc_lookup_handler.cpp
vpn/packet_router.cpp
vpn/platform.cpp
)
@ -204,6 +207,7 @@ 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

@ -104,7 +104,7 @@ namespace llarp::consensus
// We exhausted the queue so repopulate it and try again
testing_queue.clear();
const auto all = router->get_whitelist();
const auto all = router->router_whitelist();
testing_queue.insert(testing_queue.begin(), all.begin(), all.end());
std::shuffle(testing_queue.begin(), testing_queue.end(), llarp::csrng);

@ -65,7 +65,7 @@ namespace llarp
static bool
dh_client_priv(
llarp::SharedSecret& shared, const PubKey& pk, const SecretKey& sk, const SymmNonce& n)
llarp::SharedSecret& shared, const PubKey& pk, const SecretKey& sk, const TunnelNonce& 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 SymmNonce& n)
llarp::SharedSecret& shared, const PubKey& pk, const SecretKey& sk, const TunnelNonce& n)
{
llarp::SharedSecret dh_result;
if (dh(dh_result, pk, sk.toPublic(), pk.data(), sk))
{
return crypto_generichash_blake2b(shared.data(), 32, n.data(), n.size(), dh_result.data(), 32)
return crypto_generichash_blake2b(shared.data(), 32, n.data(), 32, 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, 24, dh_result.data(), 32) != -1;
return crypto_generichash_blake2b(shared, 32, nonce, 32, 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 SymmNonce& n)
crypto::xchacha20(uint8_t* buf, size_t size, const SharedSecret& k, const TunnelNonce& n)
{
return xchacha20(buf, size, n.data(), k.data());
}
@ -154,31 +154,16 @@ 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 SymmNonce& n)
llarp::SharedSecret& shared, const PubKey& pk, const SecretKey& sk, const TunnelNonce& 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 SymmNonce& n)
llarp::SharedSecret& shared, const PubKey& pk, const SecretKey& sk, const TunnelNonce& n)
{
return dh_server_priv(shared, pk, sk, n);
}
@ -192,6 +177,20 @@ 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)

@ -23,30 +23,28 @@ namespace llarp
/// xchacha symmetric cipher
bool
xchacha20(uint8_t*, size_t size, const SharedSecret&, const SymmNonce&);
xchacha20(uint8_t*, size_t size, const SharedSecret&, const TunnelNonce&);
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 SymmNonce&);
dh_client(SharedSecret&, const PubKey&, const SecretKey&, const TunnelNonce&);
/// path dh relay side
bool
dh_server(SharedSecret&, const PubKey&, const SecretKey&, const SymmNonce&);
dh_server(SharedSecret&, const PubKey&, const SecretKey&, const TunnelNonce&);
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);

@ -0,0 +1,127 @@
#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

@ -0,0 +1,87 @@
#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

@ -164,6 +164,10 @@ 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

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

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

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

@ -272,8 +272,35 @@ namespace llarp::exit
if (numHops == 1)
{
if (const auto maybe = router->node_db()->get_rc(exit_router); maybe.has_value())
router->connect_to(*maybe);
auto r = router;
if (const auto maybe = r->node_db()->get_rc(exit_router); maybe.has_value())
r->connect_to(*maybe);
else
r->lookup_router(exit_router, [r](oxen::quic::message m) mutable {
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)};
r->node_db()->put_rc_if_newer(result);
r->connect_to(result);
}
else
{
r->link_manager().handle_find_router_error(std::move(m));
}
});
}
else if (UrgentBuild(now))
BuildOneAlignedTo(exit_router);

@ -2,8 +2,8 @@
#include <llarp/dns/dns.hpp>
#include <llarp/net/net.hpp>
#include <llarp/nodedb.hpp>
#include <llarp/path/path_context.hpp>
#include <llarp/router/rc_lookup_handler.hpp>
#include <llarp/router/router.hpp>
#include <llarp/service/protocol_type.hpp>
@ -20,9 +20,9 @@ namespace llarp::handlers
ExitEndpoint::~ExitEndpoint() = default;
void
ExitEndpoint::lookup_name(std::string, std::function<void(std::string, bool)>)
ExitEndpoint::lookup_name(std::string, std::function<void(oxen::quic::message)>)
{
// TODO: implement me (or does EndpointBase having this method as virtual even make sense?)
// TODO: implement me
}
void
@ -243,8 +243,9 @@ namespace llarp::handlers
{
if (msg.questions[0].IsName("random.snode"))
{
if (auto random = GetRouter()->GetRandomGoodRouter())
msg.AddCNAMEReply(random->ToString(), 1);
RouterID random;
if (GetRouter()->GetRandomGoodRouter(random))
msg.AddCNAMEReply(random.ToString(), 1);
else
msg.AddNXReply();
}
@ -262,10 +263,11 @@ namespace llarp::handlers
const bool isV4 = msg.questions[0].qtype == dns::qTypeA;
if (msg.questions[0].IsName("random.snode"))
{
if (auto random = GetRouter()->GetRandomGoodRouter())
RouterID random;
if (GetRouter()->GetRandomGoodRouter(random))
{
msg.AddCNAMEReply(random->ToString(), 1);
auto ip = ObtainServiceNodeIP(*random);
msg.AddCNAMEReply(random.ToString(), 1);
auto ip = ObtainServiceNodeIP(random);
msg.AddINReply(ip, false);
}
else
@ -331,7 +333,7 @@ namespace llarp::handlers
void
ExitEndpoint::ObtainSNodeSession(const RouterID& rid, exit::SessionReadyFunc obtain_cb)
{
if (not router->node_db()->is_connection_allowed(rid))
if (not router->rc_lookup_handler().is_session_allowed(rid))
{
obtain_cb(nullptr);
return;
@ -764,9 +766,7 @@ namespace llarp::handlers
{
if (wantInternet && !permit_exit)
return false;
// 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{};
path::HopHandler_ptr handler = router->path_context().GetByUpstream(router->pubkey(), path);
if (handler == nullptr)
return false;
auto ip = GetIPForIdent(pk);

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

@ -606,11 +606,34 @@ namespace llarp::handlers
RouterID snode;
if (snode.FromString(qname))
{
if (auto rc = router()->node_db()->get_rc(snode))
msg.AddTXTReply(std::string{rc->view()});
else
msg.AddNXReply();
reply(msg);
router()->lookup_router(
snode, [r = router(), msg = std::move(msg), reply](oxen::quic::message m) mutable {
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;
}
r->node_db()->put_rc_if_newer(RemoteRC{payload});
msg.AddTXTReply(payload);
}
else
{
msg.AddNXReply();
r->link_manager().handle_find_router_error(std::move(m));
}
reply(msg);
});
return true;
}
@ -660,17 +683,28 @@ namespace llarp::handlers
}
else if (service::is_valid_name(ons_name))
{
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();
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;
}
reply(msg);
});
msg.AddMXReply(result, 1);
}
else
msg.AddNXReply();
reply(msg);
});
return true;
}
@ -682,9 +716,10 @@ namespace llarp::handlers
{
if (is_random_snode(msg))
{
if (auto random = router()->GetRandomGoodRouter())
RouterID random;
if (router()->GetRandomGoodRouter(random))
{
msg.AddCNAMEReply(random->ToString(), 1);
msg.AddCNAMEReply(random.ToString(), 1);
}
else
msg.AddNXReply();
@ -731,10 +766,11 @@ namespace llarp::handlers
// on MacOS this is a typeA query
else if (is_random_snode(msg))
{
if (auto random = router()->GetRandomGoodRouter())
RouterID random;
if (router()->GetRandomGoodRouter(random))
{
msg.AddCNAMEReply(random->ToString(), 1);
return ReplyToSNodeDNSWhenReady(*random, std::make_shared<dns::Message>(msg), isV6);
msg.AddCNAMEReply(random.ToString(), 1);
return ReplyToSNodeDNSWhenReady(random, std::make_shared<dns::Message>(msg), isV6);
}
msg.AddNXReply();
@ -801,15 +837,29 @@ namespace llarp::handlers
ons_name,
isV6,
reply,
ReplyToDNSWhenReady](std::string name_result, bool success) mutable {
if (not success)
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
{
log::warning(logcat, "{} (ONS name: {}) not resolved", name, ons_name);
msg->AddNXReply();
reply(*msg);
}
ReplyToDNSWhenReady(name_result, msg, isV6);
});
return true;
}

@ -71,6 +71,13 @@ namespace llarp
return obj;
}
bool
Contacts::lookup_router(const RouterID& rid, std::function<void(oxen::quic::message)> func)
{
return _router.send_control_message(
rid, "find_router", FindRouterMessage::serialize(rid, false, false), std::move(func));
}
void
Contacts::put_rc_node_async(const dht::RCNode& val)
{

@ -45,6 +45,9 @@ namespace llarp
util::StatusObject
ExtractStatus() const;
bool
lookup_router(const RouterID&, std::function<void(oxen::quic::message)> = nullptr);
void
put_rc_node_async(const dht::RCNode& val);

File diff suppressed because it is too large Load Diff

@ -4,8 +4,7 @@
#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>
#include <llarp/util/decaying_hashset.hpp>
@ -26,7 +25,12 @@ namespace
namespace llarp
{
struct LinkManager;
class NodeDB;
inline std::string
serialize_response(oxenc::bt_dict supplement = {})
{
return oxenc::bt_serialize(supplement);
}
namespace link
{
@ -167,6 +171,8 @@ 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;
@ -176,6 +182,7 @@ namespace llarp
util::DecayingHashSet<RouterID> clients{path::DEFAULT_LIFETIME};
RCLookupHandler* rc_lookup;
std::shared_ptr<NodeDB> node_db;
oxen::quic::Address addr;
@ -220,12 +227,6 @@ namespace llarp
return addr;
}
void
gossip_rc(const RouterID& rc_rid, std::string serialized_rc);
void
handle_gossip_rc(oxen::quic::message m);
bool
have_connection_to(const RouterID& remote, bool client_only = false) const;
@ -266,7 +267,7 @@ namespace llarp
extract_status() const;
void
init();
init(RCLookupHandler* rcLookup);
void
for_each_connection(std::function<void(link::Connection&)> func);
@ -287,18 +288,18 @@ namespace llarp
private:
// DHT messages
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_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
// Path messages
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
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
// Exit messages
void handle_obtain_exit(oxen::quic::message); // relay
@ -308,49 +309,33 @@ namespace llarp
// Misc
void handle_convo_intro(oxen::quic::message);
// 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},
{"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;
// 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 = {
{"publish_intro"sv, &LinkManager::handle_publish_intro},
{"find_intro"sv, &LinkManager::handle_find_intro}};
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}};
// Path relaying
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);
void handle_path_control(oxen::quic::message);
// DHT responses
void handle_find_name_response(oxen::quic::message);
void handle_find_intro_response(oxen::quic::message);
void handle_publish_intro_response(oxen::quic::message);
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);
@ -361,11 +346,18 @@ namespace llarp
std::unordered_map<std::string, void (LinkManager::*)(oxen::quic::message)> rpc_responses = {
{"find_name", &LinkManager::handle_find_name_response},
{"find_router", &LinkManager::handle_find_router_response},
{"publish_intro", &LinkManager::handle_publish_intro_response},
{"find_intro", &LinkManager::handle_find_intro_response},
{"update_exit", &LinkManager::handle_update_exit_response},
{"obtain_exit", &LinkManager::handle_obtain_exit_response},
{"close_exit", &LinkManager::handle_close_exit_response}};
public:
// Public response functions and error handling functions invoked elsehwere. These take
// r-value references s.t. that message is taken out of calling scope
void
handle_find_router_error(oxen::quic::message&& m);
};
namespace link
@ -429,6 +421,7 @@ namespace llarp
std::unordered_map<std::string, void (llarp::link::LinkManager::*)(oxen::quic::message)>
rpc_commands = {
{"find_name", &handle_find_name},
{"find_router", &handle_find_router},
// ...
};

@ -18,22 +18,6 @@ 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
{

@ -6,6 +6,7 @@ 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;
@ -51,7 +52,9 @@ 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;
@ -77,6 +80,7 @@ namespace llarp
namespace FindNameMessage
{
inline auto EXCEPTION = "EXCEPTION"sv;
inline auto NOT_FOUND = "NOT FOUND"sv;
inline static std::string
@ -116,6 +120,7 @@ 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;

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

@ -6,6 +6,8 @@ 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;
@ -27,9 +29,7 @@ namespace llarp
throw std::runtime_error{std::move(err)};
}
// generate nonceXOR value self->hop->pathKey
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
crypto::shorthash(hop.nonceXOR, hop.shared.data(), hop.shared.size());
hop.upstream = nextHop;
}
@ -56,7 +56,7 @@ namespace llarp
crypto::encryption_keygen(framekey);
SharedSecret shared;
SymmNonce outer_nonce;
TunnelNonce outer_nonce;
outer_nonce.Randomize();
// derive (outer) shared key

@ -0,0 +1,121 @@
#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

@ -0,0 +1,75 @@
#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

@ -14,6 +14,9 @@ static const std::string RC_FILE_EXT = ".signed";
namespace llarp
{
NodeDB::Entry::Entry(RemoteRC value) : rc(std::move(value)), insertedAt(llarp::time_now_ms())
{}
static void
EnsureSkiplist(fs::path nodedbDir)
{
@ -69,8 +72,8 @@ namespace llarp
// make copy of all rcs
std::vector<RemoteRC> copy;
for (const auto& item : known_rcs)
copy.push_back(item.second);
for (const auto& item : entries)
copy.push_back(item.second.rc);
// flush them to disk in one big job
// TODO: split this up? idk maybe some day...
@ -96,81 +99,6 @@ namespace llarp
return m_Root / skiplistDir / fname;
}
bool
NodeDB::want_rc(const RouterID& rid) const
{
if (not router.is_service_node())
return true;
return registered_routers.count(rid);
}
void
NodeDB::set_bootstrap_routers(const std::set<RemoteRC>& rcs)
{
bootstraps.clear(); // this function really shouldn't be called more than once, but...
for (const auto& rc : rcs)
bootstraps.emplace(rc.router_id(), rc);
}
void
NodeDB::set_router_whitelist(
const std::vector<RouterID>& whitelist,
const std::vector<RouterID>& greylist,
const std::vector<RouterID>& greenlist)
{
if (whitelist.empty())
return;
registered_routers.clear();
registered_routers.insert(whitelist.begin(), whitelist.end());
registered_routers.insert(greylist.begin(), greylist.end());
registered_routers.insert(greenlist.begin(), greenlist.end());
router_whitelist.clear();
router_whitelist.insert(whitelist.begin(), whitelist.end());
router_greylist.clear();
router_greylist.insert(greylist.begin(), greylist.end());
router_greenlist.clear();
router_greenlist.insert(greenlist.begin(), greenlist.end());
log::info(
logcat, "lokinet service node list now has ", router_whitelist.size(), " active routers");
}
std::optional<RouterID>
NodeDB::get_random_whitelist_router() const
{
const auto sz = router_whitelist.size();
if (sz == 0)
return std::nullopt;
auto itr = router_whitelist.begin();
if (sz > 1)
std::advance(itr, randint() % sz);
return *itr;
}
bool
NodeDB::is_connection_allowed(const RouterID& remote) const
{
if (pinned_edges.size() && pinned_edges.count(remote) == 0 && bootstraps.count(remote) == 0)
{
return false;
}
if (not router.is_service_node())
return true;
return router_whitelist.count(remote) or router_greylist.count(remote);
}
bool
NodeDB::is_first_hop_allowed(const RouterID& remote) const
{
if (pinned_edges.size() && pinned_edges.count(remote) == 0)
return false;
return true;
}
void
NodeDB::load_from_disk()
{
@ -209,10 +137,10 @@ namespace llarp
return true;
}
// validate signature and purge known_rcs with invalid signatures
// validate signature and purge entries with invalid signatures
// load ones with valid signatures
if (rc.verify())
known_rcs.emplace(rc.router_id(), rc);
entries.emplace(rc.router_id(), rc);
else
purge.emplace(f);
@ -237,33 +165,36 @@ namespace llarp
return;
router.loop()->call([this]() {
for (const auto& item : known_rcs)
item.second.write(get_path_by_pubkey(item.first));
for (const auto& item : entries)
item.second.rc.write(get_path_by_pubkey(item.first));
});
}
bool
NodeDB::has_rc(RouterID pk) const
NodeDB::has_router(RouterID pk) const
{
return known_rcs.count(pk);
return router.loop()->call_get(
[this, pk]() -> bool { return entries.find(pk) != entries.end(); });
}
std::optional<RemoteRC>
NodeDB::get_rc(RouterID pk) const
{
const auto itr = known_rcs.find(pk);
return router.loop()->call_get([this, pk]() -> std::optional<RemoteRC> {
const auto itr = entries.find(pk);
if (itr == known_rcs.end())
return std::nullopt;
if (itr == entries.end())
return std::nullopt;
return itr->second;
return itr->second.rc;
});
}
void
NodeDB::remove_router(RouterID pk)
{
router.loop()->call([this, pk]() {
known_rcs.erase(pk);
entries.erase(pk);
remove_many_from_disk_async({pk});
});
}
@ -271,37 +202,54 @@ namespace llarp
void
NodeDB::remove_stale_rcs(std::unordered_set<RouterID> keep, llarp_time_t cutoff)
{
(void)keep;
(void)cutoff;
// TODO: handling of "stale" is pending change, removing here for now.
router.loop()->call([this, keep, cutoff]() {
std::unordered_set<RouterID> removed;
auto itr = entries.begin();
while (itr != entries.end())
{
if (itr->second.insertedAt < cutoff and keep.count(itr->second.rc.router_id()) == 0)
{
removed.insert(itr->second.rc.router_id());
itr = entries.erase(itr);
}
else
++itr;
}
if (not removed.empty())
remove_many_from_disk_async(std::move(removed));
});
}
bool
void
NodeDB::put_rc(RemoteRC rc)
{
const auto& rid = rc.router_id();
if (not want_rc(rid))
return false;
known_rcs.erase(rid);
known_rcs.emplace(rid, std::move(rc));
return true;
router.loop()->call([this, rc]() {
const auto& rid = rc.router_id();
entries.erase(rid);
entries.emplace(rid, rc);
});
}
size_t
NodeDB::num_loaded() const
{
return router.loop()->call_get([this]() { return known_rcs.size(); });
return router.loop()->call_get([this]() { return entries.size(); });
}
bool
void
NodeDB::put_rc_if_newer(RemoteRC rc)
{
auto itr = known_rcs.find(rc.router_id());
if (itr == known_rcs.end() or itr->second.other_is_newer(rc))
{
return put_rc(std::move(rc));
}
return false;
router.loop()->call([this, rc]() {
auto itr = entries.find(rc.router_id());
if (itr == entries.end() or itr->second.rc.other_is_newer(rc))
{
// delete if existing
if (itr != entries.end())
entries.erase(itr);
// add new entry
entries.emplace(rc.router_id(), rc);
}
});
}
void
@ -348,10 +296,10 @@ namespace llarp
return router.loop()->call_get([this, location, numRouters]() -> std::vector<RemoteRC> {
std::vector<const RemoteRC*> all;
all.reserve(known_rcs.size());
for (auto& entry : known_rcs)
all.reserve(entries.size());
for (auto& entry : entries)
{
all.push_back(&entry.second);
all.push_back(&entry.second.rc);
}
auto it_mid = numRouters < all.size() ? all.begin() + numRouters : all.end();

@ -24,7 +24,16 @@ namespace llarp
class NodeDB
{
std::unordered_map<RouterID, RemoteRC> known_rcs;
struct Entry
{
const RemoteRC rc;
llarp_time_t insertedAt;
explicit Entry(RemoteRC rc);
};
using NodeMap = std::unordered_map<RouterID, Entry>;
NodeMap entries;
const Router& router;
const fs::path m_Root;
@ -40,95 +49,14 @@ namespace llarp
fs::path
get_path_by_pubkey(RouterID pk) const;
std::unordered_map<RouterID, RemoteRC> bootstraps;
// whitelist = active routers
std::unordered_set<RouterID> router_whitelist;
// greylist = fully funded, but decommissioned routers
std::unordered_set<RouterID> router_greylist;
// greenlist = registered but not fully-staked routers
std::unordered_set<RouterID> router_greenlist;
// all registered relays (snodes)
std::unordered_set<RouterID> registered_routers;
// only ever use to specific edges as path first-hops
std::unordered_set<RouterID> pinned_edges;
bool
want_rc(const RouterID& rid) const;
public:
void
set_bootstrap_routers(const std::set<RemoteRC>& rcs);
const std::unordered_set<RouterID>&
whitelist() const
{
return router_whitelist;
}
const std::unordered_set<RouterID>&
greylist() const
{
return router_greylist;
}
const std::unordered_set<RouterID>&
get_registered_routers() const
{
return registered_routers;
}
void
set_router_whitelist(
const std::vector<RouterID>& whitelist,
const std::vector<RouterID>& greylist,
const std::vector<RouterID>& greenlist);
std::optional<RouterID>
get_random_whitelist_router() const;
// client:
// if pinned edges were specified, connections are allowed only to those and
// to the configured bootstrap nodes. otherwise, always allow.
//
// relay:
// outgoing connections are allowed only to other registered, funded relays
// (whitelist and greylist, respectively).
bool
is_connection_allowed(const RouterID& remote) const;
// client:
// same as is_connection_allowed
//
// server:
// we only build new paths through registered, not decommissioned relays
// (i.e. whitelist)
bool
is_path_allowed(const RouterID& remote) const
{
return router_whitelist.count(remote);
}
// if pinned edges were specified, the remote must be in that set, else any remote
// is allowed as first hop.
bool
is_first_hop_allowed(const RouterID& remote) const;
const std::unordered_set<RouterID>&
get_pinned_edges() const
{
return pinned_edges;
}
explicit NodeDB(
fs::path rootdir, std::function<void(std::function<void()>)> diskCaller, Router* r);
/// in memory nodedb
NodeDB();
/// load all known_rcs from disk syncrhonously
/// load all entries from disk syncrhonously
void
load_from_disk();
@ -154,7 +82,7 @@ namespace llarp
/// return true if we have an rc by its ident pubkey
bool
has_rc(RouterID pk) const;
has_router(RouterID pk) const;
/// maybe get an rc by its ident pubkey
std::optional<RemoteRC>
@ -165,30 +93,44 @@ namespace llarp
GetRandom(Filter visit) const
{
return router.loop()->call_get([visit]() -> std::optional<RemoteRC> {
std::vector<const decltype(known_rcs)::value_type*> known_rcs;
for (const auto& entry : known_rcs)
known_rcs.push_back(entry);
std::vector<const decltype(entries)::value_type*> entries;
for (const auto& entry : entries)
entries.push_back(entry);
std::shuffle(known_rcs.begin(), known_rcs.end(), llarp::csrng);
std::shuffle(entries.begin(), entries.end(), llarp::csrng);
for (const auto entry : known_rcs)
for (const auto entry : entries)
{
if (visit(entry->second))
return entry->second;
if (visit(entry->second.rc))
return entry->second.rc;
}
return std::nullopt;
});
}
/// visit all known_rcs
/// visit all entries
template <typename Visit>
void
VisitAll(Visit visit) const
{
router.loop()->call([this, visit]() {
for (const auto& item : known_rcs)
visit(item.second);
for (const auto& item : entries)
visit(item.second.rc);
});
}
/// visit all entries inserted before a timestamp
template <typename Visit>
void
VisitInsertedBefore(Visit visit, llarp_time_t insertedBefore)
{
router.loop()->call([this, visit, insertedBefore]() {
for (const auto& item : entries)
{
if (item.second.insertedAt < insertedBefore)
visit(item.second.rc);
}
});
}
@ -203,13 +145,13 @@ namespace llarp
{
router.loop()->call([this, visit]() {
std::unordered_set<RouterID> removed;
auto itr = known_rcs.begin();
while (itr != known_rcs.end())
auto itr = entries.begin();
while (itr != entries.end())
{
if (visit(itr->second))
if (visit(itr->second.rc))
{
removed.insert(itr->second.router_id());
itr = known_rcs.erase(itr);
removed.insert(itr->second.rc.router_id());
itr = entries.erase(itr);
}
else
++itr;
@ -223,14 +165,12 @@ namespace llarp
void
remove_stale_rcs(std::unordered_set<RouterID> keep, llarp_time_t cutoff);
/// put (or replace) the RC if we consider it valid (want_rc). returns true if put.
bool
put_rc(RemoteRC rc);
/// if we consider it valid (want_rc),
/// put this rc into the cache if it is not there or is newer than the one there already
/// returns true if the rc was inserted
bool
/// put this rc into the cache if it is not there or newer than the one there already
void
put_rc_if_newer(RemoteRC rc);
/// unconditional put of rc into cache
void
put_rc(RemoteRC rc);
};
} // namespace llarp

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

@ -1,8 +1,8 @@
#pragma once
#include "path_types.hpp"
#include <llarp/crypto/encrypted_frame.hpp>
#include <llarp/crypto/types.hpp>
#include <llarp/messages/relay.hpp>
#include <llarp/util/decaying_hashset.hpp>
#include <llarp/util/types.hpp>
@ -22,38 +22,41 @@ 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(std::string)> func) = 0;
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*);
/// return timestamp last remote activity happened at
virtual llarp_time_t
@ -65,8 +68,29 @@ 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>;

@ -8,7 +8,6 @@
namespace llarp::path
{
Path::Path(
Router* rtr,
const std::vector<RemoteRC>& h,
@ -49,7 +48,10 @@ namespace llarp::path
bool
Path::obtain_exit(
SecretKey sk, uint64_t flag, std::string tx_id, std::function<void(std::string)> func)
SecretKey sk,
uint64_t flag,
std::string tx_id,
std::function<void(oxen::quic::message m)> func)
{
return send_path_control_message(
"obtain_exit",
@ -58,7 +60,7 @@ namespace llarp::path
}
bool
Path::close_exit(SecretKey sk, std::string tx_id, std::function<void(std::string)> func)
Path::close_exit(SecretKey sk, std::string tx_id, std::function<void(oxen::quic::message m)> func)
{
return send_path_control_message(
"close_exit", CloseExitMessage::sign_and_serialize(sk, std::move(tx_id)), std::move(func));
@ -69,92 +71,82 @@ namespace llarp::path
const dht::Key_t& location,
bool is_relayed,
uint64_t order,
std::function<void(std::string)> func)
std::function<void(oxen::quic::message m)> 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(std::string)> func)
Path::find_name(std::string name, std::function<void(oxen::quic::message m)> 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)
{
return send_path_control_message(
"find_router", FindRouterMessage::serialize(std::move(rid), false, false), std::move(func));
}
bool
Path::send_path_control_message(
std::string method, std::string body, std::function<void(std::string)> func)
std::string method, std::string body, std::function<void(oxen::quic::message m)> func)
{
oxenc::bt_dict_producer btdp;
btdp.append("BODY", body);
btdp.append("METHOD", method);
auto payload = std::move(btdp).str();
std::string payload;
// TODO: old impl padded messages if smaller than a certain size; do we still want to?
SymmNonce nonce;
{
oxenc::bt_dict_producer btdp;
btdp.append("BODY", body);
btdp.append("METHOD", method);
payload = std::move(btdp).str();
}
TunnelNonce nonce;
nonce.Randomize();
// chacha and mutate nonce for each hop
for (const auto& hop : hops)
{
nonce = crypto::onion(
reinterpret_cast<unsigned char*>(payload.data()),
payload.size(),
hop.shared,
nonce,
hop.nonceXOR);
// 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;
}
auto outer_payload = make_onion_payload(nonce, TXID(), payload);
oxenc::bt_dict_producer outer_dict;
outer_dict.append("NONCE", nonce.ToView());
outer_dict.append("PATHID", TXID().ToView());
outer_dict.append("PAYLOAD", payload);
return router.send_control_message(
upstream(),
"path_control",
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)
std::move(outer_dict).str(),
[response_cb = std::move(func)](oxen::quic::message m) {
if (m)
{
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;
// do path hop logic here
}
});
}
for (const auto& hop : self->hops)
{
nonce = crypto::onion(
reinterpret_cast<unsigned char*>(payload.data()),
payload.size(),
hop.shared,
nonce,
hop.nonceXOR);
}
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);
}
// 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::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
@ -224,9 +216,6 @@ namespace llarp::path
void
Path::EnterState(PathStatus st, llarp_time_t now)
{
if (now == 0s)
now = router.now();
if (st == ePathFailed)
{
_status = st;
@ -297,6 +286,8 @@ 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;
@ -430,6 +421,73 @@ 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;
@ -457,6 +515,47 @@ 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
@ -472,7 +571,6 @@ 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*)
{
@ -496,7 +594,6 @@ namespace llarp::path
return true;
}
*/
template <typename Samples_t>
static llarp_time_t

@ -5,7 +5,9 @@
#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>
@ -31,6 +33,8 @@ namespace llarp
struct TransitHopInfo;
struct PathHopConfig;
using TransitHop_ptr = std::shared_ptr<TransitHop>;
struct Ptr_hash;
struct Endpoint_Hash;
@ -118,6 +122,14 @@ 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;
@ -131,7 +143,7 @@ namespace llarp
}
void
EnterState(PathStatus st, llarp_time_t now = 0s);
EnterState(PathStatus st, llarp_time_t now);
llarp_time_t
ExpireTime() const
@ -174,38 +186,39 @@ namespace llarp
Tick(llarp_time_t now, Router* r);
bool
find_name(std::string name, std::function<void(std::string)> func = nullptr);
find_name(std::string name, std::function<void(oxen::quic::message m)> func = nullptr);
bool
find_router(std::string rid, std::function<void(oxen::quic::message m)> func = nullptr);
bool
find_intro(
const dht::Key_t& location,
bool is_relayed = false,
uint64_t order = 0,
std::function<void(std::string)> func = nullptr);
std::function<void(oxen::quic::message m)> func = nullptr);
bool
close_exit(SecretKey sk, std::string tx_id, std::function<void(std::string)> func = nullptr);
close_exit(
SecretKey sk,
std::string tx_id,
std::function<void(oxen::quic::message m)> func = nullptr);
bool
obtain_exit(
SecretKey sk,
uint64_t flag,
std::string tx_id,
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)
std::function<void(oxen::quic::message m)> func = nullptr);
bool
send_path_control_message(
std::string method,
std::string body,
std::function<void(std::string)> func = nullptr) override;
std::function<void(oxen::quic::message m)> func = nullptr) override;
bool
SendRoutingMessage(std::string payload, Router* r) override;
bool
IsReady() const;
@ -233,6 +246,25 @@ 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);

@ -69,80 +69,195 @@ namespace llarp::path
bool
PathContext::HopIsUs(const RouterID& k) const
{
return _router->pubkey() == k;
return std::equal(_router->pubkey(), _router->pubkey() + PUBKEYSIZE, k.begin());
}
std::vector<std::shared_ptr<Path>>
PathContext::EndpointPathPtrSet
PathContext::FindOwnedPathsWithEndpoint(const RouterID& r)
{
std::vector<std::shared_ptr<Path>> found;
for (const auto& [pathid, path] : own_paths)
EndpointPathPtrSet found;
m_OurPaths.ForEach([&](const Path_ptr& p) {
if (p->Endpoint() == r && p->IsReady())
found.insert(p);
});
return found;
}
template <
typename Lock_t,
typename Map_t,
typename Key_t,
typename CheckValue_t,
typename GetFunc_t,
typename Return_ptr = HopHandler_ptr>
Return_ptr
MapGet(Map_t& map, const Key_t& k, CheckValue_t check, GetFunc_t get)
{
Lock_t lock(map.first);
auto range = map.second.equal_range(k);
for (auto i = range.first; i != range.second; ++i)
{
// 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);
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;
}
return found;
}
void
PathContext::AddOwnPath(PathSet_ptr set, Path_ptr path)
{
set->AddPath(path);
own_paths[path->TXID()] = path;
own_paths[path->RXID()] = path;
MapPut<util::Lock>(m_OurPaths, path->TXID(), path);
MapPut<util::Lock>(m_OurPaths, path->RXID(), path);
}
bool
PathContext::HasTransitHop(const TransitHopInfo& 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;
return MapHas<SyncTransitMap_t::Lock_t>(
m_TransitPaths, info.txID, [info](const std::shared_ptr<TransitHop>& hop) -> bool {
return info == hop->info;
});
}
std::shared_ptr<TransitHop>
PathContext::GetTransitHop(const RouterID& rid, const PathID_t& path_id)
std::optional<std::weak_ptr<TransitHop>>
PathContext::TransitHopByInfo(const TransitHopInfo& info)
{
if (auto itr = transit_hops.find({rid, path_id}); itr != transit_hops.end())
return itr->second;
// 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;
}
return nullptr;
std::optional<std::weak_ptr<TransitHop>>
PathContext::TransitHopByUpstream(const RouterID& upstream, const PathID_t& id)
{
// this is ugly as sin as well
auto own = MapGet<
SyncTransitMap_t::Lock_t,
decltype(m_TransitPaths),
PathID_t,
std::function<bool(const std::shared_ptr<TransitHop>&)>,
std::function<TransitHop*(const std::shared_ptr<TransitHop>&)>,
TransitHop*>(
m_TransitPaths,
id,
[upstream](const auto& hop) -> bool { return hop->info.upstream == upstream; },
[](const auto& hop) -> TransitHop* { return hop.get(); });
if (own)
return own->weak_from_this();
return std::nullopt;
}
Path_ptr
PathContext::GetPath(const PathID_t& path_id)
HopHandler_ptr
PathContext::GetByUpstream(const RouterID& remote, const PathID_t& id)
{
if (auto itr = own_paths.find(path_id); itr != own_paths.end())
return itr->second;
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; });
}
return nullptr;
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; });
}
bool
PathContext::TransitHopPreviousIsRouter(const PathID_t& path_id, const RouterID& otherRouter)
PathContext::TransitHopPreviousIsRouter(const PathID_t& path, const RouterID& otherRouter)
{
return transit_hops.count({otherRouter, path_id});
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;
}
PathSet_ptr
PathContext::GetLocalPathSet(const PathID_t& id)
{
if (auto itr = own_paths.find(id); itr != own_paths.end())
auto& map = m_OurPaths;
util::Lock lock(map.first);
auto itr = map.second.find(id);
if (itr != map.second.end())
{
if (auto parent = itr->second->m_PathSet.lock())
return parent;
@ -156,30 +271,54 @@ namespace llarp::path
return _router->pubkey();
}
std::shared_ptr<TransitHop>
TransitHop_ptr
PathContext::GetPathForTransfer(const PathID_t& id)
{
if (auto itr = transit_hops.find({OurRouterID(), id}); itr != transit_hops.end())
const RouterID us(OurRouterID());
auto& map = m_TransitPaths;
{
return itr->second;
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 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()
{
return transit_hops.size() / 2;
SyncTransitMap_t::Lock_t lock(m_TransitPaths.first);
const auto& map = m_TransitPaths.second;
return map.size() / 2;
}
uint64_t
PathContext::CurrentOwnedPaths(path::PathStatus st)
{
uint64_t num{};
for (auto& own_path : own_paths)
util::Lock lock{m_OurPaths.first};
auto& map = m_OurPaths.second;
for (auto itr = map.begin(); itr != map.end(); ++itr)
{
if (own_path.second->Status() == st)
if (itr->second->Status() == st)
num++;
}
return num / 2;
@ -188,10 +327,8 @@ namespace llarp::path
void
PathContext::PutTransitHop(std::shared_ptr<TransitHop> 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);
MapPut<SyncTransitMap_t::Lock_t>(m_TransitPaths, hop->info.txID, hop);
MapPut<SyncTransitMap_t::Lock_t>(m_TransitPaths, hop->info.rxID, hop);
}
void
@ -201,30 +338,37 @@ namespace llarp::path
path_limits.Decay(now);
{
auto itr = transit_hops.begin();
while (itr != transit_hops.end())
SyncTransitMap_t::Lock_t lock(m_TransitPaths.first);
auto& map = m_TransitPaths.second;
auto itr = map.begin();
while (itr != map.end())
{
if (itr->second->Expired(now))
{
// TODO: this
// _router->outboundMessageHandler().RemovePath(itr->first);
itr = transit_hops.erase(itr);
itr = map.erase(itr);
}
else
{
itr->second->DecayFilters(now);
++itr;
}
}
}
{
for (auto itr = own_paths.begin(); itr != own_paths.end();)
util::Lock lock(m_OurPaths.first);
auto& map = m_OurPaths.second;
auto itr = map.begin();
while (itr != map.end())
{
if (itr->second->Expired(now))
{
itr = own_paths.erase(itr);
itr = map.erase(itr);
}
else
{
itr->second->DecayFilters(now);
++itr;
}
}

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

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

@ -7,13 +7,11 @@
#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
@ -92,9 +90,7 @@ namespace llarp
throw std::runtime_error{std::move(err)};
}
// generate nonceXOR value self->hop->pathKey
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
crypto::shorthash(hop.nonceXOR, hop.shared.data(), hop.shared.size());
hop.upstream = nextHop;
}
@ -121,7 +117,7 @@ namespace llarp
crypto::encryption_keygen(framekey);
SharedSecret shared;
SymmNonce outer_nonce;
TunnelNonce outer_nonce;
outer_nonce.Randomize();
// derive (outer) shared key
@ -428,75 +424,44 @@ namespace llarp
path_cat, "{} building path -> {} : {}", Name(), path->ShortName(), path->HopsString());
oxenc::bt_list_producer frames;
std::vector<std::string> frame_str(path::MAX_LEN);
auto& path_hops = path->hops;
size_t n_hops = path_hops.size();
size_t last_len{0};
// each hop will be able to read the outer part of its frame and decrypt
// the inner part with that information. It will then do an onion step on the
// remaining frames so the next hop can read the outer part of its frame,
// and so on. As this de-onion happens from hop 1 to n, we create and onion
// the frames from hop n downto 1 (i.e. reverse order). The first frame is
// not onioned.
//
// Onion-ing the frames in this way will prevent relays controlled by
// the same entity from knowing they are part of the same path
// (unless they're adjacent in the path; nothing we can do about that obviously).
// i from n_hops downto 0
size_t i = n_hops;
while (i > 0)
for (size_t i = 0; i < n_hops; i++)
{
i--;
bool lastHop = (i == (n_hops - 1));
const auto& nextHop =
lastHop ? path_hops[i].rc.router_id() : path_hops[i + 1].rc.router_id();
PathBuildMessage::setup_hop_keys(path_hops[i], nextHop);
frame_str[i] = PathBuildMessage::serialize(path_hops[i]);
auto frame_str = PathBuildMessage::serialize(path_hops[i]);
// all frames should be the same length...not sure what that is yet
// it may vary if path lifetime is non-default, as that is encoded as an
// integer in decimal, but it should be constant for a given path
if (last_len != 0)
assert(frame_str[i].size() == last_len);
last_len = frame_str[i].size();
assert(frame_str.size() == last_len);
// onion each previously-created frame using the established shared secret and
// onion_nonce = path_hops[i].nonce ^ path_hops[i].nonceXOR, which the transit hop
// will have recovered after decrypting its frame.
// Note: final value passed to crypto::onion is xor factor, but that's for *after* the
// onion round to compute the return value, so we don't care about it.
for (size_t j = n_hops - 1; j > i; j--)
{
auto onion_nonce = path_hops[i].nonce ^ path_hops[i].nonceXOR;
crypto::onion(
reinterpret_cast<unsigned char*>(frame_str[j].data()),
frame_str[j].size(),
path_hops[i].shared,
onion_nonce,
onion_nonce);
}
last_len = frame_str.size();
frames.append(std::move(frame_str));
}
std::string dummy;
dummy.reserve(last_len);
// append dummy frames; path build request must always have MAX_LEN frames
for (i = n_hops; i < path::MAX_LEN; i++)
{
frame_str[i].resize(last_len);
randombytes(reinterpret_cast<uint8_t*>(frame_str[i].data()), frame_str[i].size());
}
for (auto& str : frame_str) // NOLINT
// append dummy frames; path build request must always have MAX_LEN frames
// TODO: with the data structured as it is now (bt-encoded dict as each frame)
// the dummy frames can't be completely random; they need to look like
// normal frames
for (size_t i = 0; i < path::MAX_LEN - n_hops; i++)
{
frames.append(std::move(str));
randombytes(reinterpret_cast<uint8_t*>(dummy.data()), dummy.size());
frames.append(dummy);
}
router->path_context().AddOwnPath(GetSelf(), path);
auto self = GetSelf();
router->path_context().AddOwnPath(self, path);
PathBuildStarted(path);
// TODO:
@ -504,33 +469,30 @@ 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 = [path](oxen::quic::message m) {
try
auto response_cb = [self](oxen::quic::message m) {
if (m)
{
if (m)
{
// TODO: inform success (what this means needs revisiting, badly)
path->EnterState(path::ePathEstablished);
return;
}
if (m.timed_out)
std::string status;
try
{
log::warning(path_cat, "Path build timed out");
oxenc::bt_dict_consumer btdc{m.body()};
status = btdc.require<std::string>("STATUS");
}
else
catch (...)
{
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);
log::warning(path_cat, "Error: Failed to parse path build response!", status);
m.respond(serialize_response({{"STATUS", "EXCEPTION"}}), true);
throw;
}
// TODO: success logic
}
catch (const std::exception& e)
else
{
log::warning(path_cat, "Failed parsing path build response.");
log::warning(path_cat, "Path build request returned failure {}");
// TODO: failure logic
}
// TODO: inform failure (what this means needs revisiting, badly)
path->EnterState(path::ePathFailed);
};
if (not router->send_control_message(
@ -574,6 +536,30 @@ namespace llarp
router->router_profiling().MarkPathTimeout(p.get());
PathSet::HandlePathBuildTimeout(p);
DoPathBuildBackoff();
for (const auto& hop : p->hops)
{
const auto& target = hop.rc.router_id();
// look up router and see if it's still on the network
log::info(path_cat, "Looking up RouterID {} due to path build timeout", target);
router->rc_lookup_handler().get_rc(
target,
[this](auto rid, auto rc, auto success) {
if (success && rc)
{
log::info(path_cat, "Refreshed RouterContact for {}", rid);
router->node_db()->put_rc_if_newer(*rc);
}
else
{
// remove all connections to this router as it's probably not registered anymore
log::warning(path_cat, "Removing router {} due to path build timeout", rid);
router->link_manager().deregister_peer(rid);
router->node_db()->remove_router(rid);
}
},
true);
}
}
void

@ -2,8 +2,6 @@
#include "path.hpp"
#include <llarp/crypto/crypto.hpp>
namespace llarp::path
{
PathSet::PathSet(size_t num) : numDesiredPaths(num)
@ -443,4 +441,16 @@ 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

@ -12,43 +12,21 @@ namespace llarp::path
"[TransitHopInfo tx={} rx={} upstream={} downstream={}]", txID, rxID, upstream, downstream);
}
TransitHop::TransitHop() : AbstractHopHandler{}
{}
void
TransitHop::onion(ustring& data, SymmNonce& nonce, bool randomize) const
{
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
TransitHop::TransitHop()
: AbstractHopHandler{}
, m_UpstreamGather{TRANSIT_HOP_QUEUE_SIZE}
, m_DownstreamGather{TRANSIT_HOP_QUEUE_SIZE}
{
SymmNonce n;
auto& nref = nonce ? *nonce : n;
onion(payload, nref, not nonce);
return path::make_onion_payload(nref, next_id, payload);
m_UpstreamGather.enable();
m_DownstreamGather.enable();
m_UpstreamWorkCounter = 0;
m_DownstreamWorkCounter = 0;
}
bool
TransitHop::send_path_control_message(std::string, std::string, std::function<void(std::string)>)
TransitHop::send_path_control_message(
std::string, std::string, std::function<void(oxen::quic::message m)>)
{
// 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;
}
@ -82,7 +60,6 @@ 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)
{
@ -105,7 +82,157 @@ 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
@ -117,7 +244,8 @@ namespace llarp::path
void
TransitHop::Stop()
{
// TODO: still need this concept?
m_UpstreamGather.disable();
m_DownstreamGather.disable();
}
void

@ -55,29 +55,12 @@ namespace llarp
TransitHopInfo info;
SharedSecret pathKey;
SymmNonce nonceXOR;
ShortHash 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
@ -123,29 +106,47 @@ 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(std::string)> func) override;
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;
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

@ -0,0 +1,151 @@
#include "rc_gossiper.hpp"
#include <llarp/router_contact.hpp>
#include <llarp/util/time.hpp>
namespace llarp
{
// 30 minutes
static constexpr auto RCGossipFilterDecayInterval = 30min;
// (30 minutes * 2) - 5 minutes
static constexpr auto GossipOurRCInterval = (RCGossipFilterDecayInterval * 2) - (5min);
RCGossiper::RCGossiper() : filter(std::chrono::duration_cast<Time_t>(RCGossipFilterDecayInterval))
{}
void
RCGossiper::Init(LinkManager* l, const RouterID& ourID, Router* r)
{
rid = ourID;
link_manager = l;
router = r;
}
bool
RCGossiper::ShouldGossipOurRC(Time_t now) const
{
return now >= (last_rc_gossip + GossipOurRCInterval);
}
bool
RCGossiper::IsOurRC(const LocalRC& rc) const
{
return rc.router_id() == rid;
}
void
RCGossiper::Decay(Time_t now)
{
filter.Decay(now);
}
void
RCGossiper::Forget(const RouterID& pk)
{
filter.Remove(pk);
if (rid == pk)
last_rc_gossip = 0s;
}
TimePoint_t
RCGossiper::NextGossipAt() const
{
if (auto maybe = LastGossipAt())
return *maybe + GossipOurRCInterval;
return DateClock_t::now();
}
std::optional<TimePoint_t>
RCGossiper::LastGossipAt() const
{
if (last_rc_gossip == 0s)
return std::nullopt;
return DateClock_t::time_point{last_rc_gossip};
}
bool
RCGossiper::GossipRC(const LocalRC& rc)
{
// only distribute public routers
if (not rc.is_public_router())
return false;
if (link_manager == nullptr)
return false;
const RouterID pubkey(rc.router_id());
// filter check
if (filter.Contains(pubkey))
return false;
filter.Insert(pubkey);
const auto now = time_now_ms();
// is this our rc?
if (IsOurRC(rc))
{
// should we gossip our rc?
if (not ShouldGossipOurRC(now))
{
// nah drop it
return false;
}
// ya pop it
last_rc_gossip = now;
}
// send a GRCM as gossip method
// DHTImmediateMessage gossip;
// gossip.msgs.emplace_back(new dht::GotRouterMessage(dht::Key_t{}, 0, {rc}, false));
// std::vector<RouterID> gossipTo;
/*
* TODO: gossip RC via libquic
*
// select peers to gossip to
m_LinkManager->ForEachPeer(
[&](const AbstractLinkSession* peerSession, bool) {
// ensure connected session
if (not(peerSession && peerSession->IsEstablished()))
return;
// check if public router
const auto other_rc = peerSession->GetRemoteRC();
if (not other_rc.IsPublicRouter())
return;
gossipTo.emplace_back(other_rc.pubkey);
},
true);
std::unordered_set<RouterID> keys;
// grab the keys we want to use
std::sample(
gossipTo.begin(), gossipTo.end(), std::inserter(keys, keys.end()), MaxGossipPeers,
llarp::csrng);
m_LinkManager->ForEachPeer([&](AbstractLinkSession* peerSession) {
if (not(peerSession && peerSession->IsEstablished()))
return;
// exclude from gossip as we have not selected to use it
if (keys.count(peerSession->GetPubKey()) == 0)
return;
// encode message
AbstractLinkSession::Message_t msg{};
msg.resize(MAX_LINK_MSG_SIZE / 2);
llarp_buffer_t buf(msg);
if (not gossip.BEncode(&buf))
return;
msg.resize(buf.cur - buf.base);
m_router->NotifyRouterEvent<tooling::RCGossipSentEvent>(m_router->pubkey(), rc);
// send message
peerSession->SendMessageBuffer(std::move(msg), nullptr, gossip.Priority());
});
*
*
*/
return true;
}
} // namespace llarp

@ -0,0 +1,57 @@
#pragma once
#include <llarp/router_id.hpp>
#include <llarp/util/decaying_hashset.hpp>
#include <optional>
namespace llarp
{
struct Router;
/// The maximum number of peers we will flood a gossiped RC to when propagating an RC
constexpr size_t MaxGossipPeers = 20;
struct LinkManager;
struct LocalRC;
struct RCGossiper
{
using Time_t = Duration_t;
RCGossiper();
~RCGossiper() = default;
bool
GossipRC(const LocalRC& rc);
void
Decay(Time_t now);
bool
ShouldGossipOurRC(Time_t now) const;
bool
IsOurRC(const LocalRC& rc) const;
void
Init(LinkManager*, const RouterID&, Router*);
void
Forget(const RouterID& router);
TimePoint_t
NextGossipAt() const;
std::optional<TimePoint_t>
LastGossipAt() const;
private:
RouterID rid;
Time_t last_rc_gossip = 0s;
LinkManager* link_manager = nullptr;
util::DecayingHashSet<RouterID> filter;
Router* router;
};
} // namespace llarp

@ -0,0 +1,381 @@
#include "rc_lookup_handler.hpp"
#include "router.hpp"
#include <llarp/crypto/crypto.hpp>
#include <llarp/link/contacts.hpp>
#include <llarp/link/link_manager.hpp>
#include <llarp/nodedb.hpp>
#include <llarp/router_contact.hpp>
#include <llarp/service/context.hpp>
#include <llarp/util/types.hpp>
#include <functional>
#include <iterator>
namespace llarp
{
void
RCLookupHandler::add_valid_router(const RouterID& rid)
{
router->loop()->call([this, rid]() { router_whitelist.insert(rid); });
}
void
RCLookupHandler::remove_valid_router(const RouterID& rid)
{
router->loop()->call([this, rid]() { router_whitelist.erase(rid); });
}
static void
loadColourList(std::unordered_set<RouterID>& beigelist, const std::vector<RouterID>& new_beige)
{
beigelist.clear();
beigelist.insert(new_beige.begin(), new_beige.end());
}
void
RCLookupHandler::set_router_whitelist(
const std::vector<RouterID>& whitelist,
const std::vector<RouterID>& greylist,
const std::vector<RouterID>& greenlist)
{
if (whitelist.empty())
return;
router->loop()->call([this, whitelist, greylist, greenlist]() {
loadColourList(router_whitelist, whitelist);
loadColourList(router_greylist, greylist);
loadColourList(router_greenlist, greenlist);
LogInfo("lokinet service node list now has ", router_whitelist.size(), " active routers");
});
}
bool
RCLookupHandler::has_received_whitelist() const
{
return router->loop()->call_get([this]() { return not router_whitelist.empty(); });
}
std::unordered_set<RouterID>
RCLookupHandler::whitelist() const
{
return router->loop()->call_get([this]() { return router_whitelist; });
}
void
RCLookupHandler::get_rc(const RouterID& rid, RCRequestCallback callback, bool forceLookup)
{
RemoteRC remoteRC;
if (not forceLookup)
{
if (const auto maybe = node_db->get_rc(rid); maybe.has_value())
{
remoteRC = *maybe;
if (callback)
{
callback(rid, remoteRC, true);
}
return;
}
}
auto lookup_cb = [this, callback, rid](oxen::quic::message m) 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 (callback)
callback(rid, std::nullopt, false);
else
link_manager->handle_find_router_error(std::move(m));
}
};
// if we are a client try using the hidden service endpoints
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);
}
bool
RCLookupHandler::is_grey_listed(const RouterID& remote) const
{
if (strict_connect_pubkeys.size() && strict_connect_pubkeys.count(remote) == 0
&& !is_remote_in_bootstrap(remote))
{
return false;
}
if (not isServiceNode)
return false;
return router->loop()->call_get([this, remote]() { return router_greylist.count(remote); });
}
bool
RCLookupHandler::is_green_listed(const RouterID& remote) const
{
return router->loop()->call_get([this, remote]() { return router_greenlist.count(remote); });
}
bool
RCLookupHandler::is_registered(const RouterID& rid) const
{
return router->loop()->call_get([this, rid]() {
return router_whitelist.count(rid) || router_greylist.count(rid)
|| router_greenlist.count(rid);
});
}
bool
RCLookupHandler::is_path_allowed(const RouterID& rid) const
{
return router->loop()->call_get([this, rid]() {
if (strict_connect_pubkeys.size() && strict_connect_pubkeys.count(rid) == 0
&& !is_remote_in_bootstrap(rid))
{
return false;
}
if (not isServiceNode)
return true;
return router_whitelist.count(rid) != 0;
});
}
bool
RCLookupHandler::is_session_allowed(const RouterID& rid) const
{
return router->loop()->call_get([this, rid]() {
if (strict_connect_pubkeys.size() && strict_connect_pubkeys.count(rid) == 0
&& !is_remote_in_bootstrap(rid))
{
return false;
}
if (not isServiceNode)
return true;
return router_whitelist.count(rid) or router_greylist.count(rid);
});
}
bool
RCLookupHandler::check_rc(const RemoteRC& rc) const
{
if (not is_session_allowed(rc.router_id()))
{
contacts->delete_rc_node_async(dht::Key_t{rc.router_id()});
return false;
}
if (not rc.verify())
{
log::info(link_cat, "Invalid RC (rid: {})", rc.router_id());
return false;
}
// update nodedb if required
if (rc.is_public_router())
{
log::info(link_cat, "Adding or updating RC (rid: {}) to nodeDB and DHT", rc.router_id());
node_db->put_rc_if_newer(rc);
contacts->put_rc_node_async(rc);
}
return true;
}
size_t
RCLookupHandler::num_strict_connect_routers() const
{
return strict_connect_pubkeys.size();
}
bool
RCLookupHandler::get_random_whitelist_router(RouterID& rid) const
{
return router->loop()->call_get([this, rid]() mutable {
const auto sz = router_whitelist.size();
auto itr = router_whitelist.begin();
if (sz == 0)
return false;
if (sz > 1)
std::advance(itr, randint() % sz);
rid = *itr;
return true;
});
}
void
RCLookupHandler::periodic_update(llarp_time_t now)
{
// try looking up stale routers
std::unordered_set<RouterID> routersToLookUp;
node_db->VisitInsertedBefore(
[&](const RouterContact& rc) { routersToLookUp.insert(rc.router_id()); },
now - RouterContact::REPUBLISH);
for (const auto& router : routersToLookUp)
{
get_rc(router, nullptr, true);
}
node_db->remove_stale_rcs(boostrap_rid_list, now - RouterContact::STALE);
}
void
RCLookupHandler::explore_network()
{
const size_t known = node_db->num_loaded();
if (bootstrap_rc_list.empty() && known == 0)
{
LogError("we have no bootstrap nodes specified");
}
else if (known <= bootstrap_rc_list.size())
{
for (const auto& rc : bootstrap_rc_list)
{
const auto& rid = rc.router_id();
log::info(link_cat, "Doing explore via bootstrap node: {}", rid);
// TODO: replace this concept
// dht->ExploreNetworkVia(dht::Key_t{rc.pubkey});
}
}
if (isServiceNode)
{
static constexpr size_t LookupPerTick = 5;
std::vector<RouterID> lookup_routers = router->loop()->call_get([this]() {
std::vector<RouterID> lookups;
lookups.reserve(LookupPerTick);
for (const auto& r : router_whitelist)
{
if (not node_db->has_router(r))
lookups.emplace_back(r);
}
return lookups;
});
if (lookup_routers.size() > LookupPerTick)
{
std::shuffle(lookup_routers.begin(), lookup_routers.end(), llarp::csrng);
lookup_routers.resize(LookupPerTick);
}
for (const auto& r : lookup_routers)
get_rc(r, nullptr, true);
return;
}
// service nodes gossip, not explore
if (contacts->router()->is_service_node())
return;
// explore via every connected peer
/*
* TODO: DHT explore via libquic
*
_linkManager->ForEachPeer([&](ILinkSession* s) {
if (!s->IsEstablished())
return;
const RouterContact rc = s->GetRemoteRC();
if (rc.IsPublicRouter() && (_bootstrapRCList.find(rc) == _bootstrapRCList.end()))
{
LogDebug("Doing explore via public node: ", RouterID(rc.pubkey));
_dht->impl->ExploreNetworkVia(dht::Key_t{rc.pubkey});
}
});
*
*
*/
}
void
RCLookupHandler::init(
std::shared_ptr<Contacts> c,
std::shared_ptr<NodeDB> nodedb,
EventLoop_ptr l,
std::function<void(std::function<void()>)> dowork,
LinkManager* linkManager,
service::Context* hiddenServiceContext,
const std::unordered_set<RouterID>& strictConnectPubkeys,
const std::set<RemoteRC>& bootstrapRCList,
bool isServiceNode_arg)
{
contacts = c;
node_db = std::move(nodedb);
loop = std::move(l);
work_func = std::move(dowork);
hidden_service_context = hiddenServiceContext;
strict_connect_pubkeys = strictConnectPubkeys;
bootstrap_rc_list = bootstrapRCList;
link_manager = linkManager;
router = &link_manager->router();
isServiceNode = isServiceNode_arg;
for (const auto& rc : bootstrap_rc_list)
{
boostrap_rid_list.insert(rc.router_id());
}
}
bool
RCLookupHandler::is_remote_in_bootstrap(const RouterID& remote) const
{
for (const auto& rc : bootstrap_rc_list)
{
if (rc.router_id() == remote)
{
return true;
}
}
return false;
}
} // namespace llarp

@ -0,0 +1,143 @@
#pragma once
#include <llarp/router_contact.hpp>
#include <llarp/router_id.hpp>
#include <llarp/util/thread/threading.hpp>
#include <chrono>
#include <list>
#include <set>
#include <unordered_map>
#include <unordered_set>
struct llarp_dht_context;
namespace llarp
{
class NodeDB;
struct Router;
class EventLoop;
namespace service
{
struct Context;
} // namespace service
struct Contacts;
struct LinkManager;
enum class RCRequestResult
{
Success,
InvalidRouter,
RouterNotFound,
BadRC
};
using RCRequestCallback =
std::function<void(const RouterID&, std::optional<RemoteRC>, bool success)>;
struct RCLookupHandler
{
public:
~RCLookupHandler() = default;
void
add_valid_router(const RouterID& router);
void
remove_valid_router(const RouterID& router);
void
set_router_whitelist(
const std::vector<RouterID>& whitelist,
const std::vector<RouterID>& greylist,
const std::vector<RouterID>& greenlist);
bool
has_received_whitelist() const;
void
get_rc(const RouterID& router, RCRequestCallback callback, bool forceLookup = false);
bool
is_path_allowed(const RouterID& remote) const;
bool
is_session_allowed(const RouterID& remote) const;
bool
is_grey_listed(const RouterID& remote) const;
// "greenlist" = new routers (i.e. "green") that aren't fully funded yet
bool
is_green_listed(const RouterID& remote) const;
// registered just means that there is at least an operator stake, but doesn't require the node
// be fully funded, active, or not decommed. (In other words: it is any of the white, grey, or
// green list).
bool
is_registered(const RouterID& remote) const;
bool
check_rc(const RemoteRC& rc) const;
bool
get_random_whitelist_router(RouterID& router) const;
void
periodic_update(llarp_time_t now);
void
explore_network();
size_t
num_strict_connect_routers() const;
void
init(
std::shared_ptr<Contacts> contacts,
std::shared_ptr<NodeDB> nodedb,
std::shared_ptr<EventLoop> loop,
std::function<void(std::function<void()>)> dowork,
LinkManager* linkManager,
service::Context* hiddenServiceContext,
const std::unordered_set<RouterID>& strictConnectPubkeys,
const std::set<RemoteRC>& bootstrapRCList,
bool isServiceNode_arg);
std::unordered_set<RouterID>
whitelist() const;
private:
bool
is_remote_in_bootstrap(const RouterID& remote) const;
std::shared_ptr<Contacts> contacts = nullptr;
std::shared_ptr<NodeDB> node_db;
std::shared_ptr<EventLoop> loop;
std::function<void(std::function<void()>)> work_func = nullptr;
service::Context* hidden_service_context = nullptr;
LinkManager* link_manager = nullptr;
Router* router;
/// explicit whitelist of routers we will connect to directly (not for
/// service nodes)
std::unordered_set<RouterID> strict_connect_pubkeys;
std::set<RemoteRC> bootstrap_rc_list;
std::unordered_set<RouterID> boostrap_rid_list;
// Now that all calls are made through the event loop, any access to these
// booleans is not guarded by a mutex
std::atomic<bool> isServiceNode = false;
// whitelist = active routers
std::unordered_set<RouterID> router_whitelist;
// greylist = fully funded, but decommissioned routers
std::unordered_set<RouterID> router_greylist;
// greenlist = registered but not fully-staked routers
std::unordered_set<RouterID> router_greenlist;
};
} // namespace llarp

@ -11,6 +11,7 @@
#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>
@ -74,6 +75,8 @@ 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");
}
@ -227,19 +230,32 @@ namespace llarp
_link_manager.set_conn_persist(remote, until);
}
std::optional<RouterID>
Router::GetRandomGoodRouter()
void
Router::GossipRCIfNeeded(const LocalRC rc)
{
/// if we are not a service node forget about gossip
if (not is_service_node())
return;
/// wait for random uptime
if (std::chrono::milliseconds{Uptime()} < _randomStartDelay)
return;
_rcGossiper.GossipRC(rc);
}
bool
Router::GetRandomGoodRouter(RouterID& router)
{
if (is_service_node())
{
return node_db()->get_random_whitelist_router();
return _rc_lookup_handler.get_random_whitelist_router(router);
}
if (auto maybe = node_db()->GetRandom([](const auto&) -> bool { return true; }))
{
return maybe->router_id();
router = maybe->router_id();
return true;
}
return std::nullopt;
return false;
}
void
@ -260,6 +276,16 @@ namespace llarp
_link_manager.connect_to(rc);
}
void
Router::lookup_router(RouterID rid, std::function<void(oxen::quic::message)> func)
{
_link_manager.send_control_message(
rid,
"find_router",
FindRouterMessage::serialize(std::move(rid), false, false),
std::move(func));
}
bool
Router::send_data_message(const RouterID& remote, std::string payload)
{
@ -456,25 +482,25 @@ namespace llarp
bool
Router::have_snode_whitelist() const
{
return whitelist_received;
return is_service_node() and _rc_lookup_handler.has_received_whitelist();
}
bool
Router::appears_decommed() const
{
return have_snode_whitelist() and node_db()->greylist().count(pubkey());
return have_snode_whitelist() and _rc_lookup_handler.is_grey_listed(pubkey());
}
bool
Router::appears_funded() const
{
return have_snode_whitelist() and node_db()->is_connection_allowed(pubkey());
return have_snode_whitelist() and _rc_lookup_handler.is_session_allowed(pubkey());
}
bool
Router::appears_registered() const
{
return have_snode_whitelist() and node_db()->get_registered_routers().count(pubkey());
return have_snode_whitelist() and _rc_lookup_handler.is_registered(pubkey());
}
bool
@ -486,7 +512,7 @@ namespace llarp
bool
Router::SessionToRouterAllowed(const RouterID& router) const
{
return node_db()->is_connection_allowed(router);
return _rc_lookup_handler.is_session_allowed(router);
}
bool
@ -497,7 +523,7 @@ namespace llarp
// we are decom'd don't allow any paths outbound at all
return false;
}
return node_db()->is_path_allowed(router);
return _rc_lookup_handler.is_path_allowed(router);
}
size_t
@ -519,6 +545,16 @@ namespace llarp
queue_disk_io([&]() { router_contact.write(our_rc_file); });
}
bool
Router::update_rc()
{
router_contact.resign();
if (is_service_node())
save_rc();
return true;
}
bool
Router::from_config(const Config& conf)
{
@ -646,15 +682,23 @@ namespace llarp
it = bootstrap_rc_list.erase(it);
}
node_db()->set_bootstrap_routers(bootstrap_rc_list);
if (conf.bootstrap.seednode)
LogInfo("we are a seed node");
else
LogInfo("Loaded ", bootstrap_rc_list.size(), " bootstrap routers");
// Init components after relevant config settings loaded
_link_manager.init();
_link_manager.init(&_rc_lookup_handler);
_rc_lookup_handler.init(
_contacts,
_node_db,
_loop,
util::memFn(&Router::queue_work, this),
&_link_manager,
&_hidden_service_context,
strictConnectPubkeys,
bootstrap_rc_list,
_is_service_node);
// FIXME: kludge for now, will be part of larger cleanup effort.
if (_is_service_node)
@ -755,12 +799,12 @@ namespace llarp
" | {} active paths | block {} ",
path_context().CurrentTransitPaths(),
(_rpc_client ? _rpc_client->BlockHeight() : 0));
bool have_gossiped = last_rc_gossip == std::chrono::system_clock::time_point::min();
auto maybe_last = _rcGossiper.LastGossipAt();
fmt::format_to(
out,
" | gossip: (next/last) {} / {}",
short_time_from_now(next_rc_gossip),
have_gossiped ? short_time_from_now(last_rc_gossip) : "never");
short_time_from_now(_rcGossiper.NextGossipAt()),
maybe_last ? short_time_from_now(*maybe_last) : "never");
}
else
{
@ -813,28 +857,36 @@ namespace llarp
report_stats();
}
_rcGossiper.Decay(now);
_rc_lookup_handler.periodic_update(now);
const bool has_whitelist = _rc_lookup_handler.has_received_whitelist();
const bool is_snode = is_service_node();
const bool is_decommed = appears_decommed();
bool should_gossip = appears_funded();
// (relay-only) if we have fetched the relay list from oxend and
// we are registered and funded, we want to gossip our RC periodically
auto now_timepoint = std::chrono::system_clock::time_point(now);
if (is_snode and appears_funded() and (now_timepoint > next_rc_gossip))
if (is_snode
and (router_contact.expires_within_delta(now, std::chrono::milliseconds(randint() % 10000))
or (now - router_contact.timestamp().time_since_epoch()) > rc_regen_interval))
{
log::info(logcat, "regenerating and gossiping RC");
router_contact.resign();
save_rc();
auto view = router_contact.view();
_link_manager.gossip_rc(
pubkey(), std::string{reinterpret_cast<const char*>(view.data()), view.size()});
last_rc_gossip = now_timepoint;
// 1min to 5min before "stale time" is next gossip time
auto random_delta =
std::chrono::seconds{std::uniform_int_distribution<size_t>{60, 300}(llarp::csrng)};
next_rc_gossip = now_timepoint + RouterContact::STALE_AGE - random_delta;
LogInfo("regenerating RC");
if (update_rc())
{
// our rc changed so we should gossip it
should_gossip = true;
// remove our replay entry so it goes out
_rcGossiper.Forget(pubkey());
}
else
LogError("failed to update our RC");
}
if (should_gossip)
{
// if we have the whitelist enabled, we have fetched the list and we are in either
// the white or grey list, we want to gossip our RC
GossipRCIfNeeded(router_contact);
}
// remove RCs for nodes that are no longer allowed by network policy
node_db()->RemoveIf([&](const RemoteRC& rc) -> bool {
// don't purge bootstrap nodes from nodedb
@ -866,7 +918,7 @@ namespace llarp
}
// if we don't have the whitelist yet don't remove the entry
if (not whitelist_received)
if (not has_whitelist)
{
log::debug(logcat, "Skipping check on {}: don't have whitelist yet", rc.router_id());
return false;
@ -875,7 +927,7 @@ namespace llarp
// the whitelist enabled and we got the whitelist
// check against the whitelist and remove if it's not
// in the whitelist OR if there is no whitelist don't remove
if (not node_db()->is_connection_allowed(rc.router_id()))
if (has_whitelist and not _rc_lookup_handler.is_session_allowed(rc.router_id()))
{
log::debug(logcat, "Removing {}: not a valid router", rc.router_id());
return true;
@ -883,9 +935,7 @@ namespace llarp
return false;
});
/* TODO: this behavior seems incorrect, but fixing it will require discussion
*
if (not is_snode or not whitelist_received)
if (not is_snode or not has_whitelist)
{
// find all deregistered relays
std::unordered_set<RouterID> close_peers;
@ -901,18 +951,23 @@ namespace llarp
for (auto& peer : close_peers)
_link_manager.deregister_peer(peer);
}
*/
_link_manager.check_persisting_conns(now);
size_t connected = NumberOfConnectedRouters();
const int interval = is_snode ? 5 : 2;
const auto timepoint_now = std::chrono::steady_clock::now();
if (timepoint_now >= _next_explore_at and not is_decommed)
{
_rc_lookup_handler.explore_network();
_next_explore_at = timepoint_now + std::chrono::seconds(interval);
}
size_t connectToNum = _link_manager.min_connected_routers;
const auto& pinned_edges = _node_db->get_pinned_edges();
const auto pinned_count = pinned_edges.size();
if (pinned_count > 0 && connectToNum > pinned_count)
const auto strictConnect = _rc_lookup_handler.num_strict_connect_routers();
if (strictConnect > 0 && connectToNum > strictConnect)
{
connectToNum = pinned_count;
connectToNum = strictConnect;
}
if (is_snode and now >= _next_decomm_warning)
@ -960,6 +1015,14 @@ namespace llarp
_node_db->Tick(now);
std::set<dht::Key_t> peer_keys;
for_each_connection(
[&peer_keys](link::Connection& conn) { peer_keys.emplace(conn.remote_rc.router_id()); });
_contacts->rc_nodes()->RemoveIf(
[&peer_keys](const dht::Key_t& k) -> bool { return peer_keys.count(k) == 0; });
paths.ExpirePaths(now);
// update tick timestamp
@ -972,20 +1035,13 @@ namespace llarp
return _link_manager.get_random_connected(result);
}
const std::unordered_set<RouterID>&
Router::get_whitelist() const
{
return _node_db->whitelist();
}
void
Router::set_router_whitelist(
const std::vector<RouterID>& whitelist,
const std::vector<RouterID>& greylist,
const std::vector<RouterID>& unfundedlist)
{
node_db()->set_router_whitelist(whitelist, greylist, unfundedlist);
whitelist_received = true;
_rc_lookup_handler.set_router_whitelist(whitelist, greylist, unfundedlist);
}
bool
@ -1023,6 +1079,7 @@ namespace llarp
log::info(logcat, "Router initialized as service node!");
const RouterID us = pubkey();
_rcGossiper.Init(&_link_manager, us, this);
// relays do not use profiling
router_profiling().Disable();
}
@ -1233,6 +1290,7 @@ 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(); });

@ -1,5 +1,7 @@
#pragma once
#include "rc_gossiper.hpp"
#include "rc_lookup_handler.hpp"
#include "route_poker.hpp"
#include <llarp/bootstrap.hpp>
@ -57,6 +59,8 @@ namespace llarp
static constexpr size_t INTROSET_STORAGE_REDUNDANCY =
(INTROSET_RELAY_REDUNDANCY * INTROSET_REQS_PER_RELAY);
static constexpr size_t RC_LOOKUP_STORAGE_REDUNDANCY{4};
struct Contacts;
struct Router : std::enable_shared_from_this<Router>
@ -118,16 +122,17 @@ namespace llarp
const llarp_time_t _randomStartDelay;
std::shared_ptr<rpc::LokidRpcClient> _rpc_client;
bool whitelist_received{false};
oxenmq::address rpc_addr;
Profiling _router_profiling;
fs::path _profile_file;
LinkManager _link_manager{*this};
std::chrono::system_clock::time_point last_rc_gossip{
std::chrono::system_clock::time_point::min()};
std::chrono::system_clock::time_point next_rc_gossip{
std::chrono::system_clock::time_point::min()};
RCLookupHandler _rc_lookup_handler;
RCGossiper _rcGossiper;
/// how often do we resign our RC? milliseconds.
// TODO: make configurable
llarp_time_t rc_regen_interval = 1h;
// should we be sending padded messages every interval?
bool send_padding = false;
@ -145,6 +150,9 @@ namespace llarp
void
save_rc();
bool
update_rc();
bool
from_config(const Config& conf);
@ -155,6 +163,9 @@ namespace llarp
void
for_each_connection(std::function<void(link::Connection&)> func);
void
lookup_router(RouterID rid, std::function<void(oxen::quic::message)> = nullptr);
void
connect_to(const RouterID& rid);
@ -200,6 +211,12 @@ namespace llarp
return _link_manager;
}
RCLookupHandler&
rc_lookup_handler()
{
return _rc_lookup_handler;
}
inline int
outbound_udp_socket() const
{
@ -275,8 +292,11 @@ namespace llarp
util::StatusObject
ExtractSummaryStatus() const;
const std::unordered_set<RouterID>&
get_whitelist() const;
std::unordered_set<RouterID>
router_whitelist() const
{
return _rc_lookup_handler.whitelist();
}
void
set_router_whitelist(
@ -360,14 +380,17 @@ namespace llarp
std::string
status_line();
void
GossipRCIfNeeded(const LocalRC rc);
void
InitInboundLinks();
void
InitOutboundLinks();
std::optional<RouterID>
GetRandomGoodRouter();
bool
GetRandomGoodRouter(RouterID& r);
/// initialize us as a service node
/// return true on success

@ -56,18 +56,19 @@ namespace llarp
/// Timespans for RCs:
/// How long (from its signing time) before an RC is considered "stale". Relays republish
/// their RCs slightly more frequently than this so that ideally this won't happen.
static constexpr auto STALE_AGE = 6h;
/// How long (from its signing time) before an RC becomes "outdated". Outdated records are used
/// (e.g. for path building) only if there are no newer records available, such as might be
/// How long (relative to its timestamp) before an RC becomes stale. Stale records are used
/// (e.g. for path building) only if there are no non-stale records available, such as might be
/// the case when a client has been turned off for a while.
static constexpr auto OUTDATED_AGE = 12h;
static constexpr auto STALE = 12h;
/// How long before an RC becomes invalid (and thus deleted).
static constexpr auto LIFETIME = 30 * 24h;
/// How long before a relay updates and re-publish its RC to the network. (Relays can
/// re-publish more frequently than this if needed; this is meant to apply only if there are no
/// changes i.e. just to push out a new confirmation of the details).
static constexpr auto REPUBLISH = STALE / 2 - 5min;
ustring_view
view() const
{

@ -4,6 +4,7 @@
#include <llarp/crypto/crypto.hpp>
#include <llarp/crypto/types.hpp>
#include <llarp/util/meta/memfn.hpp>
#include <utility>
@ -42,9 +43,6 @@ 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);
@ -75,6 +73,5 @@ namespace llarp::service
{
LogError("failed to encrypt and sign");
}
*/
}
} // namespace llarp::service

@ -139,50 +139,63 @@ namespace llarp::service
auth = AuthInfo{token},
ranges,
result_handler,
poker = router()->route_poker()](std::string name_result, bool success) mutable {
if (not success)
poker = router()->route_poker()](oxen::quic::message m) mutable {
if (m)
{
result_handler(false, "Exit {} not found!"_format(name));
return;
}
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_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 (auto saddr = service::Address(); saddr.FromString(name))
{
ptr->SetAuthInfoForEndpoint(saddr, auth);
ptr->MarkAddressOutbound(saddr);
if (poker)
poker->put_up();
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;
}
result_handler(success, result);
};
// 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);
ctx->send_auth_async(apply_result);
},
ptr->PathAlignmentTimeout());
if (poker)
poker->put_up();
if (not result)
result_handler(false, "Could not build path to {} ({})"_format(name_result, name));
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
{
result_handler(false, "Exit {} not found!"_format(name));
}
});
}
@ -200,22 +213,20 @@ 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)](
std::string name_result, bool success) mutable {
if (!success)
name, [this, resultHandler, service = std::move(service)](oxen::quic::message m) mutable {
if (!m)
return resultHandler({});
std::string name;
try
{
oxenc::bt_dict_consumer btdc{name_result};
oxenc::bt_dict_consumer btdc{m.body()};
name = btdc.require<std::string>("NAME");
}
catch (...)
{
log::warning(link_cat, "Failed to parse find name response!");
return resultHandler({});
throw;
}
auto saddr = service::Address();
@ -305,24 +316,35 @@ namespace llarp::service
{
auto& name = item.first;
lookup_name(
name, [this, name, info = item.second](std::string name_result, bool success) mutable {
if (not success)
return;
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;
}
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(name_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(result))
{
if (maybe_range.has_value())
_exit_map.Insert(*maybe_range, saddr);
if (maybe_auth.has_value())
SetAuthInfoForEndpoint(saddr, *maybe_auth);
}
}
});
}
}
}
@ -776,7 +798,7 @@ namespace llarp::service
}
void
Endpoint::lookup_name(std::string name, std::function<void(std::string, bool)> func)
Endpoint::lookup_name(std::string name, std::function<void(oxen::quic::message)> func)
{
// TODO: so fuck all this?
@ -816,34 +838,37 @@ 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, response_cb);
path->find_name(name, func);
}
}
void
Endpoint::EnsureRouterIsKnown(const RouterID& rid)
{
if (rid.IsZero())
return;
if (!router()->node_db()->has_router(rid))
{
lookup_router(rid);
}
}
bool
Endpoint::lookup_router(RouterID rid, std::function<void(oxen::quic::message)> 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;
}
return false;
}
void
@ -1229,8 +1254,7 @@ namespace llarp::service
this);
_state->snode_sessions[snode] = session;
}
if (not router()->node_db()->has_rc(snode))
return false;
EnsureRouterIsKnown(snode);
auto range = nodeSessions.equal_range(snode);
auto itr = range.first;
while (itr != range.second)
@ -1303,43 +1327,29 @@ 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, got_it](std::string resp) mutable {
// asking many, use only first successful
if (*got_it)
return;
std::string introset;
try
path->find_intro(location, false, 0, [this, hook](oxen::quic::message m) mutable {
if (m)
{
oxenc::bt_dict_consumer btdc{resp};
auto status = btdc.require<std::string_view>(messages::STATUS_KEY);
if (status != "OK"sv)
std::string introset;
try
{
log::info(link_cat, "Error in find intro set response: {}", status);
return;
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;
}
introset = btdc.require<std::string>("INTROSET");
}
catch (...)
{
log::warning(link_cat, "Failed to parse find name response!");
throw;
}
service::EncryptedIntroSet enc{introset};
router()->contacts()->services()->PutNode(std::move(enc));
service::EncryptedIntroSet enc{introset};
router()->contacts()->services()->PutNode(std::move(enc));
// TODO: finish this
/*
if (good)
*got_it = true;
*/
// TODO: finish this
}
});
}
return hookAdded;
@ -1422,7 +1432,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)
@ -1442,6 +1452,8 @@ namespace llarp::service
// if (item.second->SendRoutingMessage(*item.first, r))
// ConvoTagTX(item.first->protocol_frame_msg.convo_tag);
// }
UpstreamFlush(r);
}
std::optional<ConvoTag>

@ -229,9 +229,18 @@ namespace llarp
bool
ProcessDataMessage(std::shared_ptr<ProtocolMessage> msg);
/// ensure that we know a router, looks up if it doesn't
void
EnsureRouterIsKnown(const RouterID& router);
// "find router" via closest path
bool
lookup_router(RouterID router, std::function<void(oxen::quic::message)> func = nullptr);
// "find name"
void
lookup_name(std::string name, std::function<void(std::string, bool)> func = nullptr) override;
lookup_name(
std::string name, std::function<void(oxen::quic::message)> func = nullptr) override;
// "find introset?"
void

@ -167,7 +167,7 @@ namespace llarp::service
return std::nullopt;
IntroSet i{other_i};
encrypted.nonce.Randomize();
encrypted.nounce.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.nonce);
crypto::xchacha20(reinterpret_cast<uint8_t*>(bte.data()), bte.size(), k, encrypted.nounce);
std::memcpy(encrypted.introsetPayload.data(), bte.data(), bte.size());

@ -14,7 +14,7 @@ namespace llarp::service
std::string s)
: signedAt{signed_at}
, introsetPayload{reinterpret_cast<uint8_t*>(enc_payload.data()), enc_payload.size()}
, nonce{reinterpret_cast<uint8_t*>(nonce.data())}
, nounce{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"));
nonce.from_string(btdc.require<std::string>("n"));
nounce.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", nonce.ToView());
btdp.append("n", nounce.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", nonce, read, key, buf))
if (not BEncodeMaybeReadDictEntry("n", nounce, 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,
nonce,
nounce,
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, nonce);
crypto::xchacha20(reinterpret_cast<uint8_t*>(payload.data()), payload.size(), k, nounce);
return IntroSet{payload};
}

@ -138,7 +138,7 @@ namespace llarp::service
PubKey derivedSigningKey;
llarp_time_t signedAt = 0s;
ustring introsetPayload;
SymmNonce nonce;
TunnelNonce nounce;
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.nonce, lhs.sig)
== std::tie(rhs.signedAt, rhs.derivedSigningKey, rhs.nonce, rhs.sig);
return std::tie(lhs.signedAt, lhs.derivedSigningKey, lhs.nounce, lhs.sig)
== std::tie(rhs.signedAt, rhs.derivedSigningKey, rhs.nounce, rhs.sig);
}
inline bool

@ -5,7 +5,6 @@
#include "endpoint_util.hpp"
#include "protocol_type.hpp"
#include <llarp/nodedb.hpp>
#include <llarp/router/router.hpp>
#include <algorithm>
@ -158,7 +157,6 @@ 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()
{
@ -175,7 +173,7 @@ namespace llarp::service
for (const auto& path : paths)
{
path->find_intro(location, false, relayOrder, [this](std::string resp) mutable {
path->find_intro(location, false, relayOrder, [this](oxen::quic::message m) mutable {
if (marked_bad)
{
log::info(link_cat, "Outbound context has been marked bad (whatever that means)");
@ -184,51 +182,43 @@ namespace llarp::service
updatingIntroSet = false;
// TODO: this parsing is probably elsewhere, may need DRYed
std::string introset;
try
if (m)
{
oxenc::bt_dict_consumer btdc{resp};
auto status = btdc.require<std::string_view>(messages::STATUS_KEY);
if (status != "OK"sv)
std::string introset;
try
{
log::info(link_cat, "Error in find intro set response: {}", status);
return;
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;
}
introset = btdc.require<std::string>("INTROSET");
}
catch (...)
{
log::warning(link_cat, "Failed to parse find name response!");
throw;
}
service::EncryptedIntroSet enc{introset};
const auto intro = enc.decrypt(PubKey{addr.as_array()});
service::EncryptedIntroSet enc{introset};
const auto intro = enc.decrypt(PubKey{addr.as_array()});
if (intro.time_signed == 0s)
{
log::warning(link_cat, "{} recieved introset with zero timestamp");
return;
}
if (current_intro.time_signed > intro.time_signed)
{
log::info(link_cat, "{} received outdated introset; dropping", Name());
return;
}
// don't "shift" to the same intro we're already using...
if (current_intro == intro)
return;
if (intro.time_signed == 0s)
{
log::warning(link_cat, "{} recieved introset with zero timestamp");
return;
}
if (current_intro.time_signed > intro.time_signed)
{
log::info(link_cat, "{} received outdated introset; dropping", Name());
return;
}
if (intro.IsExpired(llarp::time_now_ms()))
{
log::warning(link_cat, "{} received expired introset", Name());
return;
}
if (intro.IsExpired(llarp::time_now_ms()))
{
log::warning(link_cat, "{} received expired introset", Name());
return;
current_intro = intro;
ShiftIntroRouter();
}
current_intro = intro;
ShiftIntroRouter();
});
}
}
@ -317,6 +307,9 @@ namespace llarp::service
}
}
}
// lookup router in intro if set and unknown
if (not next_intro.router.IsZero())
ep.EnsureRouterIsKnown(next_intro.router);
if (ReadyToSend())
{
@ -405,18 +398,6 @@ namespace llarp::service
std::vector<Introduction> intros = current_intro.intros;
// don't consider intros for which we don't have the RC for the pivot
auto itr = intros.begin();
while (itr != intros.end())
{
if (not ep.router()->node_db()->has_rc(itr->router))
{
itr = intros.erase(itr);
continue;
}
itr++;
}
if (intros.size() > 1)
{
std::shuffle(intros.begin(), intros.end(), llarp::csrng);
@ -445,6 +426,7 @@ namespace llarp::service
{
if (ep.SnodeBlacklist().count(intro.router))
continue;
ep.EnsureRouterIsKnown(intro.router);
if (intro.ExpiresSoon(now))
continue;
if (next_intro != intro)
@ -552,9 +534,8 @@ namespace llarp::service
ex->msg.proto = ProtocolType::Auth;
ex->hook = [this, path, cb = std::move(func)](auto frame) mutable {
auto hook = [&, frame, path](std::string resp) {
auto hook = [&, frame, path](oxen::quic::message) {
// TODO: revisit this
(void)resp;
ep.HandleHiddenServiceFrame(path, *frame.get());
};

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

@ -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"
SymmNonce nonce;
KeyExchangeNonce nonce;
Signature sig;
PathID_t path_id;
service::ConvoTag convo_tag;

@ -0,0 +1,72 @@
#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
Loading…
Cancel
Save