Skip to content

Commit

Permalink
Merge pull request #6158
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
laanwj committed Jun 18, 2015
2 parents cbec57f + 9d79afe commit 0abfa8a
Show file tree
Hide file tree
Showing 13 changed files with 331 additions and 28 deletions.
1 change: 1 addition & 0 deletions qa/pull-tester/rpc-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ testScripts=(
'merkle_blocks.py'
'signrawtransactions.py'
'walletbackup.py'
'nodehandling.py'
);
testScriptsExt=(
'bipdersig-p2p.py'
Expand Down
34 changes: 17 additions & 17 deletions qa/rpc-tests/httpbasics.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

#
# Test REST interface
# Test rpc http basics
#

from test_framework.test_framework import BitcoinTestFramework
Expand All @@ -20,83 +20,83 @@
except ImportError:
import urlparse

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

def run_test(self):
def run_test(self):

#################################################
# lowlevel check for http persistent connection #
#################################################
url = urlparse.urlparse(self.nodes[0].url)
authpair = url.username + ':' + url.password
headers = {"Authorization": "Basic " + base64.b64encode(authpair)}

conn = httplib.HTTPConnection(url.hostname, url.port)
conn.connect()
conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
out1 = conn.getresponse().read();
assert_equal('"error":null' in out1, True)
assert_equal(conn.sock!=None, True) #according to http/1.1 connection must still be open!

#send 2nd request without closing connection
conn.request('POST', '/', '{"method": "getchaintips"}', headers)
out2 = conn.getresponse().read();
assert_equal('"error":null' in out1, True) #must also response with a correct json-rpc message
assert_equal(conn.sock!=None, True) #according to http/1.1 connection must still be open!
conn.close()

#same should be if we add keep-alive because this should be the std. behaviour
headers = {"Authorization": "Basic " + base64.b64encode(authpair), "Connection": "keep-alive"}

conn = httplib.HTTPConnection(url.hostname, url.port)
conn.connect()
conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
out1 = conn.getresponse().read();
assert_equal('"error":null' in out1, True)
assert_equal(conn.sock!=None, True) #according to http/1.1 connection must still be open!

#send 2nd request without closing connection
conn.request('POST', '/', '{"method": "getchaintips"}', headers)
out2 = conn.getresponse().read();
assert_equal('"error":null' in out1, True) #must also response with a correct json-rpc message
assert_equal(conn.sock!=None, True) #according to http/1.1 connection must still be open!
conn.close()

#now do the same with "Connection: close"
headers = {"Authorization": "Basic " + base64.b64encode(authpair), "Connection":"close"}

conn = httplib.HTTPConnection(url.hostname, url.port)
conn.connect()
conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
out1 = conn.getresponse().read();
assert_equal('"error":null' in out1, True)
assert_equal(conn.sock!=None, False) #now the connection must be closed after the response
assert_equal(conn.sock!=None, False) #now the connection must be closed after the response

#node1 (2nd node) is running with disabled keep-alive option
urlNode1 = urlparse.urlparse(self.nodes[1].url)
authpair = urlNode1.username + ':' + urlNode1.password
headers = {"Authorization": "Basic " + base64.b64encode(authpair)}

conn = httplib.HTTPConnection(urlNode1.hostname, urlNode1.port)
conn.connect()
conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
out1 = conn.getresponse().read();
assert_equal('"error":null' in out1, True)
assert_equal(conn.sock!=None, False) #connection must be closed because keep-alive was set to false

#node2 (third node) is running with standard keep-alive parameters which means keep-alive is off
urlNode2 = urlparse.urlparse(self.nodes[2].url)
authpair = urlNode2.username + ':' + urlNode2.password
headers = {"Authorization": "Basic " + base64.b64encode(authpair)}

conn = httplib.HTTPConnection(urlNode2.hostname, urlNode2.port)
conn.connect()
conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
out1 = conn.getresponse().read();
assert_equal('"error":null' in out1, True)
assert_equal(conn.sock!=None, True) #connection must be closed because bitcoind should use keep-alive by default

if __name__ == '__main__':
HTTPBasicsTest ().main ()
69 changes: 69 additions & 0 deletions qa/rpc-tests/nodehandling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env python2
# Copyright (c) 2014 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

#
# Test node handling
#

from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import *
import base64

try:
import http.client as httplib
except ImportError:
import httplib
try:
import urllib.parse as urlparse
except ImportError:
import urlparse

class NodeHandlingTest (BitcoinTestFramework):
def run_test(self):
###########################
# setban/listbanned tests #
###########################
assert_equal(len(self.nodes[2].getpeerinfo()), 4) #we should have 4 nodes at this point
self.nodes[2].setban("127.0.0.1", "add")
time.sleep(3) #wait till the nodes are disconected
assert_equal(len(self.nodes[2].getpeerinfo()), 0) #all nodes must be disconnected at this point
assert_equal(len(self.nodes[2].listbanned()), 1)
self.nodes[2].clearbanned()
assert_equal(len(self.nodes[2].listbanned()), 0)
self.nodes[2].setban("127.0.0.0/24", "add")
assert_equal(len(self.nodes[2].listbanned()), 1)
try:
self.nodes[2].setban("127.0.0.1", "add") #throws exception because 127.0.0.1 is within range 127.0.0.0/24
except:
pass
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
try:
self.nodes[2].setban("127.0.0.1", "remove")
except:
pass
assert_equal(len(self.nodes[2].listbanned()), 1)
self.nodes[2].setban("127.0.0.0/24", "remove")
assert_equal(len(self.nodes[2].listbanned()), 0)
self.nodes[2].clearbanned()
assert_equal(len(self.nodes[2].listbanned()), 0)

###########################
# RPC disconnectnode test #
###########################
url = urlparse.urlparse(self.nodes[1].url)
self.nodes[0].disconnectnode(url.hostname+":"+str(p2p_port(1)))
time.sleep(2) #disconnecting a node needs a little bit of time
for node in self.nodes[0].getpeerinfo():
assert(node['addr'] != url.hostname+":"+str(p2p_port(1)))

connect_nodes_bi(self.nodes,0,1) #reconnect the node
found = False
for node in self.nodes[0].getpeerinfo():
if node['addr'] == url.hostname+":"+str(p2p_port(1)):
found = True
assert(found)

if __name__ == '__main__':
NodeHandlingTest ().main ()
67 changes: 58 additions & 9 deletions src/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,15 @@ CNode* FindNode(const CNetAddr& ip)
return NULL;
}

