Merge pull request #517 from gavinandresen/DoSprevention
Denial-of-service prevention
This commit is contained in:
commit
17e2c24645
9 changed files with 215 additions and 32 deletions
|
@ -179,6 +179,8 @@ bool AppInit2(int argc, char* argv[])
|
||||||
" -addnode=<ip> \t " + _("Add a node to connect to\n") +
|
" -addnode=<ip> \t " + _("Add a node to connect to\n") +
|
||||||
" -connect=<ip> \t\t " + _("Connect only to the specified node\n") +
|
" -connect=<ip> \t\t " + _("Connect only to the specified node\n") +
|
||||||
" -nolisten \t " + _("Don't accept connections from outside\n") +
|
" -nolisten \t " + _("Don't accept connections from outside\n") +
|
||||||
|
" -banscore=<n> \t " + _("Threshold for disconnecting misbehaving peers (default: 100)\n") +
|
||||||
|
" -bantime=<n> \t " + _("Number of seconds to keep misbehaving peers from reconnecting (default: 86400)\n") +
|
||||||
#ifdef USE_UPNP
|
#ifdef USE_UPNP
|
||||||
#if USE_UPNP
|
#if USE_UPNP
|
||||||
" -noupnp \t " + _("Don't attempt to use UPnP to map the listening port\n") +
|
" -noupnp \t " + _("Don't attempt to use UPnP to map the listening port\n") +
|
||||||
|
|
75
src/main.cpp
75
src/main.cpp
|
@ -297,24 +297,24 @@ bool CTransaction::CheckTransaction() const
|
||||||
{
|
{
|
||||||
// Basic checks that don't depend on any context
|
// Basic checks that don't depend on any context
|
||||||
if (vin.empty())
|
if (vin.empty())
|
||||||
return error("CTransaction::CheckTransaction() : vin empty");
|
return DoS(10, error("CTransaction::CheckTransaction() : vin empty"));
|
||||||
if (vout.empty())
|
if (vout.empty())
|
||||||
return error("CTransaction::CheckTransaction() : vout empty");
|
return DoS(10, error("CTransaction::CheckTransaction() : vout empty"));
|
||||||
// Size limits
|
// Size limits
|
||||||
if (::GetSerializeSize(*this, SER_NETWORK) > MAX_BLOCK_SIZE)
|
if (::GetSerializeSize(*this, SER_NETWORK) > MAX_BLOCK_SIZE)
|
||||||
return error("CTransaction::CheckTransaction() : size limits failed");
|
return DoS(100, error("CTransaction::CheckTransaction() : size limits failed"));
|
||||||
|
|
||||||
// Check for negative or overflow output values
|
// Check for negative or overflow output values
|
||||||
int64 nValueOut = 0;
|
int64 nValueOut = 0;
|
||||||
BOOST_FOREACH(const CTxOut& txout, vout)
|
BOOST_FOREACH(const CTxOut& txout, vout)
|
||||||
{
|
{
|
||||||
if (txout.nValue < 0)
|
if (txout.nValue < 0)
|
||||||
return error("CTransaction::CheckTransaction() : txout.nValue negative");
|
return DoS(100, error("CTransaction::CheckTransaction() : txout.nValue negative"));
|
||||||
if (txout.nValue > MAX_MONEY)
|
if (txout.nValue > MAX_MONEY)
|
||||||
return error("CTransaction::CheckTransaction() : txout.nValue too high");
|
return DoS(100, error("CTransaction::CheckTransaction() : txout.nValue too high"));
|
||||||
nValueOut += txout.nValue;
|
nValueOut += txout.nValue;
|
||||||
if (!MoneyRange(nValueOut))
|
if (!MoneyRange(nValueOut))
|
||||||
return error("CTransaction::CheckTransaction() : txout total out of range");
|
return DoS(100, error("CTransaction::CheckTransaction() : txout total out of range"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for duplicate inputs
|
// Check for duplicate inputs
|
||||||
|
@ -329,13 +329,13 @@ bool CTransaction::CheckTransaction() const
|
||||||
if (IsCoinBase())
|
if (IsCoinBase())
|
||||||
{
|
{
|
||||||
if (vin[0].scriptSig.size() < 2 || vin[0].scriptSig.size() > 100)
|
if (vin[0].scriptSig.size() < 2 || vin[0].scriptSig.size() > 100)
|
||||||
return error("CTransaction::CheckTransaction() : coinbase script size");
|
return DoS(100, error("CTransaction::CheckTransaction() : coinbase script size"));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
BOOST_FOREACH(const CTxIn& txin, vin)
|
BOOST_FOREACH(const CTxIn& txin, vin)
|
||||||
if (txin.prevout.IsNull())
|
if (txin.prevout.IsNull())
|
||||||
return error("CTransaction::CheckTransaction() : prevout is null");
|
return DoS(10, error("CTransaction::CheckTransaction() : prevout is null"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -351,7 +351,7 @@ bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMi
|
||||||
|
|
||||||
// Coinbase is only valid in a block, not as a loose transaction
|
// Coinbase is only valid in a block, not as a loose transaction
|
||||||
if (IsCoinBase())
|
if (IsCoinBase())
|
||||||
return error("AcceptToMemoryPool() : coinbase as individual tx");
|
return DoS(100, error("AcceptToMemoryPool() : coinbase as individual tx"));
|
||||||
|
|
||||||
// To help v0.1.5 clients who would see it as a negative number
|
// To help v0.1.5 clients who would see it as a negative number
|
||||||
if ((int64)nLockTime > INT_MAX)
|
if ((int64)nLockTime > INT_MAX)
|
||||||
|
@ -364,7 +364,7 @@ bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMi
|
||||||
// 34 bytes because a TxOut is:
|
// 34 bytes because a TxOut is:
|
||||||
// 20-byte address + 8 byte bitcoin amount + 5 bytes of ops + 1 byte script length
|
// 20-byte address + 8 byte bitcoin amount + 5 bytes of ops + 1 byte script length
|
||||||
if (GetSigOpCount() > nSize / 34 || nSize < 100)
|
if (GetSigOpCount() > nSize / 34 || nSize < 100)
|
||||||
return error("AcceptToMemoryPool() : nonstandard transaction");
|
return DoS(10, error("AcceptToMemoryPool() : transaction with out-of-bounds SigOpCount"));
|
||||||
|
|
||||||
// Rather not work on nonstandard transactions (unless -testnet)
|
// Rather not work on nonstandard transactions (unless -testnet)
|
||||||
if (!fTestNet && !IsStandard())
|
if (!fTestNet && !IsStandard())
|
||||||
|
@ -855,26 +855,28 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, map<uint256, CTxIndex>& mapTestPoo
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevout.n >= txPrev.vout.size() || prevout.n >= txindex.vSpent.size())
|
if (prevout.n >= txPrev.vout.size() || prevout.n >= txindex.vSpent.size())
|
||||||
return error("ConnectInputs() : %s prevout.n out of range %d %d %d prev tx %s\n%s", GetHash().ToString().substr(0,10).c_str(), prevout.n, txPrev.vout.size(), txindex.vSpent.size(), prevout.hash.ToString().substr(0,10).c_str(), txPrev.ToString().c_str());
|
return DoS(100, error("ConnectInputs() : %s prevout.n out of range %d %d %d prev tx %s\n%s", GetHash().ToString().substr(0,10).c_str(), prevout.n, txPrev.vout.size(), txindex.vSpent.size(), prevout.hash.ToString().substr(0,10).c_str(), txPrev.ToString().c_str()));
|
||||||
|
|
||||||
// If prev is coinbase, check that it's matured
|
// If prev is coinbase, check that it's matured
|
||||||
if (txPrev.IsCoinBase())
|
if (txPrev.IsCoinBase())
|
||||||
for (CBlockIndex* pindex = pindexBlock; pindex && pindexBlock->nHeight - pindex->nHeight < COINBASE_MATURITY; pindex = pindex->pprev)
|
for (CBlockIndex* pindex = pindexBlock; pindex && pindexBlock->nHeight - pindex->nHeight < COINBASE_MATURITY; pindex = pindex->pprev)
|
||||||
if (pindex->nBlockPos == txindex.pos.nBlockPos && pindex->nFile == txindex.pos.nFile)
|
if (pindex->nBlockPos == txindex.pos.nBlockPos && pindex->nFile == txindex.pos.nFile)
|
||||||
return error("ConnectInputs() : tried to spend coinbase at depth %d", pindexBlock->nHeight - pindex->nHeight);
|
return DoS(10, error("ConnectInputs() : tried to spend coinbase at depth %d", pindexBlock->nHeight - pindex->nHeight));
|
||||||
|
|
||||||
// Verify signature
|
// Verify signature
|
||||||
if (!VerifySignature(txPrev, *this, i))
|
if (!VerifySignature(txPrev, *this, i))
|
||||||
return error("ConnectInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,10).c_str());
|
return DoS(100,error("ConnectInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,10).c_str()));
|
||||||
|
|
||||||
// Check for conflicts
|
// Check for conflicts (double-spend)
|
||||||
|
// This doesn't trigger the DoS code on purpose; if it did, it would make it easier
|
||||||
|
// for an attacker to attempt to split the network.
|
||||||
if (!txindex.vSpent[prevout.n].IsNull())
|
if (!txindex.vSpent[prevout.n].IsNull())
|
||||||
return fMiner ? false : error("ConnectInputs() : %s prev tx already used at %s", GetHash().ToString().substr(0,10).c_str(), txindex.vSpent[prevout.n].ToString().c_str());
|
return fMiner ? false : error("ConnectInputs() : %s prev tx already used at %s", GetHash().ToString().substr(0,10).c_str(), txindex.vSpent[prevout.n].ToString().c_str());
|
||||||
|
|
||||||
// Check for negative or overflow input values
|
// Check for negative or overflow input values
|
||||||
nValueIn += txPrev.vout[prevout.n].nValue;
|
nValueIn += txPrev.vout[prevout.n].nValue;
|
||||||
if (!MoneyRange(txPrev.vout[prevout.n].nValue) || !MoneyRange(nValueIn))
|
if (!MoneyRange(txPrev.vout[prevout.n].nValue) || !MoneyRange(nValueIn))
|
||||||
return error("ConnectInputs() : txin values out of range");
|
return DoS(100, error("ConnectInputs() : txin values out of range"));
|
||||||
|
|
||||||
// Mark outpoints as spent
|
// Mark outpoints as spent
|
||||||
txindex.vSpent[prevout.n] = posThisTx;
|
txindex.vSpent[prevout.n] = posThisTx;
|
||||||
|
@ -887,17 +889,17 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, map<uint256, CTxIndex>& mapTestPoo
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nValueIn < GetValueOut())
|
if (nValueIn < GetValueOut())
|
||||||
return error("ConnectInputs() : %s value in < value out", GetHash().ToString().substr(0,10).c_str());
|
return DoS(100, error("ConnectInputs() : %s value in < value out", GetHash().ToString().substr(0,10).c_str()));
|
||||||
|
|
||||||
// Tally transaction fees
|
// Tally transaction fees
|
||||||
int64 nTxFee = nValueIn - GetValueOut();
|
int64 nTxFee = nValueIn - GetValueOut();
|
||||||
if (nTxFee < 0)
|
if (nTxFee < 0)
|
||||||
return error("ConnectInputs() : %s nTxFee < 0", GetHash().ToString().substr(0,10).c_str());
|
return DoS(100, error("ConnectInputs() : %s nTxFee < 0", GetHash().ToString().substr(0,10).c_str()));
|
||||||
if (nTxFee < nMinFee)
|
if (nTxFee < nMinFee)
|
||||||
return false;
|
return false;
|
||||||
nFees += nTxFee;
|
nFees += nTxFee;
|
||||||
if (!MoneyRange(nFees))
|
if (!MoneyRange(nFees))
|
||||||
return error("ConnectInputs() : nFees out of range");
|
return DoS(100, error("ConnectInputs() : nFees out of range"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fBlock)
|
if (fBlock)
|
||||||
|
@ -1240,11 +1242,11 @@ bool CBlock::CheckBlock() const
|
||||||
|
|
||||||
// Size limits
|
// Size limits
|
||||||
if (vtx.empty() || vtx.size() > MAX_BLOCK_SIZE || ::GetSerializeSize(*this, SER_NETWORK) > MAX_BLOCK_SIZE)
|
if (vtx.empty() || vtx.size() > MAX_BLOCK_SIZE || ::GetSerializeSize(*this, SER_NETWORK) > MAX_BLOCK_SIZE)
|
||||||
return error("CheckBlock() : size limits failed");
|
return DoS(100, error("CheckBlock() : size limits failed"));
|
||||||
|
|
||||||
// Check proof of work matches claimed amount
|
// Check proof of work matches claimed amount
|
||||||
if (!CheckProofOfWork(GetHash(), nBits))
|
if (!CheckProofOfWork(GetHash(), nBits))
|
||||||
return error("CheckBlock() : proof of work failed");
|
return DoS(50, error("CheckBlock() : proof of work failed"));
|
||||||
|
|
||||||
// Check timestamp
|
// Check timestamp
|
||||||
if (GetBlockTime() > GetAdjustedTime() + 2 * 60 * 60)
|
if (GetBlockTime() > GetAdjustedTime() + 2 * 60 * 60)
|
||||||
|
@ -1252,23 +1254,23 @@ bool CBlock::CheckBlock() const
|
||||||
|
|
||||||
// First transaction must be coinbase, the rest must not be
|
// First transaction must be coinbase, the rest must not be
|
||||||
if (vtx.empty() || !vtx[0].IsCoinBase())
|
if (vtx.empty() || !vtx[0].IsCoinBase())
|
||||||
return error("CheckBlock() : first tx is not coinbase");
|
return DoS(100, error("CheckBlock() : first tx is not coinbase"));
|
||||||
for (int i = 1; i < vtx.size(); i++)
|
for (int i = 1; i < vtx.size(); i++)
|
||||||
if (vtx[i].IsCoinBase())
|
if (vtx[i].IsCoinBase())
|
||||||
return error("CheckBlock() : more than one coinbase");
|
return DoS(100, error("CheckBlock() : more than one coinbase"));
|
||||||
|
|
||||||
// Check transactions
|
// Check transactions
|
||||||
BOOST_FOREACH(const CTransaction& tx, vtx)
|
BOOST_FOREACH(const CTransaction& tx, vtx)
|
||||||
if (!tx.CheckTransaction())
|
if (!tx.CheckTransaction())
|
||||||
return error("CheckBlock() : CheckTransaction failed");
|
return DoS(tx.nDoS, error("CheckBlock() : CheckTransaction failed"));
|
||||||
|
|
||||||
// Check that it's not full of nonstandard transactions
|
// Check that it's not full of nonstandard transactions
|
||||||
if (GetSigOpCount() > MAX_BLOCK_SIGOPS)
|
if (GetSigOpCount() > MAX_BLOCK_SIGOPS)
|
||||||
return error("CheckBlock() : too many nonstandard transactions");
|
return DoS(100, error("CheckBlock() : out-of-bounds SigOpCount"));
|
||||||
|
|
||||||
// Check merkleroot
|
// Check merkleroot
|
||||||
if (hashMerkleRoot != BuildMerkleTree())
|
if (hashMerkleRoot != BuildMerkleTree())
|
||||||
return error("CheckBlock() : hashMerkleRoot mismatch");
|
return DoS(100, error("CheckBlock() : hashMerkleRoot mismatch"));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1283,13 +1285,13 @@ bool CBlock::AcceptBlock()
|
||||||
// Get prev block index
|
// Get prev block index
|
||||||
map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hashPrevBlock);
|
map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hashPrevBlock);
|
||||||
if (mi == mapBlockIndex.end())
|
if (mi == mapBlockIndex.end())
|
||||||
return error("AcceptBlock() : prev block not found");
|
return DoS(10, error("AcceptBlock() : prev block not found"));
|
||||||
CBlockIndex* pindexPrev = (*mi).second;
|
CBlockIndex* pindexPrev = (*mi).second;
|
||||||
int nHeight = pindexPrev->nHeight+1;
|
int nHeight = pindexPrev->nHeight+1;
|
||||||
|
|
||||||
// Check proof of work
|
// Check proof of work
|
||||||
if (nBits != GetNextWorkRequired(pindexPrev))
|
if (nBits != GetNextWorkRequired(pindexPrev))
|
||||||
return error("AcceptBlock() : incorrect proof of work");
|
return DoS(100, error("AcceptBlock() : incorrect proof of work"));
|
||||||
|
|
||||||
// Check timestamp against prev
|
// Check timestamp against prev
|
||||||
if (GetBlockTime() <= pindexPrev->GetMedianTimePast())
|
if (GetBlockTime() <= pindexPrev->GetMedianTimePast())
|
||||||
|
@ -1298,7 +1300,7 @@ bool CBlock::AcceptBlock()
|
||||||
// Check that all transactions are finalized
|
// Check that all transactions are finalized
|
||||||
BOOST_FOREACH(const CTransaction& tx, vtx)
|
BOOST_FOREACH(const CTransaction& tx, vtx)
|
||||||
if (!tx.IsFinal(nHeight, GetBlockTime()))
|
if (!tx.IsFinal(nHeight, GetBlockTime()))
|
||||||
return error("AcceptBlock() : contains a non-final transaction");
|
return DoS(10, error("AcceptBlock() : contains a non-final transaction"));
|
||||||
|
|
||||||
// Check that the block chain matches the known block chain up to a checkpoint
|
// Check that the block chain matches the known block chain up to a checkpoint
|
||||||
if (!fTestNet)
|
if (!fTestNet)
|
||||||
|
@ -1311,7 +1313,7 @@ bool CBlock::AcceptBlock()
|
||||||
(nHeight == 118000 && hash != uint256("0x000000000000774a7f8a7a12dc906ddb9e17e75d684f15e00f8767f9e8f36553")) ||
|
(nHeight == 118000 && hash != uint256("0x000000000000774a7f8a7a12dc906ddb9e17e75d684f15e00f8767f9e8f36553")) ||
|
||||||
(nHeight == 134444 && hash != uint256("0x00000000000005b12ffd4cd315cd34ffd4a594f430ac814c91184a0d42d2b0fe")) ||
|
(nHeight == 134444 && hash != uint256("0x00000000000005b12ffd4cd315cd34ffd4a594f430ac814c91184a0d42d2b0fe")) ||
|
||||||
(nHeight == 140700 && hash != uint256("0x000000000000033b512028abb90e1626d8b346fd0ed598ac0a3c371138dce2bd")))
|
(nHeight == 140700 && hash != uint256("0x000000000000033b512028abb90e1626d8b346fd0ed598ac0a3c371138dce2bd")))
|
||||||
return error("AcceptBlock() : rejected by checkpoint lockin at %d", nHeight);
|
return DoS(100, error("AcceptBlock() : rejected by checkpoint lockin at %d", nHeight));
|
||||||
|
|
||||||
// Write block to history file
|
// Write block to history file
|
||||||
if (!CheckDiskSpace(::GetSerializeSize(*this, SER_DISK)))
|
if (!CheckDiskSpace(::GetSerializeSize(*this, SER_DISK)))
|
||||||
|
@ -1769,7 +1771,10 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
|
||||||
{
|
{
|
||||||
// Each connection can only send one version message
|
// Each connection can only send one version message
|
||||||
if (pfrom->nVersion != 0)
|
if (pfrom->nVersion != 0)
|
||||||
|
{
|
||||||
|
pfrom->Misbehaving(1);
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
int64 nTime;
|
int64 nTime;
|
||||||
CAddress addrMe;
|
CAddress addrMe;
|
||||||
|
@ -1857,6 +1862,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
|
||||||
else if (pfrom->nVersion == 0)
|
else if (pfrom->nVersion == 0)
|
||||||
{
|
{
|
||||||
// Must have a version message before anything else
|
// Must have a version message before anything else
|
||||||
|
pfrom->Misbehaving(1);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1878,7 +1884,10 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
|
||||||
if (pfrom->nVersion < 31402 && mapAddresses.size() > 1000)
|
if (pfrom->nVersion < 31402 && mapAddresses.size() > 1000)
|
||||||
return true;
|
return true;
|
||||||
if (vAddr.size() > 1000)
|
if (vAddr.size() > 1000)
|
||||||
|
{
|
||||||
|
pfrom->Misbehaving(20);
|
||||||
return error("message addr size() = %d", vAddr.size());
|
return error("message addr size() = %d", vAddr.size());
|
||||||
|
}
|
||||||
|
|
||||||
// Store the new addresses
|
// Store the new addresses
|
||||||
CAddrDB addrDB;
|
CAddrDB addrDB;
|
||||||
|
@ -1936,7 +1945,10 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
|
||||||
vector<CInv> vInv;
|
vector<CInv> vInv;
|
||||||
vRecv >> vInv;
|
vRecv >> vInv;
|
||||||
if (vInv.size() > 50000)
|
if (vInv.size() > 50000)
|
||||||
|
{
|
||||||
|
pfrom->Misbehaving(20);
|
||||||
return error("message inv size() = %d", vInv.size());
|
return error("message inv size() = %d", vInv.size());
|
||||||
|
}
|
||||||
|
|
||||||
CTxDB txdb("r");
|
CTxDB txdb("r");
|
||||||
BOOST_FOREACH(const CInv& inv, vInv)
|
BOOST_FOREACH(const CInv& inv, vInv)
|
||||||
|
@ -1965,7 +1977,10 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
|
||||||
vector<CInv> vInv;
|
vector<CInv> vInv;
|
||||||
vRecv >> vInv;
|
vRecv >> vInv;
|
||||||
if (vInv.size() > 50000)
|
if (vInv.size() > 50000)
|
||||||
|
{
|
||||||
|
pfrom->Misbehaving(20);
|
||||||
return error("message getdata size() = %d", vInv.size());
|
return error("message getdata size() = %d", vInv.size());
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_FOREACH(const CInv& inv, vInv)
|
BOOST_FOREACH(const CInv& inv, vInv)
|
||||||
{
|
{
|
||||||
|
@ -2137,6 +2152,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
|
||||||
printf("storing orphan tx %s\n", inv.hash.ToString().substr(0,10).c_str());
|
printf("storing orphan tx %s\n", inv.hash.ToString().substr(0,10).c_str());
|
||||||
AddOrphanTx(vMsg);
|
AddOrphanTx(vMsg);
|
||||||
}
|
}
|
||||||
|
if (tx.nDoS) pfrom->Misbehaving(tx.nDoS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2153,6 +2169,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
|
||||||
|
|
||||||
if (ProcessBlock(pfrom, &block))
|
if (ProcessBlock(pfrom, &block))
|
||||||
mapAlreadyAskedFor.erase(inv);
|
mapAlreadyAskedFor.erase(inv);
|
||||||
|
if (block.nDoS) pfrom->Misbehaving(block.nDoS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -400,6 +400,9 @@ public:
|
||||||
std::vector<CTxOut> vout;
|
std::vector<CTxOut> vout;
|
||||||
unsigned int nLockTime;
|
unsigned int nLockTime;
|
||||||
|
|
||||||
|
// Denial-of-service detection:
|
||||||
|
mutable int nDoS;
|
||||||
|
bool DoS(int nDoSIn, bool fIn) const { nDoS += nDoSIn; return fIn; }
|
||||||
|
|
||||||
CTransaction()
|
CTransaction()
|
||||||
{
|
{
|
||||||
|
@ -421,6 +424,7 @@ public:
|
||||||
vin.clear();
|
vin.clear();
|
||||||
vout.clear();
|
vout.clear();
|
||||||
nLockTime = 0;
|
nLockTime = 0;
|
||||||
|
nDoS = 0; // Denial-of-service prevention
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsNull() const
|
bool IsNull() const
|
||||||
|
@ -787,6 +791,9 @@ public:
|
||||||
// memory only
|
// memory only
|
||||||
mutable std::vector<uint256> vMerkleTree;
|
mutable std::vector<uint256> vMerkleTree;
|
||||||
|
|
||||||
|
// Denial-of-service detection:
|
||||||
|
mutable int nDoS;
|
||||||
|
bool DoS(int nDoSIn, bool fIn) const { nDoS += nDoSIn; return fIn; }
|
||||||
|
|
||||||
CBlock()
|
CBlock()
|
||||||
{
|
{
|
||||||
|
@ -820,6 +827,7 @@ public:
|
||||||
nNonce = 0;
|
nNonce = 0;
|
||||||
vtx.clear();
|
vtx.clear();
|
||||||
vMerkleTree.clear();
|
vMerkleTree.clear();
|
||||||
|
nDoS = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsNull() const
|
bool IsNull() const
|
||||||
|
|
54
src/net.cpp
54
src/net.cpp
|
@ -726,6 +726,52 @@ void CNode::Cleanup()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::map<unsigned int, int64> CNode::setBanned;
|
||||||
|
CCriticalSection CNode::cs_setBanned;
|
||||||
|
|
||||||
|
void CNode::ClearBanned()
|
||||||
|
{
|
||||||
|
setBanned.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CNode::IsBanned(unsigned int ip)
|
||||||
|
{
|
||||||
|
bool fResult = false;
|
||||||
|
CRITICAL_BLOCK(cs_setBanned)
|
||||||
|
{
|
||||||
|
std::map<unsigned int, int64>::iterator i = setBanned.find(ip);
|
||||||
|
if (i != setBanned.end())
|
||||||
|
{
|
||||||
|
int64 t = (*i).second;
|
||||||
|
if (GetTime() < t)
|
||||||
|
fResult = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CNode::Misbehaving(int howmuch)
|
||||||
|
{
|
||||||
|
if (addr.IsLocal())
|
||||||
|
{
|
||||||
|
printf("Warning: local node %s misbehaving\n", addr.ToString().c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
nMisbehavior += howmuch;
|
||||||
|
if (nMisbehavior >= GetArg("-banscore", 100))
|
||||||
|
{
|
||||||
|
int64 banTime = GetTime()+GetArg("-bantime", 60*60*24); // Default 24-hour ban
|
||||||
|
CRITICAL_BLOCK(cs_setBanned)
|
||||||
|
if (setBanned[addr.ip] < banTime)
|
||||||
|
setBanned[addr.ip] = banTime;
|
||||||
|
CloseSocketDisconnect();
|
||||||
|
printf("Disconnected %s for misbehavior (score=%d)\n", addr.ToString().c_str(), nMisbehavior);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -896,6 +942,11 @@ void ThreadSocketHandler2(void* parg)
|
||||||
{
|
{
|
||||||
closesocket(hSocket);
|
closesocket(hSocket);
|
||||||
}
|
}
|
||||||
|
else if (CNode::IsBanned(addr.ip))
|
||||||
|
{
|
||||||
|
printf("connetion from %s dropped (banned)\n", addr.ToString().c_str());
|
||||||
|
closesocket(hSocket);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
printf("accepted connection %s\n", addr.ToString().c_str());
|
printf("accepted connection %s\n", addr.ToString().c_str());
|
||||||
|
@ -1454,7 +1505,8 @@ bool OpenNetworkConnection(const CAddress& addrConnect)
|
||||||
//
|
//
|
||||||
if (fShutdown)
|
if (fShutdown)
|
||||||
return false;
|
return false;
|
||||||
if (addrConnect.ip == addrLocalHost.ip || !addrConnect.IsIPv4() || FindNode(addrConnect.ip))
|
if (addrConnect.ip == addrLocalHost.ip || !addrConnect.IsIPv4() ||
|
||||||
|
FindNode(addrConnect.ip) || CNode::IsBanned(addrConnect.ip))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
vnThreadsRunning[1]--;
|
vnThreadsRunning[1]--;
|
||||||
|
|
28
src/net.h
28
src/net.h
|
@ -124,6 +124,13 @@ public:
|
||||||
bool fDisconnect;
|
bool fDisconnect;
|
||||||
protected:
|
protected:
|
||||||
int nRefCount;
|
int nRefCount;
|
||||||
|
|
||||||
|
// Denial-of-service detection/prevention
|
||||||
|
// Key is ip address, value is banned-until-time
|
||||||
|
static std::map<unsigned int, int64> setBanned;
|
||||||
|
static CCriticalSection cs_setBanned;
|
||||||
|
int nMisbehavior;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
int64 nReleaseTime;
|
int64 nReleaseTime;
|
||||||
std::map<uint256, CRequestTracker> mapRequests;
|
std::map<uint256, CRequestTracker> mapRequests;
|
||||||
|
@ -148,7 +155,6 @@ public:
|
||||||
// publish and subscription
|
// publish and subscription
|
||||||
std::vector<char> vfSubscribe;
|
std::vector<char> vfSubscribe;
|
||||||
|
|
||||||
|
|
||||||
CNode(SOCKET hSocketIn, CAddress addrIn, bool fInboundIn=false)
|
CNode(SOCKET hSocketIn, CAddress addrIn, bool fInboundIn=false)
|
||||||
{
|
{
|
||||||
nServices = 0;
|
nServices = 0;
|
||||||
|
@ -185,6 +191,7 @@ public:
|
||||||
nStartingHeight = -1;
|
nStartingHeight = -1;
|
||||||
fGetAddr = false;
|
fGetAddr = false;
|
||||||
vfSubscribe.assign(256, false);
|
vfSubscribe.assign(256, false);
|
||||||
|
nMisbehavior = 0;
|
||||||
|
|
||||||
// Be shy and don't send version until we hear
|
// Be shy and don't send version until we hear
|
||||||
if (!fInbound)
|
if (!fInbound)
|
||||||
|
@ -568,6 +575,25 @@ public:
|
||||||
void CancelSubscribe(unsigned int nChannel);
|
void CancelSubscribe(unsigned int nChannel);
|
||||||
void CloseSocketDisconnect();
|
void CloseSocketDisconnect();
|
||||||
void Cleanup();
|
void Cleanup();
|
||||||
|
|
||||||
|
|
||||||
|
// Denial-of-service detection/prevention
|
||||||
|
// The idea is to detect peers that are behaving
|
||||||
|
// badly and disconnect/ban them, but do it in a
|
||||||
|
// one-coding-mistake-won't-shatter-the-entire-network
|
||||||
|
// way.
|
||||||
|
// IMPORTANT: There should be nothing I can give a
|
||||||
|
// node that it will forward on that will make that
|
||||||
|
// node's peers drop it. If there is, an attacker
|
||||||
|
// can isolate a node and/or try to split the network.
|
||||||
|
// Dropping a node for sending stuff that is invalid
|
||||||
|
// now but might be valid in a later version is also
|
||||||
|
// dangerous, because it can cause a network split
|
||||||
|
// between nodes running old code and nodes running
|
||||||
|
// new code.
|
||||||
|
static void ClearBanned(); // needed for unit testing
|
||||||
|
static bool IsBanned(unsigned int ip);
|
||||||
|
bool Misbehaving(int howmuch); // 1 == a little, 100 == a lot
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
68
src/test/DoS_tests.cpp
Normal file
68
src/test/DoS_tests.cpp
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
//
|
||||||
|
// Unit tests for denial-of-service detection/prevention code
|
||||||
|
//
|
||||||
|
#include <boost/test/unit_test.hpp>
|
||||||
|
#include <boost/foreach.hpp>
|
||||||
|
|
||||||
|
#include "../main.h"
|
||||||
|
#include "../net.h"
|
||||||
|
#include "../util.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE(DoS_tests)
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(DoS_banning)
|
||||||
|
{
|
||||||
|
CNode::ClearBanned();
|
||||||
|
CAddress addr1(0xa0b0c001);
|
||||||
|
CNode dummyNode1(INVALID_SOCKET, addr1, true);
|
||||||
|
dummyNode1.Misbehaving(100); // Should get banned
|
||||||
|
BOOST_CHECK(CNode::IsBanned(addr1.ip));
|
||||||
|
BOOST_CHECK(!CNode::IsBanned(addr1.ip|0x0000ff00)); // Different ip, not banned
|
||||||
|
|
||||||
|
CAddress addr2(0xa0b0c002);
|
||||||
|
CNode dummyNode2(INVALID_SOCKET, addr2, true);
|
||||||
|
dummyNode2.Misbehaving(50);
|
||||||
|
BOOST_CHECK(!CNode::IsBanned(addr2.ip)); // 2 not banned yet...
|
||||||
|
BOOST_CHECK(CNode::IsBanned(addr1.ip)); // ... but 1 still should be
|
||||||
|
dummyNode2.Misbehaving(50);
|
||||||
|
BOOST_CHECK(CNode::IsBanned(addr2.ip));
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(DoS_banscore)
|
||||||
|
{
|
||||||
|
CNode::ClearBanned();
|
||||||
|
mapArgs["-banscore"] = "111"; // because 11 is my favorite number
|
||||||
|
CAddress addr1(0xa0b0c001);
|
||||||
|
CNode dummyNode1(INVALID_SOCKET, addr1, true);
|
||||||
|
dummyNode1.Misbehaving(100);
|
||||||
|
BOOST_CHECK(!CNode::IsBanned(addr1.ip));
|
||||||
|
dummyNode1.Misbehaving(10);
|
||||||
|
BOOST_CHECK(!CNode::IsBanned(addr1.ip));
|
||||||
|
dummyNode1.Misbehaving(1);
|
||||||
|
BOOST_CHECK(CNode::IsBanned(addr1.ip));
|
||||||
|
mapArgs["-banscore"] = "100";
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(DoS_bantime)
|
||||||
|
{
|
||||||
|
CNode::ClearBanned();
|
||||||
|
int64 nStartTime = GetTime();
|
||||||
|
SetMockTime(nStartTime); // Overrides future calls to GetTime()
|
||||||
|
|
||||||
|
CAddress addr(0xa0b0c001);
|
||||||
|
CNode dummyNode(INVALID_SOCKET, addr, true);
|
||||||
|
|
||||||
|
dummyNode.Misbehaving(100);
|
||||||
|
BOOST_CHECK(CNode::IsBanned(addr.ip));
|
||||||
|
|
||||||
|
SetMockTime(nStartTime+60*60);
|
||||||
|
BOOST_CHECK(CNode::IsBanned(addr.ip));
|
||||||
|
|
||||||
|
SetMockTime(nStartTime+60*60*24+1);
|
||||||
|
BOOST_CHECK(!CNode::IsBanned(addr.ip));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE_END()
|
|
@ -8,7 +8,7 @@
|
||||||
#include "uint256_tests.cpp"
|
#include "uint256_tests.cpp"
|
||||||
#include "script_tests.cpp"
|
#include "script_tests.cpp"
|
||||||
#include "transaction_tests.cpp"
|
#include "transaction_tests.cpp"
|
||||||
|
#include "DoS_tests.cpp"
|
||||||
|
|
||||||
CWallet* pwalletMain;
|
CWallet* pwalletMain;
|
||||||
|
|
||||||
|
|
|
@ -813,11 +813,20 @@ void ShrinkDebugFile()
|
||||||
// - Median of other nodes's clocks
|
// - Median of other nodes's clocks
|
||||||
// - The user (asking the user to fix the system clock if the first two disagree)
|
// - The user (asking the user to fix the system clock if the first two disagree)
|
||||||
//
|
//
|
||||||
|
static int64 nMockTime = 0; // For unit testing
|
||||||
|
|
||||||
int64 GetTime()
|
int64 GetTime()
|
||||||
{
|
{
|
||||||
|
if (nMockTime) return nMockTime;
|
||||||
|
|
||||||
return time(NULL);
|
return time(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetMockTime(int64 nMockTimeIn)
|
||||||
|
{
|
||||||
|
nMockTime = nMockTimeIn;
|
||||||
|
}
|
||||||
|
|
||||||
static int64 nTimeOffset = 0;
|
static int64 nTimeOffset = 0;
|
||||||
|
|
||||||
int64 GetAdjustedTime()
|
int64 GetAdjustedTime()
|
||||||
|
|
|
@ -197,6 +197,7 @@ void ShrinkDebugFile();
|
||||||
int GetRandInt(int nMax);
|
int GetRandInt(int nMax);
|
||||||
uint64 GetRand(uint64 nMax);
|
uint64 GetRand(uint64 nMax);
|
||||||
int64 GetTime();
|
int64 GetTime();
|
||||||
|
void SetMockTime(int64 nMockTimeIn);
|
||||||
int64 GetAdjustedTime();
|
int64 GetAdjustedTime();
|
||||||
void AddTimeData(unsigned int ip, int64 nTime);
|
void AddTimeData(unsigned int ip, int64 nTime);
|
||||||
std::string FormatFullVersion();
|
std::string FormatFullVersion();
|
||||||
|
|
Loading…
Reference in a new issue