[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 # # 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") self.nodes[2].setban("127.0.0.1", "add")
time.sleep(3) #wait till the nodes are disconected 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].getpeerinfo()), 0) #all nodes must be disconnected at this point
assert_equal(len(self.nodes[2].listbanned()), 1); assert_equal(len(self.nodes[2].listbanned()), 1)
self.nodes[2].clearbanned() 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__': if __name__ == '__main__':
HTTPBasicsTest ().main () HTTPBasicsTest ().main ()

View file

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

View file

@ -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,9 +607,12 @@ 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 IsBanned(CSubNet subnet);
static void Ban(const CNetAddr &ip, int64_t bantimeoffset = 0); 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 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); void copyStats(CNodeStats &stats);

View file

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

View file

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