CNode* FindNode(const CSubNet& subNet)
{
LOCK(cs_vNodes);
BOOST_FOREACH(CNode* pnode, vNodes)
if (subNet.Match((CNetAddr)pnode->addr))
return (pnode);
return NULL;
}

CNode* FindNode(const std::string& addrName)
{
LOCK(cs_vNodes);
Expand Down Expand Up @@ -434,7 +443,7 @@ void CNode::PushVersion()



std::map<CNetAddr, int64_t> CNode::setBanned;
std::map<CSubNet, int64_t> CNode::setBanned;
CCriticalSection CNode::cs_setBanned;

void CNode::ClearBanned()
Expand All @@ -447,7 +456,24 @@ bool CNode::IsBanned(CNetAddr ip)
bool fResult = false;
{
LOCK(cs_setBanned);
std::map<CNetAddr, int64_t>::iterator i = setBanned.find(ip);
for (std::map<CSubNet, int64_t>::iterator it = setBanned.begin(); it != setBanned.end(); it++)
{
CSubNet subNet = (*it).first;
int64_t t = (*it).second;

if(subNet.Match(ip) && GetTime() < t)
fResult = true;
}
}
return fResult;
}

bool CNode::IsBanned(CSubNet subnet)
{
bool fResult = false;
{
LOCK(cs_setBanned);
std::map<CSubNet, int64_t>::iterator i = setBanned.find(subnet);
if (i != setBanned.end())
{
int64_t t = (*i).second;
Expand All @@ -458,14 +484,37 @@ bool CNode::IsBanned(CNetAddr ip)
return fResult;
}

bool CNode::Ban(const CNetAddr &addr) {
void CNode::Ban(const CNetAddr& addr, int64_t bantimeoffset, bool sinceUnixEpoch) {
CSubNet subNet(addr.ToString()+(addr.IsIPv4() ? "/32" : "/128"));
Ban(subNet, bantimeoffset, sinceUnixEpoch);
}

void CNode::Ban(const CSubNet& subNet, int64_t bantimeoffset, bool sinceUnixEpoch) {
int64_t banTime = GetTime()+GetArg("-bantime", 60*60*24); // Default 24-hour ban
{
LOCK(cs_setBanned);
if (setBanned[addr] < banTime)
setBanned[addr] = banTime;
}
return true;
if (bantimeoffset > 0)
banTime = (sinceUnixEpoch ? 0 : GetTime() )+bantimeoffset;

LOCK(cs_setBanned);
if (setBanned[subNet] < banTime)
setBanned[subNet] = banTime;
}

bool CNode::Unban(const CNetAddr &addr) {
CSubNet subNet(addr.ToString()+(addr.IsIPv4() ? "/32" : "/128"));
return Unban(subNet);
}

bool CNode::Unban(const CSubNet &subNet) {
LOCK(cs_setBanned);
if (setBanned.erase(subNet))
return true;
return false;
}

void CNode::GetBanned(std::map<CSubNet, int64_t> &banMap)
{
LOCK(cs_setBanned);
banMap = setBanned; //create a thread safe copy
}


Expand Down
11 changes: 9 additions & 2 deletions src/net.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ unsigned int SendBufferSize();
void AddOneShot(const std::string& strDest);
void AddressCurrentlyConnected(const CService& addr);
CNode* FindNode(const CNetAddr& ip);
CNode* FindNode(const CSubNet& subNet);
CNode* FindNode(const std::string& addrName);
CNode* FindNode(const CService& ip);
CNode* ConnectNode(CAddress addrConnect, const char *pszDest = NULL);
Expand Down Expand Up @@ -284,7 +285,7 @@ class CNode

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

// Whitelisted ranges. Any node connecting from these is automatically
Expand Down Expand Up @@ -606,7 +607,13 @@ class CNode
// new code.
static void ClearBanned(); // needed for unit testing
static bool IsBanned(CNetAddr ip);
static bool Ban(const CNetAddr &ip);
static bool IsBanned(CSubNet subnet);
static void Ban(const CNetAddr &ip, int64_t bantimeoffset = 0, bool sinceUnixEpoch = false);
static void Ban(const CSubNet &subNet, int64_t bantimeoffset = 0, bool sinceUnixEpoch = false);
static bool Unban(const CNetAddr &ip);
static bool Unban(const CSubNet &ip);
static void GetBanned(std::map<CSubNet, int64_t> &banmap);

void copyStats(CNodeStats &stats);

static bool IsWhitelistedRange(const CNetAddr &ip);
Expand Down
5 changes: 5 additions & 0 deletions src/netbase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1330,6 +1330,11 @@ bool operator!=(const CSubNet& a, const CSubNet& b)
return !(a==b);
}

bool operator<(const CSubNet& a, const CSubNet& b)
{
return (a.network < b.network || (a.network == b.network && memcmp(a.netmask, b.netmask, 16) < 0));
}

#ifdef WIN32
std::string NetworkErrorString(int err)
{
Expand Down
1 change: 1 addition & 0 deletions src/netbase.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ class CSubNet

friend bool operator==(const CSubNet& a, const CSubNet& b);
friend bool operator!=(const CSubNet& a, const CSubNet& b);
friend bool operator<(const CSubNet& a, const CSubNet& b);
};

/** A combination of a network address (CNetAddr) and a (TCP) port */
Expand Down
2 changes: 2 additions & 0 deletions src/rpcclient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "estimatepriority", 0 },
{ "prioritisetransaction", 1 },
{ "prioritisetransaction", 2 },
{ "setban", 2 },
{ "setban", 3 },
};

class CRPCConvertTable
Expand Down
Loading

0 comments on commit 0abfa8a

Please sign in to comment.