Browse Source

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)
0.13
Wladimir J. van der Laan 10 years ago
parent
commit
0abfa8a22f
No known key found for this signature in database
GPG Key ID: 74810B012346C9A6
  1. 1
      qa/pull-tester/rpc-tests.sh
  2. 2
      qa/rpc-tests/httpbasics.py
  3. 69
      qa/rpc-tests/nodehandling.py
  4. 63
      src/net.cpp
  5. 11
      src/net.h
  6. 5
      src/netbase.cpp
  7. 1
      src/netbase.h
  8. 2
      src/rpcclient.cpp
  9. 106
      src/rpcnet.cpp
  10. 1
      src/rpcprotocol.h
  11. 3
      src/rpcserver.cpp
  12. 3
      src/rpcserver.h
  13. 56
      src/test/rpc_tests.cpp

1
qa/pull-tester/rpc-tests.sh

@ -32,6 +32,7 @@ testScripts=(
'merkle_blocks.py' 'merkle_blocks.py'
'signrawtransactions.py' 'signrawtransactions.py'
'walletbackup.py' 'walletbackup.py'
'nodehandling.py'
); );
testScriptsExt=( testScriptsExt=(
'bipdersig-p2p.py' 'bipdersig-p2p.py'

2
qa/rpc-tests/httpbasics.py

@ -4,7 +4,7 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php. # 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 from test_framework.test_framework import BitcoinTestFramework

69
qa/rpc-tests/nodehandling.py

@ -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 ()

63
src/net.cpp

@ -332,6 +332,15 @@ CNode* FindNode(const CNetAddr& ip)
return NULL; 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) CNode* FindNode(const std::string& addrName)
{ {
LOCK(cs_vNodes); LOCK(cs_vNodes);
@ -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; CCriticalSection CNode::cs_setBanned;
void CNode::ClearBanned() void CNode::ClearBanned()
@ -447,7 +456,24 @@ bool CNode::IsBanned(CNetAddr ip)
bool fResult = false; bool fResult = false;
{ {
LOCK(cs_setBanned); 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()) if (i != setBanned.end())
{ {
int64_t t = (*i).second; int64_t t = (*i).second;
@ -458,14 +484,37 @@ bool CNode::IsBanned(CNetAddr ip)
return fResult; 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 int64_t banTime = GetTime()+GetArg("-bantime", 60*60*24); // Default 24-hour ban
{ if (bantimeoffset > 0)
banTime = (sinceUnixEpoch ? 0 : GetTime() )+bantimeoffset;
LOCK(cs_setBanned); LOCK(cs_setBanned);
if (setBanned[addr] < banTime) if (setBanned[subNet] < banTime)
setBanned[addr] = 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 true;
return false;
}
void CNode::GetBanned(std::map<CSubNet, int64_t> &banMap)
{
LOCK(cs_setBanned);
banMap = setBanned; //create a thread safe copy
} }

11
src/net.h

@ -66,6 +66,7 @@ unsigned int SendBufferSize();
void AddOneShot(const std::string& strDest); void AddOneShot(const std::string& strDest);
void AddressCurrentlyConnected(const CService& addr); void AddressCurrentlyConnected(const CService& addr);
CNode* FindNode(const CNetAddr& ip); CNode* FindNode(const CNetAddr& ip);
CNode* FindNode(const CSubNet& subNet);
CNode* FindNode(const std::string& addrName); CNode* FindNode(const std::string& addrName);
CNode* FindNode(const CService& ip); CNode* FindNode(const CService& ip);
CNode* ConnectNode(CAddress addrConnect, const char *pszDest = NULL); CNode* ConnectNode(CAddress addrConnect, const char *pszDest = NULL);
@ -284,7 +285,7 @@ protected:
// Denial-of-service detection/prevention // Denial-of-service detection/prevention
// Key is IP address, value is banned-until-time // 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; static CCriticalSection cs_setBanned;
// Whitelisted ranges. Any node connecting from these is automatically // Whitelisted ranges. Any node connecting from these is automatically
@ -606,7 +607,13 @@ public:
// new code. // new code.
static void ClearBanned(); // needed for unit testing static void ClearBanned(); // needed for unit testing
static bool IsBanned(CNetAddr ip); 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); void copyStats(CNodeStats &stats);
static bool IsWhitelistedRange(const CNetAddr &ip); static bool IsWhitelistedRange(const CNetAddr &ip);

5
src/netbase.cpp

@ -1330,6 +1330,11 @@ bool operator!=(const CSubNet& a, const CSubNet& b)
return !(a==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 #ifdef WIN32
std::string NetworkErrorString(int err) std::string NetworkErrorString(int err)
{ {

1
src/netbase.h

@ -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); 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 */ /** A combination of a network address (CNetAddr) and a (TCP) port */

2
src/rpcclient.cpp

@ -93,6 +93,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "estimatepriority", 0 }, { "estimatepriority", 0 },
{ "prioritisetransaction", 1 }, { "prioritisetransaction", 1 },
{ "prioritisetransaction", 2 }, { "prioritisetransaction", 2 },
{ "setban", 2 },
{ "setban", 3 },
}; };
class CRPCConvertTable class CRPCConvertTable

106
src/rpcnet.cpp

@ -465,3 +465,109 @@ UniValue getnetworkinfo(const UniValue& params, bool fHelp)
obj.push_back(Pair("warnings", GetWarnings("statusbar"))); obj.push_back(Pair("warnings", GetWarnings("statusbar")));
return obj; return obj;
} }
UniValue setban(const UniValue& params, bool fHelp)
{
string strCommand;
if (params.size() >= 2)
strCommand = params[1].get_str();
if (fHelp || params.size() < 2 ||
(strCommand != "add" && strCommand != "remove"))
throw runtime_error(
"setban \"ip(/netmask)\" \"add|remove\" (bantime) (absolute)\n"
"\nAttempts add or remove a IP/Subnet from the banned list.\n"
"\nArguments:\n"
"1. \"ip(/netmask)\" (string, required) The IP/Subnet (see getpeerinfo for nodes ip) with a optional netmask (default is /32 = single ip)\n"
"2. \"command\" (string, required) 'add' to add a IP/Subnet to the list, 'remove' to remove a IP/Subnet from the list\n"
"3. \"bantime\" (numeric, optional) time in seconds how long (or until when if [absolute] is set) 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"
"4. \"absolute\" (boolean, optional) If set, the bantime must be a absolute timestamp in seconds since epoch (Jan 1 1970 GMT)\n"
"\nExamples:\n"
+ HelpExampleCli("setban", "\"192.168.0.6\" \"add\" 86400")
+ HelpExampleCli("setban", "\"192.168.0.0/24\" \"add\"")
+ HelpExampleRpc("setban", "\"192.168.0.6\", \"add\" 86400")
);
CSubNet subNet;
CNetAddr netAddr;
bool isSubnet = false;
if (params[0].get_str().find("/") != string::npos)
isSubnet = true;
if (!isSubnet)
netAddr = CNetAddr(params[0].get_str());
else
subNet = CSubNet(params[0].get_str());
if (! (isSubnet ? subNet.IsValid() : netAddr.IsValid()) )
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: Invalid IP/Subnet");
if (strCommand == "add")
{
if (isSubnet ? CNode::IsBanned(subNet) : CNode::IsBanned(netAddr))
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: IP/Subnet already banned");
int64_t banTime = 0; //use standard bantime if not specified
if (params.size() >= 3 && !params[2].isNull())
banTime = params[2].get_int64();
bool absolute = false;
if (params.size() == 4 && params[3].isTrue())
absolute = true;
isSubnet ? CNode::Ban(subNet, banTime, absolute) : CNode::Ban(netAddr, banTime, absolute);
//disconnect possible nodes
while(CNode *bannedNode = (isSubnet ? FindNode(subNet) : FindNode(netAddr)))
bannedNode->CloseSocketDisconnect();
}
else if(strCommand == "remove")
{
if (!( isSubnet ? CNode::Unban(subNet) : CNode::Unban(netAddr) ))
throw JSONRPCError(RPC_MISC_ERROR, "Error: Unban failed");
}
return NullUniValue;
}
UniValue listbanned(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 0)
throw runtime_error(
"listbanned\n"
"\nList all banned IPs/Subnets.\n"
"\nExamples:\n"
+ HelpExampleCli("listbanned", "")
+ HelpExampleRpc("listbanned", "")
);
std::map<CSubNet, int64_t> banMap;
CNode::GetBanned(banMap);
UniValue bannedAddresses(UniValue::VARR);
for (std::map<CSubNet, int64_t>::iterator it = banMap.begin(); it != banMap.end(); it++)
{
UniValue rec(UniValue::VOBJ);
rec.push_back(Pair("address", (*it).first.ToString()));
rec.push_back(Pair("banned_untill", (*it).second));
bannedAddresses.push_back(rec);
}
return bannedAddresses;
}
UniValue clearbanned(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 0)
throw runtime_error(
"clearbanned\n"
"\nClear all banned IPs.\n"
"\nExamples:\n"
+ HelpExampleCli("clearbanned", "")
+ HelpExampleRpc("clearbanned", "")
);
CNode::ClearBanned();
return NullUniValue;
}

