[RPC] extend setban to allow subnets

This commit is contained in:
Jonas Schnelli 2015-05-25 20:03:51 +02:00
parent e8b93473f1
commit 433fb1a95d
7 changed files with 139 additions and 34 deletions

View file

@ -101,13 +101,28 @@ class HTTPBasicsTest (BitcoinTestFramework):
###########################
# setban/listbanned tests #
###########################
assert_equal(len(self.nodes[2].getpeerinfo()), 4); #we should have 4 nodes at this point
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);
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);
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)
if __name__ == '__main__':
HTTPBasicsTest ().main ()

View file

@ -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);
@ -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()
@ -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;
@ -458,24 +484,34 @@ bool CNode::IsBanned(CNetAddr ip)
return fResult;
}
void CNode::Ban(const CNetAddr &addr, int64_t bantimeoffset) {
void CNode::Ban(const CNetAddr& addr, int64_t bantimeoffset) {
CSubNet subNet(addr.ToString()+(addr.IsIPv4() ? "/32" : "/128"));
Ban(subNet, bantimeoffset);
}
void CNode::Ban(const CSubNet& subNet, int64_t bantimeoffset) {
int64_t banTime = GetTime()+GetArg("-bantime", 60*60*24); // Default 24-hour ban
if (bantimeoffset > 0)
banTime = GetTime()+bantimeoffset;
LOCK(cs_setBanned);
if (setBanned[addr] < banTime)
setBanned[addr] = banTime;
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(addr))
if (setBanned.erase(subNet))
return true;
return false;
}
void CNode::GetBanned(std::map<CNetAddr, int64_t> &banMap)
void CNode::GetBanned(std::map<CSubNet, int64_t> &banMap)
{
LOCK(cs_setBanned);
banMap = setBanned; //create a thread safe copy

View file

@ -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);
@ -284,7 +285,7 @@ protected:
// 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
@ -606,9 +607,12 @@ public:
// new code.
static void ClearBanned(); // needed for unit testing
static bool IsBanned(CNetAddr ip);
static bool IsBanned(CSubNet subnet);
static void Ban(const CNetAddr &ip, int64_t bantimeoffset = 0);
static void Ban(const CSubNet &subNet, int64_t bantimeoffset = 0);
static bool Unban(const CNetAddr &ip);
static void GetBanned(std::map<CNetAddr, int64_t> &banmap);
static bool Unban(const CSubNet &ip);
static void GetBanned(std::map<CSubNet, int64_t> &banmap);
void copyStats(CNodeStats &stats);

View file

@ -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)));
}
#ifdef WIN32
std::string NetworkErrorString(int err)
{

View file

@ -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 */

View file

@ -474,39 +474,51 @@ Value setban(const Array& params, bool fHelp)
if (fHelp || params.size() < 2 ||
(strCommand != "add" && strCommand != "remove"))
throw runtime_error(
"setban \"node\" \"add|remove\" (bantime)\n"
"\nAttempts add or remove a IP from the banned list.\n"
"setban \"ip(/netmask)\" \"add|remove\" (bantime)\n"
"\nAttempts add or remove a IP/Subnet from the banned list.\n"
"\nArguments:\n"
"1. \"ip\" (string, required) The IP (see getpeerinfo for nodes ip)\n"
"2. \"command\" (string, required) 'add' to add a IP to the list, 'remove' to remove a IP from the list\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"
"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"
"\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")
);
CNetAddr netAddr(params[0].get_str());
if (!netAddr.IsValid())
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: Invalid IP Address");
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 (CNode::IsBanned(netAddr))
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: IP already banned");
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].is_null())
banTime = params[2].get_int64();
CNode::Ban(netAddr, banTime);
isSubnet ? CNode::Ban(subNet, banTime) : CNode::Ban(netAddr, banTime);
//disconnect possible nodes
while(CNode *bannedNode = FindNode(netAddr))
while(CNode *bannedNode = (isSubnet ? FindNode(subNet) : FindNode(netAddr)))
bannedNode->CloseSocketDisconnect();
}
else if(strCommand == "remove")
{
if (!CNode::Unban(netAddr))
if (!( isSubnet ? CNode::Unban(subNet) : CNode::Unban(netAddr) ))
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: Unban failed");
}
@ -518,17 +530,17 @@ Value listbanned(const Array& params, bool fHelp)
if (fHelp || params.size() != 0)
throw runtime_error(
"listbanned\n"
"\nList all banned IPs.\n"
"\nList all banned IPs/Subnets.\n"
"\nExamples:\n"
+ HelpExampleCli("listbanned", "")
+ HelpExampleRpc("listbanned", "")
);
std::map<CNetAddr, int64_t> banMap;
std::map<CSubNet, int64_t> banMap;
CNode::GetBanned(banMap);
Array bannedAddresses;
for (std::map<CNetAddr, int64_t>::iterator it = banMap.begin(); it != banMap.end(); it++)
for (std::map<CSubNet, int64_t>::iterator it = banMap.begin(); it != banMap.end(); it++)
{
Object rec;
rec.push_back(Pair("address", (*it).first.ToString()));

View file

@ -179,11 +179,43 @@ BOOST_AUTO_TEST_CASE(rpc_boostasiotocnetaddr)
BOOST_AUTO_TEST_CASE(rpc_ban)
{
BOOST_CHECK_NO_THROW(CallRPC(string("setban 127.0.0.1 add")));
BOOST_CHECK_THROW(CallRPC(string("setban 127.0.0.1:8334")), runtime_error); //portnumber for setban not allowed
BOOST_CHECK_NO_THROW(CallRPC(string("listbanned")));
BOOST_CHECK_NO_THROW(CallRPC(string("setban 127.0.0.1 remove")));
BOOST_CHECK_NO_THROW(CallRPC(string("clearbanned")));
Value 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")));
Array ar = r.get_array();
Object o1 = ar[0].get_obj();
Value 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")));
BOOST_CHECK_NO_THROW(r = CallRPC(string("listbanned")));
ar = r.get_array();
o1 = ar[0].get_obj();
adr = find_value(o1, "address");
BOOST_CHECK_EQUAL(adr.get_str(), "127.0.0.0/255.255.255.0");
// 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()