* initial relay side lns

* fix typo

* add reserved names and refactor test for dns

* lns name decryption

* all wired up (allegedly)

* refact to use service::EncryptedName for LNS responses to include nonce with ciphertext

* fully rwemove tag_lookup_job

* replace lns cache with DecayingHashTable

* check for lns name validity against the following rules:

* not localhost.loki, loki.loki, or snode.loki

* if it contains no dash then max 32 characters long, not including the .loki tld (and also assuming a leading subdomain has been stripped)

* These are from general DNS requirements, and also enforced in
registrations:

* Must be all [A-Za-z0-9-]. (A-Z will be lower-cased by the RPC call).

* cannot start or end with a -

* max 63 characters long if it does contain a dash

* cannot contain -- in the third and fourth characters unless it starts with xn--

* handle timeout in name lookup job by calling the right handler with std::nullopt
pull/1355/head
Jeff 4 years ago committed by GitHub
parent f5e5066bd5
commit 21930cf667
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -131,6 +131,8 @@ add_library(liblokinet
dht/messages/gotintro.cpp
dht/messages/gotrouter.cpp
dht/messages/pubintro.cpp
dht/messages/findname.cpp
dht/messages/gotname.cpp
dht/publishservicejob.cpp
dht/recursiverouterlookup.cpp
dht/serviceaddresslookup.cpp
@ -198,12 +200,12 @@ add_library(liblokinet
service/intro_set.cpp
service/intro.cpp
service/lookup.cpp
service/name.cpp
service/outbound_context.cpp
service/protocol.cpp
service/router_lookup_job.cpp
service/sendcontext.cpp
service/session.cpp
service/tag_lookup_job.cpp
service/tag.cpp
)

@ -24,6 +24,10 @@ namespace llarp
{
virtual ~Crypto() = 0;
/// decrypt cipherText name given the key generated from name
virtual std::optional<AlignedBuffer<32>>
maybe_decrypt_name(std::string_view ciphertext, SymmNonce nounce, std::string_view name) = 0;
/// xchacha symmetric cipher
virtual bool
xchacha20(const llarp_buffer_t&, const SharedSecret&, const TunnelNonce&) = 0;

@ -5,10 +5,12 @@
#include <sodium/crypto_scalarmult_ed25519.h>
#include <sodium/crypto_stream_xchacha20.h>
#include <sodium/crypto_core_ed25519.h>
#include <sodium/crypto_aead_xchacha20poly1305.h>
#include <sodium/randombytes.h>
#include <sodium/utils.h>
#include <util/mem.hpp>
#include <util/endian.hpp>
#include <util/str.hpp>
#include <cassert>
#include <cstring>
@ -93,6 +95,39 @@ namespace llarp
srand(seed);
}
std::optional<AlignedBuffer<32>>
CryptoLibSodium::maybe_decrypt_name(
std::string_view ciphertext, SymmNonce nounce, std::string_view name)
{
const auto payloadsize = ciphertext.size() - crypto_aead_xchacha20poly1305_ietf_ABYTES;
if (payloadsize != 32)
return {};
SharedSecret derivedKey{};
ShortHash namehash{};
const llarp_buffer_t namebuf(reinterpret_cast<const char*>(name.data()), name.size());
if (not shorthash(namehash, namebuf))
return {};
if (not hmac(derivedKey.data(), namebuf, namehash))
return {};
AlignedBuffer<32> result{};
if (crypto_aead_xchacha20poly1305_ietf_decrypt(
result.data(),
nullptr,
nullptr,
reinterpret_cast<const byte_t*>(ciphertext.data()),
ciphertext.size(),
nullptr,
0,
nounce.data(),
derivedKey.data())
== -1)
{
return {};
}
return result;
}
bool
CryptoLibSodium::xchacha20(
const llarp_buffer_t& buff, const SharedSecret& k, const TunnelNonce& n)

@ -13,6 +13,11 @@ namespace llarp
~CryptoLibSodium() override = default;
/// decrypt cipherText given the key generated from name
std::optional<AlignedBuffer<32>>
maybe_decrypt_name(
std::string_view ciphertext, SymmNonce nounce, std::string_view name) override;
/// xchacha symmetric cipher
bool
xchacha20(const llarp_buffer_t&, const SharedSecret&, const TunnelNonce&) override;

@ -7,6 +7,8 @@
#include <dht/messages/gotintro.hpp>
#include <dht/messages/gotrouter.hpp>
#include <dht/messages/pubintro.hpp>
#include <dht/messages/findname.hpp>
#include <dht/messages/gotname.hpp>
namespace llarp
{
@ -44,6 +46,12 @@ namespace llarp
llarp::LogDebug("Handle DHT message ", *strbuf.base, " relayed=", relayed);
switch (*strbuf.base)
{
case 'N':
msg = std::make_unique<FindNameMessage>(From, Key_t{}, 0);
break;
case 'M':
msg = std::make_unique<GotNameMessage>(From, 0, service::EncryptedName{});
break;
case 'F':
msg = std::make_unique<FindIntroMessage>(From, relayed, 0);
break;

@ -84,8 +84,9 @@ namespace llarp
}
if (not tagName.Empty())
{
return false;
}
// bad request (request for zero-key)
if (location.IsZero())
{

@ -0,0 +1,66 @@
#include <dht/messages/findname.hpp>
#include <lokimq/bt_serialize.h>
#include <dht/context.hpp>
#include <dht/messages/gotname.hpp>
#include <router/abstractrouter.hpp>
#include <rpc/lokid_rpc_client.hpp>
#include <path/path_context.hpp>
#include <routing/dht_message.hpp>
namespace llarp::dht
{
FindNameMessage::FindNameMessage(const Key_t& from, Key_t namehash, uint64_t txid)
: IMessage(from), NameHash(std::move(namehash)), TxID(txid)
{
}
bool
FindNameMessage::BEncode(llarp_buffer_t* buf) const
{
const auto data = lokimq::bt_serialize(
lokimq::bt_dict{{"A", "N"sv},
{"H", std::string_view{(char*)NameHash.data(), NameHash.size()}},
{"T", TxID}});
return buf->write(data.begin(), data.end());
}
bool
FindNameMessage::DecodeKey(const llarp_buffer_t& key, llarp_buffer_t* val)
{
if (key == "H")
{
return NameHash.BDecode(val);
}
if (key == "T")
{
return bencode_read_integer(val, &TxID);
}
return bencode_discard(val);
}
bool
FindNameMessage::HandleMessage(struct llarp_dht_context* dht, std::vector<Ptr_t>& replies) const
{
(void)replies;
auto r = dht->impl->GetRouter();
if (pathID.IsZero() or not r->IsServiceNode())
return false;
r->RpcClient()->LookupLNSNameHash(NameHash, [r, pathID = pathID, TxID = TxID](auto maybe) {
auto path = r->pathContext().GetPathForTransfer(pathID);
if (path == nullptr)
return;
routing::DHTMessage msg;
if (maybe.has_value())
{
msg.M.emplace_back(new GotNameMessage(dht::Key_t{}, TxID, *maybe));
}
else
{
msg.M.emplace_back(new GotNameMessage(dht::Key_t{}, TxID, service::EncryptedName{}));
}
path->SendRoutingMessage(msg, r);
});
return true;
}
} // namespace llarp::dht

@ -0,0 +1,24 @@
#pragma once
#include <dht/message.hpp>
namespace llarp::dht
{
struct FindNameMessage : public IMessage
{
explicit FindNameMessage(const Key_t& from, Key_t namehash, uint64_t txid);
bool
BEncode(llarp_buffer_t* buf) const override;
bool
DecodeKey(const llarp_buffer_t& key, llarp_buffer_t* val) override;
bool
HandleMessage(struct llarp_dht_context* dht, std::vector<Ptr_t>& replies) const override;
Key_t NameHash;
uint64_t TxID;
};
} // namespace llarp::dht

@ -0,0 +1,55 @@
#include <dht/messages/gotname.hpp>
#include <lokimq/bt_serialize.h>
namespace llarp::dht
{
constexpr size_t NameSizeLimit = 128;
GotNameMessage::GotNameMessage(const Key_t& from, uint64_t txid, service::EncryptedName data)
: IMessage(from), result(std::move(data)), TxID(txid)
{
if (result.ciphertext.size() > NameSizeLimit)
throw std::invalid_argument("name data too big");
}
bool
GotNameMessage::BEncode(llarp_buffer_t* buf) const
{
const std::string nonce((const char*)result.nonce.data(), result.nonce.size());
const auto data = lokimq::bt_serialize(
lokimq::bt_dict{{"A", "M"sv}, {"D", result.ciphertext}, {"N", nonce}, {"T", TxID}});
return buf->write(data.begin(), data.end());
}
bool
GotNameMessage::DecodeKey(const llarp_buffer_t& key, llarp_buffer_t* val)
{
if (key == "D")
{
llarp_buffer_t str{};
if (not bencode_read_string(val, &str))
return false;
if (str.sz > NameSizeLimit)
return false;
result.ciphertext.resize(str.sz);
std::copy_n(str.cur, str.sz, result.ciphertext.data());
return true;
}
if (key == "N")
{
return result.nonce.BDecode(val);
}
if (key == "T")
{
return bencode_read_integer(val, &TxID);
}
return bencode_discard(val);
}
bool
GotNameMessage::HandleMessage(struct llarp_dht_context*, std::vector<Ptr_t>&) const
{
return false;
}
} // namespace llarp::dht

@ -0,0 +1,25 @@
#pragma once
#include <dht/message.hpp>
#include <service/name.hpp>
namespace llarp::dht
{
struct GotNameMessage : public IMessage
{
explicit GotNameMessage(const Key_t& from, uint64_t txid, service::EncryptedName data);
bool
BEncode(llarp_buffer_t* buf) const override;
bool
DecodeKey(const llarp_buffer_t& key, llarp_buffer_t* val) override;
bool
HandleMessage(struct llarp_dht_context* dht, std::vector<Ptr_t>& replies) const override;
service::EncryptedName result;
uint64_t TxID;
};
} // namespace llarp::dht

@ -17,7 +17,7 @@ namespace llarp
struct MessageHeader : public Serialize
{
const static size_t Size = 12;
static constexpr size_t Size = 12;
MessageHeader() = default;

@ -2,6 +2,7 @@
#include <dns/name.hpp>
#include <net/net.hpp>
#include <net/ip.hpp>
#include <util/str.hpp>
#include <algorithm>
#include <sstream>
@ -39,7 +40,7 @@ namespace llarp
}
bool
EncodeName(llarp_buffer_t* buf, const Name_t& name)
EncodeName(llarp_buffer_t* buf, Name_t name)
{
std::stringstream ss;
if (name.size() && name[name.size() - 1] == '.')
@ -71,7 +72,7 @@ namespace llarp
}
bool
DecodePTR(const Name_t& name, huint128_t& ip)
DecodePTR(Name_t name, huint128_t& ip)
{
bool isV6 = false;
auto pos = name.find(".in-addr.arpa");
@ -123,5 +124,17 @@ namespace llarp
return false;
}
bool
NameIsReserved(Name_t name)
{
const std::vector<std::string_view> reserved_names = {
"snode.loki"sv, "loki.loki"sv, "snode.loki."sv, "loki.loki."sv};
for (const auto& reserved : reserved_names)
{
if (ends_with(name, reserved))
return true;
}
return false;
}
} // namespace dns
} // namespace llarp

@ -18,10 +18,13 @@ namespace llarp
/// encode name to buffer
bool
EncodeName(llarp_buffer_t* buf, const Name_t& name);
EncodeName(llarp_buffer_t* buf, Name_t name);
bool
DecodePTR(const Name_t& name, huint128_t& ip);
DecodePTR(Name_t name, huint128_t& ip);
bool
NameIsReserved(Name_t name);
} // namespace dns
} // namespace llarp

