Skip to content

Commit abdfd2d

Browse files
committed
Merge #19219: Replace automatic bans with discouragement filter
2ad5838 Clean up separated ban/discourage interface (Pieter Wuille) b691f2d Replace automatic bans with discouragement filter (Pieter Wuille) Pull request description: This patch improves performance and resource usage around IP addresses that are banned for misbehavior. They're already not actually banned since #14929, as connections from them are still allowed, but they are preferred for eviction if the inbound connection slots are full. Stop treating these like manually banned IP ranges, and instead just keep them in a rolling Bloom filter of misbehaving nodes, which isn't persisted to disk or exposed through the ban framework. The effect remains the same: preferred for eviction, avoided for outgoing connections, and not relayed to other peers. Also change the name of this mechanism to "discouraged" to better reflect reality. ACKs for top commit: naumenkogs: utACK 2ad5838 amitiuttarwar: code review ACK 2ad5838 jonatack: ACK 2ad5838 per changes since last review `git range-diff 3276c14 1f7e0ca 2ad5838` jnewbery: Code review ACK 2ad5838 Tree-SHA512: 5dedef401d9cbfa026812651303e6286223563dbeed7a10766ed536ac9e3f29ed4bd0df29cc6deadceeb35cbe9f066346add14ef0833958ca9f93d123fe7aab5
2 parents b52e25c + 2ad5838 commit abdfd2d

16 files changed

+154
-143
lines changed

doc/release-notes-19219.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#### Changes regarding misbehaving peers
2+
3+
Peers that misbehave (e.g. send us invalid blocks) are now referred to as
4+
discouraged nodes in log output, as they're not (and weren't) strictly banned:
5+
incoming connections are still allowed from them, but they're preferred for
6+
eviction.
7+
8+
Furthermore, a few additional changes are introduced to how discouraged
9+
addresses are treated:
10+
11+
- Discouraging an address does not time out automatically after 24 hours
12+
(or the `-bantime` setting). Depending on traffic from other peers,
13+
discouragement may time out at an indeterminate time.
14+
15+
- Discouragement is not persisted over restarts.
16+
17+
- There is no method to list discouraged addresses. They are not returned by
18+
the `listbanned` RPC. That RPC also no longer reports the `ban_reason`
19+
field, as `"manually added"` is the only remaining option.
20+
21+
- Discouragement cannot be removed with the `setban remove` RPC command.
22+
If you need to remove a discouragement, you can remove all discouragements by
23+
stop-starting your node.

src/addrdb.h

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,13 @@ class CSubNet;
1717
class CAddrMan;
1818
class CDataStream;
1919

20-
typedef enum BanReason
21-
{
22-
BanReasonUnknown = 0,
23-
BanReasonNodeMisbehaving = 1,
24-
BanReasonManuallyAdded = 2
25-
} BanReason;
26-
2720
class CBanEntry
2821
{
2922
public:
3023
static const int CURRENT_VERSION=1;
3124
int nVersion;
3225
int64_t nCreateTime;
3326
int64_t nBanUntil;
34-
uint8_t banReason;
3527

3628
CBanEntry()
3729
{
@@ -44,31 +36,17 @@ class CBanEntry
4436
nCreateTime = nCreateTimeIn;
4537
}
4638

47-
explicit CBanEntry(int64_t n_create_time_in, BanReason ban_reason_in) : CBanEntry(n_create_time_in)
39+
SERIALIZE_METHODS(CBanEntry, obj)
4840
{
49-
banReason = ban_reason_in;
41+
uint8_t ban_reason = 2; //! For backward compatibility
42+
READWRITE(obj.nVersion, obj.nCreateTime, obj.nBanUntil, ban_reason);
5043
}
5144

52-
SERIALIZE_METHODS(CBanEntry, obj) { READWRITE(obj.nVersion, obj.nCreateTime, obj.nBanUntil, obj.banReason); }
53-
5445
void SetNull()
5546
{
5647
nVersion = CBanEntry::CURRENT_VERSION;
5748
nCreateTime = 0;
5849
nBanUntil = 0;
59-
banReason = BanReasonUnknown;
60-
}
61-
62-
std::string banReasonToString() const
63-
{
64-
switch (banReason) {
65-
case BanReasonNodeMisbehaving:
66-
return "node misbehaving";
67-
case BanReasonManuallyAdded:
68-
return "manually added";
69-
default:
70-
return "unknown";
71-
}
7250
}
7351
};
7452

src/banman.cpp

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -68,28 +68,13 @@ void BanMan::ClearBanned()
6868
if (m_client_interface) m_client_interface->BannedListChanged();
6969
}
7070

