Skip to content

Commit 00a7dcc

Browse files
committed
net: allow unconnected header announcements only once
Except when there may be a moderate clock difference.
1 parent bdc5776 commit 00a7dcc

File tree

2 files changed

+33
-19
lines changed

2 files changed

+33
-19
lines changed

src/net_processing.cpp

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,6 @@ static constexpr double BLOCK_DOWNLOAD_TIMEOUT_BASE = 1;
129129
static constexpr double BLOCK_DOWNLOAD_TIMEOUT_PER_PEER = 0.5;
130130
/** Maximum number of headers to announce when relaying blocks with headers message.*/
131131
static const unsigned int MAX_BLOCKS_TO_ANNOUNCE = 8;
132-
/** Maximum number of unconnecting headers announcements before DoS score */
133-
static const int MAX_NUM_UNCONNECTING_HEADERS_MSGS = 10;
134132
/** Minimum blocks required to signal NODE_NETWORK_LIMITED */
135133
static const unsigned int NODE_NETWORK_LIMITED_MIN_BLOCKS = 288;
136134
/** Window, in blocks, for connecting to NODE_NETWORK_LIMITED peers */
@@ -379,8 +377,8 @@ struct Peer {
379377
/** Whether we've sent our peer a sendheaders message. **/
380378
std::atomic<bool> m_sent_sendheaders{false};
381379

382-
/** Length of current-streak of unconnecting headers announcements */
383-
int m_num_unconnecting_headers_msgs GUARDED_BY(NetEventsInterface::g_msgproc_mutex){0};
380+
/** Large reorgs are rare, so only permit one such announcement. */
381+
bool m_chicken_little GUARDED_BY(NetEventsInterface::g_msgproc_mutex){0};
384382

385383
/** When to potentially disconnect peer for stalling headers download */
386384
std::chrono::microseconds m_headers_sync_timeout GUARDED_BY(NetEventsInterface::g_msgproc_mutex){0us};
@@ -2544,35 +2542,51 @@ arith_uint256 PeerManagerImpl::GetAntiDoSWorkThreshold()
25442542
*
25452543
* We'll send a getheaders message in response to try to connect the chain.
25462544
*
2547-
* The peer can send up to MAX_NUM_UNCONNECTING_HEADERS_MSGS in a row that
2548-
* don't connect before given DoS points.
2545+
* Allow this only once, except when there may be a moderate clock difference.
25492546
*
25502547
* Once a headers message is received that is valid and does connect,
2551-
* m_num_unconnecting_headers_msgs gets reset back to 0.
2548+
* m_chicken_little gets reset back to false.
25522549
*/
25532550
void PeerManagerImpl::HandleFewUnconnectingHeaders(CNode& pfrom, Peer& peer,
25542551
const std::vector<CBlockHeader>& headers)
25552552
{
2556-
peer.m_num_unconnecting_headers_msgs++;
25572553
// Try to fill in the missing headers.
25582554
const CBlockIndex* best_header{WITH_LOCK(cs_main, return m_chainman.m_best_header)};
25592555
if (MaybeSendGetHeaders(pfrom, GetLocator(best_header), peer)) {
2560-
LogPrint(BCLog::NET, "received header %s: missing prev block %s, sending getheaders (%d) to end (peer=%d, m_num_unconnecting_headers_msgs=%d)\n",
2556+
LogPrint(BCLog::NET, "received header %s: missing prev block %s, sending getheaders (%d) to end (peer=%d, m_chicken_little=%d)\n",
25612557
headers[0].GetHash().ToString(),
25622558
headers[0].hashPrevBlock.ToString(),
25632559
best_header->nHeight,
2564-
pfrom.GetId(), peer.m_num_unconnecting_headers_msgs);
2560+
pfrom.GetId(), peer.m_chicken_little);
25652561
}
25662562

25672563
// Set hashLastUnknownBlock for this peer, so that if we
25682564
// eventually get the headers - even from a different peer -
25692565
// we can use this peer to download.
25702566
WITH_LOCK(cs_main, UpdateBlockAvailability(pfrom.GetId(), headers.back().GetHash()));
25712567

2572-
// The peer may just be broken, so periodically assign DoS points if this
2573-
// condition persists.
2574-
if (peer.m_num_unconnecting_headers_msgs % MAX_NUM_UNCONNECTING_HEADERS_MSGS == 0) {
2575-
Misbehaving(peer, strprintf("%d non-connecting headers", peer.m_num_unconnecting_headers_msgs));
2568+
// If the peer has a slightly different clock, it may send us headers more
2569+
// than 2 hours in the future. This is eventually caught in ContextualCheckBlock(),
2570+
// but because we might have fetched the intermediate headers from a different
2571+
// peer, there's no guarantee we'll disconnect this peer.
2572+
//
2573+
// If the block is too far in the future, we immedidately disconnect.
2574+
// This means a malicious peer could get away with a timestamp in between
2575+
// MAX_FUTURE_BLOCK_TIME and the limit here, but such an attack would be
2576+
// very expensive and because of the upper limit, not useful.
2577+
for (auto header : headers) {
2578+
if (header.Time() > NodeClock::now() + std::chrono::seconds{MAX_FUTURE_BLOCK_TIME * 10}) {
2579+
Misbehaving(peer, "time-too-new");
2580+
}
2581+
}
2582+
2583+
// Finally, if the peer told us about a (extremely rare) long reorg before,
2584+
// we assume it's broken and just disconnect.
2585+
if (peer.m_chicken_little) {
2586+
Misbehaving(peer, "non-connecting headers");
2587+
} else {
2588+
// The peer told us about a long reorg. We'll consider that once.
2589+
peer.m_chicken_little = true;
25762590
}
25772591
}
25782592

@@ -2819,10 +2833,10 @@ void PeerManagerImpl::HeadersDirectFetchBlocks(CNode& pfrom, const Peer& peer, c
28192833
void PeerManagerImpl::UpdatePeerStateForReceivedHeaders(CNode& pfrom, Peer& peer,
28202834
const CBlockIndex& last_header, bool received_new_header, bool may_have_more_headers)
28212835
{
2822-
if (peer.m_num_unconnecting_headers_msgs > 0) {
2823-
LogPrint(BCLog::NET, "peer=%d: resetting m_num_unconnecting_headers_msgs (%d -> 0)\n", pfrom.GetId(), peer.m_num_unconnecting_headers_msgs);
2836+
if (peer.m_chicken_little > 0) {
2837+
LogPrint(BCLog::NET, "peer=%d: resetting m_chicken_little (%d -> 0)\n", pfrom.GetId(), peer.m_chicken_little);
28242838
}
2825-
peer.m_num_unconnecting_headers_msgs = 0;
2839+
peer.m_chicken_little = 0;
28262840

28272841
LOCK(cs_main);
28282842
CNodeState *nodestate = State(pfrom.GetId());

test/functional/p2p_sendheaders.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ def test_nonnull_locators(self, test_node, inv_node):
546546
blocks = []
547547
# Now we test that if we repeatedly don't send connecting headers, we
548548
# don't go into an infinite loop trying to get them to connect.
549-
MAX_NUM_UNCONNECTING_HEADERS_MSGS = 10
549+
MAX_NUM_UNCONNECTING_HEADERS_MSGS = 2
550550
for _ in range(MAX_NUM_UNCONNECTING_HEADERS_MSGS + 1):
551551
blocks.append(create_block(tip, create_coinbase(height), block_time))
552552
blocks[-1].solve()
@@ -568,7 +568,7 @@ def test_nonnull_locators(self, test_node, inv_node):
568568
blocks = blocks[2:]
569569

570570
# Now try to see how many unconnecting headers we can send
571-
# before we get disconnected. Should be 5*MAX_NUM_UNCONNECTING_HEADERS_MSGS
571+
# before we get disconnected. Should be MAX_NUM_UNCONNECTING_HEADERS_MSGS
572572
for i in range(MAX_NUM_UNCONNECTING_HEADERS_MSGS - 1):
573573
# Send a header that doesn't connect, check that we get a getheaders.
574574
with p2p_lock:

0 commit comments

Comments
 (0)