1
src/rpcprotocol.h

@ -64,6 +64,7 @@ enum RPCErrorCode
RPC_CLIENT_NODE_ALREADY_ADDED = -23, //! Node is already added RPC_CLIENT_NODE_ALREADY_ADDED = -23, //! Node is already added
RPC_CLIENT_NODE_NOT_ADDED = -24, //! Node has not been added before RPC_CLIENT_NODE_NOT_ADDED = -24, //! Node has not been added before
RPC_CLIENT_NODE_NOT_CONNECTED = -29, //! Node to disconnect not found in connected nodes RPC_CLIENT_NODE_NOT_CONNECTED = -29, //! Node to disconnect not found in connected nodes
RPC_CLIENT_INVALID_IP_OR_SUBNET = -30, //! Invalid IP/Subnet
//! Wallet errors //! Wallet errors
RPC_WALLET_ERROR = -4, //! Unspecified problem with wallet (key not found etc.) RPC_WALLET_ERROR = -4, //! Unspecified problem with wallet (key not found etc.)

3
src/rpcserver.cpp

@ -279,6 +279,9 @@ static const CRPCCommand vRPCCommands[] =
{ "network", "getnettotals", &getnettotals, true }, { "network", "getnettotals", &getnettotals, true },
{ "network", "getpeerinfo", &getpeerinfo, true }, { "network", "getpeerinfo", &getpeerinfo, true },
{ "network", "ping", &ping, true }, { "network", "ping", &ping, true },
{ "network", "setban", &setban, true },
{ "network", "listbanned", &listbanned, true },
{ "network", "clearbanned", &clearbanned, true },
/* Block chain and UTXO */ /* Block chain and UTXO */
{ "blockchain", "getblockchaininfo", &getblockchaininfo, true }, { "blockchain", "getblockchaininfo", &getblockchaininfo, true },