71-
int BanMan::IsBannedLevel(CNetAddr net_addr)
71+
bool BanMan::IsDiscouraged(const CNetAddr& net_addr)
7272
{
73-
// Returns the most severe level of banning that applies to this address.
74-
// 0 - Not banned
75-
// 1 - Automatic misbehavior ban
76-
// 2 - Any other ban
77-
int level = 0;
78-
auto current_time = GetTime();
7973
LOCK(m_cs_banned);
80-
for (const auto& it : m_banned) {
81-
CSubNet sub_net = it.first;
82-
CBanEntry ban_entry = it.second;
83-
84-
if (current_time < ban_entry.nBanUntil && sub_net.Match(net_addr)) {
85-
if (ban_entry.banReason != BanReasonNodeMisbehaving) return 2;
86-
level = 1;
87-
}
88-
}
89-
return level;
74+
return m_discouraged.contains(net_addr.GetAddrBytes());
9075
}
9176

92-
bool BanMan::IsBanned(CNetAddr net_addr)
77+
bool BanMan::IsBanned(const CNetAddr& net_addr)
9378
{
9479
auto current_time = GetTime();
9580
LOCK(m_cs_banned);
@@ -104,7 +89,7 @@ bool BanMan::IsBanned(CNetAddr net_addr)
10489
return false;
10590
}
10691

107-
bool BanMan::IsBanned(CSubNet sub_net)
92+
bool BanMan::IsBanned(const CSubNet& sub_net)
10893
{
10994
auto current_time = GetTime();
11095
LOCK(m_cs_banned);
@@ -118,15 +103,21 @@ bool BanMan::IsBanned(CSubNet sub_net)
118103
return false;
119104
}
120105

121-
void BanMan::Ban(const CNetAddr& net_addr, const BanReason& ban_reason, int64_t ban_time_offset, bool since_unix_epoch)
106+
void BanMan::Ban(const CNetAddr& net_addr, int64_t ban_time_offset, bool since_unix_epoch)
122107
{
123108
CSubNet sub_net(net_addr);
124-
Ban(sub_net, ban_reason, ban_time_offset, since_unix_epoch);
109+
Ban(sub_net, ban_time_offset, since_unix_epoch);
110+
}
111+
112+
void BanMan::Discourage(const CNetAddr& net_addr)
113+
{
114+
LOCK(m_cs_banned);
115+
m_discouraged.insert(net_addr.GetAddrBytes());
125116
}
126117

127-
void BanMan::Ban(const CSubNet& sub_net, const BanReason& ban_reason, int64_t ban_time_offset, bool since_unix_epoch)
118+
void BanMan::Ban(const CSubNet& sub_net, int64_t ban_time_offset, bool since_unix_epoch)
128119
{
129-
CBanEntry ban_entry(GetTime(), ban_reason);
120+
CBanEntry ban_entry(GetTime());
130121

131122
int64_t normalized_ban_time_offset = ban_time_offset;
132123
bool normalized_since_unix_epoch = since_unix_epoch;
@@ -146,8 +137,8 @@ void BanMan::Ban(const CSubNet& sub_net, const BanReason& ban_reason, int64_t ba
146137
}
147138
if (m_client_interface) m_client_interface->BannedListChanged();
148139

149-
//store banlist to disk immediately if user requested ban
150-
if (ban_reason == BanReasonManuallyAdded) DumpBanlist();
140+
//store banlist to disk immediately
141+
DumpBanlist();
151142
}
152143

153144
bool BanMan::Unban(const CNetAddr& net_addr)

src/banman.h

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#define BITCOIN_BANMAN_H
77

88
#include <addrdb.h>
9+
#include <bloom.h>
910
#include <fs.h>
1011
#include <net_types.h> // For banmap_t
1112
#include <sync.h>
@@ -23,32 +24,55 @@ class CClientUIInterface;
2324
class CNetAddr;
2425
class CSubNet;
2526

