/* * Copyright (C) 2014-2017 Savoir-faire Linux Inc. * Author(s) : Adrien Béraud * Simon Désaulniers * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "network_engine.h" #include "request.h" #include "default_types.h" #include "log_enable.h" #include namespace dht { namespace net { const std::string DhtProtocolException::GET_NO_INFOHASH {"Get_values with no info_hash"}; const std::string DhtProtocolException::LISTEN_NO_INFOHASH {"Listen with no info_hash"}; const std::string DhtProtocolException::LISTEN_WRONG_TOKEN {"Listen with wrong token"}; const std::string DhtProtocolException::PUT_NO_INFOHASH {"Put with no info_hash"}; const std::string DhtProtocolException::PUT_WRONG_TOKEN {"Put with wrong token"}; const std::string DhtProtocolException::PUT_INVALID_ID {"Put with invalid id"}; const std::string DhtProtocolException::STORAGE_NOT_FOUND {"Access operation for unknown storage"}; constexpr std::chrono::seconds NetworkEngine::UDP_REPLY_TIME; constexpr std::chrono::seconds NetworkEngine::RX_MAX_PACKET_TIME; constexpr std::chrono::seconds NetworkEngine::RX_TIMEOUT; const std::string NetworkEngine::my_v {"RNG1"}; const constexpr uint16_t TransId::INVALID; std::mt19937 NetworkEngine::rd_device {dht::crypto::random_device{}()}; const TransPrefix TransPrefix::PING = {"pn"}; const TransPrefix TransPrefix::FIND_NODE = {"fn"}; const TransPrefix TransPrefix::GET_VALUES = {"gt"}; const TransPrefix TransPrefix::ANNOUNCE_VALUES = {"pt"}; const TransPrefix TransPrefix::REFRESH = {"rf"}; const TransPrefix TransPrefix::LISTEN = {"lt"}; constexpr long unsigned NetworkEngine::MAX_REQUESTS_PER_SEC; static const uint8_t v4prefix[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0, 0, 0, 0 }; constexpr unsigned SEND_NODES {8}; enum class MessageType { Error = 0, Reply, Ping, FindNode, GetValues, AnnounceValue, Refresh, Listen, ValueData }; struct ParsedMessage { MessageType type; /* Node ID of the sender */ InfoHash id; /* Network id */ NetId network {0}; /* hash for which values are requested */ InfoHash info_hash; /* target id around which to find nodes */ InfoHash target; /* transaction id */ TransId tid; /* tid for packets going through request socket */ TransId socket_id; /* security token */ Blob token; /* the value id (announce confirmation) */ Value::Id value_id; /* time when value was first created */ time_point created { time_point::max() }; /* IPv4 nodes in response to a 'find' request */ Blob nodes4_raw, nodes6_raw; std::vector> nodes4, nodes6; /* values to store or retreive request */ std::vector> values; /* index for fields values */ std::vector> fields; /** When part of the message header: {index -> (total size, {})} * When part of partial value data: {index -> (offset, part_data)} */ std::map> value_parts; /* query describing a filter to apply on values. */ Query query; /* states if ipv4 or ipv6 request */ want_t want; /* error code in case of error */ uint16_t error_code; /* reported address by the distant node */ std::string ua; SockAddr addr; void msgpack_unpack(msgpack::object o); bool append(const ParsedMessage& block); bool complete(); }; struct NetworkEngine::PartialMessage { SockAddr from; time_point start; time_point last_part; std::unique_ptr msg; }; std::vector serializeValues(const std::vector>& st) { std::vector svals; svals.reserve(st.size()); for (const auto& v : st) svals.emplace_back(packMsg(v)); return svals; } NetworkEngine::RequestAnswer::RequestAnswer(ParsedMessage&& msg) : ntoken(std::move(msg.token)), values(std::move(msg.values)), fields(std::move(msg.fields)), nodes4(std::move(msg.nodes4)), nodes6(std::move(msg.nodes6)) {} NetworkEngine::NetworkEngine(Logger& log, Scheduler& scheduler) : myid(zeroes), DHT_LOG(log), scheduler(scheduler) {} NetworkEngine::NetworkEngine(InfoHash& myid, NetId net, int s, int s6, Logger& log, Scheduler& scheduler, decltype(NetworkEngine::onError) onError, decltype(NetworkEngine::onNewNode) onNewNode, decltype(NetworkEngine::onReportedAddr) onReportedAddr, decltype(NetworkEngine::onPing) onPing, decltype(NetworkEngine::onFindNode) onFindNode, decltype(NetworkEngine::onGetValues) onGetValues, decltype(NetworkEngine::onListen) onListen, decltype(NetworkEngine::onAnnounce) onAnnounce, decltype(NetworkEngine::onRefresh) onRefresh) : onError(onError), onNewNode(onNewNode), onReportedAddr(onReportedAddr), onPing(onPing), onFindNode(onFindNode), onGetValues(onGetValues), onListen(onListen), onAnnounce(onAnnounce), onRefresh(onRefresh), myid(myid), network(net), dht_socket(s), dht_socket6(s6), DHT_LOG(log), scheduler(scheduler) { transaction_id = std::uniform_int_distribution{1}(rd_device); } NetworkEngine::~NetworkEngine() { clear(); } void NetworkEngine::tellListener(Sp node, uint32_t socket_id, const InfoHash& hash, want_t want, const Blob& ntoken, std::vector>&& nodes, std::vector>&& nodes6, std::vector>&& values, const Query& query) { auto nnodes = bufferNodes(node->getFamily(), hash, want, nodes, nodes6); try { sendNodesValues(node->addr, TransId((char*)&socket_id, 4), nnodes.first, nnodes.second, values, query, ntoken); } catch (const std::overflow_error& e) { DHT_LOG.e("Can't send value: buffer not large enough !"); } } bool NetworkEngine::isRunning(sa_family_t af) const { switch (af) { case 0: return dht_socket >= 0 || dht_socket6 >= 0; case AF_INET: return dht_socket >= 0; case AF_INET6: return dht_socket6 >= 0; default: return false; } } Sp NetworkEngine::openSocket(const Sp& node, TransPrefix tp, SocketCb&& cb) { auto tid = TransId {tp, getNewTid()}; auto s = opened_sockets.emplace(tid, std::make_shared(node, tid, cb)); if (not s.second) DHT_LOG.e(node->id, "[node %s] socket (tid: %d) already opened!", node->id.toString().c_str(), tid.toInt()); return s.first->second; } void NetworkEngine::closeSocket(Sp socket) { if (socket) opened_sockets.erase(socket->id); } void NetworkEngine::cancelRequest(Sp& req) { if (req) { req->cancel(); closeSocket(req->socket); requests.erase(req->tid); } } void NetworkEngine::clear() { for (auto& req : requests) req.second->setExpired(); requests.clear(); } void NetworkEngine::connectivityChanged(sa_family_t af) { cache.clearBadNodes(af); } void NetworkEngine::requestStep(Sp sreq) { auto& req = *sreq; if (not req.pending()) { if (req.cancelled()) requests.erase(req.tid); return; } auto now = scheduler.time(); auto& node = *req.node; if (req.isExpired(now)) { DHT_LOG.e(node.id, "[node %s] expired !", node.toString().c_str()); node.setExpired(); requests.erase(req.tid); return; } else if (req.attempt_count == 1) { req.on_expired(req, false); } send((char*)req.msg.data(), req.msg.size(), (node.reply_time >= now - UDP_REPLY_TIME) ? 0 : MSG_CONFIRM, node.addr); ++req.attempt_count; req.last_try = now; std::weak_ptr wreq = sreq; scheduler.add(req.last_try + Node::MAX_RESPONSE_TIME, [this,wreq] { if (auto req = wreq.lock()) requestStep(req); }); } /** * Sends a request to a node. Request::MAX_ATTEMPT_COUNT attempts will * be made before the request expires. */ void NetworkEngine::sendRequest(Sp& request) { request->start = scheduler.time(); auto e = requests.emplace(request->tid, request); if (not e.second) { // Should not happen ! // Try to handle this scenario as well as we can e.first->second->setExpired(); e.first->second = request; DHT_LOG.e(request->node->id, "Request already existed (tid: %d)!", request->tid.getTid()); } request->node->requested(request); requestStep(request); } /* Rate control for requests we receive. */ bool NetworkEngine::rateLimit(const SockAddr& addr) { const auto& now = scheduler.time(); // occasional IP limiter maintenance std::bernoulli_distribution rand_trial(1./128.); if (rand_trial(rd_device)) { for (auto it = address_rate_limiter.begin(); it != address_rate_limiter.end();) { if (it->second.maintain(now) == 0) address_rate_limiter.erase(it++); else ++it; } } auto it = address_rate_limiter.emplace(addr, IpLimiter{}); // invoke per IP, then global rate limiter return it.first->second.limit(now) and rate_limiter.limit(now); } bool NetworkEngine::isMartian(const SockAddr& addr) { if (addr.getPort() == 0) return true; switch(addr.getFamily()) { case AF_INET: { const auto& sin = addr.getIPv4(); const uint8_t* address = (const uint8_t*)&sin.sin_addr; return (address[0] == 0) || ((address[0] & 0xE0) == 0xE0); } case AF_INET6: { if (addr.second < sizeof(sockaddr_in6)) return true; const auto& sin6 = addr.getIPv6(); const uint8_t* address = (const uint8_t*)&sin6.sin6_addr; return address[0] == 0xFF || (address[0] == 0xFE && (address[1] & 0xC0) == 0x80) || memcmp(address, zeroes.data(), 16) == 0 || memcmp(address, v4prefix, 12) == 0; } default: return true; } } /* The internal blacklist is an LRU cache of nodes that have sent incorrect messages. */ void NetworkEngine::blacklistNode(const Sp& n) { n->setExpired(); for (auto rit = requests.begin(); rit != requests.end();) { if (rit->second->node == n) { //rit->second->cancel(); requests.erase(rit++); } else { ++rit; } } blacklist.emplace(n->addr); } bool NetworkEngine::isNodeBlacklisted(const SockAddr& addr) const { return blacklist.find(addr) != blacklist.end(); } void NetworkEngine::processMessage(const uint8_t *buf, size_t buflen, const SockAddr& from) { if (isMartian(from)) { DHT_LOG.w("Received packet from martian node %s", from.toString().c_str()); return; } if (isNodeBlacklisted(from)) { DHT_LOG.w("Received packet from blacklisted node %s", from.toString().c_str()); return; } std::unique_ptr msg {new ParsedMessage}; try { msgpack::unpacked msg_res = msgpack::unpack((const char*)buf, buflen); msg->msgpack_unpack(msg_res.get()); } catch (const std::exception& e) { DHT_LOG.w("Can't process message of size %lu: %s", buflen, e.what()); DHT_LOG.DEBUG.logPrintable(buf, buflen); return; } if (msg->network != network) { DHT_LOG.d("Received message from other network %u", msg->network); return; } const auto& now = scheduler.time(); // partial value data if (msg->type == MessageType::ValueData) { auto pmsg_it = partial_messages.find(msg->tid); if (pmsg_it == partial_messages.end()) { DHT_LOG.d("Can't find partial message"); rateLimit(from); return; } if (!pmsg_it->second.from.equals(from)) { DHT_LOG.d("Received partial message data from unexpected IP address"); rateLimit(from); return; } // append data block if (pmsg_it->second.msg->append(*msg)) { pmsg_it->second.last_part = now; // check data completion if (pmsg_it->second.msg->complete()) { // process the full message process(std::move(pmsg_it->second.msg), from); partial_messages.erase(pmsg_it); } else scheduler.add(now + RX_TIMEOUT, std::bind(&NetworkEngine::maintainRxBuffer, this, msg->tid)); } return; } if (msg->id == myid || msg->id == zeroes) { DHT_LOG.d("Received message from self"); return; } if (msg->type > MessageType::Reply) { /* Rate limit requests. */ if (!rateLimit(from)) { DHT_LOG.w("Dropping request due to rate limiting"); return; } } if (msg->value_parts.empty()) { process(std::move(msg), from); } else { // starting partial message session PartialMessage pmsg; pmsg.from = from; pmsg.msg = std::move(msg); pmsg.start = now; pmsg.last_part = now; auto wmsg = partial_messages.emplace(pmsg.msg->tid, std::move(pmsg)); if (wmsg.second) { scheduler.add(now + RX_MAX_PACKET_TIME, std::bind(&NetworkEngine::maintainRxBuffer, this, wmsg.first->first)); scheduler.add(now + RX_TIMEOUT, std::bind(&NetworkEngine::maintainRxBuffer, this, wmsg.first->first)); } else DHT_LOG.e("Partial message with given TID already exists"); } } void NetworkEngine::process(std::unique_ptr&& msg, const SockAddr& from) { const auto& now = scheduler.time(); if (msg->type == MessageType::Error or msg->type == MessageType::Reply) { /* either response for a request or data for an opened socket */ auto req_it = requests.find(msg->tid); auto rsocket_it = opened_sockets.end(); if (req_it == requests.end()) rsocket_it = opened_sockets.find(msg->tid); if (req_it == requests.end() and rsocket_it == opened_sockets.end()) throw DhtProtocolException {DhtProtocolException::UNKNOWN_TID, "Can't find transaction", msg->id}; auto req = req_it != requests.end() ? req_it->second : nullptr; auto rsocket = rsocket_it != opened_sockets.end() ? rsocket_it->second : nullptr; auto& node = req ? req->node : rsocket->node; if (node->id != msg->id) { if (node->id == zeroes) // received reply to a message sent when we didn't know the node ID. node = cache.getNode(msg->id, from, now, true); else { // received reply from unexpected node node->received(now, req); onNewNode(node, 2); DHT_LOG.w(node->id, "[node %s] message received from unexpected node", node->toString().c_str()); return; } } else node->update(from); node->received(now, req); onNewNode(node, 2); onReportedAddr(msg->id, msg->addr); if (req and (req->cancelled() or req->expired() or req->completed())) { DHT_LOG.w(node->id, "[node %s] response to expired, cancelled or completed request", node->toString().c_str()); requests.erase(req_it); return; } switch (msg->type) { case MessageType::Error: { if (msg->id != zeroes and ( (msg->error_code == DhtProtocolException::NOT_FOUND and msg->tid.matches(TransPrefix::REFRESH)) or (msg->error_code == DhtProtocolException::UNAUTHORIZED and (msg->tid.matches(TransPrefix::ANNOUNCE_VALUES) or msg->tid.matches(TransPrefix::LISTEN))))) { req->last_try = time_point::min(); req->reply_time = time_point::min(); onError(req, DhtProtocolException {msg->error_code}); } else { DHT_LOG.w(msg->id, "[node %s %s] received unknown error message %u", msg->id.toString().c_str(), from.toString().c_str(), msg->error_code); } break; } case MessageType::Reply: if (req) { /* request reply */ if (msg->type == MessageType::AnnounceValue or msg->type == MessageType::Listen) req->node->authSuccess(); // erase before calling callback to make sure iterator is still valid requests.erase(req_it); req->reply_time = scheduler.time(); deserializeNodes(*msg, from); req->setDone(std::move(*msg)); break; } else { /* request socket data */ deserializeNodes(*msg, from); rsocket->on_receive(rsocket->node, std::move(*msg)); } default: break; } } else { auto node = cache.getNode(msg->id, from, now, true); node->received(now, {}); onNewNode(node, 1); try { switch (msg->type) { case MessageType::Ping: ++in_stats.ping; DHT_LOG.d(node->id, "[node %s] sending pong", node->toString().c_str()); onPing(node); sendPong(from, msg->tid); break; case MessageType::FindNode: { DHT_LOG.d(msg->target, node->id, "[node %s] got 'find' request for %s (%d)", node->toString().c_str(), msg->target.toString().c_str(), msg->want); ++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); break; } case MessageType::GetValues: { DHT_LOG.d(msg->info_hash, node->id, "[node %s] got 'get' request for %s", node->toString().c_str(), msg->info_hash.toString().c_str()); ++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); break; } case MessageType::AnnounceValue: { DHT_LOG.d(msg->info_hash, node->id, "[node %s] got 'put' request for %s", node->toString().c_str(), msg->info_hash.toString().c_str()); ++in_stats.put; onAnnounce(node, msg->info_hash, msg->token, msg->values, msg->created); /* Note that if storageStore failed, we lie to the requestor. This is to prevent them from backtracking, and hence polluting the DHT. */ for (auto& v : msg->values) { sendValueAnnounced(from, msg->tid, v->id); } break; } case MessageType::Refresh: DHT_LOG.d(msg->info_hash, node->id, "[node %s] got 'refresh' request for %s", node->toString().c_str(), msg->info_hash.toString().c_str()); 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); break; case MessageType::Listen: { DHT_LOG.d(msg->info_hash, node->id, "[node %s] got 'listen' request for %s", node->toString().c_str(), msg->info_hash.toString().c_str()); ++in_stats.listen; /* TODO: backward compatibility check to remove in a few versions (see ::sendListen doc) */ auto socket_id = msg->socket_id.getTid() ? msg->socket_id : TransId { TransPrefix::GET_VALUES, msg->tid.getTid() }; RequestAnswer answer = onListen(node, msg->info_hash, msg->token, socket_id.toInt(), std::move(msg->query)); sendListenConfirmation(from, msg->tid); break; } default: break; } } catch (const std::overflow_error& e) { DHT_LOG.e("Can't send value: buffer not large enough !"); } catch (DhtProtocolException& e) { sendError(from, msg->tid, e.getCode(), e.getMsg().c_str(), true); } } } void packToken(msgpack::packer& pk, const Blob& token) { pk.pack_bin(token.size()); pk.pack_bin_body((char*)token.data(), token.size()); } void insertAddr(msgpack::packer& pk, const SockAddr& addr) { size_t addr_len = std::min(addr.second, (addr.getFamily() == AF_INET) ? sizeof(in_addr) : sizeof(in6_addr)); void* addr_ptr = (addr.getFamily() == AF_INET) ? (void*)&((sockaddr_in*)&addr.first)->sin_addr : (void*)&((sockaddr_in6*)&addr.first)->sin6_addr; pk.pack("sa"); pk.pack_bin(addr_len); pk.pack_bin_body((char*)addr_ptr, addr_len); } void NetworkEngine::send(const char *buf, size_t len, int flags, const SockAddr& addr) { if (addr.second == 0) return; int s; if (addr.getFamily() == AF_INET) s = dht_socket; else if (addr.getFamily() == AF_INET6) s = dht_socket6; else s = -1; if (s < 0) return; if (sendto(s, buf, len, flags, (const sockaddr*)&addr.first, addr.second) == -1) DHT_LOG.e("Can't send message to %s: %s", addr.toString().c_str(), strerror(errno)); } Sp NetworkEngine::sendPing(Sp node, RequestCb&& on_done, RequestExpiredCb&& on_expired) { auto tid = TransId {TransPrefix::PING, getNewTid()}; msgpack::sbuffer buffer; msgpack::packer pk(&buffer); pk.pack_map(5+(network?1:0)); pk.pack(std::string("a")); pk.pack_map(1); pk.pack(std::string("id")); pk.pack(myid); pk.pack(std::string("q")); pk.pack(std::string("ping")); pk.pack(std::string("t")); pk.pack_bin(tid.size()); pk.pack_bin_body((const char*)tid.data(), tid.size()); pk.pack(std::string("y")); pk.pack(std::string("q")); pk.pack(std::string("v")); pk.pack(my_v); if (network) { pk.pack(std::string("n")); pk.pack(network); } auto req = std::make_shared(tid, node, Blob(buffer.data(), buffer.data() + buffer.size()), [=](const Request& req_status, ParsedMessage&&) { DHT_LOG.d(req_status.node->id, "[node %s] got pong !", req_status.node->toString().c_str()); if (on_done) { on_done(req_status, {}); } }, [=](const Request& req_status, bool done) { /* on expired */ if (on_expired) { on_expired(req_status, done); } } ); sendRequest(req); ++out_stats.ping; return req; } void NetworkEngine::sendPong(const SockAddr& addr, TransId tid) { msgpack::sbuffer buffer; msgpack::packer pk(&buffer); pk.pack_map(4+(network?1:0)); pk.pack(std::string("r")); pk.pack_map(2); pk.pack(std::string("id")); pk.pack(myid); insertAddr(pk, addr); pk.pack(std::string("t")); pk.pack_bin(tid.size()); pk.pack_bin_body((const char*)tid.data(), tid.size()); pk.pack(std::string("y")); pk.pack(std::string("r")); pk.pack(std::string("v")); pk.pack(my_v); if (network) { pk.pack(std::string("n")); pk.pack(network); } send(buffer.data(), buffer.size(), 0, addr); } Sp NetworkEngine::sendFindNode(Sp n, const InfoHash& target, want_t want, RequestCb&& on_done, RequestExpiredCb&& on_expired) { auto tid = TransId {TransPrefix::FIND_NODE, getNewTid()}; msgpack::sbuffer buffer; msgpack::packer pk(&buffer); pk.pack_map(5+(network?1:0)); pk.pack(std::string("a")); pk.pack_map(2 + (want>0?1:0)); pk.pack(std::string("id")); pk.pack(myid); pk.pack(std::string("target")); pk.pack(target); if (want > 0) { pk.pack(std::string("w")); pk.pack_array(((want & WANT4)?1:0) + ((want & WANT6)?1:0)); if (want & WANT4) pk.pack(AF_INET); if (want & WANT6) pk.pack(AF_INET6); } pk.pack(std::string("q")); pk.pack(std::string("find")); pk.pack(std::string("t")); pk.pack_bin(tid.size()); pk.pack_bin_body((const char*)tid.data(), tid.size()); pk.pack(std::string("y")); pk.pack(std::string("q")); pk.pack(std::string("v")); pk.pack(my_v); if (network) { pk.pack(std::string("n")); pk.pack(network); } auto req = std::make_shared(tid, n, Blob(buffer.data(), buffer.data() + buffer.size()), [=](const Request& req_status, ParsedMessage&& msg) { /* on done */ if (on_done) { on_done(req_status, {std::forward(msg)}); } }, [=](const Request& req_status, bool done) { /* on expired */ if (on_expired) { on_expired(req_status, done); } } ); sendRequest(req); ++out_stats.find; return req; } Sp NetworkEngine::sendGetValues(Sp n, const InfoHash& info_hash, const Query& query, want_t want, RequestCb&& on_done, RequestExpiredCb&& on_expired) { auto tid = TransId {TransPrefix::GET_VALUES, getNewTid()}; msgpack::sbuffer buffer; msgpack::packer pk(&buffer); pk.pack_map(5+(network?1:0)); pk.pack(std::string("a")); pk.pack_map(2 + (query.where.getFilter() or not query.select.getSelection().empty() ? 1:0) + (want>0?1:0)); pk.pack(std::string("id")); pk.pack(myid); pk.pack(std::string("h")); pk.pack(info_hash); pk.pack(std::string("q")); pk.pack(query); if (want > 0) { pk.pack(std::string("w")); pk.pack_array(((want & WANT4)?1:0) + ((want & WANT6)?1:0)); if (want & WANT4) pk.pack(AF_INET); if (want & WANT6) pk.pack(AF_INET6); } pk.pack(std::string("q")); pk.pack(std::string("get")); pk.pack(std::string("t")); pk.pack_bin(tid.size()); pk.pack_bin_body((const char*)tid.data(), tid.size()); pk.pack(std::string("y")); pk.pack(std::string("q")); pk.pack(std::string("v")); pk.pack(my_v); if (network) { pk.pack(std::string("n")); pk.pack(network); } auto req = std::make_shared(tid, n, Blob(buffer.data(), buffer.data() + buffer.size()), [=](const Request& req_status, ParsedMessage&& msg) { /* on done */ if (on_done) { on_done(req_status, {std::forward(msg)}); } }, [=](const Request& req_status, bool done) { /* on expired */ if (on_expired) { on_expired(req_status, done); } } ); sendRequest(req); ++out_stats.get; return req; } SockAddr deserializeIPv4(const uint8_t* ni) { SockAddr addr; auto& sin = addr.getIPv4(); std::fill_n((uint8_t*)&sin, sizeof(sockaddr_in), 0); addr.setFamily(AF_INET); memcpy(&sin.sin_addr, ni, 4); memcpy(&sin.sin_port, ni + 4, 2); return addr; } SockAddr deserializeIPv6(const uint8_t* ni) { SockAddr addr; auto& sin6 = addr.getIPv6(); std::fill_n((uint8_t*)&sin6, sizeof(sockaddr_in6), 0); addr.setFamily(AF_INET6); memcpy(&sin6.sin6_addr, ni, 16); memcpy(&sin6.sin6_port, ni + 16, 2); return addr; } void NetworkEngine::deserializeNodes(ParsedMessage& msg, const SockAddr& from) { if (msg.nodes4_raw.size() % NODE4_INFO_BUF_LEN != 0 || msg.nodes6_raw.size() % NODE6_INFO_BUF_LEN != 0) { throw DhtProtocolException {DhtProtocolException::WRONG_NODE_INFO_BUF_LEN}; } else { // deserialize nodes const auto& now = scheduler.time(); for (unsigned i = 0; i < msg.nodes4_raw.size() / NODE4_INFO_BUF_LEN; i++) { uint8_t *ni = msg.nodes4_raw.data() + i * NODE4_INFO_BUF_LEN; const InfoHash& ni_id = *reinterpret_cast(ni); if (ni_id == myid) continue; SockAddr addr = deserializeIPv4(ni + ni_id.size()); if (addr.isLoopback() and from.getFamily() == AF_INET) { auto port = addr.getPort(); addr = from; addr.setPort(port); } if (isMartian(addr) || isNodeBlacklisted(addr)) continue; msg.nodes4.emplace_back(cache.getNode(ni_id, addr, now, false)); onNewNode(msg.nodes4.back(), 0); } for (unsigned i = 0; i < msg.nodes6_raw.size() / NODE6_INFO_BUF_LEN; i++) { uint8_t *ni = msg.nodes6_raw.data() + i * NODE6_INFO_BUF_LEN; const InfoHash& ni_id = *reinterpret_cast(ni); if (ni_id == myid) continue; SockAddr addr = deserializeIPv6(ni + ni_id.size()); if (addr.isLoopback() and from.getFamily() == AF_INET6) { auto port = addr.getPort(); addr = from; addr.setPort(port); } if (isMartian(addr) || isNodeBlacklisted(addr)) continue; msg.nodes6.emplace_back(cache.getNode(ni_id, addr, now, false)); onNewNode(msg.nodes6.back(), 0); } } } std::vector NetworkEngine::packValueHeader(msgpack::sbuffer& buffer, const std::vector>& st) { auto svals = serializeValues(st); size_t total_size = 0; for (const auto& v : svals) total_size += v.size(); msgpack::packer pk(&buffer); pk.pack(std::string("values")); pk.pack_array(svals.size()); // try to put everything in a single UDP packet if (svals.size() < 50 && total_size < MAX_PACKET_VALUE_SIZE) { for (const auto& b : svals) buffer.write((const char*)b.data(), b.size()); DHT_LOG.d("sending %lu bytes of values", total_size); svals.clear(); } else { for (const auto& b : svals) pk.pack(b.size()); } return svals; } void NetworkEngine::sendValueParts(TransId tid, const std::vector& svals, const SockAddr& addr) { msgpack::sbuffer buffer; unsigned i=0; for (const auto& v: svals) { size_t start {0}, end; do { end = std::min(start + MTU, v.size()); buffer.clear(); msgpack::packer pk(&buffer); pk.pack_map(3+(network?1:0)); if (network) { pk.pack(std::string("n")); pk.pack(network); } pk.pack(std::string("y")); pk.pack(std::string("v")); pk.pack(std::string("t")); pk.pack_bin(tid.size()); pk.pack_bin_body((const char*)tid.data(), tid.size()); pk.pack(std::string("p")); pk.pack_map(1); pk.pack(i); pk.pack_map(2); pk.pack(std::string("o")); pk.pack(start); pk.pack(std::string("d")); pk.pack_bin(end-start); pk.pack_bin_body((const char*)v.data()+start, end-start); send(buffer.data(), buffer.size(), 0, addr); start = end; } while (start != v.size()); i++; } } void NetworkEngine::sendNodesValues(const SockAddr& addr, TransId tid, const Blob& nodes, const Blob& nodes6, const std::vector>& st, const Query& query, const Blob& token) { msgpack::sbuffer buffer; msgpack::packer pk(&buffer); pk.pack_map(4+(network?1:0)); pk.pack(std::string("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(std::string("id")); pk.pack(myid); insertAddr(pk, addr); if (nodes.size() > 0) { pk.pack(std::string("n4")); pk.pack_bin(nodes.size()); pk.pack_bin_body((const char*)nodes.data(), nodes.size()); } if (nodes6.size() > 0) { pk.pack(std::string("n6")); pk.pack_bin(nodes6.size()); pk.pack_bin_body((const char*)nodes6.data(), nodes6.size()); } if (not token.empty()) { pk.pack(std::string("token")); packToken(pk, token); } std::vector svals {}; if (not st.empty()) { /* pack complete values */ auto fields = query.select.getSelection(); if (fields.empty()) { svals = packValueHeader(buffer, st); } else { /* pack fields */ pk.pack(std::string("fields")); pk.pack_map(2); pk.pack(std::string("f")); pk.pack(fields); pk.pack(std::string("v")); pk.pack_array(st.size()*fields.size()); for (const auto& v : st) v->msgpack_pack_fields(fields, pk); //DHT_LOG_DEBUG("sending closest nodes (%d+%d nodes.), %u value headers containing %u fields", // nodes.size(), nodes6.size(), st.size(), fields.size()); } } pk.pack(std::string("t")); pk.pack_bin(tid.size()); pk.pack_bin_body((const char*)tid.data(), tid.size()); pk.pack(std::string("y")); pk.pack(std::string("r")); pk.pack(std::string("v")); pk.pack(my_v); if (network) { pk.pack(std::string("n")); pk.pack(network); } // send response send(buffer.data(), buffer.size(), 0, addr); // send parts if (not svals.empty()) sendValueParts(tid, svals, addr); } Blob NetworkEngine::bufferNodes(sa_family_t af, const InfoHash& id, std::vector>& nodes) { std::sort(nodes.begin(), nodes.end(), [&](const Sp& a, const Sp& b){ return id.xorCmp(a->id, b->id) < 0; }); size_t nnode = std::min(SEND_NODES, nodes.size()); Blob bnodes; if (af == AF_INET) { bnodes.resize(NODE4_INFO_BUF_LEN * nnode); for (size_t i=0; isin_addr, sizeof(in_addr)); memcpy(dest + HASH_LEN + sizeof(in_addr), &sin->sin_port, sizeof(in_port_t)); } } else if (af == AF_INET6) { bnodes.resize(NODE6_INFO_BUF_LEN * nnode); for (size_t i=0; isin6_addr, sizeof(in6_addr)); memcpy(dest + HASH_LEN + sizeof(in6_addr), &sin6->sin6_port, sizeof(in_port_t)); } } return bnodes; } std::pair 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; if (want & WANT4) bnodes4 = bufferNodes(AF_INET, id, nodes4); Blob bnodes6; if (want & WANT6) bnodes6 = bufferNodes(AF_INET6, id, nodes6); return {std::move(bnodes4), std::move(bnodes6)}; } Sp NetworkEngine::sendListen(Sp n, const InfoHash& hash, const Query& query, const Blob& token, Sp previous, RequestCb&& on_done, RequestExpiredCb&& on_expired, SocketCb&& socket_cb) { Sp socket; auto tid = TransId { TransPrefix::LISTEN, previous ? previous->tid.getTid() : getNewTid() }; if (previous and previous->node == n) { socket = previous->socket; } else { if (previous) DHT_LOG.e(hash, "[node %s] trying refresh listen contract with wrong node", previous->node->toString().c_str()); /* TODO: Manually creating a socket for "listen" backward compatibility. * As soon as this is not an issue anymore, switch to using * ::createSocket function! */ auto sid = TransId {TransPrefix::GET_VALUES, tid.getTid()}; socket = std::make_shared(n, sid, socket_cb); opened_sockets.emplace(sid, socket); } if (not socket) { DHT_LOG.e(hash, "[node %s] unable to get a valid socket for listen. Aborting listen", n->toString().c_str()); return {}; } msgpack::sbuffer buffer; msgpack::packer pk(&buffer); pk.pack_map(5+(network?1:0)); auto has_query = query.where.getFilter() or not query.select.getSelection().empty(); pk.pack(std::string("a")); pk.pack_map(4 + has_query); pk.pack(std::string("id")); pk.pack(myid); pk.pack(std::string("h")); pk.pack(hash); pk.pack(std::string("token")); packToken(pk, token); pk.pack(std::string("sid")); pk.pack_bin(socket->id.size()); pk.pack_bin_body((const char*)socket->id.data(), socket->id.size()); if (has_query) { pk.pack(std::string("q")); pk.pack(query); } pk.pack(std::string("q")); pk.pack(std::string("listen")); pk.pack(std::string("t")); pk.pack_bin(tid.size()); pk.pack_bin_body((const char*)tid.data(), tid.size()); pk.pack(std::string("y")); pk.pack(std::string("q")); pk.pack(std::string("v")); pk.pack(my_v); if (network) { pk.pack(std::string("n")); pk.pack(network); } auto req = std::make_shared(tid, n, Blob(buffer.data(), buffer.data() + buffer.size()), [=](const Request& req_status, ParsedMessage&& msg) { /* on done */ if (on_done) on_done(req_status, {std::forward(msg)}); }, [=](const Request& req_status, bool done) { /* on expired */ if (on_expired) on_expired(req_status, done); }, socket ); sendRequest(req); ++out_stats.listen; return req; } void NetworkEngine::sendListenConfirmation(const SockAddr& addr, TransId tid) { msgpack::sbuffer buffer; msgpack::packer pk(&buffer); pk.pack_map(4+(network?1:0)); pk.pack(std::string("r")); pk.pack_map(2); pk.pack(std::string("id")); pk.pack(myid); insertAddr(pk, addr); pk.pack(std::string("t")); pk.pack_bin(tid.size()); pk.pack_bin_body((const char*)tid.data(), tid.size()); pk.pack(std::string("y")); pk.pack(std::string("r")); pk.pack(std::string("v")); pk.pack(my_v); if (network) { pk.pack(std::string("n")); pk.pack(network); } send(buffer.data(), buffer.size(), 0, addr); } Sp NetworkEngine::sendAnnounceValue(Sp n, const InfoHash& infohash, const Sp& value, time_point created, const Blob& token, RequestCb&& on_done, RequestExpiredCb&& on_expired) { auto tid = TransId {TransPrefix::ANNOUNCE_VALUES, getNewTid()}; msgpack::sbuffer buffer; msgpack::packer pk(&buffer); pk.pack_map(5+(network?1:0)); pk.pack(std::string("a")); pk.pack_map((created < scheduler.time() ? 5 : 4)); pk.pack(std::string("id")); pk.pack(myid); pk.pack(std::string("h")); pk.pack(infohash); auto v = packValueHeader(buffer, {value}); if (created < scheduler.time()) { pk.pack(std::string("c")); pk.pack(to_time_t(created)); } pk.pack(std::string("token")); pk.pack(token); pk.pack(std::string("q")); pk.pack(std::string("put")); pk.pack(std::string("t")); pk.pack_bin(tid.size()); pk.pack_bin_body((const char*)tid.data(), tid.size()); pk.pack(std::string("y")); pk.pack(std::string("q")); pk.pack(std::string("v")); pk.pack(my_v); if (network) { pk.pack(std::string("n")); pk.pack(network); } auto req = std::make_shared(tid, n, Blob(buffer.data(), buffer.data() + buffer.size()), [=](const Request& req_status, ParsedMessage&& msg) { /* on done */ if (msg.value_id == Value::INVALID_ID) { DHT_LOG.d(infohash, "Unknown search or announce!"); } else { if (on_done) { RequestAnswer answer {}; answer.vid = msg.value_id; on_done(req_status, std::move(answer)); } } }, [=](const Request& req_status, bool done) { /* on expired */ if (on_expired) { on_expired(req_status, done); } } ); sendRequest(req); if (not v.empty()) sendValueParts(tid, v, n->addr); ++out_stats.put; return req; } Sp NetworkEngine::sendRefreshValue(Sp n, const InfoHash& infohash, const Value::Id& vid, const Blob& token, RequestCb&& on_done, RequestExpiredCb&& on_expired) { auto tid = TransId {TransPrefix::REFRESH, getNewTid()}; msgpack::sbuffer buffer; msgpack::packer pk(&buffer); pk.pack_map(5+(network?1:0)); pk.pack(std::string("a")); pk.pack_map(4); pk.pack(std::string("id")); pk.pack(myid); pk.pack(std::string("h")); pk.pack(infohash); pk.pack(std::string("vid")); pk.pack(vid); pk.pack(std::string("token")); pk.pack(token); pk.pack(std::string("q")); pk.pack(std::string("refresh")); pk.pack(std::string("t")); pk.pack_bin(tid.size()); pk.pack_bin_body((const char*)tid.data(), tid.size()); pk.pack(std::string("y")); pk.pack(std::string("q")); pk.pack(std::string("v")); pk.pack(my_v); if (network) { pk.pack(std::string("n")); pk.pack(network); } auto req = std::make_shared(tid, n, Blob(buffer.data(), buffer.data() + buffer.size()), [=](const Request& req_status, ParsedMessage&& msg) { /* on done */ if (msg.value_id == Value::INVALID_ID) { DHT_LOG.d(infohash, "Unknown search or announce!"); } else { if (on_done) { RequestAnswer answer {}; answer.vid = msg.value_id; on_done(req_status, std::move(answer)); } } }, [=](const Request& req_status, bool done) { /* on expired */ if (on_expired) { on_expired(req_status, done); } } ); sendRequest(req); ++out_stats.refresh; return req; } void NetworkEngine::sendValueAnnounced(const SockAddr& addr, TransId tid, Value::Id vid) { msgpack::sbuffer buffer; msgpack::packer pk(&buffer); pk.pack_map(4+(network?1:0)); pk.pack(std::string("r")); pk.pack_map(3); pk.pack(std::string("id")); pk.pack(myid); pk.pack(std::string("vid")); pk.pack(vid); insertAddr(pk, addr); pk.pack(std::string("t")); pk.pack_bin(tid.size()); pk.pack_bin_body((const char*)tid.data(), tid.size()); pk.pack(std::string("y")); pk.pack(std::string("r")); pk.pack(std::string("v")); pk.pack(my_v); if (network) { pk.pack(std::string("n")); pk.pack(network); } send(buffer.data(), buffer.size(), 0, addr); } void NetworkEngine::sendError(const SockAddr& addr, TransId 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)); pk.pack(std::string("e")); pk.pack_array(2); pk.pack(code); pk.pack(message); if (include_id) { pk.pack(std::string("r")); pk.pack_map(1); pk.pack(std::string("id")); pk.pack(myid); } pk.pack(std::string("t")); pk.pack_bin(tid.size()); pk.pack_bin_body((const char*)tid.data(), tid.size()); pk.pack(std::string("y")); pk.pack(std::string("e")); pk.pack(std::string("v")); pk.pack(my_v); if (network) { pk.pack(std::string("n")); pk.pack(network); } send(buffer.data(), buffer.size(), 0, addr); } void ParsedMessage::msgpack_unpack(msgpack::object msg) { auto y = findMapValue(msg, "y"); auto r = findMapValue(msg, "r"); auto e = findMapValue(msg, "e"); auto v = findMapValue(msg, "p"); if (auto t = findMapValue(msg, "t")) tid = {t->as>()}; if (auto rv = findMapValue(msg, "v")) ua = rv->as(); if (auto netid = findMapValue(msg, "n")) network = netid->as(); std::string q; if (auto rq = findMapValue(msg, "q")) { if (rq->type != msgpack::type::STR) throw msgpack::type_error(); q = rq->as(); } if (e) type = MessageType::Error; else if (r) type = MessageType::Reply; else if (v) type = MessageType::ValueData; else if (y and y->as() != "q") throw msgpack::type_error(); else if (q == "ping") type = MessageType::Ping; else if (q == "find") type = MessageType::FindNode; else if (q == "get") type = MessageType::GetValues; else if (q == "listen") type = MessageType::Listen; else if (q == "put") type = MessageType::AnnounceValue; else if (q == "refresh") type = MessageType::Refresh; else throw msgpack::type_error(); if (type == MessageType::ValueData) { if (v->type != msgpack::type::MAP) throw msgpack::type_error(); for (size_t i = 0; i < v->via.map.size; ++i) { auto& vdat = v->via.map.ptr[i]; auto o = findMapValue(vdat.val, "o"); auto d = findMapValue(vdat.val, "d"); if (not o or not d) continue; value_parts.emplace(vdat.key.as(), std::pair(o->as(), unpackBlob(*d))); } return; } auto a = findMapValue(msg, "a"); if (!a && !r && !e) throw msgpack::type_error(); auto& req = a ? *a : (r ? *r : *e); if (e) { if (e->type != msgpack::type::ARRAY) throw msgpack::type_error(); error_code = e->via.array.ptr[0].as(); } if (auto t = findMapValue(req, "sid")) socket_id = {t->as>()}; if (auto rid = findMapValue(req, "id")) id = {*rid}; if (auto rh = findMapValue(req, "h")) info_hash = {*rh}; if (auto rtarget = findMapValue(req, "target")) target = {*rtarget}; if (auto rquery = findMapValue(req, "q")) query.msgpack_unpack(*rquery); if (auto otoken = findMapValue(req, "token")) token = unpackBlob(*otoken); if (auto vid = findMapValue(req, "vid")) value_id = vid->as(); if (auto rnodes4 = findMapValue(req, "n4")) nodes4_raw = unpackBlob(*rnodes4); if (auto rnodes6 = findMapValue(req, "n6")) nodes6_raw = unpackBlob(*rnodes6); if (auto sa = findMapValue(req, "sa")) { if (sa->type != msgpack::type::BIN) throw msgpack::type_error(); auto l = sa->via.bin.size; if (l == sizeof(in_addr)) { auto a = (sockaddr_in*)&addr.first; std::fill_n((uint8_t*)a, sizeof(sockaddr_in), 0); a->sin_family = AF_INET; a->sin_port = 0; std::copy_n(sa->via.bin.ptr, l, (char*)&a->sin_addr); addr.second = sizeof(sockaddr_in); } else if (l == sizeof(in6_addr)) { auto a = (sockaddr_in6*)&addr.first; std::fill_n((uint8_t*)a, sizeof(sockaddr_in6), 0); a->sin6_family = AF_INET6; a->sin6_port = 0; std::copy_n(sa->via.bin.ptr, l, (char*)&a->sin6_addr); addr.second = sizeof(sockaddr_in6); } } else addr.second = 0; if (auto rcreated = findMapValue(req, "c")) created = from_time_t(rcreated->as()); if (auto rvalues = findMapValue(req, "values")) { if (rvalues->type != msgpack::type::ARRAY) throw msgpack::type_error(); for (size_t i = 0; i < rvalues->via.array.size; i++) { auto& packed_v = rvalues->via.array.ptr[i]; if (packed_v.type == msgpack::type::POSITIVE_INTEGER) { // Skip oversize values with a small margin for header overhead if (packed_v.via.u64 > MAX_VALUE_SIZE + 32) continue; value_parts.emplace(i, std::make_pair(packed_v.via.u64, Blob{})); } else { try { values.emplace_back(std::make_shared(rvalues->via.array.ptr[i])); } catch (const std::exception& e) { //DHT_LOG_WARN("Error reading value: %s", e.what()); } } } } else if (auto raw_fields = findMapValue(req, "fields")) { if (auto rfields = findMapValue(*raw_fields, "f")) { auto vfields = rfields->as>(); if (auto rvalues = findMapValue(*raw_fields, "v")) { if (rvalues->type != msgpack::type::ARRAY) throw msgpack::type_error(); size_t val_num = rvalues->via.array.size / vfields.size(); for (size_t i = 0; i < val_num; ++i) { try { auto v = std::make_shared(); v->msgpack_unpack_fields(vfields, *rvalues, i*vfields.size()); fields.emplace_back(std::move(v)); } catch (const std::exception& e) { } } } } else { throw msgpack::type_error(); } } if (auto w = findMapValue(req, "w")) { if (w->type != msgpack::type::ARRAY) throw msgpack::type_error(); want = 0; for (unsigned i=0; ivia.array.size; i++) { auto& val = w->via.array.ptr[i]; try { auto w = val.as(); if (w == AF_INET) want |= WANT4; else if(w == AF_INET6) want |= WANT6; } catch (const std::exception& e) {}; } } else { want = -1; } } void NetworkEngine::maintainRxBuffer(const TransId& tid) { auto msg = partial_messages.find(tid); if (msg != partial_messages.end()) { const auto& now = scheduler.time(); if (msg->second.start + RX_MAX_PACKET_TIME < now || msg->second.last_part + RX_TIMEOUT < now) { DHT_LOG.w("Dropping expired partial message from %s", msg->second.from.toString().c_str()); partial_messages.erase(msg); } } } bool ParsedMessage::append(const ParsedMessage& block) { bool ret(false); for (const auto& ve : block.value_parts) { auto part_val = value_parts.find(ve.first); if (part_val == value_parts.end() || part_val->second.second.size() >= part_val->second.first) continue; // TODO: handle out-of-order packets if (ve.second.first != part_val->second.second.size()) { //std::cout << "skipping out-of-order packet" << std::endl; continue; } ret = true; part_val->second.second.insert(part_val->second.second.end(), ve.second.second.begin(), ve.second.second.end()); } return ret; } bool ParsedMessage::complete() { for (auto& e : value_parts) { //std::cout << "part " << e.first << ": " << e.second.second.size() << "/" << e.second.first << std::endl; if (e.second.first > e.second.second.size()) return false; } for (auto& e : value_parts) { msgpack::unpacked msg; msgpack::unpack(msg, (const char*)e.second.second.data(), e.second.second.size()); values.emplace_back(std::make_shared(msg.get())); } return true; } } /* namespace net */ } /* namespace dht */