Skip to content

Commit

Permalink
Merge pull request #335 from aguycalled/header-spam-protection
Browse files Browse the repository at this point in the history
Header spam protection
  • Loading branch information
proletesseract authored Dec 7, 2018
2 parents eb6a1a2 + fa8880e commit 210a22d
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 3 deletions.
3 changes: 3 additions & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,9 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-rpcworkqueue=<n>", strprintf("Set the depth of the work queue to service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE));
strUsage += HelpMessageOpt("-rpcservertimeout=<n>", strprintf("Timeout during HTTP requests (default: %d)", DEFAULT_HTTP_SERVER_TIMEOUT));
}
strUsage += HelpMessageOpt("-headerspamfilter=<n>", strprintf(_("Use header spam filter (default: %u)"), DEFAULT_HEADER_SPAM_FILTER));
strUsage += HelpMessageOpt("-headerspamfiltermaxsize=<n>", strprintf(_("Maximum size of the list of indexes in the header spam filter (default: %u)"), DEFAULT_HEADER_SPAM_FILTER_MAX_SIZE));
strUsage += HelpMessageOpt("-headerspamfiltermaxavg=<n>", strprintf(_("Maximum average size of an index occurrence in the header spam filter (default: %u)"), DEFAULT_HEADER_SPAM_FILTER_MAX_AVG));

return strUsage;
}
Expand Down
129 changes: 126 additions & 3 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,89 @@ struct CBlockReject {
uint256 hashBlock;
};

class CNodeHeaders
{
public:
CNodeHeaders():
maxSize(0),
maxAvg(0)
{
maxSize = GetArg("-headerspamfiltermaxsize", DEFAULT_HEADER_SPAM_FILTER_MAX_SIZE);
maxAvg = GetArg("-headerspamfiltermaxavg", DEFAULT_HEADER_SPAM_FILTER_MAX_AVG);
}

bool addHeaders(int nBegin, int nEnd)
{

if(nBegin > 0 && nEnd > 0 && maxSize && maxAvg)
{

for(int point = nBegin; point<= nEnd; point++)
{
addPoint(point);
}

return true;
}

return false;
}

bool updateState(CValidationState& state, bool ret)
{
// No headers
size_t size = points.size();
if(size == 0)
return ret;

// Compute the number of the received headers
size_t nHeaders = 0;
for(auto point : points)
{
nHeaders += point.second;
}

// Compute the average value per height
double nAvgValue = (double)nHeaders / size;

// Ban the node if try to spam
bool banNode = (nAvgValue >= 1.5 * maxAvg && size >= maxAvg) ||
(nAvgValue >= maxAvg && nHeaders >= maxSize) ||
(nHeaders >= maxSize * 3);
if(banNode)
{
// Clear the points and ban the node
points.clear();
return state.DoS(100, false, REJECT_INVALID, "header-spam", false, "ban node for sending spam");
}

return ret;
}

private:
void addPoint(int height)
{
// Erace the last element in the list
if(points.size() == maxSize)
{
points.erase(points.begin());
}

// Add the point to the list
int occurrence = 0;
auto mi = points.find(height);
if (mi != points.end())
occurrence = (*mi).second;
occurrence++;
points[height] = occurrence;
}

private:
std::map<int,int> points;
size_t maxSize;
size_t maxAvg;
};

/**
* Maintain validation-specific state about nodes, protected by cs_main, instead
* by CNode's own locks. This simplifies asynchronous operation, where
Expand Down Expand Up @@ -324,6 +407,8 @@ struct CNodeState {
//! Whether this peer can give us witnesses
bool fHaveWitness;

CNodeHeaders headers;

CNodeState() {
fCurrentlyConnected = false;
nMisbehavior = 0;
Expand Down Expand Up @@ -7194,25 +7279,63 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
return true;
}

bool ret = true;
bool bFirst = true;
string strError = "";

int nFirst = 0;
int nLast = 0;

CBlockIndex *pindexLast = NULL;

BOOST_FOREACH(const CBlock& header, headers) {
CValidationState state;
if (pindexLast != NULL && header.hashPrevBlock != pindexLast->GetBlockHash()) {
Misbehaving(pfrom->GetId(), 20);
return error("non-continuous headers sequence");
ret = false;
strError = "non-continuous headers sequence";
break;
}
CBlockHeader pblockheader = CBlockHeader(header);
if (!AcceptBlockHeader(pblockheader, state, chainparams, &pindexLast)) {

int nDoS;
if (state.IsInvalid(nDoS)) {
if (nDoS > 0)
Misbehaving(pfrom->GetId(), nDoS);
return error("invalid header received");
ret = false;
strError = "invalid header received";
break;
}
}
if (pindexLast) {
nLast = pindexLast->nHeight;
if (bFirst){
nFirst = pindexLast->nHeight;
bFirst = false;
}
}
}

if(GetBoolArg("-headerspamfilter", DEFAULT_HEADER_SPAM_FILTER))
{
LOCK(cs_main);
CValidationState state;
CNodeState *nodestate = State(pfrom->GetId());
nodestate->headers.addHeaders(nFirst, nLast);
int nDoS;
ret = nodestate->headers.updateState(state, ret);
if (state.IsInvalid(nDoS)) {
if (nDoS > 0)
Misbehaving(pfrom->GetId(), nDoS);
ret = false;
strError = strError!="" ? strError + " / ": "";
strError = "header spam protection";
}
}

if (!ret)
return error(strError.c_str());

if (nodestate->nUnconnectingHeaders > 0) {
LogPrint("net", "peer=%d: resetting nUnconnectingHeaders (%d -> 0)\n", pfrom->id, nodestate->nUnconnectingHeaders);
}
Expand Down
7 changes: 7 additions & 0 deletions src/main.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,13 @@ static const bool DEFAULT_ENABLE_REPLACEMENT = true;
/** Default for using fee filter */
static const bool DEFAULT_FEEFILTER = true;

/** Default for -headerspamfilter, use header spam filter */
static const bool DEFAULT_HEADER_SPAM_FILTER = true;
/** Default for -headerspamfiltermaxsize, maximum size of the list of indexes in the header spam filter */
static const unsigned int DEFAULT_HEADER_SPAM_FILTER_MAX_SIZE = COINBASE_MATURITY;
/** Default for -headerspamfiltermaxavg, maximum average size of an index occurrence in the header spam filter */
static const unsigned int DEFAULT_HEADER_SPAM_FILTER_MAX_AVG = 10;

/** Maximum number of headers to announce when relaying blocks with headers message.*/
static const unsigned int MAX_BLOCKS_TO_ANNOUNCE = 8;

Expand Down

0 comments on commit 210a22d

Please sign in to comment.