Gav Wood
10 years ago
36 changed files with 1765 additions and 121 deletions
@ -0,0 +1,66 @@ |
|||
cmake_policy(SET CMP0015 NEW) |
|||
|
|||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTATICLIB") |
|||
|
|||
aux_source_directory(. SRC_LIST) |
|||
|
|||
set(EXECUTABLE ethnet) |
|||
|
|||
# set(CMAKE_INSTALL_PREFIX ../lib) |
|||
if(ETH_STATIC) |
|||
add_library(${EXECUTABLE} STATIC ${SRC_LIST}) |
|||
else() |
|||
add_library(${EXECUTABLE} SHARED ${SRC_LIST}) |
|||
endif() |
|||
|
|||
file(GLOB HEADERS "*.h") |
|||
|
|||
include_directories(..) |
|||
|
|||
target_link_libraries(${EXECUTABLE} evm) |
|||
target_link_libraries(${EXECUTABLE} lll) |
|||
target_link_libraries(${EXECUTABLE} ethcore) |
|||
target_link_libraries(${EXECUTABLE} secp256k1) |
|||
if(MINIUPNPC_LS) |
|||
target_link_libraries(${EXECUTABLE} ${MINIUPNPC_LS}) |
|||
endif() |
|||
target_link_libraries(${EXECUTABLE} ${LEVELDB_LS}) |
|||
target_link_libraries(${EXECUTABLE} ${CRYPTOPP_LS}) |
|||
target_link_libraries(${EXECUTABLE} gmp) |
|||
|
|||
if("${TARGET_PLATFORM}" STREQUAL "w64") |
|||
target_link_libraries(${EXECUTABLE} boost_system-mt-s) |
|||
target_link_libraries(${EXECUTABLE} boost_regex-mt-s) |
|||
target_link_libraries(${EXECUTABLE} boost_filesystem-mt-s) |
|||
target_link_libraries(${EXECUTABLE} boost_thread_win32-mt-s) |
|||
target_link_libraries(${EXECUTABLE} iphlpapi) |
|||
target_link_libraries(${EXECUTABLE} ws2_32) |
|||
target_link_libraries(${EXECUTABLE} mswsock) |
|||
target_link_libraries(${EXECUTABLE} shlwapi) |
|||
elseif (APPLE) |
|||
# Latest mavericks boost libraries only come with -mt |
|||
target_link_libraries(${EXECUTABLE} boost_system-mt) |
|||
target_link_libraries(${EXECUTABLE} boost_regex-mt) |
|||
target_link_libraries(${EXECUTABLE} boost_filesystem-mt) |
|||
target_link_libraries(${EXECUTABLE} boost_thread-mt) |
|||
find_package(Threads REQUIRED) |
|||
target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) |
|||
elseif (UNIX) |
|||
target_link_libraries(${EXECUTABLE} ${Boost_SYSTEM_LIBRARY}) |
|||
target_link_libraries(${EXECUTABLE} ${Boost_REGEX_LIBRARY}) |
|||
target_link_libraries(${EXECUTABLE} ${Boost_FILESYSTEM_LIBRARY}) |
|||
target_link_libraries(${EXECUTABLE} ${Boost_THREAD_LIBRARY}) |
|||
target_link_libraries(${EXECUTABLE} ${Boost_DATE_TIME_LIBRARY}) |
|||
target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) |
|||
else () |
|||
target_link_libraries(${EXECUTABLE} boost_system) |
|||
target_link_libraries(${EXECUTABLE} boost_regex) |
|||
target_link_libraries(${EXECUTABLE} boost_filesystem) |
|||
target_link_libraries(${EXECUTABLE} boost_thread) |
|||
find_package(Threads REQUIRED) |
|||
target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) |
|||
endif () |
|||
|
|||
install( TARGETS ${EXECUTABLE} ARCHIVE DESTINATION lib LIBRARY DESTINATION lib ) |
|||
install( FILES ${HEADERS} DESTINATION include/${EXECUTABLE} ) |
|||
|
@ -0,0 +1,61 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file Common.cpp
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @date 2014 |
|||
*/ |
|||
|
|||
#include "Common.h" |
|||
using namespace std; |
|||
using namespace eth; |
|||
|
|||
// Helper function to determine if an address falls within one of the reserved ranges
|
|||
// For V4:
|
|||
// Class A "10.*", Class B "172.[16->31].*", Class C "192.168.*"
|
|||
// Not implemented yet for V6
|
|||
bool eth::isPrivateAddress(bi::address _addressToCheck) |
|||
{ |
|||
if (_addressToCheck.is_v4()) |
|||
{ |
|||
bi::address_v4 v4Address = _addressToCheck.to_v4(); |
|||
bi::address_v4::bytes_type bytesToCheck = v4Address.to_bytes(); |
|||
if (bytesToCheck[0] == 10 || bytesToCheck[0] == 127) |
|||
return true; |
|||
if (bytesToCheck[0] == 172 && (bytesToCheck[1] >= 16 && bytesToCheck[1] <= 31)) |
|||
return true; |
|||
if (bytesToCheck[0] == 192 && bytesToCheck[1] == 168) |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
std::string eth::reasonOf(DisconnectReason _r) |
|||
{ |
|||
switch (_r) |
|||
{ |
|||
case DisconnectRequested: return "Disconnect was requested."; |
|||
case TCPError: return "Low-level TCP communication error."; |
|||
case BadProtocol: return "Data format error."; |
|||
case UselessPeer: return "Peer had no use for this node."; |
|||
case TooManyPeers: return "Peer had too many connections."; |
|||
case DuplicatePeer: return "Peer was already connected."; |
|||
case IncompatibleProtocol: return "Peer protocol versions are incompatible."; |
|||
case ClientQuit: return "Peer is exiting."; |
|||
default: return "Unknown reason."; |
|||
} |
|||
} |
|||
|
@ -0,0 +1,91 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file Common.h
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @date 2014 |
|||
* |
|||
* Miscellanea required for the PeerHost/PeerSession classes. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <string> |
|||
#include <boost/asio.hpp> |
|||
#include <boost/asio/ip/tcp.hpp> |
|||
#include <chrono> |
|||
#include <libethential/Common.h> |
|||
#include <libethential/Log.h> |
|||
namespace ba = boost::asio; |
|||
namespace bi = boost::asio::ip; |
|||
|
|||
namespace eth |
|||
{ |
|||
|
|||
bool isPrivateAddress(bi::address _addressToCheck); |
|||
|
|||
class PeerHost; |
|||
class PeerSession; |
|||
|
|||
struct NetWarn: public LogChannel { static const char* name() { return "!N!"; } static const int verbosity = 0; }; |
|||
struct NetNote: public LogChannel { static const char* name() { return "*N*"; } static const int verbosity = 1; }; |
|||
struct NetMessageSummary: public LogChannel { static const char* name() { return "-N-"; } static const int verbosity = 2; }; |
|||
struct NetConnect: public LogChannel { static const char* name() { return "+N+"; } static const int verbosity = 4; }; |
|||
struct NetMessageDetail: public LogChannel { static const char* name() { return "=N="; } static const int verbosity = 5; }; |
|||
struct NetTriviaSummary: public LogChannel { static const char* name() { return "-N-"; } static const int verbosity = 10; }; |
|||
struct NetTriviaDetail: public LogChannel { static const char* name() { return "=N="; } static const int verbosity = 11; }; |
|||
struct NetAllDetail: public LogChannel { static const char* name() { return "=N="; } static const int verbosity = 13; }; |
|||
struct NetRight: public LogChannel { static const char* name() { return ">N>"; } static const int verbosity = 14; }; |
|||
struct NetLeft: public LogChannel { static const char* name() { return "<N<"; } static const int verbosity = 15; }; |
|||
|
|||
enum PacketType |
|||
{ |
|||
HelloPacket = 0, |
|||
DisconnectPacket, |
|||
PingPacket, |
|||
PongPacket, |
|||
GetPeersPacket, |
|||
PeersPacket, |
|||
UserPacket = 0x10 |
|||
}; |
|||
|
|||
enum DisconnectReason |
|||
{ |
|||
DisconnectRequested = 0, |
|||
TCPError, |
|||
BadProtocol, |
|||
UselessPeer, |
|||
TooManyPeers, |
|||
DuplicatePeer, |
|||
IncompatibleProtocol, |
|||
ClientQuit, |
|||
UserReason = 0x10 |
|||
}; |
|||
|
|||
/// @returns the string form of the given disconnection reason.
|
|||
std::string reasonOf(DisconnectReason _r); |
|||
|
|||
struct PeerInfo |
|||
{ |
|||
std::string clientVersion; |
|||
std::string host; |
|||
unsigned short port; |
|||
std::chrono::steady_clock::duration lastPing; |
|||
}; |
|||
|
|||
class UPnP; |
|||
|
|||
} |
@ -0,0 +1,480 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file PeerHost.cpp
|
|||
* @authors: |
|||
* Gav Wood <i@gavwood.com> |
|||
* Eric Lombrozo <elombrozo@gmail.com> |
|||
* @date 2014 |
|||
*/ |
|||
|
|||
#include "PeerHost.h" |
|||
|
|||
#include <sys/types.h> |
|||
#ifdef _WIN32 |
|||
// winsock is already included
|
|||
// #include <winsock.h>
|
|||
#else |
|||
#include <ifaddrs.h> |
|||
#endif |
|||
|
|||
#include <set> |
|||
#include <chrono> |
|||
#include <thread> |
|||
#include <libethential/Common.h> |
|||
#include <libethcore/UPnP.h> |
|||
#include <libethcore/Exceptions.h> |
|||
#include "PeerSession.h" |
|||
using namespace std; |
|||
using namespace eth; |
|||
|
|||
// Addresses we will skip during network interface discovery
|
|||
// Use a vector as the list is small
|
|||
// Why this and not names?
|
|||
// Under MacOSX loopback (127.0.0.1) can be named lo0 and br0 are bridges (0.0.0.0)
|
|||
static const set<bi::address> c_rejectAddresses = { |
|||
{bi::address_v4::from_string("127.0.0.1")}, |
|||
{bi::address_v6::from_string("::1")}, |
|||
{bi::address_v4::from_string("0.0.0.0")}, |
|||
{bi::address_v6::from_string("::")} |
|||
}; |
|||
|
|||
PeerHost::PeerHost(std::string const& _clientVersion, u256 _networkId, unsigned short _port, string const& _publicAddress, bool _upnp): |
|||
m_clientVersion(_clientVersion), |
|||
m_listenPort(_port), |
|||
m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), _port)), |
|||
m_socket(m_ioService), |
|||
m_key(KeyPair::create()), |
|||
m_networkId(_networkId) |
|||
{ |
|||
populateAddresses(); |
|||
determinePublic(_publicAddress, _upnp); |
|||
ensureAccepting(); |
|||
clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)); |
|||
} |
|||
|
|||
PeerHost::PeerHost(std::string const& _clientVersion, u256 _networkId, string const& _publicAddress, bool _upnp): |
|||
m_clientVersion(_clientVersion), |
|||
m_listenPort(0), |
|||
m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), 0)), |
|||
m_socket(m_ioService), |
|||
m_key(KeyPair::create()), |
|||
m_networkId(_networkId) |
|||
{ |
|||
m_listenPort = m_acceptor.local_endpoint().port(); |
|||
|
|||
// populate addresses.
|
|||
populateAddresses(); |
|||
determinePublic(_publicAddress, _upnp); |
|||
ensureAccepting(); |
|||
clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)); |
|||
} |
|||
|
|||
PeerHost::PeerHost(std::string const& _clientVersion, u256 _networkId): |
|||
m_clientVersion(_clientVersion), |
|||
m_listenPort(0), |
|||
m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), 0)), |
|||
m_socket(m_ioService), |
|||
m_key(KeyPair::create()), |
|||
m_networkId(_networkId) |
|||
{ |
|||
// populate addresses.
|
|||
populateAddresses(); |
|||
clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)); |
|||
} |
|||
|
|||
PeerHost::~PeerHost() |
|||
{ |
|||
disconnectPeers(); |
|||
} |
|||
|
|||
void PeerHost::registerPeer(std::shared_ptr<PeerSession> _s) |
|||
{ |
|||
Guard l(x_peers); |
|||
m_peers[_s->m_id] = _s; |
|||
} |
|||
|
|||
void PeerHost::disconnectPeers() |
|||
{ |
|||
for (unsigned n = 0;; n = 0) |
|||
{ |
|||
{ |
|||
Guard l(x_peers); |
|||
for (auto i: m_peers) |
|||
if (auto p = i.second.lock()) |
|||
{ |
|||
p->disconnect(ClientQuit); |
|||
n++; |
|||
} |
|||
} |
|||
if (!n) |
|||
break; |
|||
m_ioService.poll(); |
|||
this_thread::sleep_for(chrono::milliseconds(100)); |
|||
} |
|||
|
|||
delete m_upnp; |
|||
} |
|||
|
|||
void PeerHost::seal(bytes& _b) |
|||
{ |
|||
_b[0] = 0x22; |
|||
_b[1] = 0x40; |
|||
_b[2] = 0x08; |
|||
_b[3] = 0x91; |
|||
uint32_t len = (uint32_t)_b.size() - 8; |
|||
_b[4] = (len >> 24) & 0xff; |
|||
_b[5] = (len >> 16) & 0xff; |
|||
_b[6] = (len >> 8) & 0xff; |
|||
_b[7] = len & 0xff; |
|||
} |
|||
|
|||
void PeerHost::determinePublic(string const& _publicAddress, bool _upnp) |
|||
{ |
|||
if (_upnp) |
|||
try |
|||
{ |
|||
m_upnp = new UPnP; |
|||
} |
|||
catch (NoUPnPDevice) {} // let m_upnp continue as null - we handle it properly.
|
|||
|
|||
bi::tcp::resolver r(m_ioService); |
|||
if (m_upnp && m_upnp->isValid() && m_peerAddresses.size()) |
|||
{ |
|||
clog(NetNote) << "External addr: " << m_upnp->externalIP(); |
|||
int p = m_upnp->addRedirect(m_peerAddresses[0].to_string().c_str(), m_listenPort); |
|||
if (p) |
|||
clog(NetNote) << "Punched through NAT and mapped local port" << m_listenPort << "onto external port" << p << "."; |
|||
else |
|||
{ |
|||
// couldn't map
|
|||
clog(NetWarn) << "Couldn't punch through NAT (or no NAT in place). Assuming " << m_listenPort << " is local & external port."; |
|||
p = m_listenPort; |
|||
} |
|||
|
|||
auto eip = m_upnp->externalIP(); |
|||
if (eip == string("0.0.0.0") && _publicAddress.empty()) |
|||
m_public = bi::tcp::endpoint(bi::address(), (unsigned short)p); |
|||
else |
|||
{ |
|||
m_public = bi::tcp::endpoint(bi::address::from_string(_publicAddress.empty() ? eip : _publicAddress), (unsigned short)p); |
|||
m_addresses.push_back(m_public.address().to_v4()); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// No UPnP - fallback on given public address or, if empty, the assumed peer address.
|
|||
m_public = bi::tcp::endpoint(_publicAddress.size() ? bi::address::from_string(_publicAddress) |
|||
: m_peerAddresses.size() ? m_peerAddresses[0] |
|||
: bi::address(), m_listenPort); |
|||
m_addresses.push_back(m_public.address().to_v4()); |
|||
} |
|||
} |
|||
|
|||
void PeerHost::populateAddresses() |
|||
{ |
|||
#ifdef _WIN32 |
|||
WSAData wsaData; |
|||
if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) |
|||
throw NoNetworking(); |
|||
|
|||
char ac[80]; |
|||
if (gethostname(ac, sizeof(ac)) == SOCKET_ERROR) |
|||
{ |
|||
clog(NetWarn) << "Error " << WSAGetLastError() << " when getting local host name."; |
|||
WSACleanup(); |
|||
throw NoNetworking(); |
|||
} |
|||
|
|||
struct hostent* phe = gethostbyname(ac); |
|||
if (phe == 0) |
|||
{ |
|||
clog(NetWarn) << "Bad host lookup."; |
|||
WSACleanup(); |
|||
throw NoNetworking(); |
|||
} |
|||
|
|||
for (int i = 0; phe->h_addr_list[i] != 0; ++i) |
|||
{ |
|||
struct in_addr addr; |
|||
memcpy(&addr, phe->h_addr_list[i], sizeof(struct in_addr)); |
|||
char *addrStr = inet_ntoa(addr); |
|||
bi::address ad(bi::address::from_string(addrStr)); |
|||
m_addresses.push_back(ad.to_v4()); |
|||
bool isLocal = std::find(c_rejectAddresses.begin(), c_rejectAddresses.end(), ad) != c_rejectAddresses.end(); |
|||
if (!isLocal) |
|||
m_peerAddresses.push_back(ad.to_v4()); |
|||
clog(NetNote) << "Address: " << ac << " = " << m_addresses.back() << (isLocal ? " [LOCAL]" : " [PEER]"); |
|||
} |
|||
|
|||
WSACleanup(); |
|||
#else |
|||
ifaddrs* ifaddr; |
|||
if (getifaddrs(&ifaddr) == -1) |
|||
throw NoNetworking(); |
|||
|
|||
bi::tcp::resolver r(m_ioService); |
|||
|
|||
for (ifaddrs* ifa = ifaddr; ifa; ifa = ifa->ifa_next) |
|||
{ |
|||
if (!ifa->ifa_addr) |
|||
continue; |
|||
if (ifa->ifa_addr->sa_family == AF_INET) |
|||
{ |
|||
char host[NI_MAXHOST]; |
|||
if (getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST)) |
|||
continue; |
|||
try |
|||
{ |
|||
auto it = r.resolve({host, "30303"}); |
|||
bi::tcp::endpoint ep = it->endpoint(); |
|||
bi::address ad = ep.address(); |
|||
m_addresses.push_back(ad.to_v4()); |
|||
bool isLocal = std::find(c_rejectAddresses.begin(), c_rejectAddresses.end(), ad) != c_rejectAddresses.end(); |
|||
if (!isLocal) |
|||
m_peerAddresses.push_back(ad.to_v4()); |
|||
clog(NetNote) << "Address: " << host << " = " << m_addresses.back() << (isLocal ? " [LOCAL]" : " [PEER]"); |
|||
} |
|||
catch (...) |
|||
{ |
|||
clog(NetNote) << "Couldn't resolve: " << host; |
|||
} |
|||
} |
|||
} |
|||
|
|||
freeifaddrs(ifaddr); |
|||
#endif |
|||
} |
|||
|
|||
std::map<Public, bi::tcp::endpoint> PeerHost::potentialPeers() |
|||
{ |
|||
std::map<Public, bi::tcp::endpoint> ret; |
|||
if (!m_public.address().is_unspecified()) |
|||
ret.insert(make_pair(m_key.pub(), m_public)); |
|||
Guard l(x_peers); |
|||
for (auto i: m_peers) |
|||
if (auto j = i.second.lock()) |
|||
{ |
|||
auto ep = j->endpoint(); |
|||
// Skip peers with a listen port of zero or are on a private network
|
|||
bool peerOnNet = (j->m_listenPort != 0 && !isPrivateAddress(ep.address())); |
|||
if (peerOnNet && ep.port() && j->m_id) |
|||
ret.insert(make_pair(i.first, ep)); |
|||
} |
|||
return ret; |
|||
} |
|||
|
|||
void PeerHost::ensureAccepting() |
|||
{ |
|||
if (m_accepting == false) |
|||
{ |
|||
clog(NetConnect) << "Listening on local port " << m_listenPort << " (public: " << m_public << ")"; |
|||
m_accepting = true; |
|||
m_acceptor.async_accept(m_socket, [=](boost::system::error_code ec) |
|||
{ |
|||
if (!ec) |
|||
try |
|||
{ |
|||
try { |
|||
clog(NetConnect) << "Accepted connection from " << m_socket.remote_endpoint(); |
|||
} catch (...){} |
|||
bi::address remoteAddress = m_socket.remote_endpoint().address(); |
|||
// Port defaults to 0 - we let the hello tell us which port the peer listens to
|
|||
auto p = std::make_shared<PeerSession>(this, std::move(m_socket), m_networkId, remoteAddress); |
|||
p->start(); |
|||
} |
|||
catch (std::exception const& _e) |
|||
{ |
|||
clog(NetWarn) << "ERROR: " << _e.what(); |
|||
} |
|||
m_accepting = false; |
|||
if (ec.value() != 1) |
|||
ensureAccepting(); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
void PeerHost::connect(std::string const& _addr, unsigned short _port) noexcept |
|||
{ |
|||
try |
|||
{ |
|||
connect(bi::tcp::endpoint(bi::address::from_string(_addr), _port)); |
|||
} |
|||
catch (exception const& e) |
|||
{ |
|||
// Couldn't connect
|
|||
clog(NetConnect) << "Bad host " << _addr << " (" << e.what() << ")"; |
|||
} |
|||
} |
|||
|
|||
void PeerHost::connect(bi::tcp::endpoint const& _ep) |
|||
{ |
|||
clog(NetConnect) << "Attempting connection to " << _ep; |
|||
bi::tcp::socket* s = new bi::tcp::socket(m_ioService); |
|||
s->async_connect(_ep, [=](boost::system::error_code const& ec) |
|||
{ |
|||
if (ec) |
|||
{ |
|||
clog(NetConnect) << "Connection refused to " << _ep << " (" << ec.message() << ")"; |
|||
for (auto i = m_incomingPeers.begin(); i != m_incomingPeers.end(); ++i) |
|||
if (i->second.first == _ep && i->second.second < 3) |
|||
{ |
|||
m_freePeers.push_back(i->first); |
|||
goto OK; |
|||
} |
|||
// for-else
|
|||
clog(NetConnect) << "Giving up."; |
|||
OK:; |
|||
} |
|||
else |
|||
{ |
|||
auto p = make_shared<PeerSession>(this, std::move(*s), m_networkId, _ep.address(), _ep.port()); |
|||
clog(NetConnect) << "Connected to " << _ep; |
|||
p->start(); |
|||
} |
|||
delete s; |
|||
}); |
|||
} |
|||
|
|||
bool PeerHost::havePeer(Public _id) const |
|||
{ |
|||
Guard l(x_peers); |
|||
|
|||
// Remove dead peers from list.
|
|||
for (auto i = m_peers.begin(); i != m_peers.end();) |
|||
if (i->second.lock().get()) |
|||
++i; |
|||
else |
|||
i = m_peers.erase(i); |
|||
|
|||
return !!m_peers.count(_id); |
|||
} |
|||
|
|||
void PeerHost::growPeers() |
|||
{ |
|||
Guard l(x_peers); |
|||
while (m_peers.size() < m_idealPeerCount) |
|||
{ |
|||
if (m_freePeers.empty()) |
|||
{ |
|||
if (chrono::steady_clock::now() > m_lastPeersRequest + chrono::seconds(10)) |
|||
{ |
|||
RLPStream s; |
|||
bytes b; |
|||
(PeerSession::prep(s).appendList(1) << GetPeersPacket).swapOut(b); |
|||
seal(b); |
|||
for (auto const& i: m_peers) |
|||
if (auto p = i.second.lock()) |
|||
if (p->isOpen()) |
|||
p->send(&b); |
|||
m_lastPeersRequest = chrono::steady_clock::now(); |
|||
} |
|||
|
|||
|
|||
if (!m_accepting) |
|||
ensureAccepting(); |
|||
|
|||
break; |
|||
} |
|||
|
|||
auto x = time(0) % m_freePeers.size(); |
|||
m_incomingPeers[m_freePeers[x]].second++; |
|||
connect(m_incomingPeers[m_freePeers[x]].first); |
|||
m_freePeers.erase(m_freePeers.begin() + x); |
|||
} |
|||
} |
|||
|
|||
void PeerHost::prunePeers() |
|||
{ |
|||
Guard l(x_peers); |
|||
// We'll keep at most twice as many as is ideal, halfing what counts as "too young to kill" until we get there.
|
|||
for (uint old = 15000; m_peers.size() > m_idealPeerCount * 2 && old > 100; old /= 2) |
|||
while (m_peers.size() > m_idealPeerCount) |
|||
{ |
|||
// look for worst peer to kick off
|
|||
// first work out how many are old enough to kick off.
|
|||
shared_ptr<PeerSession> worst; |
|||
unsigned agedPeers = 0; |
|||
for (auto i: m_peers) |
|||
if (auto p = i.second.lock()) |
|||
if (/*(m_mode != NodeMode::PeerHost || p->m_caps != 0x01) &&*/ chrono::steady_clock::now() > p->m_connect + chrono::milliseconds(old)) // don't throw off new peers; peer-servers should never kick off other peer-servers.
|
|||
{ |
|||
++agedPeers; |
|||
if ((!worst || p->m_rating < worst->m_rating || (p->m_rating == worst->m_rating && p->m_connect > worst->m_connect))) // kill older ones
|
|||
worst = p; |
|||
} |
|||
if (!worst || agedPeers <= m_idealPeerCount) |
|||
break; |
|||
worst->disconnect(TooManyPeers); |
|||
} |
|||
|
|||
// Remove dead peers from list.
|
|||
for (auto i = m_peers.begin(); i != m_peers.end();) |
|||
if (i->second.lock().get()) |
|||
++i; |
|||
else |
|||
i = m_peers.erase(i); |
|||
} |
|||
|
|||
std::vector<PeerInfo> PeerHost::peers(bool _updatePing) const |
|||
{ |
|||
Guard l(x_peers); |
|||
if (_updatePing) |
|||
const_cast<PeerHost*>(this)->pingAll(); |
|||
this_thread::sleep_for(chrono::milliseconds(200)); |
|||
std::vector<PeerInfo> ret; |
|||
for (auto& i: m_peers) |
|||
if (auto j = i.second.lock()) |
|||
if (j->m_socket.is_open()) |
|||
ret.push_back(j->m_info); |
|||
return ret; |
|||
} |
|||
|
|||
void PeerHost::pingAll() |
|||
{ |
|||
Guard l(x_peers); |
|||
for (auto& i: m_peers) |
|||
if (auto j = i.second.lock()) |
|||
j->ping(); |
|||
} |
|||
|
|||
bytes PeerHost::savePeers() const |
|||
{ |
|||
Guard l(x_peers); |
|||
RLPStream ret; |
|||
int n = 0; |
|||
for (auto& i: m_peers) |
|||
if (auto p = i.second.lock()) |
|||
if (p->m_socket.is_open() && p->endpoint().port()) |
|||
{ |
|||
ret.appendList(3) << p->endpoint().address().to_v4().to_bytes() << p->endpoint().port() << p->m_id; |
|||
n++; |
|||
} |
|||
return RLPStream(n).appendRaw(ret.out(), n).out(); |
|||
} |
|||
|
|||
void PeerHost::restorePeers(bytesConstRef _b) |
|||
{ |
|||
for (auto i: RLP(_b)) |
|||
{ |
|||
auto k = (Public)i[2]; |
|||
if (!m_incomingPeers.count(k)) |
|||
{ |
|||
m_incomingPeers.insert(make_pair(k, make_pair(bi::tcp::endpoint(bi::address_v4(i[0].toArray<byte, 4>()), i[1].toInt<short>()), 0))); |
|||
m_freePeers.push_back(k); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,145 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file EthereumHost.h
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @date 2014 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <mutex> |
|||
#include <map> |
|||
#include <vector> |
|||
#include <set> |
|||
#include <memory> |
|||
#include <utility> |
|||
#include <thread> |
|||
#include <libethential/Guards.h> |
|||
#include <libethcore/CommonEth.h> |
|||
#include "Common.h" |
|||
namespace ba = boost::asio; |
|||
namespace bi = boost::asio::ip; |
|||
|
|||
namespace eth |
|||
{ |
|||
|
|||
class RLPStream; |
|||
class TransactionQueue; |
|||
class BlockQueue; |
|||
|
|||
class PeerHost |
|||
{ |
|||
friend class PeerSession; |
|||
|
|||
public: |
|||
/// Start server, listening for connections on the given port.
|
|||
PeerHost(std::string const& _clientVersion, u256 _networkId, unsigned short _port, std::string const& _publicAddress = std::string(), bool _upnp = true); |
|||
/// Start server, listening for connections on a system-assigned port.
|
|||
PeerHost(std::string const& _clientVersion, u256 _networkId, std::string const& _publicAddress = std::string(), bool _upnp = true); |
|||
/// Start server, but don't listen.
|
|||
PeerHost(std::string const& _clientVersion, u256 _networkId); |
|||
|
|||
/// Will block on network process events.
|
|||
virtual ~PeerHost(); |
|||
|
|||
/// Closes all peers.
|
|||
void disconnectPeers(); |
|||
|
|||
virtual u256 networkId() { return m_networkId; } |
|||
virtual unsigned protocolVersion() { return 0; } |
|||
|
|||
/// Connect to a peer explicitly.
|
|||
void connect(std::string const& _addr, unsigned short _port = 30303) noexcept; |
|||
void connect(bi::tcp::endpoint const& _ep); |
|||
|
|||
/// Conduct I/O, polling, syncing, whatever.
|
|||
/// Ideally all time-consuming I/O is done in a background thread or otherwise asynchronously, but you get this call every 100ms or so anyway.
|
|||
/// This won't touch alter the blockchain.
|
|||
void process() { if (isInitialised()) m_ioService.poll(); } |
|||
|
|||
/// @returns true iff we have the a peer of the given id.
|
|||
bool havePeer(Public _id) const; |
|||
|
|||
/// Set ideal number of peers.
|
|||
void setIdealPeerCount(unsigned _n) { m_idealPeerCount = _n; } |
|||
|
|||
/// Get peer information.
|
|||
std::vector<PeerInfo> peers(bool _updatePing = false) const; |
|||
|
|||
/// Get number of peers connected; equivalent to, but faster than, peers().size().
|
|||
size_t peerCount() const { Guard l(x_peers); return m_peers.size(); } |
|||
|
|||
/// Ping the peers, to update the latency information.
|
|||
void pingAll(); |
|||
|
|||
/// Get the port we're listening on currently.
|
|||
unsigned short listenPort() const { return m_public.port(); } |
|||
|
|||
/// Serialise the set of known peers.
|
|||
bytes savePeers() const; |
|||
|
|||
/// Deserialise the data and populate the set of known peers.
|
|||
void restorePeers(bytesConstRef _b); |
|||
|
|||
void registerPeer(std::shared_ptr<PeerSession> _s); |
|||
|
|||
protected: |
|||
/// Called when the session has provided us with a new peer we can connect to.
|
|||
void noteNewPeers() {} |
|||
|
|||
virtual bool isInitialised() const { return true; } |
|||
void seal(bytes& _b); |
|||
void populateAddresses(); |
|||
void determinePublic(std::string const& _publicAddress, bool _upnp); |
|||
void ensureAccepting(); |
|||
|
|||
void growPeers(); |
|||
void prunePeers(); |
|||
|
|||
std::map<Public, bi::tcp::endpoint> potentialPeers(); |
|||
|
|||
std::string m_clientVersion; |
|||
|
|||
unsigned short m_listenPort; |
|||
|
|||
ba::io_service m_ioService; |
|||
bi::tcp::acceptor m_acceptor; |
|||
bi::tcp::socket m_socket; |
|||
|
|||
UPnP* m_upnp = nullptr; |
|||
bi::tcp::endpoint m_public; |
|||
KeyPair m_key; |
|||
|
|||
u256 m_networkId; |
|||
|
|||
mutable std::mutex x_peers; |
|||
mutable std::map<Public, std::weak_ptr<PeerSession>> m_peers; // mutable because we flush zombie entries (null-weakptrs) as regular maintenance from a const method.
|
|||
|
|||
mutable std::recursive_mutex m_incomingLock; |
|||
std::map<Public, std::pair<bi::tcp::endpoint, unsigned>> m_incomingPeers; |
|||
std::vector<Public> m_freePeers; |
|||
|
|||
std::chrono::steady_clock::time_point m_lastPeersRequest; |
|||
unsigned m_idealPeerCount = 5; |
|||
|
|||
std::vector<bi::address_v4> m_addresses; |
|||
std::vector<bi::address_v4> m_peerAddresses; |
|||
|
|||
bool m_accepting = false; |
|||
}; |
|||
|
|||
} |
@ -0,0 +1,418 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file PeerSession.cpp
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @date 2014 |
|||
*/ |
|||
|
|||
#include "PeerSession.h" |
|||
|
|||
#include <chrono> |
|||
#include <libethential/Common.h> |
|||
#include <libethcore/Exceptions.h> |
|||
#include "PeerHost.h" |
|||
using namespace std; |
|||
using namespace eth; |
|||
|
|||
#define clogS(X) eth::LogOutputStream<X, true>(false) << "| " << std::setw(2) << m_socket.native_handle() << "] " |
|||
|
|||
PeerSession::PeerSession(PeerHost* _s, bi::tcp::socket _socket, u256 _rNId, bi::address _peerAddress, unsigned short _peerPort): |
|||
m_server(_s), |
|||
m_socket(std::move(_socket)), |
|||
m_reqNetworkId(_rNId), |
|||
m_listenPort(_peerPort), |
|||
m_rating(0) |
|||
{ |
|||
m_disconnect = std::chrono::steady_clock::time_point::max(); |
|||
m_connect = std::chrono::steady_clock::now(); |
|||
m_info = PeerInfo({"?", _peerAddress.to_string(), m_listenPort, std::chrono::steady_clock::duration(0)}); |
|||
} |
|||
|
|||
PeerSession::~PeerSession() |
|||
{ |
|||
// Read-chain finished for one reason or another.
|
|||
try |
|||
{ |
|||
if (m_socket.is_open()) |
|||
m_socket.close(); |
|||
} |
|||
catch (...){} |
|||
} |
|||
|
|||
bi::tcp::endpoint PeerSession::endpoint() const |
|||
{ |
|||
if (m_socket.is_open()) |
|||
try |
|||
{ |
|||
return bi::tcp::endpoint(m_socket.remote_endpoint().address(), m_listenPort); |
|||
} |
|||
catch (...){} |
|||
|
|||
return bi::tcp::endpoint(); |
|||
} |
|||
|
|||
bool PeerSession::preInterpret(RLP const& _r) |
|||
{ |
|||
clogS(NetRight) << _r; |
|||
switch (_r[0].toInt<unsigned>()) |
|||
{ |
|||
case HelloPacket: |
|||
{ |
|||
m_protocolVersion = _r[1].toInt<uint>(); |
|||
m_networkId = _r[2].toInt<u256>(); |
|||
auto clientVersion = _r[3].toString(); |
|||
m_caps = _r[4].toInt<uint>(); |
|||
m_listenPort = _r[5].toInt<unsigned short>(); |
|||
m_id = _r[6].toHash<h512>(); |
|||
|
|||
clogS(NetMessageSummary) << "Hello: " << clientVersion << "V[" << m_protocolVersion << "/" << m_networkId << "]" << m_id.abridged() << showbase << hex << m_caps << dec << m_listenPort; |
|||
|
|||
if (m_server->havePeer(m_id)) |
|||
{ |
|||
// Already connected.
|
|||
cwarn << "Already have peer id" << m_id.abridged();// << "at" << l->endpoint() << "rather than" << endpoint();
|
|||
disconnect(DuplicatePeer); |
|||
return false; |
|||
} |
|||
|
|||
if (m_protocolVersion != m_server->protocolVersion() || m_networkId != m_server->networkId() || !m_id) |
|||
{ |
|||
disconnect(IncompatibleProtocol); |
|||
return false; |
|||
} |
|||
try |
|||
{ m_info = PeerInfo({clientVersion, m_socket.remote_endpoint().address().to_string(), m_listenPort, std::chrono::steady_clock::duration()}); } |
|||
catch (...) |
|||
{ |
|||
disconnect(BadProtocol); |
|||
return false; |
|||
} |
|||
|
|||
m_server->registerPeer(shared_from_this()); |
|||
onNewPeer(); |
|||
break; |
|||
} |
|||
case DisconnectPacket: |
|||
{ |
|||
string reason = "Unspecified"; |
|||
if (_r[1].isInt()) |
|||
reason = reasonOf((DisconnectReason)_r[1].toInt<int>()); |
|||
|
|||
clogS(NetMessageSummary) << "Disconnect (reason: " << reason << ")"; |
|||
if (m_socket.is_open()) |
|||
clogS(NetNote) << "Closing " << m_socket.remote_endpoint(); |
|||
else |
|||
clogS(NetNote) << "Remote closed."; |
|||
m_socket.close(); |
|||
return false; |
|||
} |
|||
case PingPacket: |
|||
{ |
|||
clogS(NetTriviaSummary) << "Ping"; |
|||
RLPStream s; |
|||
sealAndSend(prep(s).appendList(1) << PongPacket); |
|||
break; |
|||
} |
|||
case PongPacket: |
|||
m_info.lastPing = std::chrono::steady_clock::now() - m_ping; |
|||
clogS(NetTriviaSummary) << "Latency: " << chrono::duration_cast<chrono::milliseconds>(m_info.lastPing).count() << " ms"; |
|||
break; |
|||
case GetPeersPacket: |
|||
{ |
|||
clogS(NetTriviaSummary) << "GetPeers"; |
|||
auto peers = m_server->potentialPeers(); |
|||
RLPStream s; |
|||
prep(s).appendList(peers.size() + 1); |
|||
s << PeersPacket; |
|||
for (auto i: peers) |
|||
{ |
|||
clogS(NetTriviaDetail) << "Sending peer " << i.first.abridged() << i.second; |
|||
s.appendList(3) << bytesConstRef(i.second.address().to_v4().to_bytes().data(), 4) << i.second.port() << i.first; |
|||
} |
|||
sealAndSend(s); |
|||
break; |
|||
} |
|||
case PeersPacket: |
|||
clogS(NetTriviaSummary) << "Peers (" << dec << (_r.itemCount() - 1) << " entries)"; |
|||
for (unsigned i = 1; i < _r.itemCount(); ++i) |
|||
{ |
|||
bi::address_v4 peerAddress(_r[i][0].toHash<FixedHash<4>>().asArray()); |
|||
auto ep = bi::tcp::endpoint(peerAddress, _r[i][1].toInt<short>()); |
|||
Public id = _r[i][2].toHash<Public>(); |
|||
if (isPrivateAddress(peerAddress)) |
|||
goto CONTINUE; |
|||
|
|||
clogS(NetAllDetail) << "Checking: " << ep << "(" << id.abridged() << ")"; |
|||
|
|||
// check that it's not us or one we already know:
|
|||
if (id && (m_server->m_key.pub() == id || m_server->havePeer(id) || m_server->m_incomingPeers.count(id))) |
|||
goto CONTINUE; |
|||
|
|||
// check that we're not already connected to addr:
|
|||
if (!ep.port()) |
|||
goto CONTINUE; |
|||
for (auto i: m_server->m_addresses) |
|||
if (ep.address() == i && ep.port() == m_server->listenPort()) |
|||
goto CONTINUE; |
|||
for (auto i: m_server->m_incomingPeers) |
|||
if (i.second.first == ep) |
|||
goto CONTINUE; |
|||
m_server->m_incomingPeers[id] = make_pair(ep, 0); |
|||
m_server->m_freePeers.push_back(id); |
|||
m_server->noteNewPeers(); |
|||
clogS(NetTriviaDetail) << "New peer: " << ep << "(" << id << ")"; |
|||
CONTINUE:; |
|||
} |
|||
break; |
|||
default: |
|||
return interpret(_r); |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
void PeerSession::ping() |
|||
{ |
|||
RLPStream s; |
|||
sealAndSend(prep(s).appendList(1) << PingPacket); |
|||
m_ping = std::chrono::steady_clock::now(); |
|||
} |
|||
|
|||
void PeerSession::getPeers() |
|||
{ |
|||
RLPStream s; |
|||
sealAndSend(prep(s).appendList(1) << GetPeersPacket); |
|||
} |
|||
|
|||
RLPStream& PeerSession::prep(RLPStream& _s) |
|||
{ |
|||
return _s.appendRaw(bytes(8, 0)); |
|||
} |
|||
|
|||
void PeerSession::sealAndSend(RLPStream& _s) |
|||
{ |
|||
bytes b; |
|||
_s.swapOut(b); |
|||
m_server->seal(b); |
|||
sendDestroy(b); |
|||
} |
|||
|
|||
bool PeerSession::checkPacket(bytesConstRef _msg) |
|||
{ |
|||
if (_msg.size() < 8) |
|||
return false; |
|||
if (!(_msg[0] == 0x22 && _msg[1] == 0x40 && _msg[2] == 0x08 && _msg[3] == 0x91)) |
|||
return false; |
|||
uint32_t len = ((_msg[4] * 256 + _msg[5]) * 256 + _msg[6]) * 256 + _msg[7]; |
|||
if (_msg.size() != len + 8) |
|||
return false; |
|||
RLP r(_msg.cropped(8)); |
|||
if (r.actualSize() != len) |
|||
return false; |
|||
return true; |
|||
} |
|||
|
|||
void PeerSession::sendDestroy(bytes& _msg) |
|||
{ |
|||
clogS(NetLeft) << RLP(bytesConstRef(&_msg).cropped(8)); |
|||
|
|||
if (!checkPacket(bytesConstRef(&_msg))) |
|||
{ |
|||
cwarn << "INVALID PACKET CONSTRUCTED!"; |
|||
} |
|||
|
|||
bytes buffer = bytes(std::move(_msg)); |
|||
writeImpl(buffer); |
|||
} |
|||
|
|||
void PeerSession::send(bytesConstRef _msg) |
|||
{ |
|||
clogS(NetLeft) << RLP(_msg.cropped(8)); |
|||
|
|||
if (!checkPacket(_msg)) |
|||
{ |
|||
cwarn << "INVALID PACKET CONSTRUCTED!"; |
|||
} |
|||
|
|||
bytes buffer = bytes(_msg.toBytes()); |
|||
writeImpl(buffer); |
|||
} |
|||
|
|||
void PeerSession::writeImpl(bytes& _buffer) |
|||
{ |
|||
// cerr << (void*)this << " writeImpl" << endl;
|
|||
if (!m_socket.is_open()) |
|||
return; |
|||
|
|||
lock_guard<recursive_mutex> l(m_writeLock); |
|||
m_writeQueue.push_back(_buffer); |
|||
if (m_writeQueue.size() == 1) |
|||
write(); |
|||
} |
|||
|
|||
void PeerSession::write() |
|||
{ |
|||
// cerr << (void*)this << " write" << endl;
|
|||
lock_guard<recursive_mutex> l(m_writeLock); |
|||
if (m_writeQueue.empty()) |
|||
return; |
|||
|
|||
const bytes& bytes = m_writeQueue[0]; |
|||
auto self(shared_from_this()); |
|||
ba::async_write(m_socket, ba::buffer(bytes), [this, self](boost::system::error_code ec, std::size_t /*length*/) |
|||
{ |
|||
// cerr << (void*)this << " write.callback" << endl;
|
|||
|
|||
// must check queue, as write callback can occur following dropped()
|
|||
if (ec) |
|||
{ |
|||
cwarn << "Error sending: " << ec.message(); |
|||
dropped(); |
|||
} |
|||
else |
|||
{ |
|||
m_writeQueue.pop_front(); |
|||
write(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
void PeerSession::dropped() |
|||
{ |
|||
// cerr << (void*)this << " dropped" << endl;
|
|||
if (m_socket.is_open()) |
|||
try |
|||
{ |
|||
clogS(NetConnect) << "Closing " << m_socket.remote_endpoint(); |
|||
m_socket.close(); |
|||
} |
|||
catch (...) {} |
|||
} |
|||
|
|||
void PeerSession::disconnect(int _reason) |
|||
{ |
|||
clogS(NetConnect) << "Disconnecting (reason:" << reasonOf((DisconnectReason)_reason) << ")"; |
|||
if (m_socket.is_open()) |
|||
{ |
|||
if (m_disconnect == chrono::steady_clock::time_point::max()) |
|||
{ |
|||
RLPStream s; |
|||
prep(s); |
|||
s.appendList(2) << DisconnectPacket << _reason; |
|||
sealAndSend(s); |
|||
m_disconnect = chrono::steady_clock::now(); |
|||
} |
|||
else |
|||
dropped(); |
|||
} |
|||
} |
|||
|
|||
void PeerSession::start() |
|||
{ |
|||
RLPStream s; |
|||
prep(s); |
|||
s.appendList(9) << HelloPacket |
|||
<< m_server->protocolVersion() |
|||
<< m_server->networkId() |
|||
<< m_server->m_clientVersion |
|||
<< m_server->m_public.port() |
|||
<< m_server->m_key.pub(); |
|||
sealAndSend(s); |
|||
ping(); |
|||
getPeers(); |
|||
|
|||
doRead(); |
|||
} |
|||
|
|||
void PeerSession::doRead() |
|||
{ |
|||
// ignore packets received while waiting to disconnect
|
|||
if (chrono::steady_clock::now() - m_disconnect > chrono::seconds(0)) |
|||
return; |
|||
|
|||
auto self(shared_from_this()); |
|||
m_socket.async_read_some(boost::asio::buffer(m_data), [this,self](boost::system::error_code ec, std::size_t length) |
|||
{ |
|||
// If error is end of file, ignore
|
|||
if (ec && ec.category() != boost::asio::error::get_misc_category() && ec.value() != boost::asio::error::eof) |
|||
{ |
|||
// got here with length of 1241...
|
|||
cwarn << "Error reading: " << ec.message(); |
|||
dropped(); |
|||
} |
|||
else if (ec && length == 0) |
|||
{ |
|||
return; |
|||
} |
|||
else |
|||
{ |
|||
try |
|||
{ |
|||
m_incoming.resize(m_incoming.size() + length); |
|||
memcpy(m_incoming.data() + m_incoming.size() - length, m_data.data(), length); |
|||
while (m_incoming.size() > 8) |
|||
{ |
|||
if (m_incoming[0] != 0x22 || m_incoming[1] != 0x40 || m_incoming[2] != 0x08 || m_incoming[3] != 0x91) |
|||
{ |
|||
cwarn << "INVALID SYNCHRONISATION TOKEN; expected = 22400891; received = " << toHex(bytesConstRef(m_incoming.data(), 4)); |
|||
disconnect(BadProtocol); |
|||
return; |
|||
} |
|||
else |
|||
{ |
|||
uint32_t len = fromBigEndian<uint32_t>(bytesConstRef(m_incoming.data() + 4, 4)); |
|||
uint32_t tlen = len + 8; |
|||
if (m_incoming.size() < tlen) |
|||
break; |
|||
|
|||
// enough has come in.
|
|||
auto data = bytesConstRef(m_incoming.data(), tlen); |
|||
if (!checkPacket(data)) |
|||
{ |
|||
cerr << "Received " << len << ": " << toHex(bytesConstRef(m_incoming.data() + 8, len)) << endl; |
|||
cwarn << "INVALID MESSAGE RECEIVED"; |
|||
disconnect(BadProtocol); |
|||
return; |
|||
} |
|||
else |
|||
{ |
|||
RLP r(data.cropped(8)); |
|||
if (!preInterpret(r)) |
|||
{ |
|||
// error
|
|||
dropped(); |
|||
return; |
|||
} |
|||
} |
|||
memmove(m_incoming.data(), m_incoming.data() + tlen, m_incoming.size() - tlen); |
|||
m_incoming.resize(m_incoming.size() - tlen); |
|||
} |
|||
} |
|||
doRead(); |
|||
} |
|||
catch (Exception const& _e) |
|||
{ |
|||
clogS(NetWarn) << "ERROR: " << _e.description(); |
|||
dropped(); |
|||
} |
|||
catch (std::exception const& _e) |
|||
{ |
|||
clogS(NetWarn) << "ERROR: " << _e.what(); |
|||
dropped(); |
|||
} |
|||
} |
|||
}); |
|||
} |
@ -0,0 +1,105 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file PeerSession.h
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @date 2014 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <mutex> |
|||
#include <array> |
|||
#include <set> |
|||
#include <memory> |
|||
#include <utility> |
|||
#include <libethential/RLP.h> |
|||
#include <libethcore/CommonEth.h> |
|||
#include "Common.h" |
|||
|
|||
namespace eth |
|||
{ |
|||
|
|||
/**
|
|||
* @brief The PeerSession class |
|||
* @todo Document fully. |
|||
*/ |
|||
class PeerSession: public std::enable_shared_from_this<PeerSession> |
|||
{ |
|||
friend class PeerHost; |
|||
|
|||
public: |
|||
PeerSession(PeerHost* _server, bi::tcp::socket _socket, u256 _rNId, bi::address _peerAddress, unsigned short _peerPort = 0); |
|||
virtual ~PeerSession(); |
|||
|
|||
void start(); |
|||
void disconnect(int _reason); |
|||
|
|||
void ping(); |
|||
|
|||
bool isOpen() const { return m_socket.is_open(); } |
|||
|
|||
bi::tcp::endpoint endpoint() const; ///< for other peers to connect to.
|
|||
|
|||
protected: |
|||
virtual bool interpret(RLP const& _r); |
|||
virtual void onNewPeer(); |
|||
|
|||
static RLPStream& prep(RLPStream& _s); |
|||
void sealAndSend(RLPStream& _s); |
|||
void sendDestroy(bytes& _msg); |
|||
void send(bytesConstRef _msg); |
|||
|
|||
private: |
|||
void dropped(); |
|||
void doRead(); |
|||
void doWrite(std::size_t length); |
|||
void writeImpl(bytes& _buffer); |
|||
void write(); |
|||
|
|||
void getPeers(); |
|||
bool preInterpret(RLP const& _r); |
|||
|
|||
/// @returns true iff the _msg forms a valid message for sending or receiving on the network.
|
|||
static bool checkPacket(bytesConstRef _msg); |
|||
|
|||
PeerHost* m_server; |
|||
|
|||
std::recursive_mutex m_writeLock; |
|||
std::deque<bytes> m_writeQueue; |
|||
|
|||
bi::tcp::socket m_socket; |
|||
std::array<byte, 65536> m_data; |
|||
PeerInfo m_info; |
|||
Public m_id; |
|||
|
|||
bytes m_incoming; |
|||
uint m_protocolVersion; |
|||
u256 m_networkId; |
|||
u256 m_reqNetworkId; |
|||
unsigned short m_listenPort; ///< Port that the remote client is listening on for connections. Useful for giving to peers.
|
|||
uint m_caps; |
|||
|
|||
std::chrono::steady_clock::time_point m_ping; |
|||
std::chrono::steady_clock::time_point m_connect; |
|||
std::chrono::steady_clock::time_point m_disconnect; |
|||
|
|||
uint m_rating; |
|||
|
|||
bool m_willBeDeleted = false; ///< True if we already posted a deleter on the strand.
|
|||
}; |
|||
|
|||
} |
@ -0,0 +1,183 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file UPnP.cpp
|
|||
* @authors: |
|||
* Gav Wood <i@gavwood.com> |
|||
* @date 2014 |
|||
*/ |
|||
|
|||
#include "UPnP.h" |
|||
|
|||
#include <stdio.h> |
|||
#include <string.h> |
|||
#if ETH_MINIUPNPC |
|||
#include <miniupnpc/miniwget.h> |
|||
#include <miniupnpc/miniupnpc.h> |
|||
#include <miniupnpc/upnpcommands.h> |
|||
#endif |
|||
#include <libethential/Exceptions.h> |
|||
#include <libethential/Common.h> |
|||
#include <libethential/Log.h> |
|||
using namespace std; |
|||
using namespace eth; |
|||
|
|||
UPnP::UPnP() |
|||
{ |
|||
#if ETH_MINIUPNPC |
|||
m_urls.reset(new UPNPUrls); |
|||
m_data.reset(new IGDdatas); |
|||
|
|||
m_ok = false; |
|||
|
|||
struct UPNPDev* devlist; |
|||
struct UPNPDev* dev; |
|||
char* descXML; |
|||
int descXMLsize = 0; |
|||
int upnperror = 0; |
|||
memset(m_urls.get(), 0, sizeof(struct UPNPUrls)); |
|||
memset(m_data.get(), 0, sizeof(struct IGDdatas)); |
|||
devlist = upnpDiscover(2000, NULL/*multicast interface*/, NULL/*minissdpd socket path*/, 0/*sameport*/, 0/*ipv6*/, &upnperror); |
|||
if (devlist) |
|||
{ |
|||
dev = devlist; |
|||
while (dev) |
|||
{ |
|||
if (strstr (dev->st, "InternetGatewayDevice")) |
|||
break; |
|||
dev = dev->pNext; |
|||
} |
|||
if (!dev) |
|||
dev = devlist; /* defaulting to first device */ |
|||
|
|||
cnote << "UPnP device:" << dev->descURL << "[st:" << dev->st << "]"; |
|||
#if MINIUPNPC_API_VERSION >= 9 |
|||
descXML = (char*)miniwget(dev->descURL, &descXMLsize, 0); |
|||
#else |
|||
descXML = (char*)miniwget(dev->descURL, &descXMLsize); |
|||
#endif |
|||
if (descXML) |
|||
{ |
|||
parserootdesc (descXML, descXMLsize, m_data.get()); |
|||
free (descXML); descXML = 0; |
|||
#if MINIUPNPC_API_VERSION >= 9 |
|||
GetUPNPUrls (m_urls.get(), m_data.get(), dev->descURL, 0); |
|||
#else |
|||
GetUPNPUrls (m_urls.get(), m_data.get(), dev->descURL); |
|||
#endif |
|||
m_ok = true; |
|||
} |
|||
freeUPNPDevlist(devlist); |
|||
} |
|||
else |
|||
#endif |
|||
{ |
|||
cnote << "UPnP device not found."; |
|||
throw NoUPnPDevice(); |
|||
} |
|||
} |
|||
|
|||
UPnP::~UPnP() |
|||
{ |
|||
auto r = m_reg; |
|||
for (auto i: r) |
|||
removeRedirect(i); |
|||
} |
|||
|
|||
string UPnP::externalIP() |
|||
{ |
|||
#if ETH_MINIUPNPC |
|||
char addr[16]; |
|||
if (!UPNP_GetExternalIPAddress(m_urls->controlURL, m_data->first.servicetype, addr)) |
|||
return addr; |
|||
else |
|||
#endif |
|||
return "0.0.0.0"; |
|||
} |
|||
|
|||
int UPnP::addRedirect(char const* _addr, int _port) |
|||
{ |
|||
(void)_addr; |
|||
(void)_port; |
|||
#if ETH_MINIUPNPC |
|||
if (m_urls->controlURL[0] == '\0') |
|||
{ |
|||
cwarn << "UPnP::addRedirect() called without proper initialisation?"; |
|||
return -1; |
|||
} |
|||
|
|||
// Try direct mapping first (port external, port internal).
|
|||
char port_str[16]; |
|||
sprintf(port_str, "%d", _port); |
|||
if (!UPNP_AddPortMapping(m_urls->controlURL, m_data->first.servicetype, port_str, port_str, _addr, "ethereum", "TCP", NULL, NULL)) |
|||
return _port; |
|||
|
|||
// Failed - now try (random external, port internal) and cycle up to 10 times.
|
|||
for (uint i = 0; i < 10; ++i) |
|||
{ |
|||
_port = rand() % 65535 - 1024 + 1024; |
|||
sprintf(port_str, "%d", _port); |
|||
if (!UPNP_AddPortMapping(m_urls->controlURL, m_data->first.servicetype, NULL, port_str, _addr, "ethereum", "TCP", NULL, NULL)) |
|||
return _port; |
|||
} |
|||
|
|||
// Failed. Try asking the router to give us a free external port.
|
|||
if (UPNP_AddPortMapping(m_urls->controlURL, m_data->first.servicetype, port_str, NULL, _addr, "ethereum", "TCP", NULL, NULL)) |
|||
// Failed. Exit.
|
|||
return 0; |
|||
|
|||
// We got mapped, but we don't know which ports we got mapped to. Now to find...
|
|||
unsigned num = 0; |
|||
UPNP_GetPortMappingNumberOfEntries(m_urls->controlURL, m_data->first.servicetype, &num); |
|||
for (unsigned i = 0; i < num; ++i) |
|||
{ |
|||
char extPort[16]; |
|||
char intClient[16]; |
|||
char intPort[6]; |
|||
char protocol[4]; |
|||
char desc[80]; |
|||
char enabled[4]; |
|||
char rHost[64]; |
|||
char duration[16]; |
|||
UPNP_GetGenericPortMappingEntry(m_urls->controlURL, m_data->first.servicetype, toString(i).c_str(), extPort, intClient, intPort, protocol, desc, enabled, rHost, duration); |
|||
if (string("ethereum") == desc) |
|||
{ |
|||
m_reg.insert(atoi(extPort)); |
|||
return atoi(extPort); |
|||
} |
|||
} |
|||
cerr << "ERROR: Mapped port not found." << endl; |
|||
#endif |
|||
return 0; |
|||
} |
|||
|
|||
void UPnP::removeRedirect(int _port) |
|||
{ |
|||
(void)_port; |
|||
#if ETH_MINIUPNPC |
|||
char port_str[16]; |
|||
// int t;
|
|||
printf("TB : upnp_rem_redir (%d)\n", _port); |
|||
if (m_urls->controlURL[0] == '\0') |
|||
{ |
|||
printf("TB : the init was not done !\n"); |
|||
return; |
|||
} |
|||
sprintf(port_str, "%d", _port); |
|||
UPNP_DeletePortMapping(m_urls->controlURL, m_data->first.servicetype, port_str, "TCP", NULL); |
|||
m_reg.erase(_port); |
|||
#endif |
|||
} |
@ -0,0 +1,53 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file UPnP.h
|
|||
* @authors: |
|||
* Gav Wood <i@gavwood.com> |
|||
* @date 2014 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <set> |
|||
#include <string> |
|||
#include <memory> |
|||
|
|||
struct UPNPUrls; |
|||
struct IGDdatas; |
|||
|
|||
namespace eth |
|||
{ |
|||
|
|||
class UPnP |
|||
{ |
|||
public: |
|||
UPnP(); |
|||
~UPnP(); |
|||
|
|||
std::string externalIP(); |
|||
int addRedirect(char const* addr, int port); |
|||
void removeRedirect(int port); |
|||
|
|||
bool isValid() const { return m_ok; } |
|||
|
|||
std::set<int> m_reg; |
|||
bool m_ok; |
|||
std::shared_ptr<struct UPNPUrls> m_urls; |
|||
std::shared_ptr<struct IGDdatas> m_data; |
|||
}; |
|||
|
|||
} |
Loading…
Reference in new issue