diff --git a/src/init.cpp b/src/init.cpp index 085e04fdf..3aebe4f7b 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -335,7 +335,6 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-whitebind=", _("Bind to given address and whitelist peers connecting to it. Use [host]:port notation for IPv6")); strUsage += HelpMessageOpt("-whitelist=", _("Whitelist peers connecting from the given netmask or IP address. Can be specified multiple times.") + " " + _("Whitelisted peers cannot be DoS banned and their transactions are always relayed, even if they are already in the mempool, useful e.g. for a gateway")); - strUsage += HelpMessageOpt("-whiteconnections=", strprintf(_("Reserve this many inbound connections for whitelisted peers (default: %d)"), 0)); #ifdef ENABLE_WALLET strUsage += HelpMessageGroup(_("Wallet options:")); @@ -754,25 +753,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) int nBind = std::max((int)mapArgs.count("-bind") + (int)mapArgs.count("-whitebind"), 1); int nUserMaxConnections = GetArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS); nMaxConnections = std::max(nUserMaxConnections, 0); - int nUserWhiteConnections = GetArg("-whiteconnections", 0); - nWhiteConnections = std::max(nUserWhiteConnections, 0); - - if ((mapArgs.count("-whitelist")) || (mapArgs.count("-whitebind"))) { - if (!(mapArgs.count("-maxconnections"))) { - // User is using whitelist feature, - // but did not specify -maxconnections parameter. - // Silently increase the default to compensate, - // so that the whitelist connection reservation feature - // does not inadvertently reduce the default - // inbound connection capacity of the network. - nMaxConnections += nWhiteConnections; - } - } else { - // User not using whitelist feature. - // Silently disable connection reservation, - // for the same reason as above. - nWhiteConnections = 0; - } // Trim requested connection counts, to fit into system limitations nMaxConnections = std::max(std::min(nMaxConnections, (int)(FD_SETSIZE - nBind - MIN_CORE_FILEDESCRIPTORS)), 0); @@ -784,13 +764,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if (nMaxConnections < nUserMaxConnections) InitWarning(strprintf(_("Reducing -maxconnections from %d to %d, because of system limitations."), nUserMaxConnections, nMaxConnections)); - // Connection capacity is prioritized in this order: - // outbound connections (hardcoded to 8), - // then whitelisted connections, - // then non-whitelisted connections get whatever's left (if any). - if ((nWhiteConnections > 0) && (nWhiteConnections >= (nMaxConnections - 8))) - InitWarning(strprintf(_("All non-whitelisted incoming connections will be dropped, because -whiteconnections is %d and -maxconnections is only %d."), nWhiteConnections, nMaxConnections)); - // ********************************************************* Step 3: parameter-to-internal-flags fDebug = !mapMultiArgs["-debug"].empty(); @@ -968,8 +941,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) LogPrintf("Using data directory %s\n", strDataDir); LogPrintf("Using config file %s\n", GetConfigFile().string()); LogPrintf("Using at most %i connections (%i file descriptors available)\n", nMaxConnections, nFD); - if (nWhiteConnections > 0) - LogPrintf("Reserving %i of these connections for whitelisted inbound peers\n", nWhiteConnections); std::ostringstream strErrors; LogPrintf("Using %u threads for script verification\n", nScriptCheckThreads); diff --git a/src/main.cpp b/src/main.cpp index 33b57a528..35fbec666 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4522,6 +4522,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, if (pingUsecTime > 0) { // Successful ping time measurement, replace previous pfrom->nPingUsecTime = pingUsecTime; + pfrom->nMinPingUsecTime = std::min(pfrom->nMinPingUsecTime, pingUsecTime); } else { // This should never happen sProblem = "Timing mishap"; diff --git a/src/net.cpp b/src/net.cpp index 105ccce84..4909d5fd4 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -81,7 +81,6 @@ uint64_t nLocalHostNonce = 0; static std::vector vhListenSocket; CAddrMan addrman; int nMaxConnections = DEFAULT_MAX_PEER_CONNECTIONS; -int nWhiteConnections = 0; bool fAddressesInitialized = false; std::string strSubVersion; @@ -776,6 +775,222 @@ void SocketSendData(CNode *pnode) static list vNodesDisconnected; +class CNodeRef { +public: + CNodeRef(CNode *pnode) : _pnode(pnode) { + LOCK(cs_vNodes); + _pnode->AddRef(); + } + + ~CNodeRef() { + LOCK(cs_vNodes); + _pnode->Release(); + } + + CNode& operator *() const {return *_pnode;}; + CNode* operator ->() const {return _pnode;}; + + CNodeRef& operator =(const CNodeRef& other) + { + if (this != &other) { + LOCK(cs_vNodes); + + _pnode->Release(); + _pnode = other._pnode; + _pnode->AddRef(); + } + return *this; + } + + CNodeRef(const CNodeRef& other): + _pnode(other._pnode) + { + LOCK(cs_vNodes); + _pnode->AddRef(); + } +private: + CNode *_pnode; +}; + +static bool ReverseCompareNodeMinPingTime(const CNodeRef &a, const CNodeRef &b) +{ + return a->nMinPingUsecTime > b->nMinPingUsecTime; +} + +static bool ReverseCompareNodeTimeConnected(const CNodeRef &a, const CNodeRef &b) +{ + return a->nTimeConnected > b->nTimeConnected; +} + +class CompareNetGroupKeyed +{ + std::vector vchSecretKey; +public: + CompareNetGroupKeyed() + { + vchSecretKey.resize(32, 0); + GetRandBytes(vchSecretKey.data(), vchSecretKey.size()); + } + + bool operator()(const CNodeRef &a, const CNodeRef &b) + { + std::vector vchGroupA, vchGroupB; + CSHA256 hashA, hashB; + std::vector vchA(32), vchB(32); + + vchGroupA = a->addr.GetGroup(); + vchGroupB = b->addr.GetGroup(); + + hashA.Write(begin_ptr(vchGroupA), vchGroupA.size()); + hashB.Write(begin_ptr(vchGroupB), vchGroupB.size()); + + hashA.Write(begin_ptr(vchSecretKey), vchSecretKey.size()); + hashB.Write(begin_ptr(vchSecretKey), vchSecretKey.size()); + + hashA.Finalize(begin_ptr(vchA)); + hashB.Finalize(begin_ptr(vchB)); + + return vchA < vchB; + } +}; + +static bool AttemptToEvictConnection(bool fPreferNewConnection) { + std::vector vEvictionCandidates; + { + LOCK(cs_vNodes); + + BOOST_FOREACH(CNode *node, vNodes) { + if (node->fWhitelisted) + continue; + if (!node->fInbound) + continue; + if (node->fDisconnect) + continue; + if (node->addr.IsLocal()) + continue; + vEvictionCandidates.push_back(CNodeRef(node)); + } + } + + if (vEvictionCandidates.empty()) return false; + + // Protect connections with certain characteristics + + // Deterministically select 4 peers to protect by netgroup. + // An attacker cannot predict which netgroups will be protected. + static CompareNetGroupKeyed comparerNetGroupKeyed; + std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), comparerNetGroupKeyed); + vEvictionCandidates.erase(vEvictionCandidates.end() - std::min(4, static_cast(vEvictionCandidates.size())), vEvictionCandidates.end()); + + if (vEvictionCandidates.empty()) return false; + + // Protect the 8 nodes with the best ping times. + // An attacker cannot manipulate this metric without physically moving nodes closer to the target. + std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), ReverseCompareNodeMinPingTime); + vEvictionCandidates.erase(vEvictionCandidates.end() - std::min(8, static_cast(vEvictionCandidates.size())), vEvictionCandidates.end()); + + if (vEvictionCandidates.empty()) return false; + + // Protect the half of the remaining nodes which have been connected the longest. + // This replicates the existing implicit behavior. + std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), ReverseCompareNodeTimeConnected); + vEvictionCandidates.erase(vEvictionCandidates.end() - static_cast(vEvictionCandidates.size() / 2), vEvictionCandidates.end()); + + if (vEvictionCandidates.empty()) return false; + + // Identify the network group with the most connections + std::vector naMostConnections; + unsigned int nMostConnections = 0; + std::map, std::vector > mapAddrCounts; + BOOST_FOREACH(const CNodeRef &node, vEvictionCandidates) { + mapAddrCounts[node->addr.GetGroup()].push_back(node); + + if (mapAddrCounts[node->addr.GetGroup()].size() > nMostConnections) { + nMostConnections = mapAddrCounts[node->addr.GetGroup()].size(); + naMostConnections = node->addr.GetGroup(); + } + } + + // Reduce to the network group with the most connections + vEvictionCandidates = mapAddrCounts[naMostConnections]; + + // Do not disconnect peers if there is only 1 connection from their network group + if (vEvictionCandidates.size() <= 1) + // unless we prefer the new connection (for whitelisted peers) + if (!fPreferNewConnection) + return false; + + // Disconnect the most recent connection from the network group with the most connections + std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), ReverseCompareNodeTimeConnected); + vEvictionCandidates[0]->fDisconnect = true; + + return true; +} + +static void AcceptConnection(const ListenSocket& hListenSocket) { + struct sockaddr_storage sockaddr; + socklen_t len = sizeof(sockaddr); + SOCKET hSocket = accept(hListenSocket.socket, (struct sockaddr*)&sockaddr, &len); + CAddress addr; + int nInbound = 0; + int nMaxInbound = nMaxConnections - MAX_OUTBOUND_CONNECTIONS; + + if (hSocket != INVALID_SOCKET) + if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) + LogPrintf("Warning: Unknown socket family\n"); + + bool whitelisted = hListenSocket.whitelisted || CNode::IsWhitelistedRange(addr); + { + LOCK(cs_vNodes); + BOOST_FOREACH(CNode* pnode, vNodes) + if (pnode->fInbound) + nInbound++; + } + + if (hSocket == INVALID_SOCKET) + { + int nErr = WSAGetLastError(); + if (nErr != WSAEWOULDBLOCK) + LogPrintf("socket error accept failed: %s\n", NetworkErrorString(nErr)); + return; + } + + if (!IsSelectableSocket(hSocket)) + { + LogPrintf("connection from %s dropped: non-selectable socket\n", addr.ToString()); + CloseSocket(hSocket); + return; + } + + if (CNode::IsBanned(addr) && !whitelisted) + { + LogPrintf("connection from %s dropped (banned)\n", addr.ToString()); + CloseSocket(hSocket); + return; + } + + if (nInbound >= nMaxInbound) + { + if (!AttemptToEvictConnection(whitelisted)) { + // No connection to evict, disconnect the new connection + LogPrint("net", "failed to find an eviction candidate - connection dropped (full)\n"); + CloseSocket(hSocket); + return; + } + } + + CNode* pnode = new CNode(hSocket, addr, "", true); + pnode->AddRef(); + pnode->fWhitelisted = whitelisted; + + LogPrint("net", "connection from %s accepted\n", addr.ToString()); + + { + LOCK(cs_vNodes); + vNodes.push_back(pnode); + } +} + void ThreadSocketHandler() { unsigned int nPrevNodeCount = 0; @@ -933,64 +1148,7 @@ void ThreadSocketHandler() { if (hListenSocket.socket != INVALID_SOCKET && FD_ISSET(hListenSocket.socket, &fdsetRecv)) { - struct sockaddr_storage sockaddr; - socklen_t len = sizeof(sockaddr); - SOCKET hSocket = accept(hListenSocket.socket, (struct sockaddr*)&sockaddr, &len); - CAddress addr; - int nInbound = 0; - int nMaxInbound = nMaxConnections - MAX_OUTBOUND_CONNECTIONS; - - if (hSocket != INVALID_SOCKET) - if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) - LogPrintf("Warning: Unknown socket family\n"); - - bool whitelisted = hListenSocket.whitelisted || CNode::IsWhitelistedRange(addr); - { - LOCK(cs_vNodes); - BOOST_FOREACH(CNode* pnode, vNodes) - if (pnode->fInbound) - nInbound++; - } - - if (hSocket == INVALID_SOCKET) - { - int nErr = WSAGetLastError(); - if (nErr != WSAEWOULDBLOCK) - LogPrintf("socket error accept failed: %s\n", NetworkErrorString(nErr)); - } - else if (!IsSelectableSocket(hSocket)) - { - LogPrintf("connection from %s dropped: non-selectable socket\n", addr.ToString()); - CloseSocket(hSocket); - } - else if (nInbound >= nMaxInbound) - { - LogPrint("net", "connection from %s dropped (full)\n", addr.ToString()); - CloseSocket(hSocket); - } - else if (!whitelisted && (nInbound >= (nMaxInbound - nWhiteConnections))) - { - LogPrint("net", "connection from %s dropped (non-whitelisted)\n", addr.ToString()); - CloseSocket(hSocket); - } - else if (CNode::IsBanned(addr) && !whitelisted) - { - LogPrintf("connection from %s dropped (banned)\n", addr.ToString()); - CloseSocket(hSocket); - } - else - { - CNode* pnode = new CNode(hSocket, addr, "", true); - pnode->AddRef(); - pnode->fWhitelisted = whitelisted; - - LogPrint("net", "connection from %s accepted\n", addr.ToString()); - - { - LOCK(cs_vNodes); - vNodes.push_back(pnode); - } - } + AcceptConnection(hListenSocket); } } diff --git a/src/net.h b/src/net.h index 954cdd49d..f370bf1ff 100644 --- a/src/net.h +++ b/src/net.h @@ -143,19 +143,8 @@ extern uint64_t nLocalServices; extern uint64_t nLocalHostNonce; extern CAddrMan addrman; -// The allocation of connections against the maximum allowed (nMaxConnections) -// is prioritized as follows: -// 1st: Outbound connections (MAX_OUTBOUND_CONNECTIONS) -// 2nd: Inbound connections from whitelisted peers (nWhiteConnections) -// 3rd: Inbound connections from non-whitelisted peers -// Thus, the number of connection slots for the general public to use is: -// nMaxConnections - (MAX_OUTBOUND_CONNECTIONS + nWhiteConnections) -// Any additional inbound connections beyond limits will be immediately closed - /** Maximum number of connections to simultaneously allow (aka connection slots) */ extern int nMaxConnections; -/** Number of connection slots to reserve for inbound from whitelisted peers */ -extern int nWhiteConnections; extern std::vector vNodes; extern CCriticalSection cs_vNodes; @@ -395,6 +384,8 @@ public: int64_t nPingUsecStart; // Last measured round-trip time. int64_t nPingUsecTime; + // Best measured round-trip time. + int64_t nMinPingUsecTime; // Whether a ping is requested. bool fPingQueued;