Skip to content

Commit 433fb1a

Browse files
committed
[RPC] extend setban to allow subnets
1 parent e8b9347 commit 433fb1a

File tree

7 files changed

+139
-34
lines changed

7 files changed

+139
-34
lines changed

qa/rpc-tests/httpbasics.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,28 @@ def run_test(self):
101101
###########################
102102
# setban/listbanned tests #
103103
###########################
104-
assert_equal(len(self.nodes[2].getpeerinfo()), 4); #we should have 4 nodes at this point
104+
assert_equal(len(self.nodes[2].getpeerinfo()), 4) #we should have 4 nodes at this point
105105
self.nodes[2].setban("127.0.0.1", "add")
106106
time.sleep(3) #wait till the nodes are disconected
107-
assert_equal(len(self.nodes[2].getpeerinfo()), 0); #all nodes must be disconnected at this point
108-
assert_equal(len(self.nodes[2].listbanned()), 1);
107+
assert_equal(len(self.nodes[2].getpeerinfo()), 0) #all nodes must be disconnected at this point
108+
assert_equal(len(self.nodes[2].listbanned()), 1)
109109
self.nodes[2].clearbanned()
110-
assert_equal(len(self.nodes[2].listbanned()), 0);
111-
110+
assert_equal(len(self.nodes[2].listbanned()), 0)
111+
self.nodes[2].setban("127.0.0.0/24", "add")
112+
assert_equal(len(self.nodes[2].listbanned()), 1)
113+
try:
114+
self.nodes[2].setban("127.0.0.1", "add") #throws exception because 127.0.0.1 is within range 127.0.0.0/24
115+
except:
116+
pass
117+
assert_equal(len(self.nodes[2].listbanned()), 1) #still only one banned ip because 127.0.0.1 is within the range of 127.0.0.0/24
118+
try:
119+
self.nodes[2].setban("127.0.0.1", "remove")
120+
except:
121+
pass
122+
assert_equal(len(self.nodes[2].listbanned()), 1)
123+
self.nodes[2].setban("127.0.0.0/24", "remove")
124+
assert_equal(len(self.nodes[2].listbanned()), 0)
125+
self.nodes[2].clearbanned()
126+
assert_equal(len(self.nodes[2].listbanned()), 0)
112127
if __name__ == '__main__':
113128
HTTPBasicsTest ().main ()

src/net.cpp

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,15 @@ CNode* FindNode(const CNetAddr& ip)
332332
return NULL;
333333
}
334334

