From 50bd12ce0c49e574a5baf1a8df3a667810c6ad1e Mon Sep 17 00:00:00 2001 From: Gregory Maxwell Date: Sun, 11 Dec 2016 04:39:26 +0000 Subject: [PATCH 1/3] Break addnode out from the outbound connection limits. Previously addnodes were in competition with outbound connections for access to the eight outbound slots. One result of this is that frequently a node with several addnode configured peers would end up connected to none of them, because while the addnode loop was in its two minute sleep the automatic connection logic would fill any free slots with random peers. This is particularly unwelcome to users trying to maintain links to specific nodes for fast block relay or purposes. Another result is that a group of nine or more nodes which are have addnode configured towards each other can become partitioned from the public network. This commit introduces a new limit of eight connections just for addnode peers which is not subject to any of the other connection limitations (including maxconnections). The choice of eight is sufficient so that under no condition would a user find themselves connected to fewer addnoded peers than previously. It is also low enough that users who are confused about the significance of more connections and have gotten too copy-and-paste happy will not consume more than twice the slot usage of a typical user. Any additional load on the network resulting from this will likely be offset by a reduction in users applying even more wasteful workaround for the prior behavior. The retry delays are reduced to avoid nodes sitting around without their added peers up, but are still sufficient to prevent overly aggressive repeated connections. The reduced delays also make the system much more responsive to the addnode RPC. Ban-disconnects are also exempted for peers added via addnode since the outbound addnode logic ignores bans. Previously it would ban an addnode then immediately reconnect to it. A minor change was also made to CSemaphoreGrant so that it is possible to re-acquire via an object whos grant was moved. --- src/init.cpp | 9 +++++---- src/net.cpp | 37 +++++++++++++++++++++++++++++++------ src/net.h | 11 +++++++++-- src/net_processing.cpp | 2 ++ src/rpc/net.cpp | 1 + src/sync.h | 1 - 6 files changed, 48 insertions(+), 13 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index f0334a08e..aeca75c38 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -871,11 +871,11 @@ bool AppInitParameterInteraction() nMaxConnections = std::max(nUserMaxConnections, 0); // Trim requested connection counts, to fit into system limitations - nMaxConnections = std::max(std::min(nMaxConnections, (int)(FD_SETSIZE - nBind - MIN_CORE_FILEDESCRIPTORS)), 0); - nFD = RaiseFileDescriptorLimit(nMaxConnections + MIN_CORE_FILEDESCRIPTORS); + nMaxConnections = std::max(std::min(nMaxConnections, (int)(FD_SETSIZE - nBind - MIN_CORE_FILEDESCRIPTORS - MAX_ADDNODE_CONNECTIONS)), 0); + nFD = RaiseFileDescriptorLimit(nMaxConnections + MIN_CORE_FILEDESCRIPTORS + MAX_ADDNODE_CONNECTIONS); if (nFD < MIN_CORE_FILEDESCRIPTORS) return InitError(_("Not enough file descriptors available.")); - nMaxConnections = std::min(nFD - MIN_CORE_FILEDESCRIPTORS, nMaxConnections); + nMaxConnections = std::min(nFD - MIN_CORE_FILEDESCRIPTORS - MAX_ADDNODE_CONNECTIONS, nMaxConnections); if (nMaxConnections < nUserMaxConnections) InitWarning(strprintf(_("Reducing -maxconnections from %d to %d, because of system limitations."), nUserMaxConnections, nMaxConnections)); @@ -1109,7 +1109,7 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) LogPrintf("Default data directory %s\n", GetDefaultDataDir().string()); LogPrintf("Using data directory %s\n", GetDataDir().string()); LogPrintf("Using config file %s\n", GetConfigFile(GetArg("-conf", BITCOIN_CONF_FILENAME)).string()); - LogPrintf("Using at most %i connections (%i file descriptors available)\n", nMaxConnections, nFD); + LogPrintf("Using at most %i automatic connections (%i file descriptors available)\n", nMaxConnections, nFD); InitSignatureCache(); @@ -1565,6 +1565,7 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) connOptions.nRelevantServices = nRelevantServices; connOptions.nMaxConnections = nMaxConnections; connOptions.nMaxOutbound = std::min(MAX_OUTBOUND_CONNECTIONS, connOptions.nMaxConnections); + connOptions.nMaxAddnode = MAX_ADDNODE_CONNECTIONS; connOptions.nMaxFeeler = 1; connOptions.nBestHeight = chainActive.Height(); connOptions.uiInterface = &uiInterface; diff --git a/src/net.cpp b/src/net.cpp index bf2beb774..6640cc001 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -621,6 +621,7 @@ void CNode::copyStats(CNodeStats &stats) X(nVersion); X(cleanSubVer); X(fInbound); + X(fAddnode); X(nStartingHeight); X(nSendBytes); X(mapSendBytesPerMsgCmd); @@ -1631,7 +1632,7 @@ void CConnman::ThreadOpenConnections() { LOCK(cs_vNodes); BOOST_FOREACH(CNode* pnode, vNodes) { - if (!pnode->fInbound) { + if (!pnode->fInbound && !pnode->fAddnode) { setConnected.insert(pnode->addr.GetGroup()); nOutbound++; } @@ -1776,27 +1777,35 @@ void CConnman::ThreadOpenAddedConnections() vAddedNodes = mapMultiArgs.at("-addnode"); } - for (unsigned int i = 0; true; i++) + while (true) { + CSemaphoreGrant grant(*semAddnode); std::vector vInfo = GetAddedNodeInfo(); + bool tried = false; for (const AddedNodeInfo& info : vInfo) { if (!info.fConnected) { - CSemaphoreGrant grant(*semOutbound); + if (!grant.TryAcquire()) { + // If we've used up our semaphore and need a new one, lets not wait here since while we are waiting + // the addednodeinfo state might change. + break; + } // If strAddedNode is an IP/port, decode it immediately, so // OpenNetworkConnection can detect existing connections to that IP/port. + tried = true; CService service(LookupNumeric(info.strAddedNode.c_str(), Params().GetDefaultPort())); - OpenNetworkConnection(CAddress(service, NODE_NONE), false, &grant, info.strAddedNode.c_str(), false); + OpenNetworkConnection(CAddress(service, NODE_NONE), false, &grant, info.strAddedNode.c_str(), false, false, true); if (!interruptNet.sleep_for(std::chrono::milliseconds(500))) return; } } - if (!interruptNet.sleep_for(std::chrono::minutes(2))) + // Retry every 60 seconds if a connection was attempted, otherwise two seconds + if (!interruptNet.sleep_for(std::chrono::seconds(tried ? 60 : 2))); return; } } // if successful, this moves the passed grant to the constructed node -bool CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound, const char *pszDest, bool fOneShot, bool fFeeler) +bool CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound, const char *pszDest, bool fOneShot, bool fFeeler, bool fAddnode) { // // Initiate outbound network connection @@ -1825,6 +1834,8 @@ bool CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai pnode->fOneShot = true; if (fFeeler) pnode->fFeeler = true; + if (fAddnode) + pnode->fAddnode = true; return true; } @@ -2076,8 +2087,10 @@ CConnman::CConnman(uint64_t nSeed0In, uint64_t nSeed1In) : nSeed0(nSeed0In), nSe nSendBufferMaxSize = 0; nReceiveFloodSize = 0; semOutbound = NULL; + semAddnode = NULL; nMaxConnections = 0; nMaxOutbound = 0; + nMaxAddnode = 0; nBestHeight = 0; clientInterface = NULL; flagInterruptMsgProc = false; @@ -2099,6 +2112,7 @@ bool CConnman::Start(CScheduler& scheduler, std::string& strNodeError, Options c nLocalServices = connOptions.nLocalServices; nMaxConnections = connOptions.nMaxConnections; nMaxOutbound = std::min((connOptions.nMaxOutbound), nMaxConnections); + nMaxAddnode = connOptions.nMaxAddnode; nMaxFeeler = connOptions.nMaxFeeler; nSendBufferMaxSize = connOptions.nSendBufferMaxSize; @@ -2151,6 +2165,10 @@ bool CConnman::Start(CScheduler& scheduler, std::string& strNodeError, Options c // initialize semaphore semOutbound = new CSemaphore(std::min((nMaxOutbound + nMaxFeeler), nMaxConnections)); } + if (semAddnode == NULL) { + // initialize semaphore + semAddnode = new CSemaphore(nMaxAddnode); + } // // Start threads @@ -2227,6 +2245,10 @@ void CConnman::Stop() if (threadSocketHandler.joinable()) threadSocketHandler.join(); + if (semAddnode) + for (int i=0; ipost(); + if (fAddressesInitialized) { DumpData(); @@ -2254,6 +2276,8 @@ void CConnman::Stop() vhListenSocket.clear(); delete semOutbound; semOutbound = NULL; + delete semAddnode; + semAddnode = NULL; } void CConnman::DeleteNode(CNode* pnode) @@ -2554,6 +2578,7 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn strSubVer = ""; fWhitelisted = false; fOneShot = false; + fAddnode = false; fClient = false; // set by version message fFeeler = false; fSuccessfullyConnected = false; diff --git a/src/net.h b/src/net.h index 6ca402f71..97b27dcdf 100644 --- a/src/net.h +++ b/src/net.h @@ -58,8 +58,10 @@ static const unsigned int MAX_ADDR_TO_SEND = 1000; static const unsigned int MAX_PROTOCOL_MESSAGE_LENGTH = 4 * 1000 * 1000; /** Maximum length of strSubVer in `version` message */ static const unsigned int MAX_SUBVERSION_LENGTH = 256; -/** Maximum number of outgoing nodes */ +/** Maximum number of automatic outgoing nodes */ static const int MAX_OUTBOUND_CONNECTIONS = 8; +/** Maximum number of addnode outgoing nodes */ +static const int MAX_ADDNODE_CONNECTIONS = 8; /** -listen default */ static const bool DEFAULT_LISTEN = true; /** -upnp default */ @@ -135,6 +137,7 @@ public: ServiceFlags nRelevantServices = NODE_NONE; int nMaxConnections = 0; int nMaxOutbound = 0; + int nMaxAddnode = 0; int nMaxFeeler = 0; int nBestHeight = 0; CClientUIInterface* uiInterface = nullptr; @@ -151,7 +154,7 @@ public: bool BindListenPort(const CService &bindAddr, std::string& strError, bool fWhitelisted = false); bool GetNetworkActive() const { return fNetworkActive; }; void SetNetworkActive(bool active); - bool OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound = NULL, const char *strDest = NULL, bool fOneShot = false, bool fFeeler = false); + bool OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound = NULL, const char *strDest = NULL, bool fOneShot = false, bool fFeeler = false, bool fAddnode = false); bool CheckIncomingNonce(uint64_t nonce); bool ForNode(NodeId id, std::function func); @@ -414,8 +417,10 @@ private: ServiceFlags nRelevantServices; CSemaphore *semOutbound; + CSemaphore *semAddnode; int nMaxConnections; int nMaxOutbound; + int nMaxAddnode; int nMaxFeeler; std::atomic nBestHeight; CClientUIInterface* clientInterface; @@ -529,6 +534,7 @@ public: int nVersion; std::string cleanSubVer; bool fInbound; + bool fAddnode; int nStartingHeight; uint64_t nSendBytes; mapMsgCmdSize mapSendBytesPerMsgCmd; @@ -626,6 +632,7 @@ public: bool fWhitelisted; // This peer can bypass DoS banning. bool fFeeler; // If true this node is being used as a short lived feeler. bool fOneShot; + bool fAddnode; bool fClient; const bool fInbound; bool fSuccessfullyConnected; diff --git a/src/net_processing.cpp b/src/net_processing.cpp index ccfbb77fc..3a956e89e 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -2644,6 +2644,8 @@ bool SendMessages(CNode* pto, CConnman& connman, std::atomic& interruptMsg state.fShouldBan = false; if (pto->fWhitelisted) LogPrintf("Warning: not punishing whitelisted peer %s!\n", pto->addr.ToString()); + else if (pto->fAddnode) + LogPrintf("Warning: not punishing addnoded peer %s!\n", pto->addr.ToString()); else { pto->fDisconnect = true; if (pto->addr.IsLocal()) diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 632514fc8..fe5957959 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -152,6 +152,7 @@ UniValue getpeerinfo(const JSONRPCRequest& request) // their ver message. obj.push_back(Pair("subver", stats.cleanSubVer)); obj.push_back(Pair("inbound", stats.fInbound)); + obj.push_back(Pair("addnode", stats.fAddnode)); obj.push_back(Pair("startingheight", stats.nStartingHeight)); if (fStateStats) { obj.push_back(Pair("banscore", statestats.nMisbehavior)); diff --git a/src/sync.h b/src/sync.h index 680d60304..3b29050e0 100644 --- a/src/sync.h +++ b/src/sync.h @@ -264,7 +264,6 @@ public: grant.Release(); grant.sem = sem; grant.fHaveGrant = fHaveGrant; - sem = NULL; fHaveGrant = false; } From 90f13e18228fde2e62de1c7d9b4b7e12a703bf03 Mon Sep 17 00:00:00 2001 From: Gregory Maxwell Date: Sun, 11 Dec 2016 04:59:55 +0000 Subject: [PATCH 2/3] Add release notes for addnode changes. --- doc/release-notes.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/release-notes.md b/doc/release-notes.md index fe7f69d1f..7d0d68968 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -62,6 +62,16 @@ Removal of Priority Estimation major version. To prepare for this, the default for the rate limit of priority transactions (`-limitfreerelay`) has been set to `0` kB/minute. +P2P connection management +-------------------------- + +- Peers manually added through the addnode option or addnode RPC now have their own + limit of eight connections which does not compete with other inbound or outbound + connection usage and is not subject to the maxconnections limitation. + +- New connections to manually added peers are much faster. + + 0.14.0 Change log ================= From 032ba3f0665432bd15ff76ee01cde245ad29e3e6 Mon Sep 17 00:00:00 2001 From: Gregory Maxwell Date: Sun, 11 Dec 2016 20:26:06 +0000 Subject: [PATCH 3/3] RPC help documentation for addnode peerinfo. Also adds a comment about the netgroup exclusion behavior. --- src/net.cpp | 5 +++++ src/rpc/net.cpp | 1 + 2 files changed, 6 insertions(+) diff --git a/src/net.cpp b/src/net.cpp index 6640cc001..0f0a7bf8d 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -1633,6 +1633,11 @@ void CConnman::ThreadOpenConnections() LOCK(cs_vNodes); BOOST_FOREACH(CNode* pnode, vNodes) { if (!pnode->fInbound && !pnode->fAddnode) { + // Netgroups for inbound and addnode peers are not excluded because our goal here + // is to not use multiple of our limited outbound slots on a single netgroup + // but inbound and addnode peers do not use our outbound slots. Inbound peers + // also have the added issue that they're attacker controlled and could be used + // to prevent us from connecting to particular hosts if we used them here. setConnected.insert(pnode->addr.GetGroup()); nOutbound++; } diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index fe5957959..2af00c1de 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -92,6 +92,7 @@ UniValue getpeerinfo(const JSONRPCRequest& request) " \"version\": v, (numeric) The peer version, such as 7001\n" " \"subver\": \"/Satoshi:0.8.5/\", (string) The string version\n" " \"inbound\": true|false, (boolean) Inbound (true) or Outbound (false)\n" + " \"addnode\": true|false, (boolean) Whether connection was due to addnode and is using an addnode slot\n" " \"startingheight\": n, (numeric) The starting height (block) of the peer\n" " \"banscore\": n, (numeric) The ban score\n" " \"synced_headers\": n, (numeric) The last header we have in common with this peer\n"