Skip to content

Commit 0abfa8a

Browse files
committed
Merge pull request #6158
9d79afe add RPC tests for setban & disconnectnode (Jonas Schnelli) 1f02b80 setban: add RPCErrorCode (Jonas Schnelli) d624167 fix CSubNet comparison operator (Jonas Schnelli) 4e36e9b setban: rewrite to UniValue, allow absolute bantime (Jonas Schnelli) 3de24d7 rename json field "bannedtill" to "banned_until" (Jonas Schnelli) 433fb1a [RPC] extend setban to allow subnets (Jonas Schnelli) e8b9347 [net] remove unused return type bool from CNode::Ban() (Jonas Schnelli) 1086ffb [QA] add setban/listbanned/clearbanned tests (Jonas Schnelli) d930b26 [RPC] add setban/listbanned/clearbanned RPC commands (Jonas Schnelli) 2252fb9 [net] extend core functionallity for ban/unban/listban (Jonas Schnelli)
2 parents cbec57f + 9d79afe commit 0abfa8a

File tree

13 files changed

+331
-28
lines changed

13 files changed

+331
-28
lines changed

qa/pull-tester/rpc-tests.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ testScripts=(
3232
'merkle_blocks.py'
3333
'signrawtransactions.py'
3434
'walletbackup.py'
35+
'nodehandling.py'
3536
);
3637
testScriptsExt=(
3738
'bipdersig-p2p.py'

qa/rpc-tests/httpbasics.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55

66
#
7-
# Test REST interface
7+
# Test rpc http basics
88
#
99

1010
from test_framework.test_framework import BitcoinTestFramework
@@ -20,83 +20,83 @@
2020
except ImportError:
2121
import urlparse
2222

23-
class HTTPBasicsTest (BitcoinTestFramework):
23+
class HTTPBasicsTest (BitcoinTestFramework):
2424
def setup_nodes(self):
2525
return start_nodes(4, self.options.tmpdir, extra_args=[['-rpckeepalive=1'], ['-rpckeepalive=0'], [], []])
2626

27-
def run_test(self):
28-
27+
def run_test(self):
28+
2929
#################################################
3030
# lowlevel check for http persistent connection #
3131
#################################################
3232
url = urlparse.urlparse(self.nodes[0].url)
3333
authpair = url.username + ':' + url.password
3434
headers = {"Authorization": "Basic " + base64.b64encode(authpair)}
35-
35+
3636
conn = httplib.HTTPConnection(url.hostname, url.port)
3737
conn.connect()
3838
conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
3939
out1 = conn.getresponse().read();
4040
assert_equal('"error":null' in out1, True)
4141
assert_equal(conn.sock!=None, True) #according to http/1.1 connection must still be open!
42-
42+
4343
#send 2nd request without closing connection
4444
conn.request('POST', '/', '{"method": "getchaintips"}', headers)
4545
out2 = conn.getresponse().read();
4646
assert_equal('"error":null' in out1, True) #must also response with a correct json-rpc message
4747
assert_equal(conn.sock!=None, True) #according to http/1.1 connection must still be open!
4848
conn.close()
49-
49+
5050
#same should be if we add keep-alive because this should be the std. behaviour
5151
headers = {"Authorization": "Basic " + base64.b64encode(authpair), "Connection": "keep-alive"}
52-
52+
5353
conn = httplib.HTTPConnection(url.hostname, url.port)
5454
conn.connect()
5555
conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
5656
out1 = conn.getresponse().read();
5757
assert_equal('"error":null' in out1, True)
5858
assert_equal(conn.sock!=None, True) #according to http/1.1 connection must still be open!
59-
59+
6060
#send 2nd request without closing connection
6161
conn.request('POST', '/', '{"method": "getchaintips"}', headers)
6262
out2 = conn.getresponse().read();
6363
assert_equal('"error":null' in out1, True) #must also response with a correct json-rpc message
6464
assert_equal(conn.sock!=None, True) #according to http/1.1 connection must still be open!
6565
conn.close()
66-
66+
6767
#now do the same with "Connection: close"
6868
headers = {"Authorization": "Basic " + base64.b64encode(authpair), "Connection":"close"}
69-
69+
7070
conn = httplib.HTTPConnection(url.hostname, url.port)
7171
conn.connect()
7272
conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
7373
out1 = conn.getresponse().read();
7474
assert_equal('"error":null' in out1, True)
75-
assert_equal(conn.sock!=None, False) #now the connection must be closed after the response
76-
75+
assert_equal(conn.sock!=None, False) #now the connection must be closed after the response
76+
7777
#node1 (2nd node) is running with disabled keep-alive option
7878
urlNode1 = urlparse.urlparse(self.nodes[1].url)
7979
authpair = urlNode1.username + ':' + urlNode1.password
8080
headers = {"Authorization": "Basic " + base64.b64encode(authpair)}
81-
81+
8282
conn = httplib.HTTPConnection(urlNode1.hostname, urlNode1.port)
8383
conn.connect()
8484
conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
8585
out1 = conn.getresponse().read();
8686
assert_equal('"error":null' in out1, True)
8787
assert_equal(conn.sock!=None, False) #connection must be closed because keep-alive was set to false
88-
88+
8989
#node2 (third node) is running with standard keep-alive parameters which means keep-alive is off
9090
urlNode2 = urlparse.urlparse(self.nodes[2].url)
9191
authpair = urlNode2.username + ':' + urlNode2.password
9292
headers = {"Authorization": "Basic " + base64.b64encode(authpair)}
93-
93+
9494
conn = httplib.HTTPConnection(urlNode2.hostname, urlNode2.port)
9595
conn.connect()
9696
conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
9797
out1 = conn.getresponse().read();
9898
assert_equal('"error":null' in out1, True)
9999
assert_equal(conn.sock!=None, True) #connection must be closed because bitcoind should use keep-alive by default
100-
100+
101101
if __name__ == '__main__':
102102
HTTPBasicsTest ().main ()

qa/rpc-tests/nodehandling.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/usr/bin/env python2
2+
# Copyright (c) 2014 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
6+
#
7+
# Test node handling
8+
#
9+
10+
from test_framework.test_framework import BitcoinTestFramework
11+
from test_framework.util import *
12+
import base64
13+
14+
try:
15+
import http.client as httplib
16+
except ImportError:
17+
import httplib
18+
try:
19+
import urllib.parse as urlparse
20+
except ImportError:
21+
import urlparse
22+
23+
class NodeHandlingTest (BitcoinTestFramework):
24+
def run_test(self):
25+
###########################
26+
# setban/listbanned tests #
27+
###########################
28+
assert_equal(len(self.nodes[2].getpeerinfo()), 4) #we should have 4 nodes at this point
29+
self.nodes[2].setban("127.0.0.1", "add")
30+
time.sleep(3) #wait till the nodes are disconected
31+
assert_equal(len(self.nodes[2].getpeerinfo()), 0) #all nodes must be disconnected at this point
32+
assert_equal(len(self.nodes[2].listbanned()), 1)
33+
self.nodes[2].clearbanned()
34+
assert_equal(len(self.nodes[2].listbanned()), 0)
35+
self.nodes[2].setban("127.0.0.0/24", "add")
36+
assert_equal(len(self.nodes[2].listbanned()), 1)
37+
try:
38+
self.nodes[2].setban("127.0.0.1", "add") #throws exception because 127.0.0.1 is within range 127.0.0.0/24
39+
except:
40+
pass
41+
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
42+
try:
43+
self.nodes[2].setban("127.0.0.1", "remove")
44+
except:
45+
pass
46+
assert_equal(len(self.nodes[2].listbanned()), 1)
47+
self.nodes[2].setban("127.0.0.0/24", "remove")
48+
assert_equal(len(self.nodes[2].listbanned()), 0)
49+
self.nodes[2].clearbanned()
50+
assert_equal(len(self.nodes[2].listbanned()), 0)
51+
52+
###########################
53+
# RPC disconnectnode test #
54+
###########################
55+
url = urlparse.urlparse(self.nodes[1].url)
56+
self.nodes[0].disconnectnode(url.hostname+":"+str(p2p_port(1)))
57+
time.sleep(2) #disconnecting a node needs a little bit of time
58+
for node in self.nodes[0].getpeerinfo():
59+
assert(node['addr'] != url.hostname+":"+str(p2p_port(1)))
60+
61+
connect_nodes_bi(self.nodes,0,1) #reconnect the node
62+
found = False
63+
for node in self.nodes[0].getpeerinfo():
64+
if node['addr'] == url.hostname+":"+str(p2p_port(1)):
65+
found = True
66+
assert(found)
67+
68+
if __name__ == '__main__':
69+
NodeHandlingTest ().main ()

src/net.cpp

Lines changed: 58 additions & 9 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,14 +484,37 @@ bool CNode::IsBanned(CNetAddr ip)
458484
return fResult;
459485
}
460486

