Skip to content

Commit f581d3d

Browse files
committed
banlist.dat: store banlist on disk
1 parent d6db115 commit f581d3d

File tree

5 files changed

+236
-3
lines changed

5 files changed

+236
-3
lines changed

qa/rpc-tests/nodehandling.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,25 @@ def run_test(self):
4848
assert_equal(len(self.nodes[2].listbanned()), 0)
4949
self.nodes[2].clearbanned()
5050
assert_equal(len(self.nodes[2].listbanned()), 0)
51-
51+
52+
##test persisted banlist
53+
self.nodes[2].setban("127.0.0.0/32", "add")
54+
self.nodes[2].setban("127.0.0.0/24", "add")
55+
self.nodes[2].setban("192.168.0.1", "add", 1) #ban for 1 seconds
56+
self.nodes[2].setban("2001:4d48:ac57:400:cacf:e9ff:fe1d:9c63/19", "add", 1000) #ban for 1000 seconds
57+
listBeforeShutdown = self.nodes[2].listbanned();
58+
assert_equal("192.168.0.1/255.255.255.255", listBeforeShutdown[2]['address']) #must be here
59+
time.sleep(2) #make 100% sure we expired 192.168.0.1 node time
60+
61+
#stop node
62+
stop_node(self.nodes[2], 2)
63+
64+
self.nodes[2] = start_node(2, self.options.tmpdir)
65+
listAfterShutdown = self.nodes[2].listbanned();
66+
assert_equal("127.0.0.0/255.255.255.0", listAfterShutdown[0]['address'])
67+
assert_equal("127.0.0.0/255.255.255.255", listAfterShutdown[1]['address'])
68+
assert_equal("2001:4000::/ffff:e000:0:0:0:0:0:0", listAfterShutdown[2]['address'])
69+
5270
###########################
5371
# RPC disconnectnode test #
5472
###########################

src/net.cpp

Lines changed: 184 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -445,11 +445,13 @@ void CNode::PushVersion()
445445

446446
std::map<CSubNet, int64_t> CNode::setBanned;
447447
CCriticalSection CNode::cs_setBanned;
448+
bool CNode::setBannedIsDirty;
448449

449450
void CNode::ClearBanned()
450451
{
451452
LOCK(cs_setBanned);
452453
setBanned.clear();
454+
setBannedIsDirty = true;
453455
}
454456

455457
bool CNode::IsBanned(CNetAddr ip)
@@ -498,6 +500,8 @@ void CNode::Ban(const CSubNet& subNet, int64_t bantimeoffset, bool sinceUnixEpoc
498500
LOCK(cs_setBanned);
499501
if (setBanned[subNet] < banTime)
500502
setBanned[subNet] = banTime;
503+
504+
setBannedIsDirty = true;
501505
}
502506

503507
bool CNode::Unban(const CNetAddr &addr) {
@@ -508,7 +512,10 @@ bool CNode::Unban(const CNetAddr &addr) {
508512
bool CNode::Unban(const CSubNet &subNet) {
509513
LOCK(cs_setBanned);
510514
if (setBanned.erase(subNet))
515+
{
516+
setBannedIsDirty = true;
511517
return true;
518+
}
512519
return false;
513520
}
514521

@@ -518,6 +525,43 @@ void CNode::GetBanned(std::map<CSubNet, int64_t> &banMap)
518525
banMap = setBanned; //create a thread safe copy
519526
}
520527

528+
void CNode::SetBanned(const std::map<CSubNet, int64_t> &banMap)
529+
{
530+
LOCK(cs_setBanned);
531+
setBanned = banMap;
532+
setBannedIsDirty = true;
533+
}
534+
535+
void CNode::SweepBanned()
536+
{
537+
int64_t now = GetTime();
538+
539+
LOCK(cs_setBanned);
540+
std::map<CSubNet, int64_t>::iterator it = setBanned.begin();
541+
while(it != setBanned.end())
542+
{
543+
if(now > (*it).second)
544+
{
545+
setBanned.erase(it++);
546+
setBannedIsDirty = true;
547+
}
548+
else
549+
++it;
550+
}
551+
}
552+
553+
bool CNode::BannedSetIsDirty()
554+
{
555+
LOCK(cs_setBanned);
556+
return setBannedIsDirty;
557+
}
558+
559+
void CNode::SetBannedSetDirty(bool dirty)
560+
{
561+
LOCK(cs_setBanned); //reuse setBanned lock for the isDirty flag
562+
setBannedIsDirty = dirty;
563+
}
564+
521565

522566
std::vector<CSubNet> CNode::vWhitelistedRange;
523567
CCriticalSection CNode::cs_vWhitelistedRange;
@@ -1212,6 +1256,17 @@ void DumpAddresses()
12121256
addrman.size(), GetTimeMillis() - nStart);
12131257
}
12141258