26-
// Denial-of-service detection/prevention
27-
// The idea is to detect peers that are behaving
28-
// badly and disconnect/ban them, but do it in a
29-
// one-coding-mistake-won't-shatter-the-entire-network
30-
// way.
31-
// IMPORTANT: There should be nothing I can give a
32-
// node that it will forward on that will make that
33-
// node's peers drop it. If there is, an attacker
34-
// can isolate a node and/or try to split the network.
35-
// Dropping a node for sending stuff that is invalid
36-
// now but might be valid in a later version is also
37-
// dangerous, because it can cause a network split
38-
// between nodes running old code and nodes running
39-
// new code.
27+
// Banman manages two related but distinct concepts:
28+
//
29+
// 1. Banning. This is configured manually by the user, through the setban RPC.
30+
// If an address or subnet is banned, we never accept incoming connections from
31+
// it and never create outgoing connections to it. We won't gossip its address
32+
// to other peers in addr messages. Banned addresses and subnets are stored to
33+
// banlist.dat on shutdown and reloaded on startup. Banning can be used to
34+
// prevent connections with spy nodes or other griefers.
35+
//
36+
// 2. Discouragement. If a peer misbehaves enough (see Misbehaving() in
37+
// net_processing.cpp), we'll mark that address as discouraged. We still allow
38+
// incoming connections from them, but they're preferred for eviction when
39+
// we receive new incoming connections. We never make outgoing connections to
40+
// them, and do not gossip their address to other peers. This is implemented as
41+
// a bloom filter. We can (probabilistically) test for membership, but can't
42+
// list all discouraged addresses or unmark them as discouraged. Discouragement
43+
// can prevent our limited connection slots being used up by incompatible
44+
// or broken peers.
45+
//
46+
// Neither banning nor discouragement are protections against denial-of-service
47+
// attacks, since if an attacker has a way to waste our resources and we
48+
// disconnect from them and ban that address, it's trivial for them to
49+
// reconnect from another IP address.
50+
//
51+
// Attempting to automatically disconnect or ban any class of peer carries the
52+
// risk of splitting the network. For example, if we banned/disconnected for a
53+
// transaction that fails a policy check and a future version changes the
54+
// policy check so the transaction is accepted, then that transaction could
55+
// cause the network to split between old nodes and new nodes.
4056

4157
class BanMan
4258
{
4359
public:
4460
~BanMan();
4561
BanMan(fs::path ban_file, CClientUIInterface* client_interface, int64_t default_ban_time);
46-
void Ban(const CNetAddr& net_addr, const BanReason& ban_reason, int64_t ban_time_offset = 0, bool since_unix_epoch = false);
47-
void Ban(const CSubNet& sub_net, const BanReason& ban_reason, int64_t ban_time_offset = 0, bool since_unix_epoch = false);
62+
void Ban(const CNetAddr& net_addr, int64_t ban_time_offset = 0, bool since_unix_epoch = false);
63+
void Ban(const CSubNet& sub_net, int64_t ban_time_offset = 0, bool since_unix_epoch = false);
64+
void Discourage(const CNetAddr& net_addr);
4865
void ClearBanned();
49-
int IsBannedLevel(CNetAddr net_addr);
50-
bool IsBanned(CNetAddr net_addr);
51-
bool IsBanned(CSubNet sub_net);
66+
67+
//! Return whether net_addr is banned
68+
bool IsBanned(const CNetAddr& net_addr);
69+
70+
//! Return whether sub_net is exactly banned
71+
bool IsBanned(const CSubNet& sub_net);
72+
73+
//! Return whether net_addr is discouraged.
74+
bool IsDiscouraged(const CNetAddr& net_addr);
75+
5276
bool Unban(const CNetAddr& net_addr);
5377
bool Unban(const CSubNet& sub_net);
5478
void GetBanned(banmap_t& banmap);
@@ -68,6 +92,7 @@ class BanMan
6892
CClientUIInterface* m_client_interface = nullptr;
6993
CBanDB m_ban_db;
7094
const int64_t m_default_ban_time;
95+
CRollingBloomFilter m_discouraged GUARDED_BY(m_cs_banned) {50000, 0.000001};
7196
};
7297

7398
#endif

src/init.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -431,8 +431,8 @@ void SetupServerArgs(NodeContext& node)
431431