@ -16,6 +16,7 @@
#include <service/outbound_context.hpp>
#include <service/endpoint_state.hpp>
#include <service/outbound_context.hpp>
#include <service/name.hpp>
#include <util/meta/memfn.hpp>
#include <util/thread/logic.hpp>
#include <nodedb.hpp>
@ -330,7 +331,6 @@ namespace llarp
2s);
};
std::string qname;
if (msg.answers.size() > 0)
{
const auto& answer = msg.answers[0];
@ -352,9 +352,11 @@ namespace llarp
llarp_buffer_t buf(answer.rData);
if (not dns::DecodeName(&buf, qname, true))
return false;
service::Address addr;
if (not addr.FromString(qname))
return false;
auto replyMsg = std::make_shared<dns::Message>(clear_dns_message(msg));
return ReplyToLokiDNSWhenReady(addr, replyMsg, false);
}
@ -364,7 +366,7 @@ namespace llarp
llarp::LogWarn("bad number of dns questions: ", msg.questions.size());
return false;
}
qname = msg.questions[0].Name();
std::string qname = msg.questions[0].Name();
if (msg.questions[0].qtype == dns::qTypeMX)
{
@ -468,6 +470,21 @@ namespace llarp
addr.as_array(), std::make_shared<dns::Message>(msg), isV6);
}
}
else if (ends_with(qname, ".loki") and service::NameIsValid(qname))
{
return LookupNameAsync(
qname,
[msg = std::make_shared<dns::Message>(msg), isV6, reply, ReplyToLokiDNSWhenReady](
auto maybe) {
if (not maybe.has_value())
{
msg->AddNXReply();
reply(*msg);
return;
}
ReplyToLokiDNSWhenReady(*maybe, msg, isV6);
});
}
else
msg.AddNXReply();