1259+
void DumpData()
1260+
{
1261+
DumpAddresses();
1262+
1263+
if (CNode::BannedSetIsDirty())
1264+
{
1265+
DumpBanlist();
1266+
CNode::SetBannedSetDirty(false);
1267+
}
1268+
}
1269+
12151270
void static ProcessOneShot()
12161271
{
12171272
string strDest;
@@ -1650,6 +1705,17 @@ void StartNode(boost::thread_group& threadGroup, CScheduler& scheduler)
16501705
if (!adb.Read(addrman))
16511706
LogPrintf("Invalid or missing peers.dat; recreating\n");
16521707
}
1708+
1709+
//try to read stored banlist
1710+
CBanDB bandb;
1711+
std::map<CSubNet, int64_t> banmap;
1712+
if (!bandb.Read(banmap))
1713+
LogPrintf("Invalid or missing banlist.dat; recreating\n");
1714+
1715+
CNode::SetBanned(banmap); //thread save setter
1716+
CNode::SetBannedSetDirty(false); //no need to write down just read or nonexistent data
1717+
CNode::SweepBanned(); //sweap out unused entries
1718+
16531719
LogPrintf("Loaded %i addresses from peers.dat %dms\n",
16541720
addrman.size(), GetTimeMillis() - nStart);
16551721
fAddressesInitialized = true;
@@ -1690,7 +1756,7 @@ void StartNode(boost::thread_group& threadGroup, CScheduler& scheduler)
16901756
threadGroup.create_thread(boost::bind(&TraceThread<void (*)()>, "msghand", &ThreadMessageHandler));
16911757

16921758
// Dump network addresses
1693-
scheduler.scheduleEvery(&DumpAddresses, DUMP_ADDRESSES_INTERVAL);
1759+
scheduler.scheduleEvery(&DumpData, DUMP_ADDRESSES_INTERVAL);
16941760
}
16951761

16961762
bool StopNode()
@@ -1703,7 +1769,7 @@ bool StopNode()
17031769

17041770
if (fAddressesInitialized)
17051771
{
1706-
DumpAddresses();
1772+
DumpData();
17071773
fAddressesInitialized = false;
17081774
}
17091775

@@ -2107,3 +2173,119 @@ void CNode::EndMessage() UNLOCK_FUNCTION(cs_vSend)
21072173

21082174
LEAVE_CRITICAL_SECTION(cs_vSend);
21092175
}
2176+
2177+
//
2178+
// CBanDB
2179+
//
2180+
2181+
CBanDB::CBanDB()
2182+
{
2183+
pathBanlist = GetDataDir() / "banlist.dat";
2184+
}
2185+
2186+
bool CBanDB::Write(const std::map<CSubNet, int64_t>& banSet)
2187+
{
2188+
// Generate random temporary filename
2189+
unsigned short randv = 0;
2190+
GetRandBytes((unsigned char*)&randv, sizeof(randv));
2191+
std::string tmpfn = strprintf("banlist.dat.%04x", randv);
2192+
2193+
// serialize banlist, checksum data up to that point, then append csum
2194+
CDataStream ssBanlist(SER_DISK, CLIENT_VERSION);
2195+
ssBanlist << FLATDATA(Params().MessageStart());
2196+
ssBanlist << banSet;
2197+
uint256 hash = Hash(ssBanlist.begin(), ssBanlist.end());
2198+
ssBanlist << hash;
2199+
2200+
// open temp output file, and associate with CAutoFile
2201+
boost::filesystem::path pathTmp = GetDataDir() / tmpfn;
2202+
FILE *file = fopen(pathTmp.string().c_str(), "wb");
2203+
CAutoFile fileout(file, SER_DISK, CLIENT_VERSION);
2204+
if (fileout.IsNull())
2205+
return error("%s: Failed to open file %s", __func__, pathTmp.string());
2206+
2207+
// Write and commit header, data
2208+
try {
2209+
fileout << ssBanlist;
2210+
}
2211+
catch (const std::exception& e) {
2212+
return error("%s: Serialize or I/O error - %s", __func__, e.what());
2213+
}
2214+
FileCommit(fileout.Get());
2215+
fileout.fclose();
2216+
2217+
// replace existing banlist.dat, if any, with new banlist.dat.XXXX
2218+
if (!RenameOver(pathTmp, pathBanlist))
2219+
return error("%s: Rename-into-place failed", __func__);
2220+
2221+
return true;
2222+
}
2223+
2224+
bool CBanDB::Read(std::map<CSubNet, int64_t>& banSet)
2225+
{
2226+
// open input file, and associate with CAutoFile
2227+
FILE *file = fopen(pathBanlist.string().c_str(), "rb");
2228+
CAutoFile filein(file, SER_DISK, CLIENT_VERSION);
2229+
if (filein.IsNull())
2230+
return error("%s: Failed to open file %s", __func__, pathBanlist.string());
2231+
2232+
// use file size to size memory buffer
2233+
int fileSize = boost::filesystem::file_size(pathBanlist);
2234+
int dataSize = fileSize - sizeof(uint256);
2235+
// Don't try to resize to a negative number if file is small
2236+
if (dataSize < 0)
2237+
dataSize = 0;
2238+
vector<unsigned char> vchData;
2239+
vchData.resize(dataSize);
2240+
uint256 hashIn;
2241+
2242+
// read data and checksum from file
2243+
try {
2244+
filein.read((char *)&vchData[0], dataSize);
2245+
filein >> hashIn;
2246+
}
2247+
catch (const std::exception& e) {
2248+
return error("%s: Deserialize or I/O error - %s", __func__, e.what());
2249+
}
2250+
filein.fclose();
2251+
2252+
CDataStream ssBanlist(vchData, SER_DISK, CLIENT_VERSION);
2253+
2254+
// verify stored checksum matches input data
2255+
uint256 hashTmp = Hash(ssBanlist.begin(), ssBanlist.end());
2256+
if (hashIn != hashTmp)
2257+
return error("%s: Checksum mismatch, data corrupted", __func__);
2258+
2259+
unsigned char pchMsgTmp[4];
2260+
try {
2261+
// de-serialize file header (network specific magic number) and ..
2262+
ssBanlist >> FLATDATA(pchMsgTmp);
2263+
2264+
// ... verify the network matches ours
2265+
if (memcmp(pchMsgTmp, Params().MessageStart(), sizeof(pchMsgTmp)))
2266+
return error("%s: Invalid network magic number", __func__);
2267+
2268+
// de-serialize address data into one CAddrMan object
2269+
ssBanlist >> banSet;
2270+
}
2271+
catch (const std::exception& e) {
2272+
return error("%s: Deserialize or I/O error - %s", __func__, e.what());
2273+
}
2274+
2275+
return true;
2276+
}
2277+
2278+
void DumpBanlist()
2279+
{
2280+
int64_t nStart = GetTimeMillis();
2281+
2282+
CNode::SweepBanned(); //clean unused entires (if bantime has expired)
2283+
2284+
CBanDB bandb;
2285+
std::map<CSubNet, int64_t> banmap;
2286+
CNode::GetBanned(banmap);
2287+
bandb.Write(banmap);
2288+
2289+
LogPrint("net", "Flushed %d banned node ips/subnets to banlist.dat %dms\n",
2290+
banmap.size(), GetTimeMillis() - nStart);
2291+
}

