diff --git a/src/banman.h b/src/banman.h --- a/src/banman.h +++ b/src/banman.h @@ -45,6 +45,7 @@ void Ban(const CSubNet &sub_net, const BanReason &ban_reason, int64_t ban_time_offset = 0, bool since_unix_epoch = false); void ClearBanned(); + int IsBannedLevel(CNetAddr net_addr); bool IsBanned(CNetAddr net_addr); bool IsBanned(CSubNet sub_net); bool Unban(const CNetAddr &net_addr); diff --git a/src/banman.cpp b/src/banman.cpp --- a/src/banman.cpp +++ b/src/banman.cpp @@ -79,13 +79,36 @@ } } +int BanMan::IsBannedLevel(CNetAddr net_addr) { + // Returns the most severe level of banning that applies to this address. + // 0 - Not banned + // 1 - Automatic misbehavior ban + // 2 - Any other ban + int level = 0; + auto current_time = GetTime(); + LOCK(m_cs_banned); + for (const auto &it : m_banned) { + CSubNet sub_net = it.first; + CBanEntry ban_entry = it.second; + + if (current_time < ban_entry.nBanUntil && sub_net.Match(net_addr)) { + if (ban_entry.banReason != BanReasonNodeMisbehaving) { + return 2; + } + level = 1; + } + } + return level; +} + bool BanMan::IsBanned(CNetAddr net_addr) { + auto current_time = GetTime(); LOCK(m_cs_banned); for (const auto &it : m_banned) { CSubNet sub_net = it.first; CBanEntry ban_entry = it.second; - if (sub_net.Match(net_addr) && GetTime() < ban_entry.nBanUntil) { + if (current_time < ban_entry.nBanUntil && sub_net.Match(net_addr)) { return true; } } @@ -93,11 +116,12 @@ } bool BanMan::IsBanned(CSubNet sub_net) { + auto current_time = GetTime(); LOCK(m_cs_banned); banmap_t::iterator i = m_banned.find(sub_net); if (i != m_banned.end()) { CBanEntry ban_entry = (*i).second; - if (GetTime() < ban_entry.nBanUntil) { + if (current_time < ban_entry.nBanUntil) { return true; } } diff --git a/src/net.h b/src/net.h --- a/src/net.h +++ b/src/net.h @@ -655,6 +655,8 @@ GUARDED_BY(cs_SubVer); // Used for both cleanSubVer and strSubVer. CCriticalSection cs_SubVer; + // This peer is preferred for eviction. + bool m_prefer_evict{false}; // This peer can bypass DoS banning. bool fWhitelisted{false}; // If true this node is being used as a short lived feeler. diff --git a/src/net.cpp b/src/net.cpp --- a/src/net.cpp +++ b/src/net.cpp @@ -791,6 +791,7 @@ bool fBloomFilter; CAddress addr; uint64_t nKeyedNetGroup; + bool prefer_evict; }; static bool ReverseCompareNodeMinPingTime(const NodeEvictionCandidate &a, @@ -880,7 +881,8 @@ node->fRelayTxes, node->pfilter != nullptr, node->addr, - node->nKeyedNetGroup}; + node->nKeyedNetGroup, + node->m_prefer_evict}; vEvictionCandidates.push_back(candidate); } } @@ -910,6 +912,20 @@ return false; } + // If any remaining peers are preferred for eviction consider only them. + // This happens after the other preferences since if a peer is really the + // best by other criteria (esp relaying blocks) + // then we probably don't want to evict it no matter what. + if (std::any_of( + vEvictionCandidates.begin(), vEvictionCandidates.end(), + [](NodeEvictionCandidate const &n) { return n.prefer_evict; })) { + vEvictionCandidates.erase( + std::remove_if( + vEvictionCandidates.begin(), vEvictionCandidates.end(), + [](NodeEvictionCandidate const &n) { return !n.prefer_evict; }), + vEvictionCandidates.end()); + } + // Identify the network group with the most connections and youngest member. // (vEvictionCandidates is already sorted by reverse connect time) uint64_t naMostConnections; @@ -998,7 +1014,12 @@ // sockets on all platforms. Set it again here just to be sure. SetSocketNoDelay(hSocket); - if (m_banman && m_banman->IsBanned(addr) && !whitelisted) { + int bannedlevel = m_banman ? m_banman->IsBannedLevel(addr) : 0; + + // Don't accept connections from banned peers, but if our inbound slots + // aren't almost full, accept if the only banning reason was an automatic + // misbehavior ban. + if (!whitelisted && bannedlevel > ((nInbound + 1 < nMaxInbound) ? 1 : 0)) { LogPrint(BCLog::NET, "connection from %s dropped (banned)\n", addr.ToString()); CloseSocket(hSocket); @@ -1026,6 +1047,8 @@ CalculateKeyedNetGroup(addr), nonce, addr_bind, "", true); pnode->AddRef(); pnode->fWhitelisted = whitelisted; + + pnode->m_prefer_evict = bannedlevel > 0; m_msgproc->InitializeNode(*config, pnode); LogPrint(BCLog::NET, "connection from %s accepted\n", addr.ToString());