@ -24,6 +24,7 @@ namespace llarp
{
struct GotIntroMessage;
struct GotRouterMessage;
struct GotNameMessage;
} // namespace dht
namespace path
@ -212,6 +213,12 @@ namespace llarp
return false;
}
/// override me in subtype
virtual bool HandleGotNameMessage(std::shared_ptr<const dht::GotNameMessage>)
{
return false;
}
virtual routing::IMessageHandler*
GetDHTHandler()
{

@ -215,6 +215,44 @@ namespace llarp
return ftr.get();
}
void
LokidRpcClient::LookupLNSNameHash(
dht::Key_t namehash,
std::function<void(std::optional<service::EncryptedName>)> resultHandler)
{
LogDebug("Looking Up LNS NameHash ", namehash);
const nlohmann::json req{{"type", 2}, {"name_hash", namehash.ToHex()}};
Request(
"rpc.lns_resolve",
[r = m_Router, resultHandler](bool success, std::vector<std::string> data) {
std::optional<service::EncryptedName> maybe = std::nullopt;
if (success)
{
try
{
service::EncryptedName result;
const auto j = nlohmann::json::parse(data[1]);
result.ciphertext = lokimq::from_hex(j["encrypted_value"].get<std::string>());
const auto nonce = lokimq::from_hex(j["nonce"].get<std::string>());
if (nonce.size() != result.nonce.size())
{
throw std::invalid_argument(stringify(
"nonce size mismatch: ", nonce.size(), " != ", result.nonce.size()));
}
std::copy_n(nonce.data(), nonce.size(), result.nonce.data());
maybe = result;
}
catch (std::exception& ex)
{
LogError("failed to parse response from lns lookup: ", ex.what());
}
}
LogicCall(r->logic(), [resultHandler, maybe]() { resultHandler(maybe); });
},
req.dump());
}
void
LokidRpcClient::HandleGetPeerStats(lokimq::Message& msg)
{

@ -5,8 +5,8 @@
#include <lokimq/lokimq.h>
#include <lokimq/address.h>
#include <crypto/types.hpp>
#include <crypto/types.hpp>
#include <dht/key.hpp>
#include <service/name.hpp>
namespace llarp
{
@ -30,6 +30,11 @@ namespace llarp
SecretKey
ObtainIdentityKey();
void
LookupLNSNameHash(
dht::Key_t namehash,
std::function<void(std::optional<service::EncryptedName>)> resultHandler);
private:
/// called when we have connected to lokid via lokimq
void

@ -5,8 +5,10 @@
#include <dht/context.hpp>
#include <dht/key.hpp>
#include <dht/messages/findintro.hpp>
#include <dht/messages/findname.hpp>
#include <dht/messages/findrouter.hpp>
#include <dht/messages/gotintro.hpp>
#include <dht/messages/gotname.hpp>
#include <dht/messages/gotrouter.hpp>
#include <dht/messages/pubintro.hpp>
#include <nodedb.hpp>
@ -201,6 +203,8 @@ namespace llarp
RegenAndPublishIntroSet();
}
// expire name cache
m_state->nameCache.Decay(now);
// expire snode sessions
EndpointUtil::ExpireSNodeSessions(now, m_state->m_SNodeSessions);
// expire pending tx
@ -274,7 +278,7 @@ namespace llarp
}
std::unique_ptr<IServiceLookup> lookup = std::move(itr->second);
lookups.erase(itr);
if (not lookup->HandleResponse(remote))
if (not lookup->HandleIntrosetResponse(remote))
lookups.emplace(msg->txid, std::move(lookup));
return true;
}
@ -510,7 +514,7 @@ namespace llarp
}
bool
HandleResponse(const std::set<EncryptedIntroSet>& response) override
HandleIntrosetResponse(const std::set<EncryptedIntroSet>& response) override
{
if (not response.empty())
m_Endpoint->IntroSetPublished();
@ -725,6 +729,82 @@ namespace llarp
return true;
}
struct LookupNameJob : public IServiceLookup
{
std::function<void(std::optional<Address>)> handler;
ShortHash namehash;
LookupNameJob(
Endpoint* parent,
uint64_t id,
std::string lnsName,
std::function<void(std::optional<Address>)> resultHandler)
: IServiceLookup(parent, id, lnsName), handler(resultHandler)
{
CryptoManager::instance()->shorthash(
namehash, llarp_buffer_t(lnsName.c_str(), lnsName.size()));
}
std::shared_ptr<routing::IMessage>
BuildRequestMessage() override
{
auto msg = std::make_shared<routing::DHTMessage>();
msg->M.emplace_back(std::make_unique<dht::FindNameMessage>(
dht::Key_t{}, dht::Key_t{namehash.as_array()}, txid));
return msg;
}
bool
HandleNameResponse(std::optional<Address> addr) override
{
handler(addr);
return true;
}
void
HandleTimeout() override
{
HandleNameResponse(std::nullopt);
}
};
bool
Endpoint::LookupNameAsync(std::string name, std::function<void(std::optional<Address>)> handler)
{
auto& cache = m_state->nameCache;
const auto maybe = cache.Get(name);
if (maybe.has_value())
{
handler(maybe);
return true;
}
auto path = PickRandomEstablishedPath();
auto job = new LookupNameJob(this, GenTXID(), name, handler);
return job->SendRequestViaPath(path, m_router);
}
bool
Endpoint::HandleGotNameMessage(std::shared_ptr<const dht::GotNameMessage> msg)
{
auto& lookups = m_state->m_PendingLookups;
auto itr = lookups.find(msg->TxID);
if (itr == lookups.end())
return false;
// decrypt entry
const auto maybe = msg->result.Decrypt(itr->second->name);
if (maybe.has_value())
{
// put cache entry for result
m_state->nameCache.Put(itr->second->name, *maybe);
}
// inform result
itr->second->HandleNameResponse(maybe);
lookups.erase(itr);
return true;
}
void
Endpoint::EnsureRouterIsKnown(const RouterID& router)
{

@ -15,7 +15,7 @@
#include <service/protocol.hpp>
#include <service/sendcontext.hpp>
#include <service/session.hpp>
#include <service/tag_lookup_job.hpp>
#include <service/lookup.hpp>
#include <hook/ihook.hpp>
#include <util/compare_ptr.hpp>
#include <util/thread/logic.hpp>
@ -178,6 +178,9 @@ namespace llarp
bool
HandleGotRouterMessage(std::shared_ptr<const dht::GotRouterMessage> msg) override;
bool
HandleGotNameMessage(std::shared_ptr<const dht::GotNameMessage> msg) override;
bool
HandleHiddenServiceFrame(path::Path_ptr p, const service::ProtocolFrame& msg);
@ -222,6 +225,9 @@ namespace llarp
bool
LookupRouterAnon(RouterID router, RouterLookupHandler handler);
bool
LookupNameAsync(std::string name, std::function<void(std::optional<Address>)> resultHandler);
/// called on event loop pump
virtual void
Pump(llarp_time_t now);

@ -7,9 +7,9 @@
#include <service/pendingbuffer.hpp>
#include <service/router_lookup_job.hpp>
#include <service/session.hpp>
#include <service/tag_lookup_job.hpp>
#include <service/endpoint_types.hpp>
#include <util/compare_ptr.hpp>
#include <util/decaying_hashtable.hpp>
#include <util/status.hpp>
#include <memory>
@ -80,7 +80,7 @@ namespace llarp
OutboundSessions_t m_OutboundSessions;
std::unordered_map<Tag, CachedTagResult, Tag::Hash> m_PrefetchedTags;
util::DecayingHashTable<std::string, Address, std::hash<std::string>> nameCache;
bool
Configure(const NetworkConfig& conf);

@ -54,6 +54,8 @@ namespace llarp
using PathEnsureHook = std::function<void(Address, OutboundContext*)>;
using LNSNameCache = std::unordered_map<std::string, std::pair<Address, llarp_time_t>>;
} // namespace service
} // namespace llarp