335+
CNode* FindNode(const CSubNet& subNet)
336+
{
337+
LOCK(cs_vNodes);
338+
BOOST_FOREACH(CNode* pnode, vNodes)
339+
if (subNet.Match((CNetAddr)pnode->addr))
340+
return (pnode);
341+
return NULL;
342+
}
343+
335344
CNode* FindNode(const std::string& addrName)
336345
{
337346
LOCK(cs_vNodes);
@@ -434,7 +443,7 @@ void CNode::PushVersion()
434443

435444

436445

437-
std::map<CNetAddr, int64_t> CNode::setBanned;
446+
std::map<CSubNet, int64_t> CNode::setBanned;
438447
CCriticalSection CNode::cs_setBanned;
439448

440449
void CNode::ClearBanned()
@@ -447,7 +456,24 @@ bool CNode::IsBanned(CNetAddr ip)
447456
bool fResult = false;
448457
{
449458
LOCK(cs_setBanned);
450-
std::map<CNetAddr, int64_t>::iterator i = setBanned.find(ip);
459+
for (std::map<CSubNet, int64_t>::iterator it = setBanned.begin(); it != setBanned.end(); it++)
460+
{
461+
CSubNet subNet = (*it).first;
462+
int64_t t = (*it).second;
463+
464+
if(subNet.Match(ip) && GetTime() < t)
465+
fResult = true;
466+
}
467+
}
468+
return fResult;
469+
}
470+
471+
bool CNode::IsBanned(CSubNet subnet)
472+
{
473+
bool fResult = false;
474+
{
475+
LOCK(cs_setBanned);
476+
std::map<CSubNet, int64_t>::iterator i = setBanned.find(subnet);
451477
if (i != setBanned.end())
452478
{
453479
int64_t t = (*i).second;
@@ -458,24 +484,34 @@ bool CNode::IsBanned(CNetAddr ip)
458484
return fResult;
459485
}
460486

461-
void CNode::Ban(const CNetAddr &addr, int64_t bantimeoffset) {
487+
void CNode::Ban(const CNetAddr& addr, int64_t bantimeoffset) {
488+
CSubNet subNet(addr.ToString()+(addr.IsIPv4() ? "/32" : "/128"));
489+
Ban(subNet, bantimeoffset);
490+
}
491+
492+
void CNode::Ban(const CSubNet& subNet, int64_t bantimeoffset) {
462493
int64_t banTime = GetTime()+GetArg("-bantime", 60*60*24); // Default 24-hour ban
463494
if (bantimeoffset > 0)
464495
banTime = GetTime()+bantimeoffset;
465496

466497
LOCK(cs_setBanned);
467-
if (setBanned[addr] < banTime)
468-
setBanned[addr] = banTime;
498+
if (setBanned[subNet] < banTime)
499+
setBanned[subNet] = banTime;
469500
}
470501

471502
bool CNode::Unban(const CNetAddr &addr) {
503+
CSubNet subNet(addr.ToString()+(addr.IsIPv4() ? "/32" : "/128"));
504+
return Unban(subNet);
505+
}
506+
507+
bool CNode::Unban(const CSubNet &subNet) {
472508
LOCK(cs_setBanned);
473-
if (setBanned.erase(addr))
509+
if (setBanned.erase(subNet))
474510
return true;
475511
return false;
476512
}
477513

478-
void CNode::GetBanned(std::map<CNetAddr, int64_t> &banMap)
514+
void CNode::GetBanned(std::map<CSubNet, int64_t> &banMap)
479515
{
480516
LOCK(cs_setBanned);
481517
banMap = setBanned; //create a thread safe copy

src/net.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ unsigned int SendBufferSize();
6666
void AddOneShot(const std::string& strDest);
6767
void AddressCurrentlyConnected(const CService& addr);
6868
CNode* FindNode(const CNetAddr& ip);
69+
CNode* FindNode(const CSubNet& subNet);
6970
CNode* FindNode(const std::string& addrName);
7071
CNode* FindNode(const CService& ip);
7172
CNode* ConnectNode(CAddress addrConnect, const char *pszDest = NULL);
@@ -284,7 +285,7 @@ class CNode
284285

285286
// Denial-of-service detection/prevention
286287
// Key is IP address, value is banned-until-time
287-
static std::map<CNetAddr, int64_t> setBanned;
288+
static std::map<CSubNet, int64_t> setBanned;
288289
static CCriticalSection cs_setBanned;
289290

290291
// Whitelisted ranges. Any node connecting from these is automatically
@@ -606,9 +607,12 @@ class CNode
606607
// new code.
607608
static void ClearBanned(); // needed for unit testing
608609
static bool IsBanned(CNetAddr ip);
610+
static bool IsBanned(CSubNet subnet);
609611
static void Ban(const CNetAddr &ip, int64_t bantimeoffset = 0);
612+
static void Ban(const CSubNet &subNet, int64_t bantimeoffset = 0);
610613
static bool Unban(const CNetAddr &ip);
611-
static void GetBanned(std::map<CNetAddr, int64_t> &banmap);
614+
static bool Unban(const CSubNet &ip);
615+
static void GetBanned(std::map<CSubNet, int64_t> &banmap);
612616

613617
void copyStats(CNodeStats &stats);
614618

src/netbase.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1330,6 +1330,11 @@ bool operator!=(const CSubNet& a, const CSubNet& b)
13301330
return !(a==b);
13311331
}
13321332

1333+
bool operator<(const CSubNet& a, const CSubNet& b)
1334+
{
1335+
return (a.network < b.network || (a.network == b.network && memcmp(a.netmask, b.netmask, 16)));
1336+
}
1337+
13331338
#ifdef WIN32
13341339
std::string NetworkErrorString(int err)
13351340
{

src/netbase.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ class CSubNet
125125

126126
friend bool operator==(const CSubNet& a, const CSubNet& b);
127127
friend bool operator!=(const CSubNet& a, const CSubNet& b);
128+
friend bool operator<(const CSubNet& a, const CSubNet& b);
128129
};
129130

130131
/** A combination of a network address (CNetAddr) and a (TCP) port */

src/rpcnet.cpp

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -474,39 +474,51 @@ Value setban(const Array& params, bool fHelp)
474474
if (fHelp || params.size() < 2 ||
475475
(strCommand != "add" && strCommand != "remove"))
476476
throw runtime_error(
477-
"setban \"node\" \"add|remove\" (bantime)\n"
478-
"\nAttempts add or remove a IP from the banned list.\n"
477+
"setban \"ip(/netmask)\" \"add|remove\" (bantime)\n"
478+
"\nAttempts add or remove a IP/Subnet from the banned list.\n"
479479
"\nArguments:\n"
480-
"1. \"ip\" (string, required) The IP (see getpeerinfo for nodes ip)\n"
481-
"2. \"command\" (string, required) 'add' to add a IP to the list, 'remove' to remove a IP from the list\n"
482-
"1. \"bantime\" (numeric, optional) time in seconds how long the ip is banned (0 or empty means using the default time of 24h which can also be overwritten by the -bantime startup argument)\n"
480+
"1. \"ip(/netmask)\" (string, required) The IP/Subnet (see getpeerinfo for nodes ip) with a optional netmask (default is /32 = single ip)\n"
481+
"2. \"command\" (string, required) 'add' to add a IP/Subnet to the list, 'remove' to remove a IP/Subnet from the list\n"
482+
"1. \"bantime\" (numeric, optional) time in seconds how long the ip is banned (0 or empty means using the default time of 24h which can also be overwritten by the -bantime startup argument)\n"
483483
"\nExamples:\n"
484484
+ HelpExampleCli("setban", "\"192.168.0.6\" \"add\" 86400")
485+
+ HelpExampleCli("setban", "\"192.168.0.0/24\" \"add\"")
485486
+ HelpExampleRpc("setban", "\"192.168.0.6\", \"add\" 86400")
486487
);
487488

488-
CNetAddr netAddr(params[0].get_str());
489-
if (!netAddr.IsValid())
490-
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: Invalid IP Address");
489+
CSubNet subNet;
490+
CNetAddr netAddr;
491+
bool isSubnet = false;
492+
493+
if (params[0].get_str().find("/") != string::npos)
494+
isSubnet = true;
495+
496+
if (!isSubnet)
497+
netAddr = CNetAddr(params[0].get_str());
498+
else
499+
subNet = CSubNet(params[0].get_str());
500+
501+
if (! (isSubnet ? subNet.IsValid() : netAddr.IsValid()) )
502+
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: Invalid IP/Subnet");
491503

492504
if (strCommand == "add")
493505
{
494-
if (CNode::IsBanned(netAddr))
495-
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: IP already banned");
506+
if (isSubnet ? CNode::IsBanned(subNet) : CNode::IsBanned(netAddr))
507+
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: IP/Subnet already banned");
496508

497509
int64_t banTime = 0; //use standard bantime if not specified
498510
if (params.size() == 3 && !params[2].is_null())
499511
banTime = params[2].get_int64();
500512

501-
CNode::Ban(netAddr, banTime);
513+
isSubnet ? CNode::Ban(subNet, banTime) : CNode::Ban(netAddr, banTime);
502514

503515
//disconnect possible nodes
504-
while(CNode *bannedNode = FindNode(netAddr))
516+
while(CNode *bannedNode = (isSubnet ? FindNode(subNet) : FindNode(netAddr)))
505517
bannedNode->CloseSocketDisconnect();
506518
}
507519
else if(strCommand == "remove")
508520
{
509-
if (!CNode::Unban(netAddr))
521+
if (!( isSubnet ? CNode::Unban(subNet) : CNode::Unban(netAddr) ))
510522
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: Unban failed");
511523
}
512524

@@ -518,17 +530,17 @@ Value listbanned(const Array& params, bool fHelp)
518530
if (fHelp || params.size() != 0)
519531
throw runtime_error(
520532
"listbanned\n"
521-
"\nList all banned IPs.\n"
533+
"\nList all banned IPs/Subnets.\n"
522534
"\nExamples:\n"
523535
+ HelpExampleCli("listbanned", "")
524536
+ HelpExampleRpc("listbanned", "")
525537
);
526538

527-
std::map<CNetAddr, int64_t> banMap;
539+
std::map<CSubNet, int64_t> banMap;
528540
CNode::GetBanned(banMap);
529541

530542
Array bannedAddresses;
531-
for (std::map<CNetAddr, int64_t>::iterator it = banMap.begin(); it != banMap.end(); it++)
543+
for (std::map<CSubNet, int64_t>::iterator it = banMap.begin(); it != banMap.end(); it++)
532544
{
533545
Object rec;
534546
rec.push_back(Pair("address", (*it).first.ToString()));

src/test/rpc_tests.cpp

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,11 +179,43 @@ BOOST_AUTO_TEST_CASE(rpc_boostasiotocnetaddr)
179179

180180
BOOST_AUTO_TEST_CASE(rpc_ban)
181181
{
182-
BOOST_CHECK_NO_THROW(CallRPC(string("setban 127.0.0.1 add")));
183-
BOOST_CHECK_THROW(CallRPC(string("setban 127.0.0.1:8334")), runtime_error); //portnumber for setban not allowed
184-
BOOST_CHECK_NO_THROW(CallRPC(string("listbanned")));
185-
BOOST_CHECK_NO_THROW(CallRPC(string("setban 127.0.0.1 remove")));
186182
BOOST_CHECK_NO_THROW(CallRPC(string("clearbanned")));
183+
184+
Value r;
185+
BOOST_CHECK_NO_THROW(r = CallRPC(string("setban 127.0.0.0 add")));
186+
BOOST_CHECK_THROW(r = CallRPC(string("setban 127.0.0.0:8334")), runtime_error); //portnumber for setban not allowed
187+
BOOST_CHECK_NO_THROW(r = CallRPC(string("listbanned")));
188+
Array ar = r.get_array();
189+
Object o1 = ar[0].get_obj();
190+
Value adr = find_value(o1, "address");
191+
BOOST_CHECK_EQUAL(adr.get_str(), "127.0.0.0/255.255.255.255");
192+
BOOST_CHECK_NO_THROW(CallRPC(string("setban 127.0.0.0 remove")));;
193+
BOOST_CHECK_NO_THROW(r = CallRPC(string("listbanned")));
194+
ar = r.get_array();
195+
BOOST_CHECK_EQUAL(ar.size(), 0);
196+
197+
BOOST_CHECK_NO_THROW(r = CallRPC(string("setban 127.0.0.0/24 add")));
198+
BOOST_CHECK_NO_THROW(r = CallRPC(string("listbanned")));
199+
ar = r.get_array();
200+
o1 = ar[0].get_obj();
201+
adr = find_value(o1, "address");
202+
BOOST_CHECK_EQUAL(adr.get_str(), "127.0.0.0/255.255.255.0");
203+
204+
// must throw an exception because 127.0.0.1 is in already banned suubnet range
205+
BOOST_CHECK_THROW(r = CallRPC(string("setban 127.0.0.1 add")), runtime_error);
206+
207+
BOOST_CHECK_NO_THROW(CallRPC(string("setban 127.0.0.0/24 remove")));;
208+
BOOST_CHECK_NO_THROW(r = CallRPC(string("listbanned")));
209+
ar = r.get_array();
210+
BOOST_CHECK_EQUAL(ar.size(), 0);
211+
212+
BOOST_CHECK_NO_THROW(r = CallRPC(string("setban 127.0.0.0/255.255.0.0 add")));
213+
BOOST_CHECK_THROW(r = CallRPC(string("setban 127.0.1.1 add")), runtime_error);
214+
215+
BOOST_CHECK_NO_THROW(CallRPC(string("clearbanned")));
216+
BOOST_CHECK_NO_THROW(r = CallRPC(string("listbanned")));
217+
ar = r.get_array();
218+
BOOST_CHECK_EQUAL(ar.size(), 0);
187219
}
188220

189221
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)