432432
gArgs.AddArg("-addnode=<ip>", "Add a node to connect to and attempt to keep the connection open (see the `addnode` RPC command help for more info). This option can be specified multiple times to add multiple nodes.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
433433
gArgs.AddArg("-asmap=<file>", strprintf("Specify asn mapping used for bucketing of the peers (default: %s). Relative paths will be prefixed by the net-specific datadir location.", DEFAULT_ASMAP_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
434-
gArgs.AddArg("-banscore=<n>", strprintf("Threshold for disconnecting misbehaving peers (default: %u)", DEFAULT_BANSCORE_THRESHOLD), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
435-
gArgs.AddArg("-bantime=<n>", strprintf("Number of seconds to keep misbehaving peers from reconnecting (default: %u)", DEFAULT_MISBEHAVING_BANTIME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
434+
gArgs.AddArg("-banscore=<n>", strprintf("Threshold for disconnecting and discouraging misbehaving peers (default: %u)", DEFAULT_BANSCORE_THRESHOLD), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
435+
gArgs.AddArg("-bantime=<n>", strprintf("Default duration (in seconds) of manually configured bans (default: %u)", DEFAULT_MISBEHAVING_BANTIME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
436436
gArgs.AddArg("-bind=<addr>", "Bind to given address and always listen on it. Use [host]:port notation for IPv6", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
437437
gArgs.AddArg("-connect=<ip>", "Connect only to the specified node; -noconnect disables automatic connections (the rules for this peer are the same as for -addnode). This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
438438
gArgs.AddArg("-discover", "Discover own IP addresses (default: 1 when listening and no -externalip or -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);

src/interfaces/node.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,10 @@ class NodeImpl : public Node
146146
}
147147
return false;
148148
}
149-
bool ban(const CNetAddr& net_addr, BanReason reason, int64_t ban_time_offset) override
149+
bool ban(const CNetAddr& net_addr, int64_t ban_time_offset) override
150150
{
151151
if (m_context.banman) {
152-
m_context.banman->Ban(net_addr, reason, ban_time_offset);
152+
m_context.banman->Ban(net_addr, ban_time_offset);
153153
return true;
154154
}
155155
return false;

src/interfaces/node.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ class Node
122122
virtual bool getBanned(banmap_t& banmap) = 0;
123123

124124
//! Ban node.
125-
virtual bool ban(const CNetAddr& net_addr, BanReason reason, int64_t ban_time_offset) = 0;
125+
virtual bool ban(const CNetAddr& net_addr, int64_t ban_time_offset) = 0;
126126

127127
//! Unban node.
128128
virtual bool unban(const CSubNet& ip) = 0;

src/net.cpp

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,17 +1010,24 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) {
10101010
// on all platforms. Set it again here just to be sure.
10111011
SetSocketNoDelay(hSocket);
10121012

1013-
int bannedlevel = m_banman ? m_banman->IsBannedLevel(addr) : 0;
1014-
1015-
// Don't accept connections from banned peers, but if our inbound slots aren't almost full, accept
1016-
// if the only banning reason was an automatic misbehavior ban.
1017-
if (!NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::PF_NOBAN) && bannedlevel > ((nInbound + 1 < nMaxInbound) ? 1 : 0))
1013+
// Don't accept connections from banned peers.
1014+
bool banned = m_banman->IsBanned(addr);
1015+
if (!NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::PF_NOBAN) && banned)
10181016
{
10191017
LogPrint(BCLog::NET, "connection from %s dropped (banned)\n", addr.ToString());
10201018
CloseSocket(hSocket);
10211019
return;
10221020
}
10231021

1022+
// Only accept connections from discouraged peers if our inbound slots aren't (almost) full.
1023+
bool discouraged = m_banman->IsDiscouraged(addr);
1024+
if (!NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::PF_NOBAN) && nInbound + 1 >= nMaxInbound && discouraged)
1025+
{
1026+
LogPrint(BCLog::NET, "connection from %s dropped (discouraged)\n", addr.ToString());
1027+
CloseSocket(hSocket);
1028+
return;
1029+
}
1030+
10241031
if (nInbound >= nMaxInbound)
10251032
{
10261033
if (!AttemptToEvictConnection()) {
@@ -1044,7 +1051,7 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) {
10441051
pnode->m_permissionFlags = permissionFlags;
10451052
// If this flag is present, the user probably expect that RPC and QT report it as whitelisted (backward compatibility)
10461053
pnode->m_legacyWhitelisted = legacyWhitelisted;
1047-
pnode->m_prefer_evict = bannedlevel > 0;
1054+
pnode->m_prefer_evict = discouraged;
10481055
m_msgproc->InitializeNode(pnode);
10491056

10501057
LogPrint(BCLog::NET, "connection from %s accepted\n", addr.ToString());
@@ -2045,10 +2052,10 @@ void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai
20452052
return;
20462053
}
20472054
if (!pszDest) {
2048-
if (IsLocal(addrConnect) ||
2049-
FindNode(static_cast<CNetAddr>(addrConnect)) || (m_banman && m_banman->IsBanned(addrConnect)) ||
2050-
FindNode(addrConnect.ToStringIPPort()))
2055+
bool banned_or_discouraged = m_banman && (m_banman->IsDiscouraged(addrConnect) || m_banman->IsBanned(addrConnect));
2056+
if (IsLocal(addrConnect) || FindNode(static_cast<CNetAddr>(addrConnect)) || banned_or_discouraged || FindNode(addrConnect.ToStringIPPort())) {
20512057
return;
2058+
}
20522059
} else if (FindNode(std::string(pszDest)))
20532060
return;
20542061

src/net_permissions.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ enum NetPermissionFlags
2424
// Always relay transactions from this peer, even if already in mempool
2525
// Keep parameter interaction: forcerelay implies relay
2626
PF_FORCERELAY = (1U << 2) | PF_RELAY,
27-
// Can't be banned for misbehavior
27+
// Can't be banned/disconnected/discouraged for misbehavior
2828
PF_NOBAN = (1U << 4),
2929
// Can query the mempool
3030
PF_MEMPOOL = (1U << 5),

0 commit comments

Comments
 (0)