danicoin/src/P2p/P2pNode.cpp
2016-01-18 15:33:29 +00:00

541 lines
15 KiB
C++
Executable file

// Copyright (c) 2011-2016 The Cryptonote developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "P2pNode.h"
#include <boost/uuid/uuid_io.hpp>
#include <System/ContextGroupTimeout.h>
#include <System/InterruptedException.h>
#include <System/Ipv4Address.h>
#include <System/OperationTimeout.h>
#include <System/TcpConnection.h>
#include <System/TcpConnector.h>
#include "Common/StdInputStream.h"
#include "Common/StdOutputStream.h"
#include "Serialization/BinaryInputStreamSerializer.h"
#include "Serialization/BinaryOutputStreamSerializer.h"
#include "LevinProtocol.h"
#include "P2pConnectionProxy.h"
#include "P2pContext.h"
#include "P2pContextOwner.h"
#include "P2pNetworks.h"
using namespace Common;
using namespace Logging;
using namespace System;
namespace CryptoNote {
namespace {
class PeerIndexGenerator {
public:
PeerIndexGenerator(size_t maxIndex)
: maxIndex(maxIndex), randCount(0) {
assert(maxIndex > 0);
}
bool generate(size_t& num) {
while (randCount < (maxIndex + 1) * 3) {
++randCount;
auto idx = getRandomIndex();
if (visited.count(idx) == 0) {
visited.insert(idx);
num = idx;
return true;
}
}
return false;
}
private:
size_t getRandomIndex() {
//divide by zero workaround
if (maxIndex == 0) {
return 0;
}
size_t x = Crypto::rand<size_t>() % (maxIndex + 1);
return (x * x * x) / (maxIndex * maxIndex);
}
const size_t maxIndex;
size_t randCount;
std::set<size_t> visited;
};
NetworkAddress getRemoteAddress(const TcpConnection& connection) {
auto addressAndPort = connection.getPeerAddressAndPort();
NetworkAddress remoteAddress;
remoteAddress.ip = hostToNetwork(addressAndPort.first.getValue());
remoteAddress.port = addressAndPort.second;
return remoteAddress;
}
void doWithTimeoutAndThrow(System::Dispatcher& dispatcher, std::chrono::nanoseconds timeout, std::function<void()> f) {
std::string result;
System::ContextGroup cg(dispatcher);
System::ContextGroupTimeout cgTimeout(dispatcher, cg, timeout);
cg.spawn([&] {
try {
f();
} catch (System::InterruptedException&) {
result = "Operation timeout";
} catch (std::exception& e) {
result = e.what();
}
});
cg.wait();
if (!result.empty()) {
throw std::runtime_error(result);
}
}
}
P2pNode::P2pNode(const P2pNodeConfig& cfg, Dispatcher& dispatcher, Logging::ILogger& log, const Crypto::Hash& genesisHash, PeerIdType peerId) :
logger(log, "P2pNode:" + std::to_string(cfg.getBindPort())),
m_stopRequested(false),
m_cfg(cfg),
m_myPeerId(peerId),
m_genesisHash(genesisHash),
m_genesisPayload(CORE_SYNC_DATA{ 1, genesisHash }),
m_dispatcher(dispatcher),
workingContextGroup(dispatcher),
m_connectorTimer(dispatcher),
m_queueEvent(dispatcher) {
m_peerlist.init(cfg.getAllowLocalIp());
m_listener = TcpListener(m_dispatcher, Ipv4Address(m_cfg.getBindIp()), m_cfg.getBindPort());
for (auto& peer : cfg.getPeers()) {
m_peerlist.append_with_peer_white(peer);
}
}
P2pNode::~P2pNode() {
assert(m_contexts.empty());
assert(m_connectionQueue.empty());
}
std::unique_ptr<IP2pConnection> P2pNode::receiveConnection() {
while (m_connectionQueue.empty()) {
m_queueEvent.wait();
m_queueEvent.clear();
if (m_stopRequested) {
throw InterruptedException();
}
}
auto connection = std::move(m_connectionQueue.front());
m_connectionQueue.pop_front();
return connection;
}
void P2pNode::start() {
workingContextGroup.spawn(std::bind(&P2pNode::acceptLoop, this));
workingContextGroup.spawn(std::bind(&P2pNode::connectorLoop, this));
}
void P2pNode::stop() {
if (m_stopRequested) {
return; // already stopped
}
m_stopRequested = true;
// clear prepared connections
m_connectionQueue.clear();
// stop processing
m_queueEvent.set();
workingContextGroup.interrupt();
workingContextGroup.wait();
}
void P2pNode::serialize(ISerializer& s) {
uint8_t version = 1;
s(version, "version");
if (version != 1) {
return;
}
s(m_peerlist, "peerlist");
}
void P2pNode::save(std::ostream& os) {
StdOutputStream stream(os);
BinaryOutputStreamSerializer a(stream);
CryptoNote::serialize(*this, a);
}
void P2pNode::load(std::istream& in) {
StdInputStream stream(in);
BinaryInputStreamSerializer a(stream);
CryptoNote::serialize(*this, a);
}
void P2pNode::acceptLoop() {
while (!m_stopRequested) {
try {
auto connection = m_listener.accept();
auto ctx = new P2pContext(m_dispatcher, std::move(connection), true,
getRemoteAddress(connection), m_cfg.getTimedSyncInterval(), getGenesisPayload());
logger(INFO) << "Incoming connection from " << ctx->getRemoteAddress();
workingContextGroup.spawn([this, ctx] {
preprocessIncomingConnection(ContextPtr(ctx));
});
} catch (InterruptedException&) {
break;
} catch (const std::exception& e) {
logger(WARNING) << "Exception in acceptLoop: " << e.what();
}
}
logger(DEBUGGING) << "acceptLoop finished";
}
void P2pNode::connectorLoop() {
while (!m_stopRequested) {
try {
connectPeers();
m_connectorTimer.sleep(m_cfg.getConnectInterval());
} catch (InterruptedException&) {
break;
} catch (const std::exception& e) {
logger(WARNING) << "Exception in connectorLoop: " << e.what();
}
}
}
void P2pNode::connectPeers() {
if (!m_cfg.getExclusiveNodes().empty()) {
connectPeerList(m_cfg.getExclusiveNodes());
return;
}
// if white peer list is empty, get peers from seeds
if (m_peerlist.get_white_peers_count() == 0 && !m_cfg.getSeedNodes().empty()) {
auto seedNodes = m_cfg.getSeedNodes();
std::random_shuffle(seedNodes.begin(), seedNodes.end());
for (const auto& seed : seedNodes) {
auto conn = tryToConnectPeer(seed);
if (conn != nullptr && fetchPeerList(std::move(conn))) {
break;
}
}
}
connectPeerList(m_cfg.getPriorityNodes());
const size_t totalExpectedConnectionsCount = m_cfg.getExpectedOutgoingConnectionsCount();
const size_t expectedWhiteConnections = (totalExpectedConnectionsCount * m_cfg.getWhiteListConnectionsPercent()) / 100;
const size_t outgoingConnections = getOutgoingConnectionsCount();
if (outgoingConnections < totalExpectedConnectionsCount) {
if (outgoingConnections < expectedWhiteConnections) {
//start from white list
makeExpectedConnectionsCount(m_peerlist.getWhite(), expectedWhiteConnections);
//and then do grey list
makeExpectedConnectionsCount(m_peerlist.getGray(), totalExpectedConnectionsCount);
} else {
//start from grey list
makeExpectedConnectionsCount(m_peerlist.getGray(), totalExpectedConnectionsCount);
//and then do white list
makeExpectedConnectionsCount(m_peerlist.getWhite(), totalExpectedConnectionsCount);
}
}
}
void P2pNode::makeExpectedConnectionsCount(const PeerlistManager::Peerlist& peerlist, size_t connectionsCount) {
while (getOutgoingConnectionsCount() < connectionsCount) {
if (peerlist.count() == 0) {
return;
}
if (!makeNewConnectionFromPeerlist(peerlist)) {
break;
}
}
}
bool P2pNode::makeNewConnectionFromPeerlist(const PeerlistManager::Peerlist& peerlist) {
size_t peerIndex;
PeerIndexGenerator idxGen(std::min<uint64_t>(peerlist.count() - 1, m_cfg.getPeerListConnectRange()));
for (size_t tryCount = 0; idxGen.generate(peerIndex) && tryCount < m_cfg.getPeerListGetTryCount(); ++tryCount) {
PeerlistEntry peer;
if (!peerlist.get(peer, peerIndex)) {
logger(WARNING) << "Failed to get peer from list, idx = " << peerIndex;
continue;
}
if (isPeerUsed(peer)) {
continue;
}
logger(DEBUGGING) << "Selected peer: [" << peer.id << " " << peer.adr << "] last_seen: " <<
(peer.last_seen ? Common::timeIntervalToString(time(NULL) - peer.last_seen) : "never");
auto conn = tryToConnectPeer(peer.adr);
if (conn.get()) {
enqueueConnection(createProxy(std::move(conn)));
return true;
}
}
return false;
}
void P2pNode::preprocessIncomingConnection(ContextPtr ctx) {
try {
logger(DEBUGGING) << *ctx << "preprocessIncomingConnection";
OperationTimeout<P2pContext> timeout(m_dispatcher, *ctx, m_cfg.getHandshakeTimeout());
// create proxy and process handshake
auto proxy = createProxy(std::move(ctx));
if (proxy->processIncomingHandshake()) {
enqueueConnection(std::move(proxy));
}
} catch (std::exception& e) {
logger(WARNING) << " Failed to process connection: " << e.what();
}
}
void P2pNode::connectPeerList(const std::vector<NetworkAddress>& peers) {
for (const auto& address : peers) {
if (!isPeerConnected(address)) {
auto conn = tryToConnectPeer(address);
if (conn != nullptr) {
enqueueConnection(createProxy(std::move(conn)));
}
}
}
}
bool P2pNode::isPeerConnected(const NetworkAddress& address) {
for (const auto& c : m_contexts) {
if (!c->isIncoming() && c->getRemoteAddress() == address) {
return true;
}
}
return false;
}
bool P2pNode::isPeerUsed(const PeerlistEntry& peer) {
if (m_myPeerId == peer.id) {
return true; //dont make connections to ourself
}
for (const auto& c : m_contexts) {
if (c->getPeerId() == peer.id || (!c->isIncoming() && c->getRemoteAddress() == peer.adr)) {
return true;
}
}
return false;
}
P2pNode::ContextPtr P2pNode::tryToConnectPeer(const NetworkAddress& address) {
try {
TcpConnector connector(m_dispatcher);
TcpConnection tcpConnection;
doWithTimeoutAndThrow(m_dispatcher, m_cfg.getConnectTimeout(), [&] {
tcpConnection = connector.connect(
Ipv4Address(Common::ipAddressToString(address.ip)),
static_cast<uint16_t>(address.port));
});
logger(DEBUGGING) << "connection established to " << address;
return ContextPtr(new P2pContext(m_dispatcher, std::move(tcpConnection), false, address, m_cfg.getTimedSyncInterval(), getGenesisPayload()));
} catch (std::exception& e) {
logger(DEBUGGING) << "Connection to " << address << " failed: " << e.what();
}
return ContextPtr();
}
bool P2pNode::fetchPeerList(ContextPtr connection) {
try {
COMMAND_HANDSHAKE::request request{ getNodeData(), getGenesisPayload() };
COMMAND_HANDSHAKE::response response;
OperationTimeout<P2pContext> timeout(m_dispatcher, *connection, m_cfg.getHandshakeTimeout());
connection->writeMessage(makeRequest(COMMAND_HANDSHAKE::ID, LevinProtocol::encode(request)));
LevinProtocol::Command cmd;
if (!connection->readCommand(cmd)) {
throw std::runtime_error("Connection closed unexpectedly");
}
if (!cmd.isResponse || cmd.command != COMMAND_HANDSHAKE::ID) {
throw std::runtime_error("Received unexpected reply");
}
if (!LevinProtocol::decode(cmd.buf, response)) {
throw std::runtime_error("Invalid reply format");
}
if (response.node_data.network_id != request.node_data.network_id) {
logger(ERROR) << *connection << "COMMAND_HANDSHAKE failed, wrong network: " << response.node_data.network_id;
return false;
}
return handleRemotePeerList(response.local_peerlist, response.node_data.local_time);
} catch (std::exception& e) {
logger(INFO) << *connection << "Failed to obtain peer list: " << e.what();
}
return false;
}
namespace {
std::list<PeerlistEntry> fixTimeDelta(const std::list<PeerlistEntry>& peerlist, time_t remoteTime) {
//fix time delta
int64_t delta = time(nullptr) - remoteTime;
std::list<PeerlistEntry> peerlistCopy(peerlist);
for (PeerlistEntry& be : peerlistCopy) {
if (be.last_seen > uint64_t(remoteTime)) {
throw std::runtime_error("Invalid peerlist entry (time in future)");
}
be.last_seen += delta;
}
return peerlistCopy;
}
}
bool P2pNode::handleRemotePeerList(const std::list<PeerlistEntry>& peerlist, time_t remoteTime) {
return m_peerlist.merge_peerlist(fixTimeDelta(peerlist, remoteTime));
}
const CORE_SYNC_DATA& P2pNode::getGenesisPayload() const {
return m_genesisPayload;
}
std::list<PeerlistEntry> P2pNode::getLocalPeerList() const {
std::list<PeerlistEntry> peerlist;
m_peerlist.get_peerlist_head(peerlist);
return peerlist;
}
basic_node_data P2pNode::getNodeData() const {
basic_node_data nodeData;
nodeData.network_id = m_cfg.getNetworkId();
nodeData.version = P2PProtocolVersion::CURRENT;
nodeData.local_time = time(nullptr);
nodeData.peer_id = m_myPeerId;
if (m_cfg.getHideMyPort()) {
nodeData.my_port = 0;
} else {
nodeData.my_port = m_cfg.getExternalPort() ? m_cfg.getExternalPort() : m_cfg.getBindPort();
}
return nodeData;
}
PeerIdType P2pNode::getPeerId() const {
return m_myPeerId;
}
size_t P2pNode::getOutgoingConnectionsCount() const {
size_t count = 0;
for (const auto& ctx : m_contexts) {
if (!ctx->isIncoming()) {
++count;
}
}
return count;
}
std::unique_ptr<P2pConnectionProxy> P2pNode::createProxy(ContextPtr ctx) {
return std::unique_ptr<P2pConnectionProxy>(
new P2pConnectionProxy(P2pContextOwner(ctx.release(), m_contexts), *this));
}
void P2pNode::enqueueConnection(std::unique_ptr<P2pConnectionProxy> proxy) {
if (m_stopRequested) {
return; // skip operation
}
m_connectionQueue.push_back(std::move(proxy));
m_queueEvent.set();
}
void P2pNode::tryPing(P2pContext& ctx) {
if (ctx.getPeerId() == m_myPeerId || !m_peerlist.is_ip_allowed(ctx.getRemoteAddress().ip) || ctx.getPeerPort() == 0) {
return;
}
NetworkAddress peerAddress;
peerAddress.ip = ctx.getRemoteAddress().ip;
peerAddress.port = ctx.getPeerPort();
try {
TcpConnector connector(m_dispatcher);
TcpConnection connection;
doWithTimeoutAndThrow(m_dispatcher, m_cfg.getConnectTimeout(), [&] {
connection = connector.connect(Ipv4Address(Common::ipAddressToString(peerAddress.ip)), static_cast<uint16_t>(peerAddress.port));
});
doWithTimeoutAndThrow(m_dispatcher, m_cfg.getHandshakeTimeout(), [&] {
LevinProtocol proto(connection);
COMMAND_PING::request request;
COMMAND_PING::response response;
proto.invoke(COMMAND_PING::ID, request, response);
if (response.status == PING_OK_RESPONSE_STATUS_TEXT && response.peer_id == ctx.getPeerId()) {
PeerlistEntry entry;
entry.adr = peerAddress;
entry.id = ctx.getPeerId();
entry.last_seen = time(nullptr);
m_peerlist.append_with_peer_white(entry);
} else {
logger(Logging::DEBUGGING) << ctx << "back ping invoke wrong response \"" << response.status << "\" from"
<< peerAddress << ", expected peerId=" << ctx.getPeerId() << ", got " << response.peer_id;
}
});
} catch (std::exception& e) {
logger(DEBUGGING) << "Ping to " << peerAddress << " failed: " << e.what();
}
}
void P2pNode::handleNodeData(const basic_node_data& node, P2pContext& context) {
if (node.network_id != m_cfg.getNetworkId()) {
std::ostringstream msg;
msg << context << "COMMAND_HANDSHAKE Failed, wrong network! (" << node.network_id << ")";
throw std::runtime_error(msg.str());
}
if (node.peer_id == m_myPeerId) {
throw std::runtime_error("Connection to self detected");
}
context.setPeerInfo(node.version, node.peer_id, static_cast<uint16_t>(node.my_port));
if (!context.isIncoming()) {
m_peerlist.set_peer_just_seen(node.peer_id, context.getRemoteAddress());
}
}
}