From a23251e9ace072b5ec98838a8d37ca6c90a20880 Mon Sep 17 00:00:00 2001 From: subtly Date: Sat, 6 Dec 2014 01:40:03 +0100 Subject: [PATCH] network: move static system-network functions into Network class. Further simplifaction of network lifecycle. --- libp2p/Host.cpp | 484 +++++++++++++-------------------------- libp2p/Host.h | 43 ++-- libp2p/Network.cpp | 187 +++++++++++++++ libp2p/Network.h | 63 +++++ libp2p/Session.cpp | 6 + libwebthree/WebThree.cpp | 4 +- libwebthree/WebThree.h | 5 +- 7 files changed, 433 insertions(+), 359 deletions(-) create mode 100644 libp2p/Network.cpp create mode 100644 libp2p/Network.h 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();