3
src/rpcserver.h

@ -154,6 +154,9 @@ extern UniValue addnode(const UniValue& params, bool fHelp);
extern UniValue disconnectnode(const UniValue& params, bool fHelp); extern UniValue disconnectnode(const UniValue& params, bool fHelp);
extern UniValue getaddednodeinfo(const UniValue& params, bool fHelp); extern UniValue getaddednodeinfo(const UniValue& params, bool fHelp);
extern UniValue getnettotals(const UniValue& params, bool fHelp); extern UniValue getnettotals(const UniValue& params, bool fHelp);
extern UniValue setban(const UniValue& params, bool fHelp);
extern UniValue listbanned(const UniValue& params, bool fHelp);
extern UniValue clearbanned(const UniValue& params, bool fHelp);
extern UniValue dumpprivkey(const UniValue& params, bool fHelp); // in rpcdump.cpp extern UniValue dumpprivkey(const UniValue& params, bool fHelp); // in rpcdump.cpp
extern UniValue importprivkey(const UniValue& params, bool fHelp); extern UniValue importprivkey(const UniValue& params, bool fHelp);

56
src/test/rpc_tests.cpp

@ -177,4 +177,60 @@ BOOST_AUTO_TEST_CASE(rpc_boostasiotocnetaddr)
BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::ffff:127.0.0.1")).ToString(), "127.0.0.1"); BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::ffff:127.0.0.1")).ToString(), "127.0.0.1");
} }
BOOST_AUTO_TEST_CASE(rpc_ban)
{
BOOST_CHECK_NO_THROW(CallRPC(string("clearbanned")));
UniValue r;
BOOST_CHECK_NO_THROW(r = CallRPC(string("setban 127.0.0.0 add")));
BOOST_CHECK_THROW(r = CallRPC(string("setban 127.0.0.0:8334")), runtime_error); //portnumber for setban not allowed
BOOST_CHECK_NO_THROW(r = CallRPC(string("listbanned")));
UniValue ar = r.get_array();
UniValue o1 = ar[0].get_obj();
UniValue adr = find_value(o1, "address");
BOOST_CHECK_EQUAL(adr.get_str(), "127.0.0.0/255.255.255.255");
BOOST_CHECK_NO_THROW(CallRPC(string("setban 127.0.0.0 remove")));;
BOOST_CHECK_NO_THROW(r = CallRPC(string("listbanned")));
ar = r.get_array();
BOOST_CHECK_EQUAL(ar.size(), 0);
BOOST_CHECK_NO_THROW(r = CallRPC(string("setban 127.0.0.0/24 add 1607731200 true")));
BOOST_CHECK_NO_THROW(r = CallRPC(string("listbanned")));
ar = r.get_array();
o1 = ar[0].get_obj();
adr = find_value(o1, "address");
UniValue banned_until = find_value(o1, "banned_untill");
BOOST_CHECK_EQUAL(adr.get_str(), "127.0.0.0/255.255.255.0");
BOOST_CHECK_EQUAL(banned_until.get_int64(), 1607731200); // absolute time check
BOOST_CHECK_NO_THROW(CallRPC(string("clearbanned")));
BOOST_CHECK_NO_THROW(r = CallRPC(string("setban 127.0.0.0/24 add 200")));
BOOST_CHECK_NO_THROW(r = CallRPC(string("listbanned")));
ar = r.get_array();
o1 = ar[0].get_obj();
adr = find_value(o1, "address");
banned_until = find_value(o1, "banned_untill");
BOOST_CHECK_EQUAL(adr.get_str(), "127.0.0.0/255.255.255.0");
int64_t now = GetTime();
BOOST_CHECK(banned_until.get_int64() > now);
BOOST_CHECK(banned_until.get_int64()-now <= 200);
// must throw an exception because 127.0.0.1 is in already banned suubnet range
BOOST_CHECK_THROW(r = CallRPC(string("setban 127.0.0.1 add")), runtime_error);
BOOST_CHECK_NO_THROW(CallRPC(string("setban 127.0.0.0/24 remove")));;
BOOST_CHECK_NO_THROW(r = CallRPC(string("listbanned")));
ar = r.get_array();
BOOST_CHECK_EQUAL(ar.size(), 0);
BOOST_CHECK_NO_THROW(r = CallRPC(string("setban 127.0.0.0/255.255.0.0 add")));
BOOST_CHECK_THROW(r = CallRPC(string("setban 127.0.1.1 add")), runtime_error);
BOOST_CHECK_NO_THROW(CallRPC(string("clearbanned")));
BOOST_CHECK_NO_THROW(r = CallRPC(string("listbanned")));
ar = r.get_array();
BOOST_CHECK_EQUAL(ar.size(), 0);
}
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()

Loading…
Cancel
Save