@ -47,7 +47,7 @@ namespace llarp
std::unique_ptr<IServiceLookup> lookup = std::move(itr->second);
LogWarn(lookup->name, " timed out txid=", lookup->txid);
lookup->HandleResponse({});
lookup->HandleTimeout();
itr = lookups.erase(itr);
}
}

@ -24,7 +24,7 @@ namespace llarp
}
bool
HiddenServiceAddressLookup::HandleResponse(const std::set<EncryptedIntroSet>& results)
HiddenServiceAddressLookup::HandleIntrosetResponse(const std::set<EncryptedIntroSet>& results)
{
std::optional<IntroSet> found;
const Address remote(rootkey);

@ -30,7 +30,7 @@ namespace llarp
~HiddenServiceAddressLookup() override = default;
bool
HandleResponse(const std::set<EncryptedIntroSet>& results) override;
HandleIntrosetResponse(const std::set<EncryptedIntroSet>& results) override;
std::shared_ptr<routing::IMessage>
BuildRequestMessage() override;

@ -26,13 +26,25 @@ namespace llarp
IServiceLookup() = delete;
virtual ~IServiceLookup() = default;
/// handle lookup result
/// handle lookup result for introsets
virtual bool
HandleResponse(const std::set<EncryptedIntroSet>&)
HandleIntrosetResponse(const std::set<EncryptedIntroSet>&)
{
return false;
}
/// handle lookup result for introsets
virtual bool HandleNameResponse(std::optional<Address>)
{
return false;
}
virtual void
HandleTimeout()
{
HandleIntrosetResponse({});
}
/// determine if this request has timed out
bool
IsTimedOut(llarp_time_t now, llarp_time_t timeout = 20s) const

@ -0,0 +1,66 @@
#include <service/name.hpp>
#include <crypto/crypto.hpp>
#include <util/str.hpp>
namespace llarp::service
{
std::optional<Address>
EncryptedName::Decrypt(std::string_view name) const
{
if (ciphertext.empty())
return std::nullopt;
const auto crypto = CryptoManager::instance();
const auto maybe = crypto->maybe_decrypt_name(ciphertext, nonce, name);
if (maybe.has_value())
return Address{*maybe};
return std::nullopt;
}
bool
NameIsValid(std::string_view lnsName)
{
// strip off .loki suffix
lnsName = lnsName.substr(0, lnsName.find_last_of('.'));
// ensure chars are sane
for (const auto ch : lnsName)
{
if (ch == '-')
continue;
if (ch == '.')
continue;
if (ch >= 'a' and ch <= 'z')
continue;
if (ch >= '0' and ch <= '9')
continue;
return false;
}
// split into domain parts
const auto parts = split(lnsName, ".");
// get root domain
const auto primaryName = parts[parts.size() - 1];
constexpr size_t MaxNameLen = 32;
constexpr size_t MaxPunycodeNameLen = 63;
// check against lns name blacklist
if (primaryName == "localhost")
return false;
if (primaryName == "loki")
return false;
if (primaryName == "snode")
return false;
// check for dashes
if (primaryName.find("-") == std::string_view::npos)
return primaryName.size() <= MaxNameLen;
// check for dashes and end or beginning
if (*primaryName.begin() == '-' or *(primaryName.end() - 1) == '-')
return false;
// check for punycode name length
if (primaryName.size() > MaxPunycodeNameLen)
return false;
// check for xn--
return (primaryName[2] == '-' and primaryName[3] == '-')
? (primaryName[0] == 'x' and primaryName[1] == 'n')
: true;
}
} // namespace llarp::service

@ -0,0 +1,20 @@
#pragma once
#include <crypto/types.hpp>
#include <service/address.hpp>
namespace llarp::service
{
struct EncryptedName
{
SymmNonce nonce;
std::string ciphertext;
std::optional<Address>
Decrypt(std::string_view name) const;
};
/// check if an lns name complies with the registration rules
bool
NameIsValid(std::string_view name);
} // namespace llarp::service

@ -1,50 +0,0 @@
#include <service/tag_lookup_job.hpp>
#include <dht/messages/findintro.hpp>
#include <routing/dht_message.hpp>
#include <service/endpoint.hpp>
namespace llarp
{
namespace service
{
bool
CachedTagResult::HandleResponse(const std::set<EncryptedIntroSet>&)
{
return true;
}
void
CachedTagResult::Expire(llarp_time_t now)
{
auto itr = result.begin();
while (itr != result.end())
{
if (itr->IsExpired(now))
{
itr = result.erase(itr);
lastModified = now;
}
else
{
++itr;
}
}
}
std::shared_ptr<routing::IMessage>
CachedTagResult::BuildRequestMessage(uint64_t txid)
{
auto msg = std::make_shared<routing::DHTMessage>();
msg->M.emplace_back(std::make_unique<dht::FindIntroMessage>(tag, txid));
lastRequest = m_parent->Now();
return msg;
}
TagLookupJob::TagLookupJob(Endpoint* parent, CachedTagResult* result)
: IServiceLookup(parent, parent->GenTXID(), "taglookup"), m_result(result)
{
}
} // namespace service
} // namespace llarp

@ -1,75 +0,0 @@
#ifndef LLARP_SERVICE_TAG_LOOKUP_JOB_HPP
#define LLARP_SERVICE_TAG_LOOKUP_JOB_HPP
#include <routing/message.hpp>
#include <service/intro_set.hpp>
#include <service/lookup.hpp>
#include <service/tag.hpp>
#include <util/types.hpp>
#include <set>
namespace llarp
{
namespace service
{
struct Endpoint;
struct CachedTagResult
{
static constexpr auto TTL = 10s;
llarp_time_t lastRequest = 0s;
llarp_time_t lastModified = 0s;
std::set<EncryptedIntroSet> result;
Tag tag;
Endpoint* m_parent;
CachedTagResult(const Tag& t, Endpoint* p) : tag(t), m_parent(p)
{
}
~CachedTagResult() = default;
void
Expire(llarp_time_t now);
bool
ShouldRefresh(llarp_time_t now) const
{
if (now <= lastRequest)
return false;
return (now - lastRequest) > TTL;
}
std::shared_ptr<routing::IMessage>
BuildRequestMessage(uint64_t txid);
bool
HandleResponse(const std::set<EncryptedIntroSet>& results);
};
struct TagLookupJob : public IServiceLookup
{
TagLookupJob(Endpoint* parent, CachedTagResult* result);
~TagLookupJob() override = default;
std::shared_ptr<routing::IMessage>
BuildRequestMessage() override
{
return m_result->BuildRequestMessage(txid);
}
bool
HandleResponse(const std::set<EncryptedIntroSet>& results) override
{
return m_result->HandleResponse(results);
}
CachedTagResult* m_result;
};
} // namespace service
} // namespace llarp
#endif

@ -0,0 +1,67 @@
#pragma once
#include <util/time.hpp>
#include <unordered_map>
namespace llarp::util
{
template <typename Key_t, typename Value_t, typename Hash_t = typename Key_t::Hash>
struct DecayingHashTable
{
DecayingHashTable(std::chrono::milliseconds cacheInterval = 1h) : m_CacheInterval(cacheInterval)
{
}
void
Decay(llarp_time_t now)
{
EraseIf([&](const auto& item) { return item.second.second + m_CacheInterval <= now; });
}
bool
Has(const Key_t& k) const
{
return m_Values.find(k) != m_Values.end();
}
/// return true if inserted
/// return false if not inserted
bool
Put(Key_t key, Value_t value, llarp_time_t now = 0s)
{
if (now == 0s)
now = llarp::time_now_ms();
return m_Values.try_emplace(std::move(key), std::make_pair(std::move(value), now)).second;
}
std::optional<Value_t>
Get(Key_t k) const
{
const auto itr = m_Values.find(k);
if (itr == m_Values.end())
return std::nullopt;
return itr->second.first;
}
private:
template <typename Predicate_t>
void
EraseIf(Predicate_t pred)
{
for (auto i = m_Values.begin(), last = m_Values.end(); i != last;)
{
if (pred(*i))
{
i = m_Values.erase(i);
}
else
{
++i;
}
}
}
llarp_time_t m_CacheInterval;
std::unordered_map<Key_t, std::pair<Value_t, llarp_time_t>, Hash_t> m_Values;
};
} // namespace llarp::util

@ -27,7 +27,6 @@ add_executable(testAll
dht/test_llarp_dht_node.cpp
dht/test_llarp_dht_tx.cpp
dht/test_llarp_dht_txowner.cpp
dns/test_llarp_dns_dns.cpp
llarp_test.cpp
net/test_llarp_net.cpp
router/test_llarp_router_version.cpp
@ -63,6 +62,7 @@ add_subdirectory(Catch2)
add_executable(catchAll
nodedb/test_nodedb.cpp
path/test_path.cpp
dns/test_llarp_dns_dns.cpp
regress/2020-06-08-key-backup-bug.cpp
util/test_llarp_util_bits.cpp
util/test_llarp_util_printer.cpp
@ -74,6 +74,7 @@ add_executable(catchAll
config/test_llarp_config_output.cpp
net/test_ip_address.cpp
net/test_sock_addr.cpp
service/test_llarp_service_name.cpp
exit/test_llarp_exit_context.cpp
iwp/test_iwp_session.cpp
check_main.cpp)

@ -11,6 +11,9 @@ namespace llarp
{
struct MockCrypto final : public Crypto
{
MOCK_METHOD3(maybe_decrypt_name,std::optional<AlignedBuffer<32>>(std::string_view, llarp::SymmNonce, std::string_view));
MOCK_METHOD3(xchacha20,
bool(const llarp_buffer_t &, const SharedSecret &,
const TunnelNonce &));

@ -1,5 +1,4 @@
#include <gtest/gtest.h>
#include <catch2/catch.hpp>
#include <dns/dns.hpp>
#include <dns/message.hpp>
#include <dns/name.hpp>
@ -10,113 +9,98 @@
#include <algorithm>
struct DNSLibTest : public ::testing::Test
{
const std::string tld = ".loki";
std::array< byte_t, 1500 > mem;
llarp_buffer_t buf;
DNSLibTest() : buf(mem)
{
Rewind();
std::fill(mem.begin(), mem.end(), '$');
}
void
Rewind()
{
buf.cur = buf.base;
}
};
constexpr auto tld = ".loki";
TEST_F(DNSLibTest, TestHasTLD)
TEST_CASE("Test Has TLD", "[dns]")
{
llarp::dns::Question question;
question.qname = "a.loki.";
ASSERT_TRUE(question.HasTLD(tld));
CHECK(question.HasTLD(tld));
question.qname = "a.loki..";
ASSERT_FALSE(question.HasTLD(tld));
CHECK(not question.HasTLD(tld));
question.qname = "bepis.loki.";
ASSERT_TRUE(question.HasTLD(tld));
CHECK(question.HasTLD(tld));
question.qname = "bepis.logi.";
ASSERT_FALSE(question.HasTLD(tld));
CHECK(not question.HasTLD(tld));
question.qname = "a.net.";
ASSERT_FALSE(question.HasTLD(tld));
CHECK(not question.HasTLD(tld));
question.qname = "a.boki.";
ASSERT_FALSE(question.HasTLD(tld));
CHECK(not question.HasTLD(tld));
question.qname = "t.co.";
ASSERT_FALSE(question.HasTLD(tld));
CHECK(not question.HasTLD(tld));
};
TEST_F(DNSLibTest, TestIsLocalhost)
TEST_CASE("Test Is Localhost.loki", "[dns]")
{
llarp::dns::Question question;
question.qname = "localhost.loki.";
ASSERT_TRUE(question.IsLocalhost());
CHECK(question.IsLocalhost());
question.qname = "foo.localhost.loki.";
ASSERT_TRUE(question.IsLocalhost());
CHECK(question.IsLocalhost());
question.qname = "foo.bar.localhost.loki.";
ASSERT_TRUE(question.IsLocalhost());
CHECK(question.IsLocalhost());
question.qname = "something.loki.";
ASSERT_FALSE(question.IsLocalhost());
CHECK(not question.IsLocalhost());
question.qname = "localhost.something.loki.";
ASSERT_FALSE(question.IsLocalhost());
CHECK(not question.IsLocalhost());
question.qname = "notlocalhost.loki.";
ASSERT_FALSE(question.IsLocalhost());
CHECK(not question.IsLocalhost());
};
TEST_F(DNSLibTest, TestGetSubdomains)
TEST_CASE("Test Get Subdomains" , "[dns]")
{
llarp::dns::Question question;
std::string expected;
question.qname = "localhost.loki.";
expected = "";
ASSERT_EQ(question.Subdomains(), expected);
CHECK(question.Subdomains() == expected);
question.qname = "foo.localhost.loki.";
expected = "foo";
ASSERT_EQ(question.Subdomains(), expected);
CHECK(question.Subdomains() == expected);
question.qname = "foo.bar.localhost.loki.";
expected = "foo.bar";
ASSERT_EQ(question.Subdomains(), expected);
CHECK(question.Subdomains() == expected);
// not legal, but test it anyway
question.qname = ".localhost.loki.";
expected = "";
ASSERT_EQ(question.Subdomains(), expected);
CHECK(question.Subdomains() == expected);
question.qname = ".loki.";
expected = "";
ASSERT_EQ(question.Subdomains(), expected);
CHECK(question.Subdomains() == expected);
question.qname = "loki.";
expected = "";
ASSERT_EQ(question.Subdomains(), expected);
CHECK(question.Subdomains() == expected);
question.qname = ".";
expected = "";
ASSERT_EQ(question.Subdomains(), expected);
CHECK(question.Subdomains() == expected);
question.qname = "";
expected = "";
ASSERT_EQ(question.Subdomains(), expected);
CHECK(question.Subdomains() == expected);
};
TEST_F(DNSLibTest, TestPTR)
TEST_CASE("Test PTR records", "[dns]")
{
llarp::huint128_t ip = {0};
llarp::huint128_t expected =
llarp::net::ExpandV4(llarp::ipaddr_ipv4_bits(10, 10, 10, 1));
ASSERT_TRUE(llarp::dns::DecodePTR("1.10.10.10.in-addr.arpa.", ip));
ASSERT_EQ(ip, expected);
CHECK(llarp::dns::DecodePTR("1.10.10.10.in-addr.arpa.", ip));
CHECK(ip == expected);
}
TEST_F(DNSLibTest, TestSerializeHeader)
TEST_CASE("Test Serialize Header", "[dns]")
{
std::array<byte_t, 1500> data{};
llarp_buffer_t buf(data);
llarp::dns::MessageHeader hdr, other;
hdr.id = 0x1234;
hdr.fields = (1 << 15);
@ -124,110 +108,86 @@ TEST_F(DNSLibTest, TestSerializeHeader)
hdr.an_count = 1;
hdr.ns_count = 0;
hdr.ar_count = 0;
ASSERT_TRUE(hdr.Encode(&buf));
ASSERT_TRUE((buf.cur - buf.base) == llarp::dns::MessageHeader::Size);
Rewind();
ASSERT_TRUE(other.Decode(&buf));
ASSERT_TRUE(hdr == other);
ASSERT_TRUE(other.id == 0x1234);
ASSERT_TRUE(other.fields == (1 << 15));
CHECK(hdr.Encode(&buf));
CHECK((buf.cur - buf.base) == llarp::dns::MessageHeader::Size);
// rewind
buf.cur = buf.base;
CHECK(other.Decode(&buf));
CHECK(hdr == other);
CHECK(other.id == 0x1234);
CHECK(other.fields == (1 << 15));
}
TEST_F(DNSLibTest, TestSerializeName)
TEST_CASE("Test Serialize Name" , "[dns]")
{
const llarp::dns::Name_t name = "whatever.tld";
const llarp::dns::Name_t expected = "whatever.tld.";
llarp::dns::Name_t other;
Rewind();
ASSERT_TRUE(llarp::dns::EncodeName(&buf, name));
Rewind();
ASSERT_EQ(buf.base[0], 8);
ASSERT_EQ(buf.base[1], 'w');
ASSERT_EQ(buf.base[2], 'h');
ASSERT_EQ(buf.base[3], 'a');
ASSERT_EQ(buf.base[4], 't');
ASSERT_EQ(buf.base[5], 'e');
ASSERT_EQ(buf.base[6], 'v');
ASSERT_EQ(buf.base[7], 'e');
ASSERT_EQ(buf.base[8], 'r');
ASSERT_EQ(buf.base[9], 3);
ASSERT_EQ(buf.base[10], 't');
ASSERT_EQ(buf.base[11], 'l');
ASSERT_EQ(buf.base[12], 'd');
ASSERT_EQ(buf.base[13], 0);
ASSERT_TRUE(llarp::dns::DecodeName(&buf, other));
ASSERT_EQ(expected, other);
std::array<byte_t, 1500> data{};
llarp_buffer_t buf(data);
CHECK(llarp::dns::EncodeName(&buf, name));
buf.cur = buf.base;
CHECK(buf.base[0] == 8);
CHECK(buf.base[1] == 'w');
CHECK(buf.base[2] == 'h');
CHECK(buf.base[3] == 'a');
CHECK(buf.base[4] == 't');
CHECK(buf.base[5] == 'e');
CHECK(buf.base[6] == 'v');
CHECK(buf.base[7] == 'e');
CHECK(buf.base[8] == 'r');
CHECK(buf.base[9] == 3);
CHECK(buf.base[10] == 't');
CHECK(buf.base[11] == 'l');
CHECK(buf.base[12] == 'd');
CHECK(buf.base[13] == 0);
CHECK(llarp::dns::DecodeName(&buf, other));
CHECK(expected == other);
}
TEST_F(DNSLibTest, TestSerializeQuestion)
TEST_CASE("Test serialize question", "[dns]")
{
const std::string name = "whatever.tld";
const std::string expected_name = name + ".";
llarp::dns::Question q, other;
std::array<byte_t, 1500> data{};
llarp_buffer_t buf(data);
q.qname = name;
q.qclass = 1;
q.qtype = 1;
ASSERT_TRUE(q.Encode(&buf));
Rewind();
ASSERT_TRUE(other.Decode(&buf));
ASSERT_EQ(other.qname, expected_name);
ASSERT_EQ(q.qclass, other.qclass);
ASSERT_EQ(q.qtype, other.qtype);
CHECK(q.Encode(&buf));
buf.cur = buf.base;
CHECK(other.Decode(&buf));
CHECK(other.qname == expected_name);
CHECK(q.qclass == other.qclass);
CHECK(q.qtype == other.qtype);
}
/*
TEST_F(DNSLibTest, TestSerializeMessage)
TEST_CASE("Test Encode/Decode RData" , "[dns]")
{
llarp::dns::Question expected_question;
expected_question.qname = "whatever.tld.";
expected_question.qclass = 1;
expected_question.qtype = 1;
llarp::dns::MessageHeader hdr, otherHdr;
hdr.id = 0xfeed;
hdr.fields = (1 << 15);
hdr.qd_count = 1;
hdr.an_count = 0;
hdr.ns_count = 0;
hdr.ar_count = 0;
llarp::dns::Message m(hdr);
m.hdr_id = 0x1234;
m.hdr_fields = (1 << 15);
auto& q = m.questions[0];
q.qname = "whatever.tld";
q.qclass = 1;
q.qtype = 1;
m.AddINReply({1}, false);
ASSERT_EQ(m.questions.size(), 1U);
ASSERT_EQ(m.answers.size(), 1U);
ASSERT_TRUE(m.Encode(&buf));
Rewind();
ASSERT_TRUE(otherHdr.Decode(&buf));
llarp::dns::Message other(otherHdr);
ASSERT_TRUE(buf.cur - buf.base == llarp::dns::MessageHeader::Size);
ASSERT_TRUE(other.Decode(&buf));
ASSERT_EQ(other.questions.size(), 1U);
ASSERT_EQ(expected_question.qname, other.questions[0].qname);
ASSERT_EQ(expected_question.qclass, other.questions[0].qclass);
ASSERT_EQ(expected_question.qtype, other.questions[0].qtype);
ASSERT_TRUE(expected_question == other.questions[0]);
ASSERT_EQ(other.answers.size(), 1U);
ASSERT_EQ(other.answers[0].rData.size(), 4U);
}
*/
std::array<byte_t, 1500> data{};
llarp_buffer_t buf(data);
TEST_F(DNSLibTest, TestEncodeDecode_RData)
{
static constexpr size_t rdatasize = 32;
llarp::dns::RR_RData_t rdata(rdatasize);
std::fill(rdata.begin(), rdata.end(), 'a');
llarp::dns::RR_RData_t other_rdata;
ASSERT_TRUE(llarp::dns::EncodeRData(&buf, rdata));
ASSERT_TRUE(buf.cur - buf.base == rdatasize + sizeof(uint16_t));
Rewind();
ASSERT_TRUE(llarp::dns::DecodeRData(&buf, other_rdata));
ASSERT_TRUE(rdata == other_rdata);
CHECK(llarp::dns::EncodeRData(&buf, rdata));
CHECK(buf.cur - buf.base == rdatasize + sizeof(uint16_t));
buf.cur = buf.base;
CHECK(llarp::dns::DecodeRData(&buf, other_rdata));
CHECK(rdata == other_rdata);
}

@ -0,0 +1,43 @@
#include "catch2/catch.hpp"
#include <crypto/crypto_libsodium.hpp>
#include <service/name.hpp>
#include <lokimq/hex.h>
using namespace std::literals;
TEST_CASE("Test LNS name decrypt", "[lns]")
{
llarp::sodium::CryptoLibSodium crypto;
constexpr auto recordhex = "0ba76cbfdb6dc8f950da57ae781912f31c8ad0c55dbf86b88cb0391f563261a9656571a817be4092969f8a78ee0fcee260424acb4a1f4bbdd27348b71de006b6152dd04ed11bf3c4"sv;
const auto recordbin = lokimq::from_hex(recordhex);
CHECK(not recordbin.empty());
llarp::SymmNonce n{};
std::vector<byte_t> ciphertext{};
const auto len = recordbin.size() - n.size();
std::copy_n(recordbin.cbegin() + len, n.size(), n.data());
std::copy_n(recordbin.cbegin(), len, std::back_inserter(ciphertext));
const auto maybe = crypto.maybe_decrypt_name(std::string_view{reinterpret_cast<const char *>(ciphertext.data()), ciphertext.size()}, n, "jason.loki");
CHECK(maybe.has_value());
const llarp::service::Address addr{*maybe};
CHECK(addr.ToString() == "azfoj73snr9f3neh5c6sf7rtbaeabyxhr1m4un5aydsmsrxo964o.loki");
}
TEST_CASE("Test LNS validity", "[lns]")
{
CHECK(not llarp::service::NameIsValid("loki.loki"));
CHECK(not llarp::service::NameIsValid("snode.loki"));
CHECK(not llarp::service::NameIsValid("localhost.loki"));
CHECK(not llarp::service::NameIsValid("gayballs22.loki.loki"));
CHECK(not llarp::service::NameIsValid("-loki.loki"));
CHECK(not llarp::service::NameIsValid("super-mario-gayballs-.loki"));
CHECK(not llarp::service::NameIsValid("bn--lolexdeeeeee.loki"));
CHECK(not llarp::service::NameIsValid("2222222222a-.loki"));
CHECK(not llarp::service::NameIsValid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.loki"));
CHECK(llarp::service::NameIsValid("xn--animewasindeedamistake.loki"));
CHECK(llarp::service::NameIsValid("memerionos.loki"));
CHECK(llarp::service::NameIsValid("whyis.xn--animehorrible.loki"));
CHECK(llarp::service::NameIsValid("the.goog.loki"));
CHECK(llarp::service::NameIsValid("420.loki"));
}
Loading…
Cancel
Save