1 // Copyright (c) 2011-2017 The Bitcoin Core developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 // Unit tests for denial-of-service detection/prevention code
7 #include <chainparams.h>
10 #include <net_processing.h>
12 #include <script/sign.h>
13 #include <serialize.h>
15 #include <validation.h>
17 #include <test/test_bitcoin.h>
21 #include <boost/test/unit_test.hpp>
23 // Tests these internal-to-net_processing.cpp methods:
24 extern bool AddOrphanTx(const CTransactionRef
& tx
, NodeId peer
);
25 extern void EraseOrphansFor(NodeId peer
);
26 extern unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans
);
32 extern std::map
<uint256
, COrphanTx
> mapOrphanTransactions
;
34 CService
ip(uint32_t i
)
38 return CService(CNetAddr(s
), Params().GetDefaultPort());
43 void UpdateLastBlockAnnounceTime(NodeId node
, int64_t time_in_seconds
);
45 BOOST_FIXTURE_TEST_SUITE(DoS_tests
, TestingSetup
)
47 // Test eviction of an outbound peer whose chain never advances
48 // Mock a node connection, and use mocktime to simulate a peer
49 // which never sends any headers messages. PeerLogic should
50 // decide to evict that outbound peer, after the appropriate timeouts.
51 // Note that we protect 4 outbound nodes from being subject to
52 // this logic; this test takes advantage of that protection only
53 // being applied to nodes which send headers with sufficient
55 BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction
)
57 std::atomic
<bool> interruptDummy(false);
59 // Mock an outbound peer
60 CAddress
addr1(ip(0xa0b0c001), NODE_NONE
);
61 CNode
dummyNode1(id
++, ServiceFlags(NODE_NETWORK
|NODE_WITNESS
), 0, INVALID_SOCKET
, addr1
, 0, 0, CAddress(), "", /*fInboundIn=*/ false);
62 dummyNode1
.SetSendVersion(PROTOCOL_VERSION
);
64 peerLogic
->InitializeNode(&dummyNode1
);
65 dummyNode1
.nVersion
= 1;
66 dummyNode1
.fSuccessfullyConnected
= true;
68 // This test requires that we have a chain with non-zero work.
70 BOOST_CHECK(chainActive
.Tip() != nullptr);
71 BOOST_CHECK(chainActive
.Tip()->nChainWork
> 0);
74 LOCK(dummyNode1
.cs_sendProcessing
);
75 peerLogic
->SendMessages(&dummyNode1
, interruptDummy
); // should result in getheaders
76 LOCK(dummyNode1
.cs_vSend
);
77 BOOST_CHECK(dummyNode1
.vSendMsg
.size() > 0);
78 dummyNode1
.vSendMsg
.clear();
80 int64_t nStartTime
= GetTime();
82 SetMockTime(nStartTime
+21*60);
83 peerLogic
->SendMessages(&dummyNode1
, interruptDummy
); // should result in getheaders
84 BOOST_CHECK(dummyNode1
.vSendMsg
.size() > 0);
85 // Wait 3 more minutes
86 SetMockTime(nStartTime
+24*60);
87 peerLogic
->SendMessages(&dummyNode1
, interruptDummy
); // should result in disconnect
88 BOOST_CHECK(dummyNode1
.fDisconnect
== true);
92 peerLogic
->FinalizeNode(dummyNode1
.GetId(), dummy
);
95 void AddRandomOutboundPeer(std::vector
<CNode
*> &vNodes
, PeerLogicValidation
&peerLogic
)
97 CAddress
addr(ip(GetRandInt(0xffffffff)), NODE_NONE
);
98 vNodes
.emplace_back(new CNode(id
++, ServiceFlags(NODE_NETWORK
|NODE_WITNESS
), 0, INVALID_SOCKET
, addr
, 0, 0, CAddress(), "", /*fInboundIn=*/ false));
99 CNode
&node
= *vNodes
.back();
100 node
.SetSendVersion(PROTOCOL_VERSION
);
102 peerLogic
.InitializeNode(&node
);
104 node
.fSuccessfullyConnected
= true;
106 CConnmanTest::AddNode(node
);
109 BOOST_AUTO_TEST_CASE(stale_tip_peer_management
)
111 const Consensus::Params
& consensusParams
= Params().GetConsensus();
112 constexpr int nMaxOutbound
= 8;
113 CConnman::Options options
;
114 options
.nMaxConnections
= 125;
115 options
.nMaxOutbound
= nMaxOutbound
;
116 options
.nMaxFeeler
= 1;
118 connman
->Init(options
);
119 std::vector
<CNode
*> vNodes
;
121 // Mock some outbound peers
122 for (int i
=0; i
<nMaxOutbound
; ++i
) {
123 AddRandomOutboundPeer(vNodes
, *peerLogic
);
126 peerLogic
->CheckForStaleTipAndEvictPeers(consensusParams
);
128 // No nodes should be marked for disconnection while we have no extra peers
129 for (const CNode
*node
: vNodes
) {
130 BOOST_CHECK(node
->fDisconnect
== false);
133 SetMockTime(GetTime() + 3*consensusParams
.nPowTargetSpacing
+ 1);
135 // Now tip should definitely be stale, and we should look for an extra
137 peerLogic
->CheckForStaleTipAndEvictPeers(consensusParams
);
138 BOOST_CHECK(connman
->GetTryNewOutboundPeer());
140 // Still no peers should be marked for disconnection
141 for (const CNode
*node
: vNodes
) {
142 BOOST_CHECK(node
->fDisconnect
== false);
145 // If we add one more peer, something should get marked for eviction
146 // on the next check (since we're mocking the time to be in the future, the
147 // required time connected check should be satisfied).
148 AddRandomOutboundPeer(vNodes
, *peerLogic
);
150 peerLogic
->CheckForStaleTipAndEvictPeers(consensusParams
);
151 for (int i
=0; i
<nMaxOutbound
; ++i
) {
152 BOOST_CHECK(vNodes
[i
]->fDisconnect
== false);
154 // Last added node should get marked for eviction
155 BOOST_CHECK(vNodes
.back()->fDisconnect
== true);
157 vNodes
.back()->fDisconnect
= false;
159 // Update the last announced block time for the last
160 // peer, and check that the next newest node gets evicted.
161 UpdateLastBlockAnnounceTime(vNodes
.back()->GetId(), GetTime());
163 peerLogic
->CheckForStaleTipAndEvictPeers(consensusParams
);
164 for (int i
=0; i
<nMaxOutbound
-1; ++i
) {
165 BOOST_CHECK(vNodes
[i
]->fDisconnect
== false);
167 BOOST_CHECK(vNodes
[nMaxOutbound
-1]->fDisconnect
== true);
168 BOOST_CHECK(vNodes
.back()->fDisconnect
== false);
171 for (const CNode
*node
: vNodes
) {
172 peerLogic
->FinalizeNode(node
->GetId(), dummy
);
175 CConnmanTest::ClearNodes();
178 BOOST_AUTO_TEST_CASE(DoS_banning
)
180 std::atomic
<bool> interruptDummy(false);
182 connman
->ClearBanned();
183 CAddress
addr1(ip(0xa0b0c001), NODE_NONE
);
184 CNode
dummyNode1(id
++, NODE_NETWORK
, 0, INVALID_SOCKET
, addr1
, 0, 0, CAddress(), "", true);
185 dummyNode1
.SetSendVersion(PROTOCOL_VERSION
);
186 peerLogic
->InitializeNode(&dummyNode1
);
187 dummyNode1
.nVersion
= 1;
188 dummyNode1
.fSuccessfullyConnected
= true;
191 Misbehaving(dummyNode1
.GetId(), 100); // Should get banned
193 LOCK(dummyNode1
.cs_sendProcessing
);
194 peerLogic
->SendMessages(&dummyNode1
, interruptDummy
);
195 BOOST_CHECK(connman
->IsBanned(addr1
));
196 BOOST_CHECK(!connman
->IsBanned(ip(0xa0b0c001|0x0000ff00))); // Different IP, not banned
198 CAddress
addr2(ip(0xa0b0c002), NODE_NONE
);
199 CNode
dummyNode2(id
++, NODE_NETWORK
, 0, INVALID_SOCKET
, addr2
, 1, 1, CAddress(), "", true);
200 dummyNode2
.SetSendVersion(PROTOCOL_VERSION
);
201 peerLogic
->InitializeNode(&dummyNode2
);
202 dummyNode2
.nVersion
= 1;
203 dummyNode2
.fSuccessfullyConnected
= true;
206 Misbehaving(dummyNode2
.GetId(), 50);
208 LOCK(dummyNode2
.cs_sendProcessing
);
209 peerLogic
->SendMessages(&dummyNode2
, interruptDummy
);
210 BOOST_CHECK(!connman
->IsBanned(addr2
)); // 2 not banned yet...
211 BOOST_CHECK(connman
->IsBanned(addr1
)); // ... but 1 still should be
214 Misbehaving(dummyNode2
.GetId(), 50);
216 peerLogic
->SendMessages(&dummyNode2
, interruptDummy
);
217 BOOST_CHECK(connman
->IsBanned(addr2
));
220 peerLogic
->FinalizeNode(dummyNode1
.GetId(), dummy
);
221 peerLogic
->FinalizeNode(dummyNode2
.GetId(), dummy
);
224 BOOST_AUTO_TEST_CASE(DoS_banscore
)
226 std::atomic
<bool> interruptDummy(false);
228 connman
->ClearBanned();
229 gArgs
.ForceSetArg("-banscore", "111"); // because 11 is my favorite number
230 CAddress
addr1(ip(0xa0b0c001), NODE_NONE
);
231 CNode
dummyNode1(id
++, NODE_NETWORK
, 0, INVALID_SOCKET
, addr1
, 3, 1, CAddress(), "", true);
232 dummyNode1
.SetSendVersion(PROTOCOL_VERSION
);
233 peerLogic
->InitializeNode(&dummyNode1
);
234 dummyNode1
.nVersion
= 1;
235 dummyNode1
.fSuccessfullyConnected
= true;
238 Misbehaving(dummyNode1
.GetId(), 100);
240 LOCK(dummyNode1
.cs_sendProcessing
);
241 peerLogic
->SendMessages(&dummyNode1
, interruptDummy
);
242 BOOST_CHECK(!connman
->IsBanned(addr1
));
245 Misbehaving(dummyNode1
.GetId(), 10);
247 peerLogic
->SendMessages(&dummyNode1
, interruptDummy
);
248 BOOST_CHECK(!connman
->IsBanned(addr1
));
251 Misbehaving(dummyNode1
.GetId(), 1);
253 peerLogic
->SendMessages(&dummyNode1
, interruptDummy
);
254 BOOST_CHECK(connman
->IsBanned(addr1
));
255 gArgs
.ForceSetArg("-banscore", std::to_string(DEFAULT_BANSCORE_THRESHOLD
));
258 peerLogic
->FinalizeNode(dummyNode1
.GetId(), dummy
);
261 BOOST_AUTO_TEST_CASE(DoS_bantime
)
263 std::atomic
<bool> interruptDummy(false);
265 connman
->ClearBanned();
266 int64_t nStartTime
= GetTime();
267 SetMockTime(nStartTime
); // Overrides future calls to GetTime()
269 CAddress
addr(ip(0xa0b0c001), NODE_NONE
);
270 CNode
dummyNode(id
++, NODE_NETWORK
, 0, INVALID_SOCKET
, addr
, 4, 4, CAddress(), "", true);
271 dummyNode
.SetSendVersion(PROTOCOL_VERSION
);
272 peerLogic
->InitializeNode(&dummyNode
);
273 dummyNode
.nVersion
= 1;
274 dummyNode
.fSuccessfullyConnected
= true;
278 Misbehaving(dummyNode
.GetId(), 100);
280 LOCK(dummyNode
.cs_sendProcessing
);
281 peerLogic
->SendMessages(&dummyNode
, interruptDummy
);
282 BOOST_CHECK(connman
->IsBanned(addr
));
284 SetMockTime(nStartTime
+60*60);
285 BOOST_CHECK(connman
->IsBanned(addr
));
287 SetMockTime(nStartTime
+60*60*24+1);
288 BOOST_CHECK(!connman
->IsBanned(addr
));
291 peerLogic
->FinalizeNode(dummyNode
.GetId(), dummy
);
294 CTransactionRef
RandomOrphan()
296 std::map
<uint256
, COrphanTx
>::iterator it
;
298 it
= mapOrphanTransactions
.lower_bound(InsecureRand256());
299 if (it
== mapOrphanTransactions
.end())
300 it
= mapOrphanTransactions
.begin();
301 return it
->second
.tx
;
304 BOOST_AUTO_TEST_CASE(DoS_mapOrphans
)
307 key
.MakeNewKey(true);
308 CBasicKeyStore keystore
;
309 keystore
.AddKey(key
);
311 // 50 orphan transactions:
312 for (int i
= 0; i
< 50; i
++)
314 CMutableTransaction tx
;
316 tx
.vin
[0].prevout
.n
= 0;
317 tx
.vin
[0].prevout
.hash
= InsecureRand256();
318 tx
.vin
[0].scriptSig
<< OP_1
;
320 tx
.vout
[0].nValue
= 1*CENT
;
321 tx
.vout
[0].scriptPubKey
= GetScriptForDestination(key
.GetPubKey().GetID());
323 AddOrphanTx(MakeTransactionRef(tx
), i
);
326 // ... and 50 that depend on other orphans:
327 for (int i
= 0; i
< 50; i
++)
329 CTransactionRef txPrev
= RandomOrphan();
331 CMutableTransaction tx
;
333 tx
.vin
[0].prevout
.n
= 0;
334 tx
.vin
[0].prevout
.hash
= txPrev
->GetHash();
336 tx
.vout
[0].nValue
= 1*CENT
;
337 tx
.vout
[0].scriptPubKey
= GetScriptForDestination(key
.GetPubKey().GetID());
338 SignSignature(keystore
, *txPrev
, tx
, 0, SIGHASH_ALL
);
340 AddOrphanTx(MakeTransactionRef(tx
), i
);
343 // This really-big orphan should be ignored:
344 for (int i
= 0; i
< 10; i
++)
346 CTransactionRef txPrev
= RandomOrphan();
348 CMutableTransaction tx
;
350 tx
.vout
[0].nValue
= 1*CENT
;
351 tx
.vout
[0].scriptPubKey
= GetScriptForDestination(key
.GetPubKey().GetID());
353 for (unsigned int j
= 0; j
< tx
.vin
.size(); j
++)
355 tx
.vin
[j
].prevout
.n
= j
;
356 tx
.vin
[j
].prevout
.hash
= txPrev
->GetHash();
358 SignSignature(keystore
, *txPrev
, tx
, 0, SIGHASH_ALL
);
359 // Re-use same signature for other inputs
360 // (they don't have to be valid for this test)
361 for (unsigned int j
= 1; j
< tx
.vin
.size(); j
++)
362 tx
.vin
[j
].scriptSig
= tx
.vin
[0].scriptSig
;
364 BOOST_CHECK(!AddOrphanTx(MakeTransactionRef(tx
), i
));
368 // Test EraseOrphansFor:
369 for (NodeId i
= 0; i
< 3; i
++)
371 size_t sizeBefore
= mapOrphanTransactions
.size();
373 BOOST_CHECK(mapOrphanTransactions
.size() < sizeBefore
);
376 // Test LimitOrphanTxSize() function:
377 LimitOrphanTxSize(40);
378 BOOST_CHECK(mapOrphanTransactions
.size() <= 40);
379 LimitOrphanTxSize(10);
380 BOOST_CHECK(mapOrphanTransactions
.size() <= 10);
381 LimitOrphanTxSize(0);
382 BOOST_CHECK(mapOrphanTransactions
.empty());
385 BOOST_AUTO_TEST_SUITE_END()