Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Header spam protection #335

Merged
merged 2 commits into from Dec 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/init.cpp
Expand Up @@ -539,6 +539,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
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
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