aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonas Schnelli <jonas.schnelli@include7.ch>2015-06-19 15:27:37 +0200
committerJonas Schnelli <jonas.schnelli@include7.ch>2015-07-02 20:29:36 +0200
commitf581d3d656cf269ea09ac6f130f4bd70b40a9e55 (patch)
treed598d6e1842273946fbbfb7b91744b9e6ca73c4b
parentd6db1157bcab7ace13f046bdfa107b456c1dfbe3 (diff)
downloadbitcoin-f581d3d656cf269ea09ac6f130f4bd70b40a9e55.tar.xz
banlist.dat: store banlist on disk
-rwxr-xr-xqa/rpc-tests/nodehandling.py20
-rw-r--r--src/net.cpp186
-rw-r--r--src/net.h22
-rw-r--r--src/netbase.h9
-rw-r--r--src/rpcnet.cpp2
5 files changed, 236 insertions, 3 deletions
diff --git a/qa/rpc-tests/nodehandling.py b/qa/rpc-tests/nodehandling.py
index 9a77bd97e8..d89cfcf59b 100755
--- a/qa/rpc-tests/nodehandling.py
+++ b/qa/rpc-tests/nodehandling.py
@@ -48,7 +48,25 @@ class NodeHandlingTest (BitcoinTestFramework):
assert_equal(len(self.nodes[2].listbanned()), 0)
self.nodes[2].clearbanned()
assert_equal(len(self.nodes[2].listbanned()), 0)
-
+
+ ##test persisted banlist
+ self.nodes[2].setban("127.0.0.0/32", "add")
+ self.nodes[2].setban("127.0.0.0/24", "add")
+ self.nodes[2].setban("192.168.0.1", "add", 1) #ban for 1 seconds
+ self.nodes[2].setban("2001:4d48:ac57:400:cacf:e9ff:fe1d:9c63/19", "add", 1000) #ban for 1000 seconds
+ listBeforeShutdown = self.nodes[2].listbanned();
+ assert_equal("192.168.0.1/255.255.255.255", listBeforeShutdown[2]['address']) #must be here
+ time.sleep(2) #make 100% sure we expired 192.168.0.1 node time
+
+ #stop node
+ stop_node(self.nodes[2], 2)
+
+ self.nodes[2] = start_node(2, self.options.tmpdir)
+ listAfterShutdown = self.nodes[2].listbanned();
+ assert_equal("127.0.0.0/255.255.255.0", listAfterShutdown[0]['address'])
+ assert_equal("127.0.0.0/255.255.255.255", listAfterShutdown[1]['address'])
+ assert_equal("2001:4000::/ffff:e000:0:0:0:0:0:0", listAfterShutdown[2]['address'])
+
###########################
# RPC disconnectnode test #
###########################
diff --git a/src/net.cpp b/src/net.cpp
index 0511256e55..3a549d65c4 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -445,11 +445,13 @@ void CNode::PushVersion()
std::map<CSubNet, int64_t> CNode::setBanned;
CCriticalSection CNode::cs_setBanned;
+bool CNode::setBannedIsDirty;
void CNode::ClearBanned()
{
LOCK(cs_setBanned);
setBanned.clear();
+ setBannedIsDirty = true;
}
bool CNode::IsBanned(CNetAddr ip)
@@ -498,6 +500,8 @@ void CNode::Ban(const CSubNet& subNet, int64_t bantimeoffset, bool sinceUnixEpoc
LOCK(cs_setBanned);
if (setBanned[subNet] < banTime)
setBanned[subNet] = banTime;
+
+ setBannedIsDirty = true;
}
bool CNode::Unban(const CNetAddr &addr) {
@@ -508,7 +512,10 @@ bool CNode::Unban(const CNetAddr &addr) {
bool CNode::Unban(const CSubNet &subNet) {
LOCK(cs_setBanned);
if (setBanned.erase(subNet))
+ {
+ setBannedIsDirty = true;
return true;
+ }
return false;
}
@@ -518,6 +525,43 @@ void CNode::GetBanned(std::map<CSubNet, int64_t> &banMap)
banMap = setBanned; //create a thread safe copy
}
+void CNode::SetBanned(const std::map<CSubNet, int64_t> &banMap)
+{
+ LOCK(cs_setBanned);
+ setBanned = banMap;
+ setBannedIsDirty = true;
+}
+
+void CNode::SweepBanned()
+{
+ int64_t now = GetTime();
+
+ LOCK(cs_setBanned);
+ std::map<CSubNet, int64_t>::iterator it = setBanned.begin();
+ while(it != setBanned.end())
+ {
+ if(now > (*it).second)
+ {
+ setBanned.erase(it++);
+ setBannedIsDirty = true;
+ }
+ else
+ ++it;
+ }
+}
+
+bool CNode::BannedSetIsDirty()
+{
+ LOCK(cs_setBanned);
+ return setBannedIsDirty;
+}
+
+void CNode::SetBannedSetDirty(bool dirty)
+{
+ LOCK(cs_setBanned); //reuse setBanned lock for the isDirty flag
+ setBannedIsDirty = dirty;
+}
+
std::vector<CSubNet> CNode::vWhitelistedRange;
CCriticalSection CNode::cs_vWhitelistedRange;
@@ -1212,6 +1256,17 @@ void DumpAddresses()
addrman.size(), GetTimeMillis() - nStart);
}
+void DumpData()
+{
+ DumpAddresses();
+
+ if (CNode::BannedSetIsDirty())
+ {
+ DumpBanlist();
+ CNode::SetBannedSetDirty(false);
+ }
+}
+
void static ProcessOneShot()
{
string strDest;
@@ -1650,6 +1705,17 @@ void StartNode(boost::thread_group& threadGroup, CScheduler& scheduler)
if (!adb.Read(addrman))
LogPrintf("Invalid or missing peers.dat; recreating\n");
}
+
+ //try to read stored banlist
+ CBanDB bandb;
+ std::map<CSubNet, int64_t> banmap;
+ if (!bandb.Read(banmap))
+ LogPrintf("Invalid or missing banlist.dat; recreating\n");
+
+ CNode::SetBanned(banmap); //thread save setter
+ CNode::SetBannedSetDirty(false); //no need to write down just read or nonexistent data
+ CNode::SweepBanned(); //sweap out unused entries
+
LogPrintf("Loaded %i addresses from peers.dat %dms\n",
addrman.size(), GetTimeMillis() - nStart);
fAddressesInitialized = true;
@@ -1690,7 +1756,7 @@ void StartNode(boost::thread_group& threadGroup, CScheduler& scheduler)
threadGroup.create_thread(boost::bind(&TraceThread<void (*)()>, "msghand", &ThreadMessageHandler));
// Dump network addresses
- scheduler.scheduleEvery(&DumpAddresses, DUMP_ADDRESSES_INTERVAL);
+ scheduler.scheduleEvery(&DumpData, DUMP_ADDRESSES_INTERVAL);
}
bool StopNode()
@@ -1703,7 +1769,7 @@ bool StopNode()
if (fAddressesInitialized)
{
- DumpAddresses();
+ DumpData();
fAddressesInitialized = false;
}
@@ -2107,3 +2173,119 @@ void CNode::EndMessage() UNLOCK_FUNCTION(cs_vSend)
LEAVE_CRITICAL_SECTION(cs_vSend);
}
+
+//
+// CBanDB
+//
+
+CBanDB::CBanDB()
+{
+ pathBanlist = GetDataDir() / "banlist.dat";
+}
+
+bool CBanDB::Write(const std::map<CSubNet, int64_t>& banSet)
+{
+ // Generate random temporary filename
+ unsigned short randv = 0;
+ GetRandBytes((unsigned char*)&randv, sizeof(randv));
+ std::string tmpfn = strprintf("banlist.dat.%04x", randv);
+
+ // serialize banlist, checksum data up to that point, then append csum
+ CDataStream ssBanlist(SER_DISK, CLIENT_VERSION);
+ ssBanlist << FLATDATA(Params().MessageStart());
+ ssBanlist << banSet;
+ uint256 hash = Hash(ssBanlist.begin(), ssBanlist.end());
+ ssBanlist << hash;
+
+ // open temp output file, and associate with CAutoFile
+ boost::filesystem::path pathTmp = GetDataDir() / tmpfn;
+ FILE *file = fopen(pathTmp.string().c_str(), "wb");
+ CAutoFile fileout(file, SER_DISK, CLIENT_VERSION);
+ if (fileout.IsNull())
+ return error("%s: Failed to open file %s", __func__, pathTmp.string());
+
+ // Write and commit header, data
+ try {
+ fileout << ssBanlist;
+ }
+ catch (const std::exception& e) {
+ return error("%s: Serialize or I/O error - %s", __func__, e.what());
+ }
+ FileCommit(fileout.Get());
+ fileout.fclose();
+
+ // replace existing banlist.dat, if any, with new banlist.dat.XXXX
+ if (!RenameOver(pathTmp, pathBanlist))
+ return error("%s: Rename-into-place failed", __func__);
+
+ return true;
+}
+
+bool CBanDB::Read(std::map<CSubNet, int64_t>& banSet)
+{
+ // open input file, and associate with CAutoFile
+ FILE *file = fopen(pathBanlist.string().c_str(), "rb");
+ CAutoFile filein(file, SER_DISK, CLIENT_VERSION);
+ if (filein.IsNull())
+ return error("%s: Failed to open file %s", __func__, pathBanlist.string());
+
+ // use file size to size memory buffer
+ int fileSize = boost::filesystem::file_size(pathBanlist);
+ int dataSize = fileSize - sizeof(uint256);
+ // Don't try to resize to a negative number if file is small
+ if (dataSize < 0)
+ dataSize = 0;
+ vector<unsigned char> vchData;
+ vchData.resize(dataSize);
+ uint256 hashIn;
+
+ // read data and checksum from file
+ try {
+ filein.read((char *)&vchData[0], dataSize);
+ filein >> hashIn;
+ }
+ catch (const std::exception& e) {
+ return error("%s: Deserialize or I/O error - %s", __func__, e.what());
+ }
+ filein.fclose();
+
+ CDataStream ssBanlist(vchData, SER_DISK, CLIENT_VERSION);
+
+ // verify stored checksum matches input data
+ uint256 hashTmp = Hash(ssBanlist.begin(), ssBanlist.end());
+ if (hashIn != hashTmp)
+ return error("%s: Checksum mismatch, data corrupted", __func__);
+
+ unsigned char pchMsgTmp[4];
+ try {
+ // de-serialize file header (network specific magic number) and ..
+ ssBanlist >> FLATDATA(pchMsgTmp);
+
+ // ... verify the network matches ours
+ if (memcmp(pchMsgTmp, Params().MessageStart(), sizeof(pchMsgTmp)))
+ return error("%s: Invalid network magic number", __func__);
+
+ // de-serialize address data into one CAddrMan object
+ ssBanlist >> banSet;
+ }
+ catch (const std::exception& e) {
+ return error("%s: Deserialize or I/O error - %s", __func__, e.what());
+ }
+
+ return true;
+}
+
+void DumpBanlist()
+{
+ int64_t nStart = GetTimeMillis();
+
+ CNode::SweepBanned(); //clean unused entires (if bantime has expired)
+
+ CBanDB bandb;
+ std::map<CSubNet, int64_t> banmap;
+ CNode::GetBanned(banmap);
+ bandb.Write(banmap);
+
+ LogPrint("net", "Flushed %d banned node ips/subnets to banlist.dat %dms\n",
+ banmap.size(), GetTimeMillis() - nStart);
+} \ No newline at end of file
diff --git a/src/net.h b/src/net.h
index 69e4c592a9..42c859e460 100644
--- a/src/net.h
+++ b/src/net.h
@@ -287,6 +287,7 @@ protected:
// Key is IP address, value is banned-until-time
static std::map<CSubNet, int64_t> setBanned;
static CCriticalSection cs_setBanned;
+ static bool setBannedIsDirty;
// Whitelisted ranges. Any node connecting from these is automatically
// whitelisted (as well as those connecting to whitelisted binds).
@@ -613,6 +614,14 @@ public:
static bool Unban(const CNetAddr &ip);
static bool Unban(const CSubNet &ip);
static void GetBanned(std::map<CSubNet, int64_t> &banmap);
+ static void SetBanned(const std::map<CSubNet, int64_t> &banmap);
+
+ //!check is the banlist has unwritten changes
+ static bool BannedSetIsDirty();
+ //!set the "dirty" flag for the banlist
+ static void SetBannedSetDirty(bool dirty=true);
+ //!clean unused entires (if bantime has expired)
+ static void SweepBanned();
void copyStats(CNodeStats &stats);
@@ -644,4 +653,17 @@ public:
bool Read(CAddrMan& addr);
};
+/** Access to the banlist database (banlist.dat) */
+class CBanDB
+{
+private:
+ boost::filesystem::path pathBanlist;
+public:
+ CBanDB();
+ bool Write(const std::map<CSubNet, int64_t>& banSet);
+ bool Read(std::map<CSubNet, int64_t>& banSet);
+};
+
+void DumpBanlist();
+
#endif // BITCOIN_NET_H
diff --git a/src/netbase.h b/src/netbase.h
index 27f0eac2a2..48c2d3455e 100644
--- a/src/netbase.h
+++ b/src/netbase.h
@@ -126,6 +126,15 @@ 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);
+
+ ADD_SERIALIZE_METHODS;
+
+ template <typename Stream, typename Operation>
+ inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
+ READWRITE(network);
+ READWRITE(FLATDATA(netmask));
+ READWRITE(FLATDATA(valid));
+ }
};
/** A combination of a network address (CNetAddr) and a (TCP) port */
diff --git a/src/rpcnet.cpp b/src/rpcnet.cpp
index 1572b16687..0c3745a7da 100644
--- a/src/rpcnet.cpp
+++ b/src/rpcnet.cpp
@@ -527,6 +527,7 @@ UniValue setban(const UniValue& params, bool fHelp)
throw JSONRPCError(RPC_MISC_ERROR, "Error: Unban failed");
}
+ DumpBanlist(); //store banlist to disk
return NullUniValue;
}
@@ -568,6 +569,7 @@ UniValue clearbanned(const UniValue& params, bool fHelp)
);
CNode::ClearBanned();
+ DumpBanlist(); //store banlist to disk
return NullUniValue;
}