From 21930cf667d09f24b5f8a74ac2d7ccd256186909 Mon Sep 17 00:00:00 2001 From: Jeff Date: Thu, 17 Sep 2020 15:18:08 -0400 Subject: [PATCH] LNS (#1342) * 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 --- llarp/CMakeLists.txt | 4 +- llarp/crypto/crypto.hpp | 4 + llarp/crypto/crypto_libsodium.cpp | 35 +++ llarp/crypto/crypto_libsodium.hpp | 5 + llarp/dht/message.cpp | 8 + llarp/dht/messages/findintro.cpp | 3 +- llarp/dht/messages/findname.cpp | 66 ++++++ llarp/dht/messages/findname.hpp | 24 ++ llarp/dht/messages/gotname.cpp | 55 +++++ llarp/dht/messages/gotname.hpp | 25 ++ llarp/dns/message.hpp | 2 +- llarp/dns/name.cpp | 17 +- llarp/dns/name.hpp | 7 +- llarp/handlers/tun.cpp | 21 +- llarp/path/pathset.hpp | 7 + llarp/rpc/lokid_rpc_client.cpp | 38 +++ llarp/rpc/lokid_rpc_client.hpp | 9 +- llarp/service/endpoint.cpp | 84 ++++++- llarp/service/endpoint.hpp | 8 +- llarp/service/endpoint_state.hpp | 4 +- llarp/service/endpoint_types.hpp | 2 + llarp/service/endpoint_util.cpp | 2 +- .../service/hidden_service_address_lookup.cpp | 2 +- .../service/hidden_service_address_lookup.hpp | 2 +- llarp/service/lookup.hpp | 16 +- llarp/service/name.cpp | 66 ++++++ llarp/service/name.hpp | 20 ++ llarp/service/tag_lookup_job.cpp | 50 ---- llarp/service/tag_lookup_job.hpp | 75 ------ llarp/util/decaying_hashtable.hpp | 67 ++++++ test/CMakeLists.txt | 3 +- test/crypto/mock_crypto.hpp | 3 + test/dns/test_llarp_dns_dns.cpp | 220 +++++++----------- test/service/test_llarp_service_name.cpp | 43 ++++ 34 files changed, 720 insertions(+), 277 deletions(-) create mode 100644 llarp/dht/messages/findname.cpp create mode 100644 llarp/dht/messages/findname.hpp create mode 100644 llarp/dht/messages/gotname.cpp create mode 100644 llarp/dht/messages/gotname.hpp create mode 100644 llarp/service/name.cpp create mode 100644 llarp/service/name.hpp delete mode 100644 llarp/service/tag_lookup_job.cpp delete mode 100644 llarp/service/tag_lookup_job.hpp create mode 100644 llarp/util/decaying_hashtable.hpp create mode 100644 test/service/test_llarp_service_name.cpp diff --git a/llarp/CMakeLists.txt b/llarp/CMakeLists.txt index c5693977b..54a1e0f45 100644 --- a/llarp/CMakeLists.txt +++ b/llarp/CMakeLists.txt @@ -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 ) diff --git a/llarp/crypto/crypto.hpp b/llarp/crypto/crypto.hpp index 823908965..79b417d78 100644 --- a/llarp/crypto/crypto.hpp +++ b/llarp/crypto/crypto.hpp @@ -24,6 +24,10 @@ namespace llarp { virtual ~Crypto() = 0; + /// decrypt cipherText name given the key generated from name + virtual std::optional> + 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; diff --git a/llarp/crypto/crypto_libsodium.cpp b/llarp/crypto/crypto_libsodium.cpp index b7eb38032..2e89e8934 100644 --- a/llarp/crypto/crypto_libsodium.cpp +++ b/llarp/crypto/crypto_libsodium.cpp @@ -5,10 +5,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include @@ -93,6 +95,39 @@ namespace llarp srand(seed); } + std::optional> + 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(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(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) diff --git a/llarp/crypto/crypto_libsodium.hpp b/llarp/crypto/crypto_libsodium.hpp index f9b648695..3577d1d1b 100644 --- a/llarp/crypto/crypto_libsodium.hpp +++ b/llarp/crypto/crypto_libsodium.hpp @@ -13,6 +13,11 @@ namespace llarp ~CryptoLibSodium() override = default; + /// decrypt cipherText given the key generated from name + std::optional> + 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; diff --git a/llarp/dht/message.cpp b/llarp/dht/message.cpp index 35e29c9f9..f8833ab0c 100644 --- a/llarp/dht/message.cpp +++ b/llarp/dht/message.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include 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(From, Key_t{}, 0); + break; + case 'M': + msg = std::make_unique(From, 0, service::EncryptedName{}); + break; case 'F': msg = std::make_unique(From, relayed, 0); break; diff --git a/llarp/dht/messages/findintro.cpp b/llarp/dht/messages/findintro.cpp index 571f18f67..8a3f63d0a 100644 --- a/llarp/dht/messages/findintro.cpp +++ b/llarp/dht/messages/findintro.cpp @@ -84,8 +84,9 @@ namespace llarp } if (not tagName.Empty()) + { return false; - + } // bad request (request for zero-key) if (location.IsZero()) { diff --git a/llarp/dht/messages/findname.cpp b/llarp/dht/messages/findname.cpp new file mode 100644 index 000000000..3245252ec --- /dev/null +++ b/llarp/dht/messages/findname.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +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& 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 diff --git a/llarp/dht/messages/findname.hpp b/llarp/dht/messages/findname.hpp new file mode 100644 index 000000000..0b9ff6741 --- /dev/null +++ b/llarp/dht/messages/findname.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include + +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& replies) const override; + + Key_t NameHash; + uint64_t TxID; + }; + +} // namespace llarp::dht diff --git a/llarp/dht/messages/gotname.cpp b/llarp/dht/messages/gotname.cpp new file mode 100644 index 000000000..a0c30f81a --- /dev/null +++ b/llarp/dht/messages/gotname.cpp @@ -0,0 +1,55 @@ +#include +#include + +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&) const + { + return false; + } + +} // namespace llarp::dht diff --git a/llarp/dht/messages/gotname.hpp b/llarp/dht/messages/gotname.hpp new file mode 100644 index 000000000..bbe2272e1 --- /dev/null +++ b/llarp/dht/messages/gotname.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +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& replies) const override; + + service::EncryptedName result; + uint64_t TxID; + }; + +} // namespace llarp::dht diff --git a/llarp/dns/message.hpp b/llarp/dns/message.hpp index 0cdfad2d1..b4e6f7d5b 100644 --- a/llarp/dns/message.hpp +++ b/llarp/dns/message.hpp @@ -17,7 +17,7 @@ namespace llarp struct MessageHeader : public Serialize { - const static size_t Size = 12; + static constexpr size_t Size = 12; MessageHeader() = default; diff --git a/llarp/dns/name.cpp b/llarp/dns/name.cpp index 3af1a961a..6f627a652 100644 --- a/llarp/dns/name.cpp +++ b/llarp/dns/name.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -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 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 diff --git a/llarp/dns/name.hpp b/llarp/dns/name.hpp index aecab2747..7bf91baad 100644 --- a/llarp/dns/name.hpp +++ b/llarp/dns/name.hpp @@ -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 diff --git a/llarp/handlers/tun.cpp b/llarp/handlers/tun.cpp index 6d956fd1f..dec25d14f 100644 --- a/llarp/handlers/tun.cpp +++ b/llarp/handlers/tun.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -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(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(msg), isV6); } } + else if (ends_with(qname, ".loki") and service::NameIsValid(qname)) + { + return LookupNameAsync( + qname, + [msg = std::make_shared(msg), isV6, reply, ReplyToLokiDNSWhenReady]( + auto maybe) { + if (not maybe.has_value()) + { + msg->AddNXReply(); + reply(*msg); + return; + } + ReplyToLokiDNSWhenReady(*maybe, msg, isV6); + }); + } else msg.AddNXReply(); diff --git a/llarp/path/pathset.hpp b/llarp/path/pathset.hpp index 3c9a205da..5612d598d 100644 --- a/llarp/path/pathset.hpp +++ b/llarp/path/pathset.hpp @@ -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) + { + return false; + } + virtual routing::IMessageHandler* GetDHTHandler() { diff --git a/llarp/rpc/lokid_rpc_client.cpp b/llarp/rpc/lokid_rpc_client.cpp index 54f74b6e6..98a97a868 100644 --- a/llarp/rpc/lokid_rpc_client.cpp +++ b/llarp/rpc/lokid_rpc_client.cpp @@ -215,6 +215,44 @@ namespace llarp return ftr.get(); } + void + LokidRpcClient::LookupLNSNameHash( + dht::Key_t namehash, + std::function)> 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 data) { + std::optional 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()); + const auto nonce = lokimq::from_hex(j["nonce"].get()); + 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) { diff --git a/llarp/rpc/lokid_rpc_client.hpp b/llarp/rpc/lokid_rpc_client.hpp index b8cef923e..3354316c8 100644 --- a/llarp/rpc/lokid_rpc_client.hpp +++ b/llarp/rpc/lokid_rpc_client.hpp @@ -5,8 +5,8 @@ #include #include #include - -#include +#include +#include namespace llarp { @@ -30,6 +30,11 @@ namespace llarp SecretKey ObtainIdentityKey(); + void + LookupLNSNameHash( + dht::Key_t namehash, + std::function)> resultHandler); + private: /// called when we have connected to lokid via lokimq void diff --git a/llarp/service/endpoint.cpp b/llarp/service/endpoint.cpp index a0c13e83e..08ac56479 100644 --- a/llarp/service/endpoint.cpp +++ b/llarp/service/endpoint.cpp @@ -5,8 +5,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -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 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& response) override + HandleIntrosetResponse(const std::set& response) override { if (not response.empty()) m_Endpoint->IntroSetPublished(); @@ -725,6 +729,82 @@ namespace llarp return true; } + struct LookupNameJob : public IServiceLookup + { + std::function)> handler; + ShortHash namehash; + + LookupNameJob( + Endpoint* parent, + uint64_t id, + std::string lnsName, + std::function)> resultHandler) + : IServiceLookup(parent, id, lnsName), handler(resultHandler) + { + CryptoManager::instance()->shorthash( + namehash, llarp_buffer_t(lnsName.c_str(), lnsName.size())); + } + + std::shared_ptr + BuildRequestMessage() override + { + auto msg = std::make_shared(); + msg->M.emplace_back(std::make_unique( + dht::Key_t{}, dht::Key_t{namehash.as_array()}, txid)); + return msg; + } + + bool + HandleNameResponse(std::optional
addr) override + { + handler(addr); + return true; + } + + void + HandleTimeout() override + { + HandleNameResponse(std::nullopt); + } + }; + + bool + Endpoint::LookupNameAsync(std::string name, std::function)> 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 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) { diff --git a/llarp/service/endpoint.hpp b/llarp/service/endpoint.hpp index 24104d98f..4459447cc 100644 --- a/llarp/service/endpoint.hpp +++ b/llarp/service/endpoint.hpp @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include #include @@ -178,6 +178,9 @@ namespace llarp bool HandleGotRouterMessage(std::shared_ptr msg) override; + bool + HandleGotNameMessage(std::shared_ptr 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)> resultHandler); + /// called on event loop pump virtual void Pump(llarp_time_t now); diff --git a/llarp/service/endpoint_state.hpp b/llarp/service/endpoint_state.hpp index a189cbdc5..3efddd597 100644 --- a/llarp/service/endpoint_state.hpp +++ b/llarp/service/endpoint_state.hpp @@ -7,9 +7,9 @@ #include #include #include -#include #include #include +#include #include #include @@ -80,7 +80,7 @@ namespace llarp OutboundSessions_t m_OutboundSessions; - std::unordered_map m_PrefetchedTags; + util::DecayingHashTable> nameCache; bool Configure(const NetworkConfig& conf); diff --git a/llarp/service/endpoint_types.hpp b/llarp/service/endpoint_types.hpp index 23320cc37..3724927c6 100644 --- a/llarp/service/endpoint_types.hpp +++ b/llarp/service/endpoint_types.hpp @@ -54,6 +54,8 @@ namespace llarp using PathEnsureHook = std::function; + using LNSNameCache = std::unordered_map>; + } // namespace service } // namespace llarp diff --git a/llarp/service/endpoint_util.cpp b/llarp/service/endpoint_util.cpp index 7e4061649..cc0bb9570 100644 --- a/llarp/service/endpoint_util.cpp +++ b/llarp/service/endpoint_util.cpp @@ -47,7 +47,7 @@ namespace llarp std::unique_ptr lookup = std::move(itr->second); LogWarn(lookup->name, " timed out txid=", lookup->txid); - lookup->HandleResponse({}); + lookup->HandleTimeout(); itr = lookups.erase(itr); } } diff --git a/llarp/service/hidden_service_address_lookup.cpp b/llarp/service/hidden_service_address_lookup.cpp index cab40072f..47ecd6794 100644 --- a/llarp/service/hidden_service_address_lookup.cpp +++ b/llarp/service/hidden_service_address_lookup.cpp @@ -24,7 +24,7 @@ namespace llarp } bool - HiddenServiceAddressLookup::HandleResponse(const std::set& results) + HiddenServiceAddressLookup::HandleIntrosetResponse(const std::set& results) { std::optional found; const Address remote(rootkey); diff --git a/llarp/service/hidden_service_address_lookup.hpp b/llarp/service/hidden_service_address_lookup.hpp index 761ac11e0..c4c45ff18 100644 --- a/llarp/service/hidden_service_address_lookup.hpp +++ b/llarp/service/hidden_service_address_lookup.hpp @@ -30,7 +30,7 @@ namespace llarp ~HiddenServiceAddressLookup() override = default; bool - HandleResponse(const std::set& results) override; + HandleIntrosetResponse(const std::set& results) override; std::shared_ptr BuildRequestMessage() override; diff --git a/llarp/service/lookup.hpp b/llarp/service/lookup.hpp index c5005242b..9571c15f1 100644 --- a/llarp/service/lookup.hpp +++ b/llarp/service/lookup.hpp @@ -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&) + HandleIntrosetResponse(const std::set&) { return false; } + /// handle lookup result for introsets + virtual bool HandleNameResponse(std::optional
) + { + 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 diff --git a/llarp/service/name.cpp b/llarp/service/name.cpp new file mode 100644 index 000000000..32c1e3183 --- /dev/null +++ b/llarp/service/name.cpp @@ -0,0 +1,66 @@ +#include +#include +#include + +namespace llarp::service +{ + std::optional
+ 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 diff --git a/llarp/service/name.hpp b/llarp/service/name.hpp new file mode 100644 index 000000000..def04dc5c --- /dev/null +++ b/llarp/service/name.hpp @@ -0,0 +1,20 @@ +#pragma once +#include +#include + +namespace llarp::service +{ + struct EncryptedName + { + SymmNonce nonce; + std::string ciphertext; + + std::optional
+ 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 diff --git a/llarp/service/tag_lookup_job.cpp b/llarp/service/tag_lookup_job.cpp deleted file mode 100644 index 32ddbb8fe..000000000 --- a/llarp/service/tag_lookup_job.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include - -#include -#include -#include - -namespace llarp -{ - namespace service - { - bool - CachedTagResult::HandleResponse(const std::set&) - { - 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 - CachedTagResult::BuildRequestMessage(uint64_t txid) - { - auto msg = std::make_shared(); - msg->M.emplace_back(std::make_unique(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 diff --git a/llarp/service/tag_lookup_job.hpp b/llarp/service/tag_lookup_job.hpp deleted file mode 100644 index 3d0db9fa7..000000000 --- a/llarp/service/tag_lookup_job.hpp +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef LLARP_SERVICE_TAG_LOOKUP_JOB_HPP -#define LLARP_SERVICE_TAG_LOOKUP_JOB_HPP - -#include -#include -#include -#include -#include - -#include - -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 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 - BuildRequestMessage(uint64_t txid); - - bool - HandleResponse(const std::set& results); - }; - - struct TagLookupJob : public IServiceLookup - { - TagLookupJob(Endpoint* parent, CachedTagResult* result); - - ~TagLookupJob() override = default; - - std::shared_ptr - BuildRequestMessage() override - { - return m_result->BuildRequestMessage(txid); - } - - bool - HandleResponse(const std::set& results) override - { - return m_result->HandleResponse(results); - } - - CachedTagResult* m_result; - }; - - } // namespace service -} // namespace llarp - -#endif diff --git a/llarp/util/decaying_hashtable.hpp b/llarp/util/decaying_hashtable.hpp new file mode 100644 index 000000000..410f6cec0 --- /dev/null +++ b/llarp/util/decaying_hashtable.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include +#include + +namespace llarp::util +{ + template + 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 + 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 + 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, Hash_t> m_Values; + }; +} // namespace llarp::util diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b921350a5..83fff5339 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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) diff --git a/test/crypto/mock_crypto.hpp b/test/crypto/mock_crypto.hpp index ab25e8686..ad2bf0323 100644 --- a/test/crypto/mock_crypto.hpp +++ b/test/crypto/mock_crypto.hpp @@ -11,6 +11,9 @@ namespace llarp { struct MockCrypto final : public Crypto { + MOCK_METHOD3(maybe_decrypt_name,std::optional>(std::string_view, llarp::SymmNonce, std::string_view)); + + MOCK_METHOD3(xchacha20, bool(const llarp_buffer_t &, const SharedSecret &, const TunnelNonce &)); diff --git a/test/dns/test_llarp_dns_dns.cpp b/test/dns/test_llarp_dns_dns.cpp index 8cd62f484..d572fa933 100644 --- a/test/dns/test_llarp_dns_dns.cpp +++ b/test/dns/test_llarp_dns_dns.cpp @@ -1,5 +1,4 @@ -#include - +#include #include #include #include @@ -10,113 +9,98 @@ #include -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 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 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 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 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); } diff --git a/test/service/test_llarp_service_name.cpp b/test/service/test_llarp_service_name.cpp new file mode 100644 index 000000000..cdad7ce53 --- /dev/null +++ b/test/service/test_llarp_service_name.cpp @@ -0,0 +1,43 @@ +#include "catch2/catch.hpp" +#include +#include +#include + +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 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(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")); +}