diff --git a/include/opendht/callbacks.h b/include/opendht/callbacks.h index ef142fca..9f6c8603 100644 --- a/include/opendht/callbacks.h +++ b/include/opendht/callbacks.h @@ -4,6 +4,7 @@ #include "infohash.h" #include "value.h" +#include "transport.h" #include #include @@ -97,7 +98,7 @@ struct OPENDHT_PUBLIC NodeInfo */ struct OPENDHT_PUBLIC Config { - /** DHT node ID */ + /** DHT node ID. Ignored if transport encryption is enabled: the node ID derives from the transport key. */ InfoHash node_id {}; /** @@ -143,6 +144,9 @@ struct OPENDHT_PUBLIC Config /* Client mode, node will not be used by other nodes to store data. */ bool client_mode {false}; + + /** Encryption of the DHT protocol, and optional private network */ + TransportConfig transport {}; }; /** diff --git a/include/opendht/network_engine.h b/include/opendht/network_engine.h index 8eb39d47..cf4a67c5 100644 --- a/include/opendht/network_engine.h +++ b/include/opendht/network_engine.h @@ -12,6 +12,7 @@ #include "rate_limiter.h" #include "logger.h" #include "network_utils.h" +#include "transport.h" #include #include @@ -37,6 +38,8 @@ struct NetworkConfig ssize_t max_req_per_sec {0}; ssize_t max_peer_req_per_sec {0}; bool is_client {false}; + /** Transport encryption is used only if enabled and a key is set */ + TransportConfig transport {}; }; class DhtProtocolException : public DhtException @@ -214,6 +217,9 @@ public: net::DatagramSocket* getSocket() const { return dht_socket.get(); }; + /** Our transport key, or nullptr if transport encryption is disabled */ + const transport::KeyPair* getTransportKey() const { return transport_ ? &config.transport.key : nullptr; } + void clear(); /** @@ -487,10 +493,35 @@ private: static constexpr unsigned BLACKLISTED_MAX {10}; static constexpr size_t MTU {1280}; + /* Value fragment size for encrypted peers, keeping packets close to the MTU */ + static constexpr size_t ENCRYPTED_FRAGMENT_SIZE {MTU - transport::MAX_ENVELOPE_OVERHEAD}; static constexpr size_t MAX_PACKET_VALUE_SIZE {600}; static constexpr size_t MAX_MESSAGE_VALUE_SIZE {56 * 1024}; static constexpr size_t RX_MAX_EARLY_BYTES {MAX_MESSAGE_VALUE_SIZE + MAX_VALUE_SIZE + 32}; - static constexpr size_t RX_MAX_EARLY_PARTS {RX_MAX_EARLY_BYTES / MTU + 2}; + static constexpr size_t RX_MAX_EARLY_PARTS {RX_MAX_EARLY_BYTES / ENCRYPTED_FRAGMENT_SIZE + 2}; + + struct OpenedEnvelope + { + Sp peer; + Blob payload; + }; + std::optional openEnvelope(const uint8_t* buf, size_t buflen, const SockAddr& from); + bool authorize(transport::Peer& peer, const uint8_t* credential, size_t size) const; + const transport::Key* sharedKey(transport::Peer& peer) const; + void attachPeer(Node& node, const Sp& peer); + Sp makePeer(const InfoHash& id, const transport::Key& key) const; + + /* Advertise our transport key in plaintext messages to nodes not known to support encryption */ + bool advertiseKey(const Node& node) const + { + return transport_ and not config.transport.isPrivate() and not node.getTransport(); + } + /* Number of optional top-level fields packed by packOptionalFields */ + unsigned optionalFields(bool key) const + { + return (config.network ? 1 : 0) + (config.is_client ? 1 : 0) + (key ? 1 : 0); + } + void packOptionalFields(msgpack::packer& pk, bool key) const; void process(std::unique_ptr&&, const SockAddr& from); bool isExpectedReply(const ParsedMessage&, const SockAddr& from); @@ -521,9 +552,11 @@ private: }; // basic wrapper for socket sendto function - int send(const SockAddr& addr, const char* buf, size_t len, bool confirmed = false); + int sendRaw(const SockAddr& addr, const uint8_t* buf, size_t len, bool confirmed = false); + // send to a node, encrypted if supported + int send(const Node& node, const SockAddr& addr, const char* buf, size_t len, bool confirmed = false); - void sendValueParts(Tid tid, const std::vector& svals, const SockAddr& addr); + void sendValueParts(Tid tid, const std::vector& svals, const Node& node, const SockAddr& addr); std::vector packValueHeader(msgpack::sbuffer&, std::vector>::const_iterator, std::vector>::const_iterator) const; @@ -538,31 +571,44 @@ private: * Answers * *************/ /* answer to a ping request */ - void sendPong(const SockAddr& addr, Tid tid); + void sendPong(const Node& node, const SockAddr& addr, Tid tid); + struct BufferedNodes + { + Blob nodes4, nodes6; + /* transport keys, aligned with nodes, zeros if unknown. Empty if none is known. */ + Blob keys4, keys6; + }; /* answer to findnodes/getvalues request */ - void sendNodesValues(const SockAddr& addr, + void sendNodesValues(const Node& node, + const SockAddr& addr, Tid tid, - const Blob& nodes, - const Blob& nodes6, + const BufferedNodes& nodes, const std::vector>& st, const Query& query, const Blob& token); - Blob bufferNodes(sa_family_t af, const InfoHash& id, std::vector>& nodes); + std::pair bufferNodes(sa_family_t af, const InfoHash& id, std::vector>& nodes); - std::pair bufferNodes( + BufferedNodes bufferNodes( sa_family_t af, const InfoHash& id, want_t want, std::vector>& nodes, std::vector>& nodes6); /* answer to a listen request */ - void sendListenConfirmation(const SockAddr& addr, Tid tid, const Blob& token = {}); + void sendListenConfirmation(const Node& node, const SockAddr& addr, Tid tid, const Blob& token = {}); /* answer to put request */ - void sendValueAnnounced(const SockAddr& addr, Tid, Value::Id); + void sendValueAnnounced(const Node& node, const SockAddr& addr, Tid, Value::Id); /* answer in case of error */ - void sendError(const SockAddr& addr, Tid tid, uint16_t code, const std::string& message, bool include_id = false); + void sendError(const Node& node, + const SockAddr& addr, + Tid tid, + uint16_t code, + const std::string& message, + bool include_id = false); void deserializeNodes(ParsedMessage& msg, const SockAddr& from); /* DHT info */ const InfoHash& myid; const NetworkConfig config {}; + const bool transport_ {false}; + const Blob credential_ {}; const std::unique_ptr dht_socket; Sp logger_; std::mt19937_64& rd; diff --git a/include/opendht/node.h b/include/opendht/node.h index 8b5b6135..f3f312ef 100644 --- a/include/opendht/node.h +++ b/include/opendht/node.h @@ -18,6 +18,9 @@ struct Request; struct Socket; struct RequestAnswer; } /* namespace net */ +namespace transport { +struct Peer; +} using Tid = uint32_t; using SocketCb = std::function&, net::RequestAnswer&&)>; @@ -46,6 +49,10 @@ struct Node bool isClient() const { return is_client; } bool isIncoming() { return time > reply_time; } + /** Transport encryption state, if the node is known to support it */ + const Sp& getTransport() const { return transport_; } + void setTransport(const Sp& peer) { transport_ = peer; } + const time_point& getTime() const { return time; } const time_point& getReplyTime() const { return reply_time; } void setTime(const time_point& t) { time = t; } @@ -155,6 +162,7 @@ private: std::map> requests_ {}; std::map> sockets_; + Sp transport_ {}; }; } // namespace dht diff --git a/src/dht.cpp b/src/dht.cpp index ba8fad30..c6955eab 100644 --- a/src/dht.cpp +++ b/src/dht.cpp @@ -1987,6 +1987,18 @@ fromDhtConfig(const Config& config) netConf.max_peer_req_per_sec = config.max_peer_req_per_sec ? config.max_peer_req_per_sec : netConf.max_req_per_sec / 8; netConf.is_client = config.client_mode; + netConf.transport = config.transport; + auto& transport = netConf.transport; + if (transport.isPrivate() and not transport.enabled) + throw DhtException("Private networks require transport encryption"); + if (transport.enabled and not transport.key) + transport.key = transport::KeyPair::generate(); + if (transport.isPrivate()) { + if (transport.credential.key != transport.key.pub) + throw DhtException("Transport credential doesn't match the transport key"); + if (not transport.credential.verify(transport.authorities)) + throw DhtException("Invalid or expired transport credential"); + } return netConf; } @@ -2024,6 +2036,8 @@ Dht::Dht(std::unique_ptr&& sock, , maintain_storage(config.maintain_storage) , public_stable(config.public_stable) { + if (auto key = network_engine.getTransportKey()) + myid = transport::nodeId(key->pub); scheduler.syncTime(); auto s = network_engine.getSocket(); if (not s or (not s->hasIPv4() and not s->hasIPv6())) @@ -2053,6 +2067,9 @@ Dht::Dht(std::unique_ptr&& sock, if (logger_) logger_->debug("DHT node initialised with ID {:s}", myid); + if (config.transport.isPrivate() and logger_ + and config.transport.credential.expiration < std::chrono::system_clock::now() + std::chrono::hours(24 * 7)) + logger_->warn("Transport credential expires in less than a week"); } bool @@ -2948,7 +2965,7 @@ Dht::loadState(const std::string& path) auto state = oh.get().as(); if (logger_) logger_->debug("Importing {} nodes", state.nodes.size()); - if (state.id) + if (state.id and not network_engine.getTransportKey()) myid = state.id; std::vector> tmpNodes; tmpNodes.reserve(state.nodes.size()); diff --git a/src/network_engine.cpp b/src/network_engine.cpp index e5f04073..015f7800 100644 --- a/src/network_engine.cpp +++ b/src/network_engine.cpp @@ -131,6 +131,8 @@ NetworkEngine::NetworkEngine(InfoHash& myid, , onRefresh(std::move(onRefresh)) , myid(myid) , config(c) + , transport_(config.transport.enabled and config.transport.key) + , credential_(transport_ and config.transport.isPrivate() ? config.transport.credential.pack() : Blob {}) , dht_socket(std::move(sock)) , logger_(log) , rd(rand) @@ -161,7 +163,7 @@ NetworkEngine::tellListener(const Sp& node, if (version >= 1) { sendUpdateValues(node, hash, std::move(values), scheduler.time(), ntoken, socket_id); } else { - sendNodesValues(node->getAddr(), socket_id, nnodes.first, nnodes.second, std::move(values), query, ntoken); + sendNodesValues(*node, node->getAddr(), socket_id, nnodes, std::move(values), query, ntoken); } } catch (const std::overflow_error& e) { if (logger_) @@ -179,8 +181,9 @@ NetworkEngine::tellListenerRefreshed(const Sp& n, { msgpack::sbuffer buffer; msgpack::packer pk(&buffer); + bool advertise = advertiseKey(*n); - pk.pack_map(4 + (version >= 1 ? 1 : 0) + (config.network ? 1 : 0) + (config.is_client ? 1 : 0)); + pk.pack_map(4 + (version >= 1 ? 1 : 0) + optionalFields(advertise)); pk.pack(version >= 1 ? KEY_A : KEY_U); pk.pack_map(1 + (version >= 1 ? 1 : 0) + (not values.empty() ? 1 : 0) + (not token.empty() ? 1 : 0)); @@ -205,14 +208,7 @@ NetworkEngine::tellListenerRefreshed(const Sp& n, pk.pack(version >= 1 ? KEY_Q : KEY_R); pk.pack(KEY_UA); pk.pack(OPENDHT_UA); - if (config.network) { - pk.pack(KEY_NETID); - pk.pack(config.network); - } - if (config.is_client) { - pk.pack(KEY_ISCLIENT); - pk.pack(config.is_client); - } + packOptionalFields(pk, advertise); if (version >= 1) { Tid tid(n->getNewTid()); @@ -237,7 +233,7 @@ NetworkEngine::tellListenerRefreshed(const Sp& n, pk.pack(socket_id); // send response - send(n->getAddr(), buffer.data(), buffer.size()); + send(*n, n->getAddr(), buffer.data(), buffer.size()); } void @@ -250,8 +246,9 @@ NetworkEngine::tellListenerExpired(const Sp& n, { msgpack::sbuffer buffer; msgpack::packer pk(&buffer); + bool advertise = advertiseKey(*n); - pk.pack_map(4 + (version >= 1 ? 1 : 0) + (config.network ? 1 : 0) + (config.is_client ? 1 : 0)); + pk.pack_map(4 + (version >= 1 ? 1 : 0) + optionalFields(advertise)); pk.pack(version >= 1 ? KEY_A : KEY_U); pk.pack_map(1 + (version >= 1 ? 1 : 0) + (not values.empty() ? 1 : 0) + (not token.empty() ? 1 : 0)); @@ -276,14 +273,7 @@ NetworkEngine::tellListenerExpired(const Sp& n, pk.pack(version >= 1 ? KEY_Q : KEY_R); pk.pack(KEY_UA); pk.pack(OPENDHT_UA); - if (config.network) { - pk.pack(KEY_NETID); - pk.pack(config.network); - } - if (config.is_client) { - pk.pack(KEY_ISCLIENT); - pk.pack(config.is_client); - } + packOptionalFields(pk, advertise); if (version >= 1) { Tid tid(n->getNewTid()); @@ -308,7 +298,7 @@ NetworkEngine::tellListenerExpired(const Sp& n, pk.pack(socket_id); // send response - send(n->getAddr(), buffer.data(), buffer.size()); + send(*n, n->getAddr(), buffer.data(), buffer.size()); } bool @@ -364,7 +354,11 @@ NetworkEngine::requestStep(Sp sreq) req.on_expired(req, false); } - auto err = send(node.getAddr(), (char*) req.msg.data(), req.msg.size(), node.getReplyTime() < now - UDP_REPLY_TIME); + auto err = send(node, + node.getAddr(), + (char*) req.msg.data(), + req.msg.size(), + node.getReplyTime() < now - UDP_REPLY_TIME); if (err == ENETUNREACH || err == EHOSTUNREACH || err == EAFNOSUPPORT || err == EPIPE || err == EPERM) { node.setExpired(); if (not node.id) @@ -376,7 +370,7 @@ NetworkEngine::requestStep(Sp sreq) req.attempt_duration += req.attempt_duration + uniform_duration_distribution<>(0ms, ((duration) Node::MAX_RESPONSE_TIME) / 4)(rd); if (not req.parts.empty()) { - sendValueParts(req.tid, req.parts, node.getAddr()); + sendValueParts(req.tid, req.parts, node, node.getAddr()); } } std::weak_ptr wreq = sreq; @@ -480,6 +474,19 @@ NetworkEngine::processMessage(const uint8_t* buf, size_t buflen, SockAddr f) return; } + std::optional envelope; + if (transport::isEnvelope(buf, buflen)) { + if (not transport_) + return; + envelope = openEnvelope(buf, buflen, from); + if (not envelope) + return; + buf = envelope->payload.data(); + buflen = envelope->payload.size(); + } else if (transport_ and config.transport.isPrivate()) { + return; + } + auto msg = std::make_unique(); try { msgpack::unpacked msg_res = msgpack::unpack((const char*) buf, buflen); @@ -498,6 +505,19 @@ NetworkEngine::processMessage(const uint8_t* buf, size_t buflen, SockAddr f) return; } + if (envelope) { + // value fragments carry no node ID + if (msg->type != MessageType::ValueData and msg->id != transport::nodeId(envelope->peer->key)) { + if (logger_) + logger_->warn("Dropping encrypted message from {}: node ID doesn't match its key", from.toString()); + return; + } + msg->peer = std::move(envelope->peer); + msg->encrypted = true; + } else if (transport_ and msg->transport_key) { + msg->peer = makePeer(msg->id, *msg->transport_key); + } + const auto& now = scheduler.time(); // partial value data @@ -626,9 +646,24 @@ void NetworkEngine::process(std::unique_ptr&& msg, const SockAddr& from) { const auto& now = scheduler.time(); - auto node = cache.getNode(msg->id, from, now, true, msg->is_client); + // Plaintext can be spoofed: it never moves a node known to support encryption to a new address. + Sp node; + bool trusted = msg->encrypted; + if (not trusted) { + node = cache.getNode(msg->id, from.getFamily()); + if (not node or not node->getTransport() or node->getAddr() == from) { + node.reset(); + trusted = true; + } + } + if (not node) + node = cache.getNode(msg->id, from, now, true, msg->is_client); + if (msg->peer) + attachPeer(*node, msg->peer); if (msg->type == MessageType::ValueUpdate) { + if (not trusted) + return; auto rsocket = node->getSocket(msg->tid); if (not rsocket) throw DhtProtocolException {DhtProtocolException::UNKNOWN_TID, "Unable to find socket", msg->id}; @@ -642,6 +677,10 @@ NetworkEngine::process(std::unique_ptr&& msg, const SockAddr& fro logger_->warn("Unable to deserialize nodes {}", e.what()); } } else if (msg->type == MessageType::Error or msg->type == MessageType::Reply) { + if (not trusted) + return; + if (msg->encrypted) + node->getTransport()->acked = true; auto rsocket = node->getSocket(msg->tid); auto req = node->getRequest(msg->tid); @@ -724,9 +763,11 @@ NetworkEngine::process(std::unique_ptr&& msg, const SockAddr& fro break; } } else { - node->received(now, {}); - if (not node->isClient()) - onNewNode(node, 1); + if (trusted) { + node->received(now, {}); + if (not node->isClient()) + onNewNode(node, 1); + } try { switch (msg->type) { case MessageType::Ping: @@ -735,7 +776,7 @@ NetworkEngine::process(std::unique_ptr&& msg, const SockAddr& fro if (logger_) logger_->debug("[node {}] sending pong", node->toString()); onPing(node); - sendPong(from, msg->tid); + sendPong(*node, from, msg->tid); break; case MessageType::FindNode: { // if (logger_) @@ -744,7 +785,7 @@ NetworkEngine::process(std::unique_ptr&& msg, const SockAddr& fro ++in_stats.find; RequestAnswer answer = onFindNode(node, msg->target, msg->want); auto nnodes = bufferNodes(from.getFamily(), msg->target, msg->want, answer.nodes4, answer.nodes6); - sendNodesValues(from, msg->tid, nnodes.first, nnodes.second, {}, {}, answer.ntoken); + sendNodesValues(*node, from, msg->tid, nnodes, {}, {}, answer.ntoken); break; } case MessageType::GetValues: { @@ -754,7 +795,7 @@ NetworkEngine::process(std::unique_ptr&& msg, const SockAddr& fro ++in_stats.get; RequestAnswer answer = onGetValues(node, msg->info_hash, msg->want, msg->query); auto nnodes = bufferNodes(from.getFamily(), msg->info_hash, msg->want, answer.nodes4, answer.nodes6); - sendNodesValues(from, msg->tid, nnodes.first, nnodes.second, answer.values, msg->query, answer.ntoken); + sendNodesValues(*node, from, msg->tid, nnodes, answer.values, msg->query, answer.ntoken); break; } case MessageType::AnnounceValue: { @@ -767,7 +808,7 @@ NetworkEngine::process(std::unique_ptr&& msg, const SockAddr& fro This is to prevent them from backtracking, and hence polluting the DHT. */ for (auto& v : msg->values) { - sendValueAnnounced(from, msg->tid, v->id); + sendValueAnnounced(*node, from, msg->tid, v->id); } break; } @@ -778,7 +819,7 @@ NetworkEngine::process(std::unique_ptr&& msg, const SockAddr& fro msg->info_hash.toString()); onRefresh(node, msg->info_hash, msg->token, msg->value_id); /* Same note as above in MessageType::AnnounceValue applies. */ - sendValueAnnounced(from, msg->tid, msg->value_id); + sendValueAnnounced(*node, from, msg->tid, msg->value_id); break; case MessageType::Listen: { if (logIncoming_ and logger_) @@ -787,7 +828,7 @@ NetworkEngine::process(std::unique_ptr&& msg, const SockAddr& fro RequestAnswer answer = onListen(node, msg->info_hash, msg->token, msg->socket_id, std::move(msg->query), msg->version); auto nnodes = bufferNodes(from.getFamily(), msg->info_hash, msg->want, answer.nodes4, answer.nodes6); - sendListenConfirmation(from, msg->tid, answer.ntoken); + sendListenConfirmation(*node, from, msg->tid, answer.ntoken); break; } case MessageType::UpdateValue: { @@ -800,7 +841,7 @@ NetworkEngine::process(std::unique_ptr&& msg, const SockAddr& fro logger_->error("[node {}] 'update' request without socket for {}", node->toString(), msg->info_hash.toString()); - sendListenConfirmation(from, msg->tid); + sendListenConfirmation(*node, from, msg->tid); break; } default: @@ -810,7 +851,7 @@ NetworkEngine::process(std::unique_ptr&& msg, const SockAddr& fro if (logger_) logger_->e("Unable to send value: buffer not large enough."); } catch (const DhtProtocolException& e) { - sendError(from, msg->tid, e.getCode(), e.getMsg().c_str(), true); + sendError(*node, from, msg->tid, e.getCode(), e.getMsg().c_str(), true); } } } @@ -827,10 +868,151 @@ insertAddr(msgpack::packer& pk, const SockAddr& addr) pk.pack_bin_body((char*) addr_ptr, addr_len); } -int -NetworkEngine::send(const SockAddr& addr, const char* buf, size_t len, bool confirmed) +void +NetworkEngine::packOptionalFields(msgpack::packer& pk, bool key) const { - return dht_socket ? dht_socket->sendTo(addr, (const uint8_t*) buf, len, confirmed) : ENOTCONN; + if (config.network) { + pk.pack(KEY_NETID); + pk.pack(config.network); + } + if (config.is_client) { + pk.pack(KEY_ISCLIENT); + pk.pack(config.is_client); + } + if (key) { + const auto& pub = config.transport.key.pub; + pk.pack(KEY_TRANSPORT); + pk.pack_bin(pub.size()); + pk.pack_bin_body((const char*) pub.data(), pub.size()); + } +} + +const transport::Key* +NetworkEngine::sharedKey(transport::Peer& peer) const +{ + if (not peer.shared) + peer.shared = transport::deriveSharedKey(config.transport.key, peer.key, config.network); + return peer.shared ? &*peer.shared : nullptr; +} + +Sp +NetworkEngine::makePeer(const InfoHash& id, const transport::Key& key) const +{ + if (key == transport::Key {} or transport::nodeId(key) != id) + return {}; + return std::make_shared(key); +} + +void +NetworkEngine::attachPeer(Node& node, const Sp& peer) +{ + const auto& current = node.getTransport(); + if (not current) { + node.setTransport(peer); + } else if (current != peer) { + // Both keys match the node ID, so they are the same + current->authorizedUntil = std::max(current->authorizedUntil, peer->authorizedUntil); + if (not current->shared) + current->shared = peer->shared; + } +} + +bool +NetworkEngine::authorize(transport::Peer& peer, const uint8_t* data, size_t size) const +{ + if (peer.isAuthorized(std::chrono::system_clock::now())) + return true; + try { + auto credential = transport::Credential::unpack(data, size); + if (credential.key != peer.key or not credential.verify(config.transport.authorities)) + return false; + peer.authorizedUntil = credential.expiration; + return true; + } catch (const std::exception&) { + return false; + } +} + +std::optional +NetworkEngine::openEnvelope(const uint8_t* buf, size_t buflen, const SockAddr& from) +{ + using transport::EnvelopeType; + constexpr size_t CREDENTIAL_SIZE = transport::Credential::PACKED_SIZE; + const auto type = static_cast(buf[1]); + transport::Key sender; + std::copy_n(buf + 2, sender.size(), sender.begin()); + if (sender == config.transport.key.pub) + return std::nullopt; + + Sp peer; + if (auto node = cache.getNode(transport::nodeId(sender), from.getFamily())) + peer = node->getTransport(); + if (not peer) + peer = std::make_shared(sender); + + const bool isPrivate = config.transport.isPrivate(); + if (type == EnvelopeType::Hello) { + constexpr size_t credentialEnd = transport::HEADER_SIZE + CREDENTIAL_SIZE; + if (not isPrivate or buflen <= credentialEnd or not rateLimit(from) + or not authorize(*peer, buf + transport::HEADER_SIZE, CREDENTIAL_SIZE)) + return std::nullopt; + return OpenedEnvelope {std::move(peer), Blob(buf + credentialEnd, buf + buflen)}; + } + if (type != EnvelopeType::Data and type != EnvelopeType::DataCredential) + return std::nullopt; + + // Key agreement is the costly part of an unknown sender + if (not peer->shared and not rateLimit(from)) + return std::nullopt; + auto key = sharedKey(*peer); + if (not key) + return std::nullopt; + auto payload = transport::open(*key, buf, buflen); + if (not payload) { + if (logger_) + logger_->debug("Unable to decrypt message from {}", from.toString()); + return std::nullopt; + } + if (type == EnvelopeType::DataCredential) { + if (payload->size() < CREDENTIAL_SIZE or (isPrivate and not authorize(*peer, payload->data(), CREDENTIAL_SIZE))) + return std::nullopt; + payload->erase(payload->begin(), payload->begin() + CREDENTIAL_SIZE); + } else if (isPrivate and not peer->isAuthorized(std::chrono::system_clock::now())) { + return std::nullopt; + } + return OpenedEnvelope {std::move(peer), std::move(*payload)}; +} + +int +NetworkEngine::sendRaw(const SockAddr& addr, const uint8_t* buf, size_t len, bool confirmed) +{ + return dht_socket ? dht_socket->sendTo(addr, buf, len, confirmed) : ENOTCONN; +} + +int +NetworkEngine::send(const Node& node, const SockAddr& addr, const char* buf, size_t len, bool confirmed) +{ + const auto data = (const uint8_t*) buf; + if (not transport_) + return sendRaw(addr, data, len, confirmed); + const bool isPrivate = config.transport.isPrivate(); + const auto& pub = config.transport.key.pub; + if (const auto& peer = node.getTransport()) { + if (auto key = sharedKey(*peer)) { + auto packet + = isPrivate and not peer->acked + ? transport::seal(*key, pub, transport::EnvelopeType::DataCredential, data, len, credential_) + : transport::seal(*key, pub, transport::EnvelopeType::Data, data, len); + return sendRaw(addr, packet.data(), packet.size(), confirmed); + } + } else if (isPrivate and not node.id) { + // ping to an address: introduce ourselves + auto packet = transport::hello(pub, credential_, data, len); + return sendRaw(addr, packet.data(), packet.size(), confirmed); + } + if (isPrivate) + return EPERM; + return sendRaw(addr, data, len, confirmed); } Sp @@ -839,7 +1021,8 @@ NetworkEngine::sendPing(const Sp& node, RequestCb&& on_done, RequestExpire Tid tid(node->getNewTid()); msgpack::sbuffer buffer; msgpack::packer pk(&buffer); - pk.pack_map(5 + (config.network ? 1 : 0) + (config.is_client ? 1 : 0)); + bool advertise = advertiseKey(*node); + pk.pack_map(5 + optionalFields(advertise)); pk.pack(KEY_A); pk.pack_map(1); @@ -854,14 +1037,7 @@ NetworkEngine::sendPing(const Sp& node, RequestCb&& on_done, RequestExpire pk.pack(KEY_Q); pk.pack(KEY_UA); pk.pack(OPENDHT_UA); - if (config.network) { - pk.pack(KEY_NETID); - pk.pack(config.network); - } - if (config.is_client) { - pk.pack(KEY_ISCLIENT); - pk.pack(config.is_client); - } + packOptionalFields(pk, advertise); auto req = std::make_shared( MessageType::Ping, @@ -882,11 +1058,12 @@ NetworkEngine::sendPing(const Sp& node, RequestCb&& on_done, RequestExpire } void -NetworkEngine::sendPong(const SockAddr& addr, Tid tid) +NetworkEngine::sendPong(const Node& node, const SockAddr& addr, Tid tid) { msgpack::sbuffer buffer; msgpack::packer pk(&buffer); - pk.pack_map(4 + (config.network ? 1 : 0) + (config.is_client ? 1 : 0)); + bool advertise = advertiseKey(node); + pk.pack_map(4 + optionalFields(advertise)); pk.pack(KEY_R); pk.pack_map(2); @@ -900,16 +1077,9 @@ NetworkEngine::sendPong(const SockAddr& addr, Tid tid) pk.pack(KEY_R); pk.pack(KEY_UA); pk.pack(OPENDHT_UA); - if (config.network) { - pk.pack(KEY_NETID); - pk.pack(config.network); - } - if (config.is_client) { - pk.pack(KEY_ISCLIENT); - pk.pack(config.is_client); - } + packOptionalFields(pk, advertise); - send(addr, buffer.data(), buffer.size()); + send(node, addr, buffer.data(), buffer.size()); } Sp @@ -919,7 +1089,8 @@ NetworkEngine::sendFindNode( Tid tid(n->getNewTid()); msgpack::sbuffer buffer; msgpack::packer pk(&buffer); - pk.pack_map(5 + (config.network ? 1 : 0) + (config.is_client ? 1 : 0)); + bool advertise = advertiseKey(*n); + pk.pack_map(5 + optionalFields(advertise)); pk.pack(KEY_A); pk.pack_map(2 + (want > 0 ? 1 : 0)); @@ -944,14 +1115,7 @@ NetworkEngine::sendFindNode( pk.pack(KEY_Q); pk.pack(KEY_UA); pk.pack(OPENDHT_UA); - if (config.network) { - pk.pack(KEY_NETID); - pk.pack(config.network); - } - if (config.is_client) { - pk.pack(KEY_ISCLIENT); - pk.pack(config.is_client); - } + packOptionalFields(pk, advertise); auto req = std::make_shared( MessageType::FindNode, @@ -981,7 +1145,8 @@ NetworkEngine::sendGetValues(const Sp& n, Tid tid(n->getNewTid()); msgpack::sbuffer buffer; msgpack::packer pk(&buffer); - pk.pack_map(5 + (config.network ? 1 : 0) + (config.is_client ? 1 : 0)); + bool advertise = advertiseKey(*n); + pk.pack_map(5 + optionalFields(advertise)); unsigned sendQuery = (not query.where.empty() or not query.select.empty()) ? 1 : 0; unsigned sendWant = (want > 0) ? 1 : 0; @@ -1015,14 +1180,7 @@ NetworkEngine::sendGetValues(const Sp& n, pk.pack(KEY_Q); pk.pack(KEY_UA); pk.pack(OPENDHT_UA); - if (config.network) { - pk.pack(KEY_NETID); - pk.pack(config.network); - } - if (config.is_client) { - pk.pack(KEY_ISCLIENT); - pk.pack(config.is_client); - } + packOptionalFields(pk, advertise); auto req = std::make_shared( MessageType::GetValues, @@ -1070,6 +1228,14 @@ NetworkEngine::deserializeNodes(ParsedMessage& msg, const SockAddr& from) } // deserialize nodes const auto& now = scheduler.time(); + const bool isPrivate = transport_ and config.transport.isPrivate(); + auto listedPeer = [&](const Blob& keys, size_t count, size_t i, const InfoHash& id) -> Sp { + if (not transport_ or keys.size() != count * transport::KEY_SIZE) + return {}; + transport::Key key; + std::copy_n(keys.data() + i * transport::KEY_SIZE, key.size(), key.begin()); + return makePeer(id, key); + }; for (unsigned i = 0, n = msg.nodes4_raw.size() / NODE4_INFO_BUF_LEN; i < n; i++) { const uint8_t* ni = msg.nodes4_raw.data() + i * NODE4_INFO_BUF_LEN; const auto& ni_id = *reinterpret_cast(ni); @@ -1083,7 +1249,12 @@ NetworkEngine::deserializeNodes(ParsedMessage& msg, const SockAddr& from) } if (isMartian(addr) || isNodeBlacklisted(addr)) continue; + auto peer = listedPeer(msg.nodes4_keys, n, i, ni_id); + if (isPrivate and not peer) + continue; msg.nodes4.emplace_back(cache.getNode(ni_id, addr, now, false)); + if (peer) + attachPeer(*msg.nodes4.back(), peer); onNewNode(msg.nodes4.back(), 0); } for (unsigned i = 0, n = msg.nodes6_raw.size() / NODE6_INFO_BUF_LEN; i < n; i++) { @@ -1099,7 +1270,12 @@ NetworkEngine::deserializeNodes(ParsedMessage& msg, const SockAddr& from) } if (isMartian(addr) || isNodeBlacklisted(addr)) continue; + auto peer = listedPeer(msg.nodes6_keys, n, i, ni_id); + if (isPrivate and not peer) + continue; msg.nodes6.emplace_back(cache.getNode(ni_id, addr, now, false)); + if (peer) + attachPeer(*msg.nodes6.back(), peer); onNewNode(msg.nodes6.back(), 0); } } @@ -1136,25 +1312,19 @@ NetworkEngine::packValueHeader(msgpack::sbuffer& buffer, } void -NetworkEngine::sendValueParts(Tid tid, const std::vector& svals, const SockAddr& addr) +NetworkEngine::sendValueParts(Tid tid, const std::vector& svals, const Node& node, const SockAddr& addr) { + const size_t fragmentSize = transport_ and node.getTransport() ? ENCRYPTED_FRAGMENT_SIZE : MTU; msgpack::sbuffer buffer; unsigned i = 0; for (const auto& v : svals) { size_t start {0}, end; do { - end = std::min(start + MTU, v.size()); + end = std::min(start + fragmentSize, v.size()); buffer.clear(); msgpack::packer pk(&buffer); - pk.pack_map(3 + (config.network ? 1 : 0) + (config.is_client ? 1 : 0)); - if (config.network) { - pk.pack(KEY_NETID); - pk.pack(config.network); - } - if (config.is_client) { - pk.pack(KEY_ISCLIENT); - pk.pack(config.is_client); - } + pk.pack_map(3 + optionalFields(false)); + packOptionalFields(pk, false); pk.pack(KEY_Y); pk.pack(KEY_V); pk.pack(KEY_TID); @@ -1168,7 +1338,7 @@ NetworkEngine::sendValueParts(Tid tid, const std::vector& svals, const Soc pk.pack("d"sv); pk.pack_bin(end - start); pk.pack_bin_body((const char*) v.data() + start, end - start); - send(addr, buffer.data(), buffer.size()); + send(node, addr, buffer.data(), buffer.size()); start = end; } while (start != v.size()); i++; @@ -1176,34 +1346,36 @@ NetworkEngine::sendValueParts(Tid tid, const std::vector& svals, const Soc } void -NetworkEngine::sendNodesValues(const SockAddr& addr, +NetworkEngine::sendNodesValues(const Node& node, + const SockAddr& addr, Tid tid, - const Blob& nodes, - const Blob& nodes6, + const BufferedNodes& nodes, const std::vector>& st, const Query& query, const Blob& token) { msgpack::sbuffer buffer; msgpack::packer pk(&buffer); - pk.pack_map(4 + (config.network ? 1 : 0) + (config.is_client ? 1 : 0)); + bool advertise = advertiseKey(node); + pk.pack_map(4 + optionalFields(advertise)); + auto packBlob = [&](std::string_view key, const Blob& b) { + if (b.empty()) + return; + pk.pack(key); + pk.pack_bin(b.size()); + pk.pack_bin_body((const char*) b.data(), b.size()); + }; pk.pack(KEY_R); - pk.pack_map(2 + (not st.empty() ? 1 : 0) + (nodes.size() > 0 ? 1 : 0) + (nodes6.size() > 0 ? 1 : 0) - + (not token.empty() ? 1 : 0)); + pk.pack_map(2 + (not st.empty() ? 1 : 0) + (not nodes.nodes4.empty() ? 1 : 0) + (not nodes.nodes6.empty() ? 1 : 0) + + (not nodes.keys4.empty() ? 1 : 0) + (not nodes.keys6.empty() ? 1 : 0) + (not token.empty() ? 1 : 0)); pk.pack(KEY_REQ_ID); pk.pack(myid); insertAddr(pk, addr); - if (nodes.size() > 0) { - pk.pack(KEY_REQ_NODES4); - pk.pack_bin(nodes.size()); - pk.pack_bin_body((const char*) nodes.data(), nodes.size()); - } - if (nodes6.size() > 0) { - pk.pack(KEY_REQ_NODES6); - pk.pack_bin(nodes6.size()); - pk.pack_bin_body((const char*) nodes6.data(), nodes6.size()); - } + packBlob(KEY_REQ_NODES4, nodes.nodes4); + packBlob(KEY_REQ_NODES6, nodes.nodes6); + packBlob(KEY_REQ_KEYS4, nodes.keys4); + packBlob(KEY_REQ_KEYS6, nodes.keys6); if (not token.empty()) { pk.pack(KEY_REQ_TOKEN); packToken(pk, token); @@ -1233,24 +1405,17 @@ NetworkEngine::sendNodesValues(const SockAddr& addr, pk.pack(KEY_R); pk.pack(KEY_UA); pk.pack(OPENDHT_UA); - if (config.network) { - pk.pack(KEY_NETID); - pk.pack(config.network); - } - if (config.is_client) { - pk.pack(KEY_ISCLIENT); - pk.pack(config.is_client); - } + packOptionalFields(pk, advertise); // send response - send(addr, buffer.data(), buffer.size()); + send(node, addr, buffer.data(), buffer.size()); // send parts if (not svals.empty()) - sendValueParts(tid, svals, addr); + sendValueParts(tid, svals, node, addr); } -Blob +std::pair NetworkEngine::bufferNodes(sa_family_t af, const InfoHash& id, std::vector>& nodes) { std::sort(nodes.begin(), nodes.end(), [&](const Sp& a, const Sp& b) { @@ -1278,26 +1443,39 @@ NetworkEngine::bufferNodes(sa_family_t af, const InfoHash& id, std::vectorgetTransport()) { + std::copy(peer->key.begin(), peer->key.end(), bkeys.begin() + transport::KEY_SIZE * i); + hasKey = true; + } + } + if (not hasKey) + bkeys.clear(); + } + return {std::move(bnodes), std::move(bkeys)}; } -std::pair +NetworkEngine::BufferedNodes NetworkEngine::bufferNodes( sa_family_t af, const InfoHash& id, want_t want, std::vector>& nodes4, std::vector>& nodes6) { if (want < 0) want = af == AF_INET ? WANT4 : WANT6; - Blob bnodes4; + BufferedNodes buffered; if (want & WANT4) - bnodes4 = bufferNodes(AF_INET, id, nodes4); - - Blob bnodes6; + std::tie(buffered.nodes4, buffered.keys4) = bufferNodes(AF_INET, id, nodes4); if (want & WANT6) - bnodes6 = bufferNodes(AF_INET6, id, nodes6); - - return {std::move(bnodes4), std::move(bnodes6)}; + std::tie(buffered.nodes6, buffered.keys6) = bufferNodes(AF_INET6, id, nodes6); + return buffered; } Sp @@ -1312,7 +1490,8 @@ NetworkEngine::sendListen(const Sp& n, Tid tid(n->getNewTid()); msgpack::sbuffer buffer; msgpack::packer pk(&buffer); - pk.pack_map(5 + (config.network ? 1 : 0) + (config.is_client ? 1 : 0)); + bool advertise = advertiseKey(*n); + pk.pack_map(5 + optionalFields(advertise)); auto has_query = not query.where.empty() or not query.select.empty(); pk.pack(KEY_A); @@ -1340,14 +1519,7 @@ NetworkEngine::sendListen(const Sp& n, pk.pack(KEY_Q); pk.pack(KEY_UA); pk.pack(OPENDHT_UA); - if (config.network) { - pk.pack(KEY_NETID); - pk.pack(config.network); - } - if (config.is_client) { - pk.pack(KEY_ISCLIENT); - pk.pack(config.is_client); - } + packOptionalFields(pk, advertise); auto req = std::make_shared( MessageType::Listen, @@ -1366,11 +1538,12 @@ NetworkEngine::sendListen(const Sp& n, } void -NetworkEngine::sendListenConfirmation(const SockAddr& addr, Tid tid, const Blob& token) +NetworkEngine::sendListenConfirmation(const Node& node, const SockAddr& addr, Tid tid, const Blob& token) { msgpack::sbuffer buffer; msgpack::packer pk(&buffer); - pk.pack_map(4 + (config.network ? 1 : 0) + (config.is_client ? 1 : 0)); + bool advertise = advertiseKey(node); + pk.pack_map(4 + optionalFields(advertise)); pk.pack(KEY_R); pk.pack_map(2 + (not token.empty() ? 1 : 0)); @@ -1388,16 +1561,9 @@ NetworkEngine::sendListenConfirmation(const SockAddr& addr, Tid tid, const Blob& pk.pack(KEY_R); pk.pack(KEY_UA); pk.pack(OPENDHT_UA); - if (config.network) { - pk.pack(KEY_NETID); - pk.pack(config.network); - } - if (config.is_client) { - pk.pack(KEY_ISCLIENT); - pk.pack(config.is_client); - } + packOptionalFields(pk, advertise); - send(addr, buffer.data(), buffer.size()); + send(node, addr, buffer.data(), buffer.size()); } Sp @@ -1412,7 +1578,8 @@ NetworkEngine::sendAnnounceValue(const Sp& n, Tid tid(n->getNewTid()); msgpack::sbuffer buffer; msgpack::packer pk(&buffer); - pk.pack_map(5 + (config.network ? 1 : 0) + (config.is_client ? 1 : 0)); + bool advertise = advertiseKey(*n); + pk.pack_map(5 + optionalFields(advertise)); bool add_created = created < scheduler.time(); pk.pack(KEY_A); @@ -1437,14 +1604,7 @@ NetworkEngine::sendAnnounceValue(const Sp& n, pk.pack(KEY_Q); pk.pack(KEY_UA); pk.pack(OPENDHT_UA); - if (config.network) { - pk.pack(KEY_NETID); - pk.pack(config.network); - } - if (config.is_client) { - pk.pack(KEY_ISCLIENT); - pk.pack(config.is_client); - } + packOptionalFields(pk, advertise); auto req = std::make_shared( MessageType::AnnounceValue, @@ -1510,7 +1670,8 @@ NetworkEngine::sendUpdateValues(const Sp& n, msgpack::sbuffer buffer; msgpack::packer pk(&buffer); - pk.pack_map(5 + (config.network ? 1 : 0) + (config.is_client ? 1 : 0)); + bool advertise = advertiseKey(*n); + pk.pack_map(5 + optionalFields(advertise)); pk.pack(KEY_A); pk.pack_map((created < scheduler.time() ? 7 : 6)); @@ -1538,14 +1699,7 @@ NetworkEngine::sendUpdateValues(const Sp& n, pk.pack(KEY_Q); pk.pack(KEY_UA); pk.pack(OPENDHT_UA); - if (config.network) { - pk.pack(KEY_NETID); - pk.pack(config.network); - } - if (config.is_client) { - pk.pack(KEY_ISCLIENT); - pk.pack(config.is_client); - } + packOptionalFields(pk, advertise); auto req = std::make_shared( MessageType::UpdateValue, @@ -1572,7 +1726,8 @@ NetworkEngine::sendRefreshValue(const Sp& n, Tid tid(n->getNewTid()); msgpack::sbuffer buffer; msgpack::packer pk(&buffer); - pk.pack_map(5 + (config.network ? 1 : 0) + (config.is_client ? 1 : 0)); + bool advertise = advertiseKey(*n); + pk.pack_map(5 + optionalFields(advertise)); pk.pack(KEY_A); pk.pack_map(4); @@ -1593,14 +1748,7 @@ NetworkEngine::sendRefreshValue(const Sp& n, pk.pack(KEY_Q); pk.pack(KEY_UA); pk.pack(OPENDHT_UA); - if (config.network) { - pk.pack(KEY_NETID); - pk.pack(config.network); - } - if (config.is_client) { - pk.pack(KEY_ISCLIENT); - pk.pack(config.is_client); - } + packOptionalFields(pk, advertise); auto req = std::make_shared( MessageType::Refresh, @@ -1628,11 +1776,12 @@ NetworkEngine::sendRefreshValue(const Sp& n, } void -NetworkEngine::sendValueAnnounced(const SockAddr& addr, Tid tid, Value::Id vid) +NetworkEngine::sendValueAnnounced(const Node& node, const SockAddr& addr, Tid tid, Value::Id vid) { msgpack::sbuffer buffer; msgpack::packer pk(&buffer); - pk.pack_map(4 + (config.network ? 1 : 0) + (config.is_client ? 1 : 0)); + bool advertise = advertiseKey(node); + pk.pack_map(4 + optionalFields(advertise)); pk.pack(KEY_R); pk.pack_map(3); @@ -1648,24 +1797,19 @@ NetworkEngine::sendValueAnnounced(const SockAddr& addr, Tid tid, Value::Id vid) pk.pack(KEY_R); pk.pack(KEY_UA); pk.pack(OPENDHT_UA); - if (config.network) { - pk.pack(KEY_NETID); - pk.pack(config.network); - } - if (config.is_client) { - pk.pack(KEY_ISCLIENT); - pk.pack(config.is_client); - } + packOptionalFields(pk, advertise); - send(addr, buffer.data(), buffer.size()); + send(node, addr, buffer.data(), buffer.size()); } void -NetworkEngine::sendError(const SockAddr& addr, Tid tid, uint16_t code, const std::string& message, bool include_id) +NetworkEngine::sendError( + const Node& node, const SockAddr& addr, Tid tid, uint16_t code, const std::string& message, bool include_id) { msgpack::sbuffer buffer; msgpack::packer pk(&buffer); - pk.pack_map(4 + (include_id ? 1 : 0) + (config.network ? 1 : 0) + (config.is_client ? 1 : 0)); + bool advertise = advertiseKey(node); + pk.pack_map(4 + (include_id ? 1 : 0) + optionalFields(advertise)); pk.pack(KEY_E); pk.pack_array(2); @@ -1685,16 +1829,9 @@ NetworkEngine::sendError(const SockAddr& addr, Tid tid, uint16_t code, const std pk.pack(KEY_E); pk.pack(KEY_UA); pk.pack(OPENDHT_UA); - if (config.network) { - pk.pack(KEY_NETID); - pk.pack(config.network); - } - if (config.is_client) { - pk.pack(KEY_ISCLIENT); - pk.pack(config.is_client); - } + packOptionalFields(pk, advertise); - send(addr, buffer.data(), buffer.size()); + send(node, addr, buffer.data(), buffer.size()); } void diff --git a/src/parsed_message.h b/src/parsed_message.h index e467416d..49690eb8 100644 --- a/src/parsed_message.h +++ b/src/parsed_message.h @@ -4,6 +4,7 @@ #include "infohash.h" #include "sockaddr.h" +#include "transport.h" #include "net.h" #include @@ -26,6 +27,7 @@ static constexpr auto KEY_NETID = "n"sv; static constexpr auto KEY_ISCLIENT = "s"sv; static constexpr auto KEY_Q = "q"sv; static constexpr auto KEY_A = "a"sv; +static constexpr auto KEY_TRANSPORT = "k"sv; static constexpr auto KEY_REQ_SID = "sid"sv; static constexpr auto KEY_REQ_ID = "id"sv; @@ -36,6 +38,8 @@ static constexpr auto KEY_REQ_TOKEN = "token"sv; static constexpr auto KEY_REQ_VALUE_ID = "vid"sv; static constexpr auto KEY_REQ_NODES4 = "n4"sv; static constexpr auto KEY_REQ_NODES6 = "n6"sv; +static constexpr auto KEY_REQ_KEYS4 = "k4"sv; +static constexpr auto KEY_REQ_KEYS6 = "k6"sv; static constexpr auto KEY_REQ_CREATION = "c"sv; static constexpr auto KEY_REQ_ADDRESS = "sa"sv; static constexpr auto KEY_REQ_VALUES = "values"sv; @@ -93,6 +97,8 @@ struct ParsedMessage time_point created {time_point::max()}; /* IPv4 nodes in response to a 'find' request */ Blob nodes4_raw, nodes6_raw; + /* transport keys of the nodes, or zeros if unknown */ + Blob nodes4_keys, nodes6_keys; std::vector> nodes4, nodes6; /* values to store or retreive request */ std::vector> values; @@ -129,6 +135,11 @@ struct ParsedMessage std::string ua; int version {0}; SockAddr addr; + /* transport key advertised by the sender in plaintext */ + std::optional transport_key; + /* transport state of the sender, if authenticated */ + Sp peer; + bool encrypted {false}; void msgpack_unpack(const msgpack::object& o); bool append(const ParsedMessage& block); @@ -229,6 +240,11 @@ ParsedMessage::msgpack_unpack(const msgpack::object& msg) parsed.q = o.val.as(); else if (key == KEY_A) parsed.a = &o.val; + else if (key == KEY_TRANSPORT and o.val.type == msgpack::type::BIN + and o.val.via.bin.size == transport::KEY_SIZE) { + transport_key.emplace(); + std::copy_n((const uint8_t*) o.val.via.bin.ptr, transport::KEY_SIZE, transport_key->begin()); + } } if (parsed.e) @@ -321,6 +337,10 @@ ParsedMessage::msgpack_unpack(const msgpack::object& msg) nodes4_raw = unpackBlob(o.val); else if (key == KEY_REQ_NODES6) nodes6_raw = unpackBlob(o.val); + else if (key == KEY_REQ_KEYS4) + nodes4_keys = unpackBlob(o.val); + else if (key == KEY_REQ_KEYS6) + nodes6_keys = unpackBlob(o.val); else if (key == KEY_REQ_ADDRESS) parsedReq.sa = &o.val; else if (key == KEY_REQ_CREATION)