Files
opendht/src/network_engine.cpp
T
Simon Désaulniers dafae934c4 actually cancelling listens when appropriate
Just before, listens were "cancelled", but unsued memory in network engine's
data structure would be left there.
2016-05-02 15:25:15 -04:00

868 lines
32 KiB
C++

/*
Copyright (C) 2009-2014 Juliusz Chroboczek
Copyright (C) 2014-2016 Savoir-faire Linux Inc.
Author(s) : Adrien Béraud <adrien.beraud@savoirfairelinux.com>,
Simon Désaulniers <sim.desaulniers@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include "network_engine.h"
#include <msgpack.hpp>
namespace dht {
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"};
constexpr std::chrono::seconds NetworkEngine::UDP_REPLY_TIME;
const std::string NetworkEngine::my_v {"RNG1"};
const constexpr uint16_t NetworkEngine::TransId::INVALID;
std::mt19937 NetworkEngine::rd_device {dht::crypto::random_device{}()};
const NetworkEngine::TransPrefix NetworkEngine::TransPrefix::PING = {"pn"};
const NetworkEngine::TransPrefix NetworkEngine::TransPrefix::FIND_NODE = {"fn"};
const NetworkEngine::TransPrefix NetworkEngine::TransPrefix::GET_VALUES = {"gt"};
const NetworkEngine::TransPrefix NetworkEngine::TransPrefix::ANNOUNCE_VALUES = {"pt"};
const NetworkEngine::TransPrefix NetworkEngine::TransPrefix::LISTEN = {"lt"};
constexpr long unsigned NetworkEngine::MAX_REQUESTS_PER_SEC;
void
NetworkEngine::tellListener(const sockaddr *sa, socklen_t salen, uint16_t rid, InfoHash hash, want_t want,
Blob ntoken, std::vector<std::shared_ptr<Node>> nodes, std::vector<std::shared_ptr<Node>> nodes6,
std::vector<std::shared_ptr<Value>> values)
{
auto nnodes = bufferNodes(sa->sa_family, hash, want, nodes, nodes6);
try {
sendNodesValues(sa, salen, TransId {TransPrefix::GET_VALUES, (uint16_t)rid}, nnodes.first, nnodes.second,
values, ntoken);
} catch (const std::overflow_error& e) {
DHT_LOG.ERROR("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;
}
}
/* Rate control for requests we receive. */
bool
NetworkEngine::rateLimit()
{
using namespace std::chrono;
while (not rate_limit_time.empty() and duration_cast<seconds>(now - rate_limit_time.front()) > seconds(1))
rate_limit_time.pop();
if (rate_limit_time.size() >= MAX_REQUESTS_PER_SEC)
return false;
rate_limit_time.emplace(now);
return true;
}
void
NetworkEngine::processMessage(const uint8_t *buf, size_t buflen, const sockaddr *from, socklen_t fromlen) {
ParsedMessage msg;
try {
msgpack::unpacked msg_res = msgpack::unpack((const char*)buf, buflen);
msg.msgpack_unpack(msg_res.get());
if (msg.type != MessageType::Error && msg.id == zeroes)
throw DhtException("no or invalid InfoHash");
} catch (const std::exception& e) {
DHT_LOG.WARN("Can't process message of size %lu: %s.", buflen, e.what());
DHT_LOG.DEBUG.logPrintable(buf, buflen);
return;
}
if (msg.id == myid) {
DHT_LOG.DEBUG("Received message from self.");
return;
}
if (msg.type > MessageType::Reply) {
/* Rate limit requests. */
if (!rateLimit()) {
DHT_LOG.WARN("Dropping request due to rate limiting.");
return;
}
}
if (msg.tid.length != 4) {
DHT_LOG.ERROR("Broken node truncates transaction ids (len: %d): ", msg.tid.length);
DHT_LOG.ERROR.logPrintable(buf, buflen);
throw DhtProtocolException {DhtProtocolException::INVALID_TID_SIZE};
}
uint16_t ttid = 0;
if (msg.type == MessageType::Error or msg.type == MessageType::Reply) {
auto reqp = requests.find(msg.tid.getTid());
if (reqp == requests.end())
throw DhtProtocolException {DhtProtocolException::UNKNOWN_TID, "Can't find transaction", msg.id};
auto req = reqp->second;
auto node = onNewNode(msg.id, from, fromlen, 2);
onReportedAddr(msg.id, (sockaddr*)&msg.addr.first, msg.addr.second);
switch (msg.type) {
case MessageType::Error: {
if (msg.error_code == DhtProtocolException::UNAUTHORIZED
&& msg.id != zeroes
&& (msg.tid.matches(TransPrefix::ANNOUNCE_VALUES, &ttid)
|| msg.tid.matches(TransPrefix::LISTEN, &ttid)))
{
req->last_try = TIME_INVALID;
req->reply_time = TIME_INVALID;
onError(req, DhtProtocolException {DhtProtocolException::UNAUTHORIZED});
} else {
DHT_LOG.WARN("[node %s %s] received unknown error message %u",
msg.id.toString().c_str(), print_addr(from, fromlen).c_str(), msg.error_code);
DHT_LOG.WARN.logPrintable(buf, buflen);
}
break;
}
case MessageType::Reply:
if (not reqp->second->persistent or reqp->second->cancelled)
requests.erase(reqp);
req->reply_time = scheduler.time();
req->completed = true;
req->on_done(req, std::move(msg));
break;
default:
break;
}
} else {
auto node = onNewNode(msg.id, from, fromlen, 1);
try {
switch (msg.type) {
case MessageType::Ping:
++in_stats.ping;
DHT_LOG.DEBUG("Sending pong.");
onPing(node);
sendPong(from, fromlen, msg.tid);
break;
case MessageType::FindNode: {
DHT_LOG.DEBUG("[node %s %s] got 'find' request (%d).",
msg.id.toString().c_str(), print_addr(from, fromlen).c_str(), msg.want);
++in_stats.find;
RequestAnswer answer = onFindNode(node, msg.target, msg.want);
auto nnodes = bufferNodes(from->sa_family, msg.target, msg.want, answer.nodes, answer.nodes6);
sendNodesValues(from, fromlen, msg.tid, nnodes.first, nnodes.second, {}, answer.ntoken);
break;
}
case MessageType::GetValues: {
DHT_LOG.DEBUG("[node %s %s] got 'get' request for %s.",
msg.id.toString().c_str(), print_addr(from, fromlen).c_str(), msg.info_hash.toString().c_str());
++in_stats.get;
RequestAnswer answer = onGetValues(node, msg.info_hash, msg.want);
auto nnodes = bufferNodes(from->sa_family, msg.info_hash, msg.want, answer.nodes, answer.nodes6);
sendNodesValues(from, fromlen, msg.tid, nnodes.first, nnodes.second, answer.values, answer.ntoken);
break;
}
case MessageType::AnnounceValue: {
DHT_LOG.DEBUG("[node %s %s] got 'put' request for %s.",
msg.id.toString().c_str(), print_addr(from, fromlen).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, fromlen, msg.tid, v->id);
}
break;
}
case MessageType::Listen: {
DHT_LOG.DEBUG("[node %s %s] got 'listen' request for %s.",
msg.id.toString().c_str(), print_addr(from, fromlen).c_str(), msg.info_hash.toString().c_str());
if (!msg.tid.matches(TransPrefix::LISTEN, &ttid)) {
break;
}
++in_stats.listen;
RequestAnswer answer = onListen(node, msg.info_hash, msg.token, msg.tid.getTid());
sendListenConfirmation(from, fromlen, msg.tid);
break;
}
default:
break;
}
} catch (const std::overflow_error& e) {
DHT_LOG.ERROR("Can't send value: buffer not large enough !");
} catch (DhtProtocolException& e) {
sendError(from, fromlen, msg.tid, e.getCode(), e.getMsg().c_str());
}
}
}
void
packToken(msgpack::packer<msgpack::sbuffer>& pk, Blob token)
{
pk.pack_bin(token.size());
pk.pack_bin_body((char*)token.data(), token.size());
}
void
insertAddr(msgpack::packer<msgpack::sbuffer>& pk, const sockaddr *sa, socklen_t)
{
size_t addr_len = (sa->sa_family == AF_INET) ? sizeof(in_addr) : sizeof(in6_addr);
void* addr_ptr = (sa->sa_family == AF_INET) ? (void*)&((sockaddr_in*)sa)->sin_addr
: (void*)&((sockaddr_in6*)sa)->sin6_addr;
pk.pack("sa");
pk.pack_bin(addr_len);
pk.pack_bin_body((char*)addr_ptr, addr_len);
}
int
NetworkEngine::send(const char *buf, size_t len, int flags, const sockaddr *sa, socklen_t salen)
{
if (salen == 0)
return -1;
int s;
if (sa->sa_family == AF_INET)
s = dht_socket;
else if (sa->sa_family == AF_INET6)
s = dht_socket6;
else
s = -1;
if (s < 0)
return -1;
return sendto(s, buf, len, flags, sa, salen);
}
std::shared_ptr<NetworkEngine::Request>
NetworkEngine::sendPing(const sockaddr* sa, socklen_t salen, RequestCb on_done, RequestExpiredCb on_expired) {
auto tid = TransId {TransPrefix::PING, getNewTid()};
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_map(5);
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);
Blob b {buffer.data(), buffer.data() + buffer.size()};
std::shared_ptr<Request> req(new Request {tid.getTid(), std::make_shared<Node>(InfoHash {}, sa, salen), std::move(b),
[=](std::shared_ptr<Request> req_status, ParsedMessage&&){
if (on_done) {
on_done(req_status, {});
}
},
[=](std::shared_ptr<Request> req_status, bool) { /* on expired */
if (on_expired) {
on_expired(req_status, {});
}
}
});
sendRequest(req);
++out_stats.ping;
return req;
}
void
NetworkEngine::sendPong(const sockaddr* sa, socklen_t salen, TransId tid) {
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_map(4);
pk.pack(std::string("r")); pk.pack_map(2);
pk.pack(std::string("id")); pk.pack(myid);
insertAddr(pk, sa, salen);
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);
send(buffer.data(), buffer.size(), 0, sa, salen);
}
std::shared_ptr<NetworkEngine::Request>
NetworkEngine::sendFindNode(std::shared_ptr<Node> 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<msgpack::sbuffer> pk(&buffer);
pk.pack_map(5);
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);
Blob b {buffer.data(), buffer.data() + buffer.size()};
std::shared_ptr<Request> req(new Request {tid.getTid(), n, std::move(b),
[=](std::shared_ptr<Request> req_status, ParsedMessage&& msg) { /* on done */
if (on_done) {
on_done(req_status, deserializeNodesValues(msg));
}
},
[=](std::shared_ptr<Request> req_status, bool) { /* on expired */
if (on_expired) {
on_expired(req_status, {});
}
}
});
sendRequest(req);
++out_stats.find;
return req;
}
std::shared_ptr<NetworkEngine::Request>
NetworkEngine::sendGetValues(std::shared_ptr<Node> n, const InfoHash& info_hash, want_t want,
RequestCb on_done, RequestExpiredCb on_expired) {
auto tid = TransId {TransPrefix::GET_VALUES, getNewTid()};
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_map(5);
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("h")); pk.pack(info_hash);
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);
Blob b {buffer.data(), buffer.data() + buffer.size()};
std::shared_ptr<Request> req(new Request {tid.getTid(), n, std::move(b),
[=](std::shared_ptr<Request> req_status, ParsedMessage&& msg) { /* on done */
if (on_done) {
on_done(req_status, deserializeNodesValues(msg));
}
},
[=](std::shared_ptr<Request> req_status, bool) { /* on expired */
if (on_expired) {
on_expired(req_status, {});
}
}
});
sendRequest(req);
++out_stats.get;
return req;
}
NetworkEngine::RequestAnswer
NetworkEngine::deserializeNodesValues(ParsedMessage& msg) {
RequestAnswer req_a {msg.token, 0, std::move(msg.values), {}, {}};
if (msg.nodes4.size() % NODE4_INFO_BUF_LEN != 0 || msg.nodes6.size() % NODE6_INFO_BUF_LEN != 0) {
throw DhtProtocolException {DhtProtocolException::WRONG_NODE_INFO_BUF_LEN};
} else {
// deserialize nodes
for (unsigned i = 0; i < msg.nodes4.size() / NODE4_INFO_BUF_LEN; i++) {
uint8_t *ni = msg.nodes4.data() + i * NODE4_INFO_BUF_LEN;
const InfoHash& ni_id = *reinterpret_cast<InfoHash*>(ni);
if (ni_id == myid)
continue;
sockaddr_in sin;
std::fill_n((uint8_t*)&sin, sizeof(sockaddr_in), 0);
sin.sin_family = AF_INET;
memcpy(&sin.sin_addr, ni + ni_id.size(), 4);
memcpy(&sin.sin_port, ni + ni_id.size() + 4, 2);
if (auto n = onNewNode(ni_id, (sockaddr*)&sin, sizeof(sin), 0))
req_a.nodes.emplace_back(std::move(n));
}
for (unsigned i = 0; i < msg.nodes6.size() / NODE6_INFO_BUF_LEN; i++) {
uint8_t *ni = msg.nodes6.data() + i * NODE6_INFO_BUF_LEN;
const InfoHash& ni_id = *reinterpret_cast<InfoHash*>(ni);
if (ni_id == myid)
continue;
sockaddr_in6 sin6;
std::fill_n((uint8_t*)&sin6, sizeof(sockaddr_in6), 0);
sin6.sin6_family = AF_INET6;
memcpy(&sin6.sin6_addr, ni + HASH_LEN, 16);
memcpy(&sin6.sin6_port, ni + HASH_LEN + 16, 2);
if (auto n = onNewNode(ni_id, (sockaddr*)&sin6, sizeof(sin6), 0))
req_a.nodes.emplace_back(std::move(n));
}
}
return req_a;
}
void
NetworkEngine::sendNodesValues(const sockaddr* sa, socklen_t salen, TransId tid, const Blob& nodes, const Blob& nodes6,
const std::vector<std::shared_ptr<Value>>& st, const Blob& token) {
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_map(4);
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, sa, salen);
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);
}
if (not st.empty()) {
// We treat the storage as a circular list, and serve a randomly
// chosen slice. In order to make sure we fit,
// we limit ourselves to 50 values.
std::uniform_int_distribution<> pos_dis(0, st.size()-1);
std::vector<Blob> subset {};
subset.reserve(std::min<size_t>(st.size(), 50));
size_t total_size = 0;
unsigned j0 = pos_dis(rd_device);
unsigned j = j0;
unsigned k = 0;
do {
subset.emplace_back(packMsg(st[j]));
total_size += subset.back().size();
++k;
j = (j + 1) % st.size();
} while (j != j0 && k < 50 && total_size < MAX_VALUE_SIZE);
pk.pack(std::string("values"));
pk.pack_array(subset.size());
for (const auto& b : subset)
buffer.write((const char*)b.data(), b.size());
}
DHT_LOG.DEBUG("sending closest nodes (%d+%d nodes.)", nodes.size(), nodes6.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);
send(buffer.data(), buffer.size(), 0, sa, salen);
}
unsigned
NetworkEngine::insertClosestNode(uint8_t *nodes, unsigned numnodes, const InfoHash& id, const Node& n)
{
unsigned i, size;
if (n.ss.ss_family == AF_INET)
size = HASH_LEN + sizeof(in_addr) + sizeof(in_port_t); // 26
else if (n.ss.ss_family == AF_INET6)
size = HASH_LEN + sizeof(in6_addr) + sizeof(in_port_t); // 38
else
return numnodes;
for (i = 0; i < numnodes; i++) {
const InfoHash* nid = reinterpret_cast<const InfoHash*>(nodes + size * i);
if (InfoHash::cmp(n.id, *nid) == 0)
return numnodes;
if (id.xorCmp(n.id, *nid) < 0)
break;
}
if (i >= TARGET_NODES)
return numnodes;
if (numnodes < TARGET_NODES)
++numnodes;
if (i < numnodes - 1)
memmove(nodes + size * (i + 1), nodes + size * i, size * (numnodes - i - 1));
if (n.ss.ss_family == AF_INET) {
sockaddr_in *sin = (sockaddr_in*)&n.ss;
memcpy(nodes + size * i, n.id.data(), HASH_LEN);
memcpy(nodes + size * i + HASH_LEN, &sin->sin_addr, sizeof(in_addr));
memcpy(nodes + size * i + HASH_LEN + sizeof(in_addr), &sin->sin_port, 2);
}
else if (n.ss.ss_family == AF_INET6) {
sockaddr_in6 *sin6 = (sockaddr_in6*)&n.ss;
memcpy(nodes + size * i, n.id.data(), HASH_LEN);
memcpy(nodes + size * i + HASH_LEN, &sin6->sin6_addr, sizeof(in6_addr));
memcpy(nodes + size * i + HASH_LEN + sizeof(in6_addr), &sin6->sin6_port, 2);
}
return numnodes;
}
std::pair<Blob, Blob>
NetworkEngine::bufferNodes(sa_family_t af, const InfoHash& id, want_t want,
const std::vector<std::shared_ptr<Node>>& nodes, const std::vector<std::shared_ptr<Node>>& nodes6)
{
if (want < 0)
want = af == AF_INET ? WANT4 : WANT6;
auto buff = [=](Blob& nodes, const InfoHash& id, const std::vector<std::shared_ptr<Node>>& closest_nodes) {
size_t numnodes = 0;
for (const auto& n : closest_nodes)
numnodes = insertClosestNode(nodes.data(), numnodes, id, *n);
return numnodes;
};
Blob bnodes4;
if (want & WANT4) {
bnodes4.resize(NODE4_INFO_BUF_LEN * TARGET_NODES);
bnodes4.resize(NODE4_INFO_BUF_LEN * buff(bnodes4, id, nodes));
}
Blob bnodes6;
if (want & WANT6) {
bnodes6.resize(NODE6_INFO_BUF_LEN * TARGET_NODES);
bnodes6.resize(NODE6_INFO_BUF_LEN * buff(bnodes6, id, nodes6));
}
return {std::move(bnodes4), std::move(bnodes6)};
}
std::shared_ptr<NetworkEngine::Request>
NetworkEngine::sendListen(std::shared_ptr<Node> n, const InfoHash& infohash, const Blob& token,
RequestCb on_done, RequestExpiredCb on_expired) {
auto tid = TransId {TransPrefix::LISTEN, getNewTid()};
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_map(5);
pk.pack(std::string("a")); pk.pack_map(3);
pk.pack(std::string("id")); pk.pack(myid);
pk.pack(std::string("h")); pk.pack(infohash);
pk.pack(std::string("token")); packToken(pk, token);
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);
Blob b {buffer.data(), buffer.data() + buffer.size()};
std::shared_ptr<Request> req(new Request {tid.getTid(), n, std::move(b),
[=](std::shared_ptr<Request> req_status, ParsedMessage&& msg) { /* on done */
if (on_done)
on_done(req_status, deserializeNodesValues(msg));
},
[=](std::shared_ptr<Request> req_status, bool) { /* on expired */
if (on_expired)
on_expired(req_status, {});
},
true
});
sendRequest(req);
++out_stats.listen;
return req;
}
void
NetworkEngine::sendListenConfirmation(const sockaddr* sa, socklen_t salen, TransId tid) {
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_map(4);
pk.pack(std::string("r")); pk.pack_map(2);
pk.pack(std::string("id")); pk.pack(myid);
insertAddr(pk, sa, salen);
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);
send(buffer.data(), buffer.size(), 0, sa, salen);
}
std::shared_ptr<NetworkEngine::Request>
NetworkEngine::sendAnnounceValue(std::shared_ptr<Node> n, const InfoHash& infohash, const Value& 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<msgpack::sbuffer> pk(&buffer);
pk.pack_map(5);
pk.pack(std::string("a")); pk.pack_map((created < now ? 5 : 4));
pk.pack(std::string("id")); pk.pack(myid);
pk.pack(std::string("h")); pk.pack(infohash);
pk.pack(std::string("values")); pk.pack_array(1); pk.pack(value);
if (created < now) {
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);
Blob b {buffer.data(), buffer.data() + buffer.size()};
std::shared_ptr<Request> req(new Request {tid.getTid(), n, std::move(b),
[=](std::shared_ptr<Request> req_status, ParsedMessage&& msg) { /* on done */
if (msg.value_id == Value::INVALID_ID) {
DHT_LOG.DEBUG("Unknown search or announce!");
} else {
if (on_done) {
RequestAnswer answer {};
answer.vid = msg.value_id;
on_done(req_status, std::move(answer));
}
}
},
[=](std::shared_ptr<Request> req_status, bool) { /* on expired */
if (on_expired) {
on_expired(req_status, {});
}
}
});
sendRequest(req);
++out_stats.put;
return req;
}
void
NetworkEngine::sendValueAnnounced(const sockaddr* sa, socklen_t salen, TransId tid, Value::Id vid) {
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_map(4);
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, sa, salen);
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);
send(buffer.data(), buffer.size(), 0, sa, salen);
}
void
NetworkEngine::sendError(const sockaddr* sa,
socklen_t salen,
TransId tid,
uint16_t code,
const char *message,
bool include_id) {
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_map(4 + (include_id?1:0));
pk.pack(std::string("e")); pk.pack_array(2);
pk.pack(code);
pk.pack_str(strlen(message));
pk.pack_str_body(message, strlen(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);
send(buffer.data(), buffer.size(), 0, sa, salen);
}
msgpack::object*
findMapValue(msgpack::object& map, const std::string& key) {
if (map.type != msgpack::type::MAP) throw msgpack::type_error();
for (unsigned i = 0; i < map.via.map.size; i++) {
auto& o = map.via.map.ptr[i];
if(o.key.type != msgpack::type::STR)
continue;
if (o.key.as<std::string>() == key) {
return &o.val;
}
}
return nullptr;
}
void
NetworkEngine::ParsedMessage::msgpack_unpack(msgpack::object msg)
{
auto y = findMapValue(msg, "y");
auto a = findMapValue(msg, "a");
auto r = findMapValue(msg, "r");
auto e = findMapValue(msg, "e");
std::string query;
if (auto q = findMapValue(msg, "q")) {
if (q->type != msgpack::type::STR)
throw msgpack::type_error();
query = q->as<std::string>();
}
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<uint16_t>();
}
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 otoken = findMapValue(req, "token"))
token = unpackBlob(*otoken);
if (auto vid = findMapValue(req, "vid"))
value_id = vid->as<Value::Id>();
if (auto rnodes4 = findMapValue(req, "n4"))
nodes4 = unpackBlob(*rnodes4);
if (auto rnodes6 = findMapValue(req, "n6"))
nodes6 = 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<std::time_t>());
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++)
try {
values.emplace_back(std::make_shared<Value>(rvalues->via.array.ptr[i]));
} catch (const std::exception& e) {
//DHT_LOG.WARN("Error reading value: %s", e.what());
}
}
if (auto w = findMapValue(req, "w")) {
if (w->type != msgpack::type::ARRAY)
throw msgpack::type_error();
want = 0;
for (unsigned i=0; i<w->via.array.size; i++) {
auto& val = w->via.array.ptr[i];
try {
auto w = val.as<sa_family_t>();
if (w == AF_INET)
want |= WANT4;
else if(w == AF_INET6)
want |= WANT6;
} catch (const std::exception& e) {};
}
} else {
want = -1;
}
if (auto t = findMapValue(msg, "t"))
tid = {t->as<std::array<char, 4>>()};
if (auto rv = findMapValue(msg, "v"))
ua = rv->as<std::string>();
if (e)
type = MessageType::Error;
else if (r)
type = MessageType::Reply;
else if (y and y->as<std::string>() != "q")
throw msgpack::type_error();
else if (query == "ping")
type = MessageType::Ping;
else if (query == "find")
type = MessageType::FindNode;
else if (query == "get")
type = MessageType::GetValues;
else if (query == "listen")
type = MessageType::Listen;
else if (query == "put")
type = MessageType::AnnounceValue;
else
throw msgpack::type_error();
}
}