src/net.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ class CNode
287287
// Key is IP address, value is banned-until-time
288288
static std::map<CSubNet, int64_t> setBanned;
289289
static CCriticalSection cs_setBanned;
290+
static bool setBannedIsDirty;
290291

291292
// Whitelisted ranges. Any node connecting from these is automatically
292293
// whitelisted (as well as those connecting to whitelisted binds).
@@ -613,6 +614,14 @@ class CNode
613614
static bool Unban(const CNetAddr &ip);
614615
static bool Unban(const CSubNet &ip);
615616
static void GetBanned(std::map<CSubNet, int64_t> &banmap);
617+
static void SetBanned(const std::map<CSubNet, int64_t> &banmap);
618+
619+
//!check is the banlist has unwritten changes
620+
static bool BannedSetIsDirty();
621+
//!set the "dirty" flag for the banlist
622+
static void SetBannedSetDirty(bool dirty=true);
623+
//!clean unused entires (if bantime has expired)
624+
static void SweepBanned();
616625

617626
void copyStats(CNodeStats &stats);
618627

@@ -644,4 +653,17 @@ class CAddrDB
644653
bool Read(CAddrMan& addr);
645654
};
646655

656+
/** Access to the banlist database (banlist.dat) */
657+
class CBanDB
658+
{
659+
private:
660+
boost::filesystem::path pathBanlist;
661+
public:
662+
CBanDB();
663+
bool Write(const std::map<CSubNet, int64_t>& banSet);
664+
bool Read(std::map<CSubNet, int64_t>& banSet);
665+
};
666+
667+
void DumpBanlist();
668+
647669
#endif // BITCOIN_NET_H

src/netbase.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,15 @@ class CSubNet
126126
friend bool operator==(const CSubNet& a, const CSubNet& b);
127127
friend bool operator!=(const CSubNet& a, const CSubNet& b);
128128
friend bool operator<(const CSubNet& a, const CSubNet& b);
129+
130+
ADD_SERIALIZE_METHODS;
131+
132+
template <typename Stream, typename Operation>
133+
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
134+
READWRITE(network);
135+
READWRITE(FLATDATA(netmask));
136+
READWRITE(FLATDATA(valid));
137+
}
129138
};
130139

131140
/** A combination of a network address (CNetAddr) and a (TCP) port */

src/rpcnet.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,7 @@ UniValue setban(const UniValue& params, bool fHelp)
527527
throw JSONRPCError(RPC_MISC_ERROR, "Error: Unban failed");
528528
}
529529

530+
DumpBanlist(); //store banlist to disk
530531
return NullUniValue;
531532
}
532533

@@ -568,6 +569,7 @@ UniValue clearbanned(const UniValue& params, bool fHelp)
568569
);
569570

570571
CNode::ClearBanned();
572+
DumpBanlist(); //store banlist to disk
571573

572574
return NullUniValue;
573575
}

0 commit comments

Comments
 (0)