461-
bool CNode::Ban(const CNetAddr &addr) {
487+
void CNode::Ban(const CNetAddr& addr, int64_t bantimeoffset, bool sinceUnixEpoch) {
488+
CSubNet subNet(addr.ToString()+(addr.IsIPv4() ? "/32" : "/128"));
489+
Ban(subNet, bantimeoffset, sinceUnixEpoch);
490+
}
491+
492+
void CNode::Ban(const CSubNet& subNet, int64_t bantimeoffset, bool sinceUnixEpoch) {
462493
int64_t banTime = GetTime()+GetArg("-bantime", 60*60*24); // Default 24-hour ban
463-
{
464-
LOCK(cs_setBanned);
465-
if (setBanned[addr] < banTime)
466-
setBanned[addr] = banTime;
467-
}
468-
return true;
494+
if (bantimeoffset > 0)
495+
banTime = (sinceUnixEpoch ? 0 : GetTime() )+bantimeoffset;
496+
497+
LOCK(cs_setBanned);
498+
if (setBanned[subNet] < banTime)
499+
setBanned[subNet] = banTime;
500+
}
501+
502+
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) {
508+
LOCK(cs_setBanned);
509+
if (setBanned.erase(subNet))
510+
return true;
511+
return false;
512+
}
513+
514+
void CNode::GetBanned(std::map<CSubNet, int64_t> &banMap)
515+
{
516+
LOCK(cs_setBanned);
517+
banMap = setBanned; //create a thread safe copy
469518
}
470519

471520

src/net.h

Lines changed: 9 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,7 +607,13 @@ class CNode
606607
// new code.
607608
static void ClearBanned(); // needed for unit testing
608609
static bool IsBanned(CNetAddr ip);
609-
static bool Ban(const CNetAddr &ip);
610+
static bool IsBanned(CSubNet subnet);
611+
static void Ban(const CNetAddr &ip, int64_t bantimeoffset = 0, bool sinceUnixEpoch = false);
612+
static void Ban(const CSubNet &subNet, int64_t bantimeoffset = 0, bool sinceUnixEpoch = false);
613+
static bool Unban(const CNetAddr &ip);
614+
static bool Unban(const CSubNet &ip);
615+
static void GetBanned(std::map<CSubNet, int64_t> &banmap);
616+
610617
void copyStats(CNodeStats &stats);
611618

612619
static bool IsWhitelistedRange(const CNetAddr &ip);

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) < 0));
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/rpcclient.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
9393
{ "estimatepriority", 0 },
9494
{ "prioritisetransaction", 1 },
9595
{ "prioritisetransaction", 2 },
96+
{ "setban", 2 },
97+
{ "setban", 3 },
9698
};
9799

98100
class CRPCConvertTable

0 commit comments

Comments
 (0)