From 14c4d0819a577a1e3f19a32fd1280a838c32e6cc Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 6 Feb 2014 21:47:44 +0000 Subject: [PATCH] Repotted UPnP. Random ports searched on fallback now. Beginnings of foundations for event-based QML/JS integration. --- eth/CMakeLists.txt | 2 +- libethereum/Client.h | 14 +- libethereum/Exceptions.h | 6 +- libethereum/PeerNetwork.cpp | 252 ++++++------------------------- libethereum/PeerNetwork.h | 22 +-- libethereum/State.cpp | 9 +- libethereum/Transaction.h | 2 + libethereum/TransactionQueue.cpp | 4 +- libethereum/TransactionQueue.h | 10 +- libethereum/UPnP.cpp | 168 +++++++++++++++++++++ libethereum/UPnP.h | 52 +++++++ 11 files changed, 303 insertions(+), 238 deletions(-) create mode 100644 libethereum/UPnP.cpp create mode 100644 libethereum/UPnP.h diff --git a/eth/CMakeLists.txt b/eth/CMakeLists.txt index 9f082595b..c705fa006 100644 --- a/eth/CMakeLists.txt +++ b/eth/CMakeLists.txt @@ -18,5 +18,5 @@ target_link_libraries(eth boost_system) target_link_libraries(eth boost_filesystem) target_link_libraries(eth ${CMAKE_THREAD_LIBS_INIT}) -install( TARGETS eth RUNTIME DESTINATION bin ) +install( TARGETS eth DESTINATION bin ) diff --git a/libethereum/Client.h b/libethereum/Client.h index 3e439a23d..89053a203 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -52,11 +52,21 @@ public: /// Executes the given transaction. void transact(Secret _secret, Address _dest, u256 _amount, u256 _fee, u256s _data = u256s()); + /// Requires transactions involving this address be queued for inspection. + void setInterest(Address _dest); + + /// @returns incoming minable transactions that we wanted to be notified of. Clears the queue. + Transactions pendingQueue(); + + /// @returns alterations in state of a mined block that we wanted to be notified of. Clears the queue. + std::vector> minedQueue(); + + // Not yet - probably best as using some sort of signals implementation. /// Calls @a _f when a valid transaction is received that involves @a _dest and once per such transaction. - void onPending(Address _dest, function const& _f); +// void onPending(Address _dest, function const& _f); /// Calls @a _f when a transaction is mined that involves @a _dest and once per change. - void onConfirmed(Address _dest, function const& _f); +// void onConfirmed(Address _dest, function const& _f); // Informational stuff: diff --git a/libethereum/Exceptions.h b/libethereum/Exceptions.h index bc152e9b5..ee6ef8e5d 100644 --- a/libethereum/Exceptions.h +++ b/libethereum/Exceptions.h @@ -9,8 +9,8 @@ namespace eth class Exception: public std::exception { public: - virtual std::string description() const { return "Exception"; } - virtual char const* what() const noexcept { return description().c_str(); } + virtual std::string description() const { return "Unknown exception"; } + virtual char const* what() const noexcept { return typeid(*this).name(); } }; class BadHexCharacter: public Exception {}; @@ -35,5 +35,7 @@ class InvalidTimestamp: public Exception {}; class InvalidNonce: public Exception { public: InvalidNonce(u256 _required = 0, u256 _candidate = 0): required(_required), candidate(_candidate) {} u256 required; u256 candidate; }; class InvalidParentHash: public Exception {}; class InvalidContractAddress: public Exception {}; +class NoNetworking: public Exception {}; +class NoUPnPDevice: public Exception {}; } diff --git a/libethereum/PeerNetwork.cpp b/libethereum/PeerNetwork.cpp index 1148caf91..0730f23f9 100644 --- a/libethereum/PeerNetwork.cpp +++ b/libethereum/PeerNetwork.cpp @@ -30,11 +30,12 @@ #endif #include -#include +#include "Exceptions.h" #include "Common.h" #include "BlockChain.h" #include "BlockInfo.h" #include "TransactionQueue.h" +#include "UPnP.h" #include "PeerNetwork.h" using namespace std; using namespace eth; @@ -87,7 +88,7 @@ bool PeerSession::interpret(RLP const& _r) cout << ">>> " << _r << endl; switch (_r[0].toInt()) { - case Hello: + case HelloPacket: { m_protocolVersion = _r[1].toInt(); m_networkId = _r[2].toInt(); @@ -116,7 +117,7 @@ bool PeerSession::interpret(RLP const& _r) unsigned count = std::min(c_maxHashes, m_server->m_chain->details(m_server->m_latestBlockSent).number + 1); RLPStream s; prep(s).appendList(2 + count); - s << (uint)GetChain; + s << GetChainPacket; auto h = m_server->m_latestBlockSent; for (unsigned i = 0; i < count; ++i, h = m_server->m_chain->details(h).parent) s << h; @@ -124,12 +125,12 @@ bool PeerSession::interpret(RLP const& _r) sealAndSend(s); s.clear(); prep(s).appendList(1); - s << GetTransactions; + s << GetTransactionsPacket; sealAndSend(s); } break; } - case Disconnect: + case DisconnectPacket: if (m_server->m_verbosity >= 2) cout << std::setw(2) << m_socket.native_handle() << " | Disconnect" << endl; if (m_server->m_verbosity >= 1) @@ -141,25 +142,25 @@ bool PeerSession::interpret(RLP const& _r) } m_socket.close(); return false; - case Ping: + case PingPacket: { // cout << std::setw(2) << m_socket.native_handle() << " | Ping" << endl; RLPStream s; - sealAndSend(prep(s).appendList(1) << (uint)Pong); + sealAndSend(prep(s).appendList(1) << PongPacket); break; } - case Pong: + case PongPacket: m_info.lastPing = std::chrono::steady_clock::now() - m_ping; // cout << "Latency: " << chrono::duration_cast(m_lastPing).count() << " ms" << endl; break; - case GetPeers: + case GetPeersPacket: { if (m_server->m_verbosity >= 2) cout << std::setw(2) << m_socket.native_handle() << " | GetPeers" << endl; std::vector peers = m_server->potentialPeers(); RLPStream s; prep(s).appendList(peers.size() + 1); - s << (uint)Peers; + s << PeersPacket; for (auto i: peers) { if (m_server->m_verbosity >= 3) @@ -169,7 +170,7 @@ bool PeerSession::interpret(RLP const& _r) sealAndSend(s); break; } - case Peers: + case PeersPacket: if (m_server->m_verbosity >= 2) cout << std::setw(2) << m_socket.native_handle() << " | Peers (" << dec << (_r.itemCount() - 1) << " entries)" << endl; for (unsigned i = 1; i < _r.itemCount(); ++i) @@ -200,7 +201,7 @@ bool PeerSession::interpret(RLP const& _r) CONTINUE:; } break; - case Transactions: + case TransactionsPacket: if (m_server->m_mode == NodeMode::PeerServer) break; if (m_server->m_verbosity >= 2) @@ -212,7 +213,7 @@ bool PeerSession::interpret(RLP const& _r) m_knownTransactions.insert(sha3(_r[i].data())); } break; - case Blocks: + case BlocksPacket: if (m_server->m_mode == NodeMode::PeerServer) break; if (m_server->m_verbosity >= 2) @@ -237,13 +238,13 @@ bool PeerSession::interpret(RLP const& _r) { RLPStream s; prep(s).appendList(3); - s << (uint)GetChain; + s << GetChainPacket; s << sha3(_r[1].data()); s << c_maxBlocksAsk; sealAndSend(s); } break; - case GetChain: + case GetChainPacket: { if (m_server->m_mode == NodeMode::PeerServer) break; @@ -279,7 +280,7 @@ bool PeerSession::interpret(RLP const& _r) << latest << " - " << parent << endl; prep(s); - s.appendList(1 + count) << (uint)Blocks; + s.appendList(1 + count) << BlocksPacket; uint endNumber = m_server->m_chain->details(parent).number; uint startNumber = endNumber + count; if (m_server->m_verbosity >= 6) @@ -309,7 +310,7 @@ bool PeerSession::interpret(RLP const& _r) cout << std::setw(2) << m_socket.native_handle() << " | GetChain failed; not in chain" << endl; // No good - must have been on a different branch. s.clear(); - prep(s).appendList(2) << (uint)NotInChain << parents.back(); + prep(s).appendList(2) << NotInChainPacket << parents.back(); } else // still some parents left - try them. @@ -322,7 +323,7 @@ bool PeerSession::interpret(RLP const& _r) } break; } - case NotInChain: + case NotInChainPacket: { if (m_server->m_mode == NodeMode::PeerServer) break; @@ -340,7 +341,7 @@ bool PeerSession::interpret(RLP const& _r) unsigned count = std::min(c_maxHashes, m_server->m_chain->details(noGood).number); RLPStream s; prep(s).appendList(2 + count); - s << (uint)GetChain; + s << GetChainPacket; auto h = m_server->m_chain->details(noGood).parent; for (unsigned i = 0; i < count; ++i, h = m_server->m_chain->details(h).parent) s << h; @@ -349,7 +350,7 @@ bool PeerSession::interpret(RLP const& _r) } break; } - case GetTransactions: + case GetTransactionsPacket: { if (m_server->m_mode == NodeMode::PeerServer) break; @@ -365,7 +366,7 @@ bool PeerSession::interpret(RLP const& _r) void PeerSession::ping() { RLPStream s; - sealAndSend(prep(s).appendList(1) << Ping); + sealAndSend(prep(s).appendList(1) << PingPacket); m_ping = std::chrono::steady_clock::now(); } @@ -377,7 +378,7 @@ RLPStream& PeerSession::prep(RLPStream& _s) void PeerServer::seal(bytes& _b) { if (m_verbosity >= 9) - cout << "<<< " << RLP(bytesConstRef(&_b).cropped(8)) << endl; + cbug(LeftChannel) << RLP(bytesConstRef(&_b).cropped(8)); _b[0] = 0x22; _b[1] = 0x40; _b[2] = 0x08; @@ -451,7 +452,7 @@ void PeerSession::disconnect() { RLPStream s; prep(s); - s.appendList(1) << (uint)Disconnect; + s.appendList(1) << DisconnectPacket; sealAndSend(s); m_disconnect = chrono::steady_clock::now(); } @@ -475,7 +476,7 @@ void PeerSession::start() { RLPStream s; prep(s); - s.appendList(m_server->m_public.port() ? 6 : 5) << (uint)Hello << (uint)0 << (uint)0 << m_server->m_clientVersion << (m_server->m_mode == NodeMode::Full ? 0x07 : m_server->m_mode == NodeMode::PeerServer ? 0x01 : 0); + s.appendList(m_server->m_public.port() ? 6 : 5) << HelloPacket << (uint)0 << (uint)0 << m_server->m_clientVersion << (m_server->m_mode == NodeMode::Full ? 0x07 : m_server->m_mode == NodeMode::PeerServer ? 0x01 : 0); if (m_server->m_public.port()) s << m_server->m_public.port(); sealAndSend(s); @@ -535,168 +536,6 @@ void PeerSession::doRead() }); } -#include -#include - -#include -#include -#include - -namespace eth { -struct UPnP -{ - UPnP() - { - ok = false; - - struct UPNPDev * devlist; - struct UPNPDev * dev; - char * descXML; - int descXMLsize = 0; - int upnperror = 0; - printf("TB : init_upnp()\n"); - memset(&urls, 0, sizeof(struct UPNPUrls)); - memset(&data, 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 */ - - printf("UPnP device :\n" - " desc: %s\n st: %s\n", - dev->descURL, 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, &data); - free (descXML); descXML = 0; -#if MINIUPNPC_API_VERSION >= 9 - GetUPNPUrls (&urls, &data, dev->descURL, 0); -#else - GetUPNPUrls (&urls, &data, dev->descURL); -#endif - ok = true; - } - freeUPNPDevlist(devlist); - } - else - { - /* error ! */ - } - } - ~UPnP() - { - auto r = m_reg; - for (auto i: r) - removeRedirect(i); - } - - string externalIP() - { - char addr[16]; - if (!UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, addr)) - return addr; - else - return "0.0.0.0"; - } - - int addRedirect(char const* addr, int port) - { - char port_str[16]; - int r; - printf("TB : upnp_add_redir (%d)\n", port); - if (urls.controlURL[0] == '\0') - { - printf("TB : the init was not done !\n"); - return -1; - } - sprintf(port_str, "%d", port); - r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port_str, port_str, addr, "ethereum", "TCP", NULL, NULL); - if (r) - { - printf("AddPortMapping(%s, %s, %s) failed with %d. Trying non-specific external port...\n", port_str, port_str, addr, r); - r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port_str, NULL, addr, "ethereum", "TCP", NULL, NULL); - } - if (r) - { - printf("AddPortMapping(%s, NULL, %s) failed with %d. Trying non-specific internal port...\n", port_str, addr, r); - r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, NULL, port_str, addr, "ethereum", "TCP", NULL, NULL); - } - if (r) - { - printf("AddPortMapping(NULL, %s, %s) failed with %d. Trying non-specific both ports...\n", port_str, addr, r); - r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, NULL, NULL, addr, "ethereum", "TCP", NULL, NULL); - } - if (r) - printf("AddPortMapping(NULL, NULL, %s) failed with %d\n", addr, r); - else - { - unsigned num = 0; - UPNP_GetPortMappingNumberOfEntries(urls.controlURL, 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(urls.controlURL, 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; - } - return 0; - } - - void removeRedirect(int port) - { - char port_str[16]; -// int t; - printf("TB : upnp_rem_redir (%d)\n", port); - if (urls.controlURL[0] == '\0') - { - printf("TB : the init was not done !\n"); - return; - } - sprintf(port_str, "%d", port); - UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port_str, "TCP", NULL); - m_reg.erase(port); - } - - bool isValid() const - { - return ok; - } - - set m_reg; - - bool ok; - struct UPNPUrls urls; - struct IGDdatas data; -}; -} - -class NoNetworking: public std::exception {}; - PeerServer::PeerServer(std::string const& _clientVersion, BlockChain const& _ch, uint _networkId, short _port, NodeMode _m, string const& _publicAddress, bool _upnp): m_clientVersion(_clientVersion), m_mode(_m), @@ -737,17 +576,23 @@ PeerServer::~PeerServer() void PeerServer::determinePublic(string const& _publicAddress, bool _upnp) { if (_upnp) - m_upnp = new 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()) { - cout << "external addr: " << m_upnp->externalIP() << endl; + cnote << "External addr: " << m_upnp->externalIP(); int p = m_upnp->addRedirect(m_peerAddresses[0].to_string().c_str(), m_listenPort); - if (!p) + if (p) + cnote << "Punched through NAT and mapped local port" << m_listenPort << "onto external port" << p << "."; + else { // couldn't map - cerr << "*** WARNING: Couldn't punch through NAT (or no NAT in place). Using " << m_listenPort << endl; + cwarn << "Couldn't punch through NAT (or no NAT in place). Assuming " << m_listenPort << " is local & external port."; p = m_listenPort; } @@ -756,31 +601,18 @@ void PeerServer::determinePublic(string const& _publicAddress, bool _upnp) m_public = bi::tcp::endpoint(bi::address(), p); else { - auto it = r.resolve({_publicAddress.empty() ? eip : _publicAddress, toString(p)}); - m_public = it->endpoint(); + m_public = bi::tcp::endpoint(bi::address::from_string(_publicAddress.empty() ? eip : _publicAddress), 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. - if (_publicAddress.size()) - m_public = r.resolve({_publicAddress, toString(m_listenPort)})->endpoint(); - else if (m_peerAddresses.size()) - m_public = r.resolve({m_peerAddresses[0].to_string(), toString(m_listenPort)})->endpoint(); - else - m_public = bi::tcp::endpoint(bi::address(), m_listenPort); + 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()); } -/* int er; - UPNPDev* dlist = upnpDiscover(250, 0, 0, 0, 0, &er); - for (UPNPDev* d = dlist; d; d = dlist->pNext) - { - IGDdatas data; - parserootdesc(d->descURL, 0, &data); - data.presentationurl() - } - freeUPNPDevlist(dlist);*/ } void PeerServer::populateAddresses() @@ -1003,7 +835,7 @@ bool PeerServer::process(BlockChain& _bc, TransactionQueue& _tq, Overlay& _o) { RLPStream ts; PeerSession::prep(ts); - ts.appendList(n + 1) << Transactions; + ts.appendList(n + 1) << TransactionsPacket; ts.appendRaw(b).swapOut(b); seal(b); p->send(&b); @@ -1019,7 +851,7 @@ bool PeerServer::process(BlockChain& _bc, TransactionQueue& _tq, Overlay& _o) // TODO: find where they diverge and send complete new branch. RLPStream ts; PeerSession::prep(ts); - ts.appendList(2) << Blocks; + ts.appendList(2) << BlocksPacket; bytes b; ts.appendRaw(_bc.block(_bc.currentHash())).swapOut(b); seal(b); @@ -1070,7 +902,7 @@ bool PeerServer::process(BlockChain& _bc, TransactionQueue& _tq, Overlay& _o) { RLPStream s; bytes b; - (PeerSession::prep(s).appendList(1) << GetPeers).swapOut(b); + (PeerSession::prep(s).appendList(1) << GetPeersPacket).swapOut(b); seal(b); for (auto const& i: m_peers) if (auto p = i.lock()) diff --git a/libethereum/PeerNetwork.h b/libethereum/PeerNetwork.h index 363658b64..b4408699b 100644 --- a/libethereum/PeerNetwork.h +++ b/libethereum/PeerNetwork.h @@ -39,17 +39,17 @@ class TransactionQueue; enum PacketType { - Hello = 0, - Disconnect, - Ping, - Pong, - GetPeers = 0x10, - Peers, - Transactions, - Blocks, - GetChain, - NotInChain, - GetTransactions + HelloPacket = 0, + DisconnectPacket, + PingPacket, + PongPacket, + GetPeersPacket = 0x10, + PeersPacket, + TransactionsPacket, + BlocksPacket, + GetChainPacket, + NotInChainPacket, + GetTransactionsPacket }; class PeerServer; diff --git a/libethereum/State.cpp b/libethereum/State.cpp index a52e6078d..a4f7428a1 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -95,17 +95,10 @@ State::State(Address _coinbaseAddress, Overlay const& _db): m_db(_db), m_state(& // Initialise to the state entailed by the genesis block; this guarantees the trie is built correctly. m_state.init(); eth::commit(genesisState(), m_db, m_state); - cout << "State::State: state root initialised to " << m_state.root() << endl; + cnote << "State root: " << m_state.root(); m_previousBlock = BlockInfo::genesis(); cnote << "Genesis hash:" << m_previousBlock.hash; - { - RLPStream s; - m_previousBlock.fillStream(s, true); - cnote << RLP(s.out()); - cnote << asHex(s.out()); - cnote << sha3(s.out()); - } resetCurrent(); assert(m_state.root() == m_previousBlock.stateRoot); diff --git a/libethereum/Transaction.h b/libethereum/Transaction.h index 742f7d40c..2e077e875 100644 --- a/libethereum/Transaction.h +++ b/libethereum/Transaction.h @@ -60,6 +60,8 @@ struct Transaction bytes sha3Bytes(bool _sig = true) const { RLPStream s; fillStream(s, _sig); return eth::sha3Bytes(s.out()); } }; +using Transactions = std::vector; + } diff --git a/libethereum/TransactionQueue.cpp b/libethereum/TransactionQueue.cpp index 9c5704f06..f5774dd02 100644 --- a/libethereum/TransactionQueue.cpp +++ b/libethereum/TransactionQueue.cpp @@ -36,7 +36,9 @@ bool TransactionQueue::import(bytes const& _block) // Check validity of _block as a transaction. To do this we just deserialise and attempt to determine the sender. If it doesn't work, the signature is bad. // The transaction's nonce may yet be invalid (or, it could be "valid" but we may be missing a marginally older transaction). Transaction t(_block); - t.sender(); + auto s = t.sender(); + if (m_interest.count(s)) + m_interestQueue.push_back(t); // If valid, append to blocks. m_data[h] = _block; diff --git a/libethereum/TransactionQueue.h b/libethereum/TransactionQueue.h index f4aa67e28..4028e7eaf 100644 --- a/libethereum/TransactionQueue.h +++ b/libethereum/TransactionQueue.h @@ -22,6 +22,7 @@ #pragma once #include "Common.h" +#include "Transaction.h" namespace eth { @@ -35,15 +36,18 @@ class TransactionQueue { public: bool attemptImport(bytes const& _block) { try { import(_block); return true; } catch (...) { return false; } } - bool import(bytes const& _block); - void drop(h256 _txHash) { m_data.erase(_txHash); } - std::map const& transactions() const { return m_data; } + Transactions interestQueue() { Transactions ret; swap(ret, m_interestQueue); return ret; } + void pushInterest(Address _a) { m_interest[_a]++; } + void popInterest(Address _a) { if (m_interest[_a] > 1) m_interest[_a]--; else if (m_interest[_a]) m_interest.erase(_a); } + private: std::map m_data; ///< the queue. + Transactions m_interestQueue; + std::map m_interest; }; } diff --git a/libethereum/UPnP.cpp b/libethereum/UPnP.cpp new file mode 100644 index 000000000..5b92d5373 --- /dev/null +++ b/libethereum/UPnP.cpp @@ -0,0 +1,168 @@ +/* + 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. + + Foobar 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 Foobar. If not, see . +*/ +/** @file UPnP.cpp + * @authors: + * Gav Wood + * @date 2014 + */ + +#include +#include +#include +#include +#include +#include "Common.h" +#include "Exceptions.h" +#include "UPnP.h" +using namespace std; +using namespace eth; + +UPnP::UPnP() +{ + m_urls = new UPNPUrls; + m_data = new IGDdatas; + + m_ok = false; + + struct UPNPDev* devlist; + struct UPNPDev* dev; + char* descXML; + int descXMLsize = 0; + int upnperror = 0; + memset(m_urls, 0, sizeof(struct UPNPUrls)); + memset(m_data, 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); + free (descXML); descXML = 0; +#if MINIUPNPC_API_VERSION >= 9 + GetUPNPUrls (m_urls, m_data, dev->descURL, 0); +#else + GetUPNPUrls (m_urls, m_data, dev->descURL); +#endif + m_ok = true; + } + freeUPNPDevlist(devlist); + } + else + { + cnote << "UPnP device not found."; + throw NoUPnPDevice(); + } +} + +UPnP::~UPnP() +{ + auto r = m_reg; + for (auto i: r) + removeRedirect(i); +} + +string UPnP::externalIP() +{ + char addr[16]; + if (!UPNP_GetExternalIPAddress(m_urls->controlURL, m_data->first.servicetype, addr)) + return addr; + else + return "0.0.0.0"; +} + +int UPnP::addRedirect(char const* addr, int port) +{ + 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 = random() % 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; + return 0; +} + +void UPnP::removeRedirect(int port) +{ + 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); +} diff --git a/libethereum/UPnP.h b/libethereum/UPnP.h new file mode 100644 index 000000000..f5cdcd576 --- /dev/null +++ b/libethereum/UPnP.h @@ -0,0 +1,52 @@ +/* + 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. + + Foobar 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 Foobar. If not, see . +*/ +/** @file UPnP.h + * @authors: + * Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include + +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 m_reg; + bool m_ok; + struct UPNPUrls* m_urls; + struct IGDdatas* m_data; +}; + +}