diff --git a/libp2p/Host.cpp b/libp2p/Host.cpp
index 7d08910aa..04c9c8a85 100644
--- a/libp2p/Host.cpp
+++ b/libp2p/Host.cpp
@@ -15,22 +15,11 @@
along with cpp-ethereum. If not, see .
*/
/** @file Host.cpp
- * @authors:
- * Gav Wood
- * Eric Lombrozo (Windows version of populateAddresses())
+ * @author Alex Leverington
+ * @author Gav Wood
* @date 2014
*/
-#include "Host.h"
-
-#include
-#ifdef _WIN32
-// winsock is already included
-// #include
-#else
-#include
-#endif
-
#include
#include
#include
@@ -43,166 +32,18 @@
#include "Common.h"
#include "Capability.h"
#include "UPnP.h"
+#include "Host.h"
using namespace std;
using namespace dev;
using namespace dev::p2p;
-std::vector Host::getInterfaceAddresses()
-{
- std::vector addresses;
-
-#ifdef _WIN32
- WSAData wsaData;
- if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0)
- BOOST_THROW_EXCEPTION(NoNetworking());
-
- char ac[80];
- if (gethostname(ac, sizeof(ac)) == SOCKET_ERROR)
- {
- clog(NetWarn) << "Error " << WSAGetLastError() << " when getting local host name.";
- WSACleanup();
- BOOST_THROW_EXCEPTION(NoNetworking());
- }
-
- struct hostent* phe = gethostbyname(ac);
- if (phe == 0)
- {
- clog(NetWarn) << "Bad host lookup.";
- WSACleanup();
- BOOST_THROW_EXCEPTION(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 address(bi::address::from_string(addrStr));
- if (!isLocalHostAddress(address))
- addresses.push_back(address.to_v4());
- }
-
- WSACleanup();
-#else
- ifaddrs* ifaddr;
- if (getifaddrs(&ifaddr) == -1)
- BOOST_THROW_EXCEPTION(NoNetworking());
-
- for (auto ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next)
- {
- if (!ifa->ifa_addr || string(ifa->ifa_name) == "lo0")
- continue;
-
- if (ifa->ifa_addr->sa_family == AF_INET)
- {
- in_addr addr = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
- boost::asio::ip::address_v4 address(boost::asio::detail::socket_ops::network_to_host_long(addr.s_addr));
- if (!isLocalHostAddress(address))
- addresses.push_back(address);
- }
- else if (ifa->ifa_addr->sa_family == AF_INET6)
- {
- sockaddr_in6* sockaddr = ((struct sockaddr_in6 *)ifa->ifa_addr);
- in6_addr addr = sockaddr->sin6_addr;
- boost::asio::ip::address_v6::bytes_type bytes;
- memcpy(&bytes[0], addr.s6_addr, 16);
- boost::asio::ip::address_v6 address(bytes, sockaddr->sin6_scope_id);
- if (!isLocalHostAddress(address))
- addresses.push_back(address);
- }
- }
-
- if (ifaddr!=NULL)
- freeifaddrs(ifaddr);
-
-#endif
-
- return std::move(addresses);
-}
-
-int Host::listen4(bi::tcp::acceptor* _acceptor, unsigned short _listenPort)
-{
- int retport = -1;
- for (unsigned i = 0; i < 2; ++i)
- {
- // try to connect w/listenPort, else attempt net-allocated port
- bi::tcp::endpoint endpoint(bi::tcp::v4(), i ? 0 : _listenPort);
- try
- {
- _acceptor->open(endpoint.protocol());
- _acceptor->set_option(ba::socket_base::reuse_address(true));
- _acceptor->bind(endpoint);
- _acceptor->listen();
- retport = _acceptor->local_endpoint().port();
- break;
- }
- catch (...)
- {
- if (i)
- {
- // both attempts failed
- cwarn << "Couldn't start accepting connections on host. Something very wrong with network?\n" << boost::current_exception_diagnostic_information();
- }
-
- // first attempt failed
- _acceptor->close();
- continue;
- }
- }
- return retport;
-}
-
-bi::tcp::endpoint Host::traverseNAT(std::vector const& _ifAddresses, unsigned short _listenPort, bi::address& o_upnpifaddr)
-{
- asserts(_listenPort != 0);
-
- UPnP* upnp = nullptr;
- try
- {
- upnp = new UPnP;
- }
- // let m_upnp continue as null - we handle it properly.
- catch (NoUPnPDevice) {}
-
- bi::tcp::endpoint upnpep;
- if (upnp && upnp->isValid())
- {
- bi::address paddr;
- int extPort = 0;
- for (auto const& addr: _ifAddresses)
- if (addr.is_v4() && isPrivateAddress(addr) && (extPort = upnp->addRedirect(addr.to_string().c_str(), _listenPort)))
- {
- paddr = addr;
- break;
- }
-
- auto eip = upnp->externalIP();
- bi::address eipaddr(bi::address::from_string(eip));
- if (extPort && eip != string("0.0.0.0") && !isPrivateAddress(eipaddr))
- {
- clog(NetNote) << "Punched through NAT and mapped local port" << _listenPort << "onto external port" << extPort << ".";
- clog(NetNote) << "External addr:" << eip;
- o_upnpifaddr = paddr;
- upnpep = bi::tcp::endpoint(eipaddr, (unsigned short)extPort);
- }
- else
- clog(NetWarn) << "Couldn't punch through NAT (or no NAT in place).";
-
- if (upnp)
- delete upnp;
- }
-
- return upnpep;
-}
-
Host::Host(std::string const& _clientVersion, NetworkPreferences const& _n, bool _start):
Worker("p2p", 0),
m_clientVersion(_clientVersion),
m_netPrefs(_n),
- m_ifAddresses(getInterfaceAddresses()),
- m_ioService(new ba::io_service(2)),
- m_acceptor(new bi::tcp::acceptor(*m_ioService)),
- m_socket(new bi::tcp::socket(*m_ioService)),
+ m_ifAddresses(Network::getInterfaceAddresses()),
+ m_ioService(2),
+ m_acceptorV4(m_ioService),
m_key(KeyPair::create())
{
for (auto address: m_ifAddresses)
@@ -216,7 +57,7 @@ Host::Host(std::string const& _clientVersion, NetworkPreferences const& _n, bool
Host::~Host()
{
- quit();
+ stop();
}
void Host::start()
@@ -226,30 +67,78 @@ void Host::start()
void Host::stop()
{
+ // called to force io_service to kill any remaining tasks it might have -
+ // such tasks may involve socket reads from Capabilities that maintain references
+ // to resources we're about to free.
+
{
- // prevent m_run from being set to false at same time as set to true by start()
+ // Although m_run is set by stop() or start(), it effects m_runTimer so x_runTimer is used instead of a mutex for m_run.
+ // when m_run == false, run() will cause this::run() to stop() ioservice
Guard l(x_runTimer);
- // once m_run is false the scheduler will shutdown network and stopWorking()
+ // ignore if already stopped/stopping
+ if (!m_run)
+ return;
m_run = false;
}
- // we know shutdown is complete when m_timer is reset
- while (m_timer)
+ // wait for m_timer to reset (indicating network scheduler has stopped)
+ while (!!m_timer)
this_thread::sleep_for(chrono::milliseconds(50));
+
+ // stop worker thread
stopWorking();
}
-void Host::quit()
+void Host::doneWorking()
{
- // called to force io_service to kill any remaining tasks it might have -
- // such tasks may involve socket reads from Capabilities that maintain references
- // to resources we're about to free.
- if (isWorking())
- stop();
- m_acceptor.reset();
- m_socket.reset();
+ // reset ioservice (allows manually polling network, below)
+ m_ioService.reset();
+
+ // shutdown acceptor
+ m_acceptorV4.cancel();
+ if (m_acceptorV4.is_open())
+ m_acceptorV4.close();
+
+ // There maybe an incoming connection which started but hasn't finished.
+ // Wait for acceptor to end itself instead of assuming it's complete.
+ // This helps ensure a peer isn't stopped at the same time it's starting
+ // and that socket for pending connection is closed.
+ while (m_accepting)
+ m_ioService.poll();
+
+ // stop capabilities (eth: stops syncing or block/tx broadcast)
+ for (auto const& h: m_capabilities)
+ h.second->onStopping();
+
+ // disconnect peers
+ for (unsigned n = 0;; n = 0)
+ {
+ {
+ RecursiveGuard l(x_peers);
+ for (auto i: m_peers)
+ if (auto p = i.second.lock())
+ if (p->isOpen())
+ {
+ p->disconnect(ClientQuit);
+ n++;
+ }
+ }
+ if (!n)
+ break;
+
+ // poll so that peers send out disconnect packets
+ m_ioService.poll();
+ }
+
+ // stop network (again; helpful to call before subsequent reset())
+ m_ioService.stop();
+
+ // reset network (allows reusing ioservice in future)
m_ioService.reset();
- // m_acceptor & m_socket are DANGEROUS now.
+
+ // finally, clear out peers (in case they're lingering)
+ RecursiveGuard l(x_peers);
+ m_peers.clear();
}
unsigned Host::protocolVersion() const
@@ -407,7 +296,7 @@ void Host::determinePublic(string const& _publicAddress, bool _upnp)
if (_upnp)
{
bi::address upnpifaddr;
- bi::tcp::endpoint upnpep = traverseNAT(m_ifAddresses, m_listenPort, upnpifaddr);
+ bi::tcp::endpoint upnpep = Network::traverseNAT(m_ifAddresses, m_listenPort, upnpifaddr);
if (!upnpep.address().is_unspecified() && !upnpifaddr.is_unspecified())
{
if (!m_peerAddresses.count(upnpep.address()))
@@ -431,18 +320,18 @@ void Host::determinePublic(string const& _publicAddress, bool _upnp)
m_public = bi::tcp::endpoint(bi::address(), m_listenPort);
}
-void Host::ensureAccepting()
+void Host::runAcceptor()
{
- // return if there's no io-server (quit called) or we're not listening
- if (!m_ioService || m_listenPort < 1)
- return;
-
- if (!m_accepting)
+ assert(m_listenPort > 0);
+
+ if (m_run && !m_accepting)
{
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)
+ m_socket.reset(new bi::tcp::socket(m_ioService));
+ m_acceptorV4.async_accept(*m_socket, [=](boost::system::error_code ec)
{
+ bool success = false;
if (!ec)
{
try
@@ -452,8 +341,9 @@ void Host::ensureAccepting()
} 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(this, std::move(*m_socket), bi::tcp::endpoint(remoteAddress, 0));
+ auto p = std::make_shared(this, std::move(*m_socket.release()), bi::tcp::endpoint(remoteAddress, 0));
p->start();
+ success = true;
}
catch (Exception const& _e)
{
@@ -464,9 +354,18 @@ void Host::ensureAccepting()
clog(NetWarn) << "ERROR: " << _e.what();
}
}
+
+ if (!success)
+ if (m_socket->is_open())
+ {
+ boost::system::error_code ec;
+ m_socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
+ m_socket->close();
+ }
+
m_accepting = false;
if (ec.value() < 1)
- ensureAccepting();
+ runAcceptor();
});
}
}
@@ -480,17 +379,16 @@ string Host::pocHost()
void Host::connect(std::string const& _addr, unsigned short _port) noexcept
{
- // if there's no ioService, it means we've had quit() called - bomb out - we're not allowed in here.
- if (!m_ioService)
+ if (!m_run)
return;
- for (int i = 0; i < 2; ++i)
+ for (auto first: {true, false})
{
try
{
- if (i == 0)
+ if (first)
{
- bi::tcp::resolver r(*m_ioService);
+ bi::tcp::resolver r(m_ioService);
connect(r.resolve({_addr, toString(_port)})->endpoint());
}
else
@@ -512,12 +410,11 @@ void Host::connect(std::string const& _addr, unsigned short _port) noexcept
void Host::connect(bi::tcp::endpoint const& _ep)
{
- // if there's no ioService, it means we've had quit() called - bomb out - we're not allowed in here.
- if (!m_ioService)
+ if (!m_run)
return;
clog(NetConnect) << "Attempting single-shot connection to " << _ep;
- bi::tcp::socket* s = new bi::tcp::socket(*m_ioService);
+ bi::tcp::socket* s = new bi::tcp::socket(m_ioService);
s->async_connect(_ep, [=](boost::system::error_code const& ec)
{
if (ec)
@@ -534,8 +431,7 @@ void Host::connect(bi::tcp::endpoint const& _ep)
void Host::connect(std::shared_ptr const& _n)
{
- // if there's no ioService, it means we've had quit() called - bomb out - we're not allowed in here.
- if (!m_ioService)
+ if (!m_run)
return;
// prevent concurrently connecting to a node; todo: better abstraction
@@ -551,7 +447,7 @@ void Host::connect(std::shared_ptr const& _n)
_n->lastAttempted = std::chrono::system_clock::now();
_n->failedAttempts++;
m_ready -= _n->index;
- bi::tcp::socket* s = new bi::tcp::socket(*m_ioService);
+ bi::tcp::socket* s = new bi::tcp::socket(m_ioService);
s->async_connect(_n->address, [=](boost::system::error_code const& ec)
{
if (ec)
@@ -680,8 +576,7 @@ void Host::prunePeers()
PeerInfos Host::peers(bool _updatePing) const
{
- // if there's no ioService, it means we've had quit() called - bomb out - we're not allowed in here.
- if (!m_ioService)
+ if (!m_run)
return PeerInfos();
RecursiveGuard l(x_peers);
@@ -698,152 +593,95 @@ PeerInfos Host::peers(bool _updatePing) const
return ret;
}
-void Host::run(boost::system::error_code const& error)
+void Host::run(boost::system::error_code const&)
{
- m_lastTick += c_timerInterval;
-
- if (error || !m_ioService)
+ if (!m_run)
{
- // timer died or io service went away, so stop here
+ // stopping io service allows running manual network operations for shutdown
+ // and also stops blocking worker thread, allowing worker thread to exit
+ m_ioService.stop();
+
+ // resetting timer signals network that nothing else can be scheduled to run
m_timer.reset();
return;
}
- // network running
- if (m_run)
+ m_lastTick += c_timerInterval;
+ if (m_lastTick >= c_timerInterval * 10)
{
- if (m_lastTick >= c_timerInterval * 10)
- {
- growPeers();
- prunePeers();
- m_lastTick = 0;
- }
-
- if (m_hadNewNodes)
- {
- for (auto p: m_peers)
- if (auto pp = p.second.lock())
- pp->serviceNodesRequest();
-
- m_hadNewNodes = false;
- }
-
- if (chrono::steady_clock::now() - m_lastPing > chrono::seconds(30)) // ping every 30s.
- {
- for (auto p: m_peers)
- if (auto pp = p.second.lock())
- if (chrono::steady_clock::now() - pp->m_lastReceived > chrono::seconds(60))
- pp->disconnect(PingTimeout);
- pingAll();
- }
-
- auto runcb = [this](boost::system::error_code const& error) -> void { run(error); };
- m_timer->expires_from_now(boost::posix_time::milliseconds(c_timerInterval));
- m_timer->async_wait(runcb);
-
- return;
+ growPeers();
+ prunePeers();
+ m_lastTick = 0;
}
- // network stopping
- if (!m_run)
+ if (m_hadNewNodes)
{
- // close acceptor
- if (m_acceptor->is_open())
- {
- if (m_accepting)
- m_acceptor->cancel();
- m_acceptor->close();
- m_accepting = false;
- }
-
- // stop capabilities (eth: stops syncing or block/tx broadcast)
- for (auto const& h: m_capabilities)
- h.second->onStopping();
+ for (auto p: m_peers)
+ if (auto pp = p.second.lock())
+ pp->serviceNodesRequest();
- // disconnect peers
- for (unsigned n = 0;; n = 0)
- {
- {
- RecursiveGuard l(x_peers);
- for (auto i: m_peers)
- if (auto p = i.second.lock())
- if (p->isOpen())
- {
- p->disconnect(ClientQuit);
- n++;
- }
- }
- if (!n)
- break;
- this_thread::sleep_for(chrono::milliseconds(100));
- }
-
- if (m_socket->is_open())
- m_socket->close();
-
- // m_run is false, so we're stopping; kill timer
- m_lastTick = 0;
-
- // causes parent thread's stop() to continue which calls stopWorking()
- m_timer.reset();
-
- // stop ioservice (stops blocking worker thread, allowing thread to join)
- if (!!m_ioService)
- m_ioService->stop();
- return;
+ m_hadNewNodes = false;
}
+
+ if (chrono::steady_clock::now() - m_lastPing > chrono::seconds(30)) // ping every 30s.
+ {
+ for (auto p: m_peers)
+ if (auto pp = p.second.lock())
+ if (chrono::steady_clock::now() - pp->m_lastReceived > chrono::seconds(60))
+ pp->disconnect(PingTimeout);
+ pingAll();
+ }
+
+ auto runcb = [this](boost::system::error_code const& error) -> void { run(error); };
+ m_timer->expires_from_now(boost::posix_time::milliseconds(c_timerInterval));
+ m_timer->async_wait(runcb);
}
void Host::startedWorking()
{
- if (!m_timer)
+ asserts(!m_timer);
+
{
- // no timer means this is first run and network must be started
- // (run once when host worker thread calls startedWorking())
-
- {
- // prevent m_run from being set to true at same time as set to false by stop()
- // don't release mutex until m_timer is set so in case stop() is called at same
- // time, stop will wait on m_timer and graceful network shutdown.
- Guard l(x_runTimer);
- // reset io service and create deadline timer
- m_timer.reset(new boost::asio::deadline_timer(*m_ioService));
- m_run = true;
- }
- m_ioService->reset();
-
- // try to open acceptor (todo: ipv6)
- m_listenPort = listen4(m_acceptor.get(), m_netPrefs.listenPort);
-
- // start capability threads
- for (auto const& h: m_capabilities)
- h.second->onStarting();
-
- // determine public IP, but only if we're able to listen for connections
- // todo: GUI when listen is unavailable in UI
- if (m_listenPort)
- {
- determinePublic(m_netPrefs.publicIP, m_netPrefs.upnp);
- ensureAccepting();
- }
-
- // if m_public address is valid then add us to node list
- // todo: abstract empty() and emplace logic
- if (!m_public.address().is_unspecified() && (m_nodes.empty() || m_nodes[m_nodesList[0]]->id != id()))
- noteNode(id(), m_public, Origin::Perfect, false);
+ // prevent m_run from being set to true at same time as set to false by stop()
+ // don't release mutex until m_timer is set so in case stop() is called at same
+ // time, stop will wait on m_timer and graceful network shutdown.
+ Guard l(x_runTimer);
+ // create deadline timer
+ m_timer.reset(new boost::asio::deadline_timer(m_ioService));
+ m_run = true;
+ }
+
+ // try to open acceptor (todo: ipv6)
+ m_listenPort = Network::listen4(m_acceptorV4, m_netPrefs.listenPort);
+
+ // start capability threads
+ for (auto const& h: m_capabilities)
+ h.second->onStarting();
+
+ // determine public IP, but only if we're able to listen for connections
+ // todo: GUI when listen is unavailable in UI
+ if (m_listenPort)
+ {
+ determinePublic(m_netPrefs.publicIP, m_netPrefs.upnp);
- clog(NetNote) << "Id:" << id().abridged();
+ if (m_listenPort > 0)
+ runAcceptor();
}
+ // if m_public address is valid then add us to node list
+ // todo: abstract empty() and emplace logic
+ if (!m_public.address().is_unspecified() && (m_nodes.empty() || m_nodes[m_nodesList[0]]->id != id()))
+ noteNode(id(), m_public, Origin::Perfect, false);
+
+ clog(NetNote) << "Id:" << id().abridged();
+
run(boost::system::error_code());
}
void Host::doWork()
{
- // no ioService means we've had quit() called - bomb out - we're not allowed in here.
- if (asserts(!!m_ioService))
- return;
- m_ioService->run();
+ if (m_run)
+ m_ioService.run();
}
void Host::pingAll()
diff --git a/libp2p/Host.h b/libp2p/Host.h
index 644afeb69..bc0e83174 100644
--- a/libp2p/Host.h
+++ b/libp2p/Host.h
@@ -14,7 +14,8 @@
You should have received a copy of the GNU General Public License
along with cpp-ethereum. If not, see .
*/
-/** @file EthereumHost.h
+/** @file Host.h
+ * @author Alex Leverington
* @author Gav Wood
* @date 2014
*/
@@ -34,9 +35,10 @@
#include
#include
#include "HostCapability.h"
+#include "Network.h"
#include "Common.h"
namespace ba = boost::asio;
-namespace bi = boost::asio::ip;
+namespace bi = ba::ip;
namespace dev
{
@@ -101,16 +103,6 @@ struct Node
using Nodes = std::vector;
-struct NetworkPreferences
-{
- NetworkPreferences(unsigned short p = 30303, std::string i = std::string(), bool u = true, bool l = false): listenPort(p), publicIP(i), upnp(u), localNetworking(l) {}
-
- unsigned short listenPort = 30303;
- std::string publicIP;
- bool upnp = true;
- bool localNetworking = false;
-};
-
/**
* @brief The Host class
* Capabilities should be registered prior to startNetwork, since m_capabilities is not thread-safe.
@@ -120,17 +112,6 @@ class Host: public Worker
friend class Session;
friend class HostCapabilityFace;
friend struct Node;
-
- /// Static network interface methods
-public:
- /// @returns public and private interface addresses
- static std::vector getInterfaceAddresses();
-
- /// Try to bind and listen on _listenPort, else attempt net-allocated port.
- static int listen4(bi::tcp::acceptor* _acceptor, unsigned short _listenPort);
-
- /// Return public endpoint of upnp interface. If successful o_upnpifaddr will be a private interface address and endpoint will contain public address and port.
- static bi::tcp::endpoint traverseNAT(std::vector const& _ifAddresses, unsigned short _listenPort, bi::address& o_upnpifaddr);
public:
/// Start server, listening for connections on the given port.
@@ -187,14 +168,12 @@ public:
void start();
/// Stop network. @threadsafe
+ /// Resets acceptor, socket, and IO service. Called by deallocator.
void stop();
/// @returns if network is running.
bool isStarted() const { return m_run; }
- /// Reset acceptor, socket, and IO service. Called by deallocator. Maybe called by implementation when ordered deallocation is required.
- void quit();
-
NodeId id() const { return m_key.pub(); }
void registerPeer(std::shared_ptr _s, CapDescs const& _caps);
@@ -205,7 +184,8 @@ private:
/// Populate m_peerAddresses with available public addresses.
void determinePublic(std::string const& _publicAddress, bool _upnp);
- void ensureAccepting();
+ /// Called only from startedWorking().
+ void runAcceptor();
void seal(bytes& _b);
@@ -217,8 +197,11 @@ private:
/// Called by startedWorking. Not thread-safe; to be called only be worker callback.
void run(boost::system::error_code const& error); ///< Run network. Called serially via ASIO deadline timer. Manages connection state transitions.
- /// Run network
+ /// Run network. Called by Worker. Not thread-safe; to be called only by worker.
virtual void doWork();
+
+ /// Shutdown network. Called by Worker. Not thread-safe; to be called only by worker.
+ virtual void doneWorking();
std::shared_ptr noteNode(NodeId _id, bi::tcp::endpoint _a, Origin _o, bool _ready, NodeId _oldId = NodeId());
Nodes potentialPeers(RangeMask const& _known);
@@ -235,8 +218,8 @@ private:
int m_listenPort = -1; ///< What port are we listening on. -1 means binding failed or acceptor hasn't been initialized.
- std::unique_ptr m_ioService; ///< IOService for network stuff.
- std::unique_ptr m_acceptor; ///< Listening acceptor.
+ ba::io_service m_ioService; ///< IOService for network stuff.
+ bi::tcp::acceptor m_acceptorV4; ///< Listening acceptor.
std::unique_ptr m_socket; ///< Listening socket.
std::unique_ptr m_timer; ///< Timer which, when network is running, calls scheduler() every c_timerInterval ms.
diff --git a/libp2p/Network.cpp b/libp2p/Network.cpp
new file mode 100644
index 000000000..8ca8dd135
--- /dev/null
+++ b/libp2p/Network.cpp
@@ -0,0 +1,187 @@
+/*
+ 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 .
+*/
+/** @file Network.cpp
+ * @author Alex Leverington
+ * @author Gav Wood
+ * @author Eric Lombrozo (Windows version of getInterfaceAddresses())
+ * @date 2014
+ */
+
+#include
+#ifndef _WIN32
+#include
+#endif
+
+#include
+#include
+#include
+#include
+#include "Common.h"
+#include "UPnP.h"
+#include "Network.h"
+
+using namespace std;
+using namespace dev;
+using namespace dev::p2p;
+
+std::vector Network::getInterfaceAddresses()
+{
+ std::vector addresses;
+
+#ifdef _WIN32
+ WSAData wsaData;
+ if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0)
+ BOOST_THROW_EXCEPTION(NoNetworking());
+
+ char ac[80];
+ if (gethostname(ac, sizeof(ac)) == SOCKET_ERROR)
+ {
+ clog(NetWarn) << "Error " << WSAGetLastError() << " when getting local host name.";
+ WSACleanup();
+ BOOST_THROW_EXCEPTION(NoNetworking());
+ }
+
+ struct hostent* phe = gethostbyname(ac);
+ if (phe == 0)
+ {
+ clog(NetWarn) << "Bad host lookup.";
+ WSACleanup();
+ BOOST_THROW_EXCEPTION(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 address(bi::address::from_string(addrStr));
+ if (!isLocalHostAddress(address))
+ addresses.push_back(address.to_v4());
+ }
+
+ WSACleanup();
+#else
+ ifaddrs* ifaddr;
+ if (getifaddrs(&ifaddr) == -1)
+ BOOST_THROW_EXCEPTION(NoNetworking());
+
+ for (auto ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next)
+ {
+ if (!ifa->ifa_addr || string(ifa->ifa_name) == "lo0")
+ continue;
+
+ if (ifa->ifa_addr->sa_family == AF_INET)
+ {
+ in_addr addr = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
+ boost::asio::ip::address_v4 address(boost::asio::detail::socket_ops::network_to_host_long(addr.s_addr));
+ if (!isLocalHostAddress(address))
+ addresses.push_back(address);
+ }
+ else if (ifa->ifa_addr->sa_family == AF_INET6)
+ {
+ sockaddr_in6* sockaddr = ((struct sockaddr_in6 *)ifa->ifa_addr);
+ in6_addr addr = sockaddr->sin6_addr;
+ boost::asio::ip::address_v6::bytes_type bytes;
+ memcpy(&bytes[0], addr.s6_addr, 16);
+ boost::asio::ip::address_v6 address(bytes, sockaddr->sin6_scope_id);
+ if (!isLocalHostAddress(address))
+ addresses.push_back(address);
+ }
+ }
+
+ if (ifaddr!=NULL)
+ freeifaddrs(ifaddr);
+
+#endif
+
+ return std::move(addresses);
+}
+
+int Network::listen4(bi::tcp::acceptor& _acceptor, unsigned short _listenPort)
+{
+ int retport = -1;
+ for (unsigned i = 0; i < 2; ++i)
+ {
+ // try to connect w/listenPort, else attempt net-allocated port
+ bi::tcp::endpoint endpoint(bi::tcp::v4(), i ? 0 : _listenPort);
+ try
+ {
+ _acceptor.open(endpoint.protocol());
+ _acceptor.set_option(ba::socket_base::reuse_address(true));
+ _acceptor.bind(endpoint);
+ _acceptor.listen();
+ retport = _acceptor.local_endpoint().port();
+ break;
+ }
+ catch (...)
+ {
+ if (i)
+ {
+ // both attempts failed
+ cwarn << "Couldn't start accepting connections on host. Something very wrong with network?\n" << boost::current_exception_diagnostic_information();
+ }
+
+ // first attempt failed
+ _acceptor.close();
+ continue;
+ }
+ }
+ return retport;
+}
+
+bi::tcp::endpoint Network::traverseNAT(std::vector const& _ifAddresses, unsigned short _listenPort, bi::address& o_upnpifaddr)
+{
+ asserts(_listenPort != 0);
+
+ UPnP* upnp = nullptr;
+ try
+ {
+ upnp = new UPnP;
+ }
+ // let m_upnp continue as null - we handle it properly.
+ catch (NoUPnPDevice) {}
+
+ bi::tcp::endpoint upnpep;
+ if (upnp && upnp->isValid())
+ {
+ bi::address paddr;
+ int extPort = 0;
+ for (auto const& addr: _ifAddresses)
+ if (addr.is_v4() && isPrivateAddress(addr) && (extPort = upnp->addRedirect(addr.to_string().c_str(), _listenPort)))
+ {
+ paddr = addr;
+ break;
+ }
+
+ auto eip = upnp->externalIP();
+ bi::address eipaddr(bi::address::from_string(eip));
+ if (extPort && eip != string("0.0.0.0") && !isPrivateAddress(eipaddr))
+ {
+ clog(NetNote) << "Punched through NAT and mapped local port" << _listenPort << "onto external port" << extPort << ".";
+ clog(NetNote) << "External addr:" << eip;
+ o_upnpifaddr = paddr;
+ upnpep = bi::tcp::endpoint(eipaddr, (unsigned short)extPort);
+ }
+ else
+ clog(NetWarn) << "Couldn't punch through NAT (or no NAT in place).";
+
+ if (upnp)
+ delete upnp;
+ }
+
+ return upnpep;
+}
diff --git a/libp2p/Network.h b/libp2p/Network.h
new file mode 100644
index 000000000..944d390c8
--- /dev/null
+++ b/libp2p/Network.h
@@ -0,0 +1,63 @@
+/*
+ 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 .
+*/
+/** @file Network.h
+ * @author Alex Leverington
+ * @author Gav Wood
+ * @date 2014
+ */
+
+#pragma once
+
+#include
+namespace ba = boost::asio;
+namespace bi = ba::ip;
+
+namespace dev
+{
+
+namespace p2p
+{
+
+struct NetworkPreferences
+{
+ NetworkPreferences(unsigned short p = 30303, std::string i = std::string(), bool u = true, bool l = false): listenPort(p), publicIP(i), upnp(u), localNetworking(l) {}
+
+ unsigned short listenPort = 30303;
+ std::string publicIP;
+ bool upnp = true;
+ bool localNetworking = false;
+};
+
+/**
+ * @brief Network Class
+ * Static network operations and interface(s).
+ */
+class Network
+{
+public:
+ /// @returns public and private interface addresses
+ static std::vector getInterfaceAddresses();
+
+ /// Try to bind and listen on _listenPort, else attempt net-allocated port.
+ static int listen4(bi::tcp::acceptor& _acceptor, unsigned short _listenPort);
+
+ /// Return public endpoint of upnp interface. If successful o_upnpifaddr will be a private interface address and endpoint will contain public address and port.
+ static bi::tcp::endpoint traverseNAT(std::vector const& _ifAddresses, unsigned short _listenPort, bi::address& o_upnpifaddr);
+};
+
+}
+}
diff --git a/libp2p/Session.cpp b/libp2p/Session.cpp
index 958473870..cb0a60a92 100644
--- a/libp2p/Session.cpp
+++ b/libp2p/Session.cpp
@@ -75,7 +75,11 @@ Session::~Session()
try
{
if (m_socket.is_open())
+ {
+ boost::system::error_code ec;
+ m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
m_socket.close();
+ }
}
catch (...){}
}
@@ -479,6 +483,8 @@ void Session::drop(DisconnectReason _reason)
try
{
clogS(NetConnect) << "Closing " << m_socket.remote_endpoint() << "(" << reasonOf(_reason) << ")";
+ boost::system::error_code ec;
+ m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
m_socket.close();
}
catch (...) {}
diff --git a/libwebthree/WebThree.cpp b/libwebthree/WebThree.cpp
index b5256ac22..c9f9d56e3 100644
--- a/libwebthree/WebThree.cpp
+++ b/libwebthree/WebThree.cpp
@@ -57,11 +57,11 @@ WebThreeDirect::~WebThreeDirect()
// eth::Client (owned by us via a unique_ptr) uses eth::EthereumHost (via a weak_ptr).
// Really need to work out a clean way of organising ownership and guaranteeing startup/shutdown is perfect.
- // Have to call quit here to get the Host to kill its io_service otherwise we end up with left-over reads,
+ // Have to call stop here to get the Host to kill its io_service otherwise we end up with left-over reads,
// still referencing Sessions getting deleted *after* m_ethereum is reset, causing bad things to happen, since
// the guarantee is that m_ethereum is only reset *after* all sessions have ended (sessions are allowed to
// use bits of data owned by m_ethereum).
- m_net.quit();
+ m_net.stop();
m_ethereum.reset();
}
diff --git a/libwebthree/WebThree.h b/libwebthree/WebThree.h
index a135f77ff..ec7bf2406 100644
--- a/libwebthree/WebThree.h
+++ b/libwebthree/WebThree.h
@@ -14,7 +14,7 @@
You should have received a copy of the GNU General Public License
along with cpp-ethereum. If not, see .
*/
-/** @file Client.h
+/** @file WebThree.h
* @author Gav Wood
* @date 2014
*/
@@ -92,9 +92,6 @@ public:
/// Connect to a particular peer.
void connect(std::string const& _seedHost, unsigned short _port = 30303);
- /// Is the network subsystem up?
- bool haveNetwork() { return peerCount() != 0; }
-
/// Save peers
dev::bytes saveNodes();