denialofservice_tests.cpp raw
1 // Copyright (c) 2011-present 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.
4
5 // Unit tests for denial-of-service detection/prevention code
6
7 #include <banman.h>
8 #include <chainparams.h>
9 #include <common/args.h>
10 #include <net.h>
11 #include <net_processing.h>
12 #include <pubkey.h>
13 #include <script/sign.h>
14 #include <script/signingprovider.h>
15 #include <serialize.h>
16 #include <test/util/net.h>
17 #include <test/util/random.h>
18 #include <test/util/setup_common.h>
19 #include <test/util/time.h>
20 #include <util/string.h>
21 #include <util/time.h>
22 #include <validation.h>
23
24 #include <array>
25 #include <cstdint>
26
27 #include <boost/test/unit_test.hpp>
28
29 static CService ip(uint32_t i)
30 {
31 struct in_addr s;
32 s.s_addr = i;
33 return CService(CNetAddr(s), Params().GetDefaultPort());
34 }
35
36 BOOST_FIXTURE_TEST_SUITE(denialofservice_tests, TestingSetup)
37
38 // Test eviction of an outbound peer whose chain never advances
39 // Mock a node connection, and use mocktime to simulate a peer
40 // which never sends any headers messages. PeerLogic should
41 // decide to evict that outbound peer, after the appropriate timeouts.
42 // Note that we protect 4 outbound nodes from being subject to
43 // this logic; this test takes advantage of that protection only
44 // being applied to nodes which send headers with sufficient
45 // work.
46 BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction)
47 {
48 LOCK(NetEventsInterface::g_msgproc_mutex);
49
50 ConnmanTestMsg& connman = static_cast<ConnmanTestMsg&>(*m_node.connman);
51 // Disable inactivity checks for this test to avoid interference
52 connman.SetPeerConnectTimeout(99999s);
53 PeerManager& peerman = *m_node.peerman;
54
55 // Mock an outbound peer
56 CAddress addr1(ip(0xa0b0c001), NODE_NONE);
57 NodeId id{0};
58 CNode dummyNode1{id++,
59 /*sock=*/nullptr,
60 addr1,
61 /*nKeyedNetGroupIn=*/0,
62 /*nLocalHostNonceIn=*/0,
63 CAddress(),
64 /*addrNameIn=*/"",
65 ConnectionType::OUTBOUND_FULL_RELAY,
66 /*inbound_onion=*/false,
67 /*network_key=*/0};
68
69 connman.Handshake(
70 /*node=*/dummyNode1,
71 /*successfully_connected=*/true,
72 /*remote_services=*/ServiceFlags(NODE_NETWORK | NODE_WITNESS),
73 /*local_services=*/ServiceFlags(NODE_NETWORK | NODE_WITNESS),
74 /*version=*/PROTOCOL_VERSION,
75 /*relay_txs=*/true);
76
77 // This test requires that we have a chain with non-zero work.
78 {
79 LOCK(cs_main);
80 BOOST_CHECK(m_node.chainman->ActiveChain().Tip() != nullptr);
81 BOOST_CHECK(m_node.chainman->ActiveChain().Tip()->nChainWork > 0);
82 }
83
84 // Test starts here
85 BOOST_CHECK(peerman.SendMessages(dummyNode1)); // should result in getheaders
86
87 {
88 LOCK(dummyNode1.cs_vSend);
89 const auto& [to_send, _more, _msg_type] = dummyNode1.m_transport->GetBytesToSend(false);
90 BOOST_CHECK(!to_send.empty());
91 }
92 connman.FlushSendBuffer(dummyNode1);
93
94 FakeNodeClock clock{};
95 clock += 21min;
96
97 BOOST_CHECK(peerman.SendMessages(dummyNode1)); // should result in getheaders
98 {
99 LOCK(dummyNode1.cs_vSend);
100 const auto& [to_send, _more, _msg_type] = dummyNode1.m_transport->GetBytesToSend(false);
101 BOOST_CHECK(!to_send.empty());
102 }
103
104 clock += 3min;
105 BOOST_CHECK(peerman.SendMessages(dummyNode1)); // should result in disconnect
106 BOOST_CHECK(dummyNode1.fDisconnect == true);
107
108 peerman.FinalizeNode(dummyNode1);
109 }
110
111 struct OutboundTest : TestingSetup {
112 void AddRandomOutboundPeer(NodeId& id, std::vector<CNode*>& vNodes, PeerManager& peerLogic, ConnmanTestMsg& connman, ConnectionType connType, bool onion_peer = false)
113 {
114 CAddress addr;
115
116 if (onion_peer) {
117 auto tor_addr{m_rng.randbytes(ADDR_TORV3_SIZE)};
118 BOOST_REQUIRE(addr.SetSpecial(OnionToString(tor_addr)));
119 }
120
121 while (!addr.IsRoutable()) {
122 addr = CAddress(ip(m_rng.randbits(32)), NODE_NONE);
123 }
124
125 vNodes.emplace_back(new CNode{id++,
126 /*sock=*/nullptr,
127 addr,
128 /*nKeyedNetGroupIn=*/0,
129 /*nLocalHostNonceIn=*/0,
130 CAddress(),
131 /*addrNameIn=*/"",
132 connType,
133 /*inbound_onion=*/false,
134 /*network_key=*/0});
135 CNode &node = *vNodes.back();
136 node.SetCommonVersion(PROTOCOL_VERSION);
137
138 peerLogic.InitializeNode(node, ServiceFlags(NODE_NETWORK | NODE_WITNESS));
139 node.fSuccessfullyConnected = true;
140
141 connman.AddTestNode(node);
142 }
143 }; // struct OutboundTest
144
145 BOOST_FIXTURE_TEST_CASE(stale_tip_peer_management, OutboundTest)
146 {
147 NodeId id{0};
148 auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman, Params());
149 auto peerLogic = PeerManager::make(*connman, *m_node.addrman, nullptr, *m_node.chainman, *m_node.mempool, *m_node.warnings, {});
150
151 constexpr int max_outbound_full_relay = MAX_OUTBOUND_FULL_RELAY_CONNECTIONS;
152 CConnman::Options options;
153 options.m_max_automatic_connections = DEFAULT_MAX_PEER_CONNECTIONS;
154
155 const auto time_init{Now<NodeSeconds>()};
156 FakeNodeClock clock{time_init};
157 const auto delta{3 * std::chrono::seconds{m_node.chainman->GetConsensus().nPowTargetSpacing} + 1s};
158 connman->Init(options);
159 std::vector<CNode *> vNodes;
160
161 // Mock some outbound peers
162 for (int i = 0; i < max_outbound_full_relay; ++i) {
163 AddRandomOutboundPeer(id, vNodes, *peerLogic, *connman, ConnectionType::OUTBOUND_FULL_RELAY);
164 }
165
166 peerLogic->CheckForStaleTipAndEvictPeers();
167
168 // No nodes should be marked for disconnection while we have no extra peers
169 for (const CNode *node : vNodes) {
170 BOOST_CHECK(node->fDisconnect == false);
171 }
172
173 clock += delta;
174
175 // Now tip should definitely be stale, and we should look for an extra
176 // outbound peer
177 peerLogic->CheckForStaleTipAndEvictPeers();
178 BOOST_CHECK(connman->GetTryNewOutboundPeer());
179
180 // Still no peers should be marked for disconnection
181 for (const CNode *node : vNodes) {
182 BOOST_CHECK(node->fDisconnect == false);
183 }
184
185 // If we add one more peer, something should get marked for eviction
186 // on the next check (since we're mocking the time to be in the future, the
187 // required time connected check should be satisfied).
188 clock.set(time_init);
189 AddRandomOutboundPeer(id, vNodes, *peerLogic, *connman, ConnectionType::OUTBOUND_FULL_RELAY);
190 clock += delta;
191
192 peerLogic->CheckForStaleTipAndEvictPeers();
193 for (int i = 0; i < max_outbound_full_relay; ++i) {
194 BOOST_CHECK(vNodes[i]->fDisconnect == false);
195 }
196 // Last added node should get marked for eviction
197 BOOST_CHECK(vNodes.back()->fDisconnect == true);
198
199 vNodes.back()->fDisconnect = false;
200
201 // Update the last announced block time for the last
202 // peer, and check that the next newest node gets evicted.
203 peerLogic->UpdateLastBlockAnnounceTime(vNodes.back()->GetId(), GetTime());
204
205 peerLogic->CheckForStaleTipAndEvictPeers();
206 for (int i = 0; i < max_outbound_full_relay - 1; ++i) {
207 BOOST_CHECK(vNodes[i]->fDisconnect == false);
208 }
209 BOOST_CHECK(vNodes[max_outbound_full_relay-1]->fDisconnect == true);
210 BOOST_CHECK(vNodes.back()->fDisconnect == false);
211
212 vNodes[max_outbound_full_relay - 1]->fDisconnect = false;
213
214 // Add an onion peer, that will be protected because it is the only one for
215 // its network, so another peer gets disconnected instead.
216 clock.set(time_init);
217 AddRandomOutboundPeer(id, vNodes, *peerLogic, *connman, ConnectionType::OUTBOUND_FULL_RELAY, /*onion_peer=*/true);
218 clock += delta;
219 peerLogic->CheckForStaleTipAndEvictPeers();
220
221 for (int i = 0; i < max_outbound_full_relay - 2; ++i) {
222 BOOST_CHECK(vNodes[i]->fDisconnect == false);
223 }
224 BOOST_CHECK(vNodes[max_outbound_full_relay - 2]->fDisconnect == false);
225 BOOST_CHECK(vNodes[max_outbound_full_relay - 1]->fDisconnect == true);
226 BOOST_CHECK(vNodes[max_outbound_full_relay]->fDisconnect == false);
227
228 // Add a second onion peer which won't be protected
229 clock.set(time_init);
230 AddRandomOutboundPeer(id, vNodes, *peerLogic, *connman, ConnectionType::OUTBOUND_FULL_RELAY, /*onion_peer=*/true);
231 clock += delta;
232 peerLogic->CheckForStaleTipAndEvictPeers();
233
234 BOOST_CHECK(vNodes.back()->fDisconnect == true);
235
236 for (const CNode *node : vNodes) {
237 peerLogic->FinalizeNode(*node);
238 }
239
240 connman->ClearTestNodes();
241 }
242
243 BOOST_FIXTURE_TEST_CASE(block_relay_only_eviction, OutboundTest)
244 {
245 NodeId id{0};
246 FakeNodeClock clock{};
247 auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman, Params());
248 auto peerLogic = PeerManager::make(*connman, *m_node.addrman, nullptr, *m_node.chainman, *m_node.mempool, *m_node.warnings, {});
249
250 constexpr int max_outbound_block_relay{MAX_BLOCK_RELAY_ONLY_CONNECTIONS};
251 constexpr auto MINIMUM_CONNECT_TIME{30s};
252 CConnman::Options options;
253 options.m_max_automatic_connections = DEFAULT_MAX_PEER_CONNECTIONS;
254
255 connman->Init(options);
256 std::vector<CNode*> vNodes;
257
258 // Add block-relay-only peers up to the limit
259 for (int i = 0; i < max_outbound_block_relay; ++i) {
260 AddRandomOutboundPeer(id, vNodes, *peerLogic, *connman, ConnectionType::BLOCK_RELAY);
261 }
262 peerLogic->CheckForStaleTipAndEvictPeers();
263
264 for (int i = 0; i < max_outbound_block_relay; ++i) {
265 BOOST_CHECK(vNodes[i]->fDisconnect == false);
266 }
267
268 // Add an extra block-relay-only peer breaking the limit (mocks logic in ThreadOpenConnections)
269 AddRandomOutboundPeer(id, vNodes, *peerLogic, *connman, ConnectionType::BLOCK_RELAY);
270 peerLogic->CheckForStaleTipAndEvictPeers();
271
272 // The extra peer should only get marked for eviction after MINIMUM_CONNECT_TIME
273 for (int i = 0; i < max_outbound_block_relay; ++i) {
274 BOOST_CHECK(vNodes[i]->fDisconnect == false);
275 }
276 BOOST_CHECK(vNodes.back()->fDisconnect == false);
277
278 clock += MINIMUM_CONNECT_TIME;
279 peerLogic->CheckForStaleTipAndEvictPeers();
280 for (int i = 0; i < max_outbound_block_relay; ++i) {
281 BOOST_CHECK(vNodes[i]->fDisconnect == false);
282 }
283 BOOST_CHECK(vNodes.back()->fDisconnect == true);
284
285 // Update the last block time for the extra peer,
286 // and check that the next youngest peer gets evicted.
287 vNodes.back()->fDisconnect = false;
288 vNodes.back()->m_last_block_time = GetTime<std::chrono::seconds>();
289
290 peerLogic->CheckForStaleTipAndEvictPeers();
291 for (int i = 0; i < max_outbound_block_relay - 1; ++i) {
292 BOOST_CHECK(vNodes[i]->fDisconnect == false);
293 }
294 BOOST_CHECK(vNodes[max_outbound_block_relay - 1]->fDisconnect == true);
295 BOOST_CHECK(vNodes.back()->fDisconnect == false);
296
297 for (const CNode* node : vNodes) {
298 peerLogic->FinalizeNode(*node);
299 }
300 connman->ClearTestNodes();
301 }
302
303 BOOST_AUTO_TEST_CASE(peer_discouragement)
304 {
305 LOCK(NetEventsInterface::g_msgproc_mutex);
306
307 auto banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist", nullptr, DEFAULT_MISBEHAVING_BANTIME);
308 auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman, Params());
309 auto peerLogic = PeerManager::make(*connman, *m_node.addrman, banman.get(), *m_node.chainman, *m_node.mempool, *m_node.warnings, {});
310
311 CNetAddr tor_netaddr;
312 BOOST_REQUIRE(
313 tor_netaddr.SetSpecial("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion"));
314 const CService tor_service{tor_netaddr, Params().GetDefaultPort()};
315
316 const std::array<CAddress, 3> addr{CAddress{ip(0xa0b0c001), NODE_NONE},
317 CAddress{ip(0xa0b0c002), NODE_NONE},
318 CAddress{tor_service, NODE_NONE}};
319
320 const CNetAddr other_addr{ip(0xa0b0ff01)}; // Not any of addr[].
321
322 std::array<CNode*, 3> nodes;
323
324 banman->ClearBanned();
325 NodeId id{0};
326 nodes[0] = new CNode{id++,
327 /*sock=*/nullptr,
328 addr[0],
329 /*nKeyedNetGroupIn=*/0,
330 /*nLocalHostNonceIn=*/0,
331 CAddress(),
332 /*addrNameIn=*/"",
333 ConnectionType::INBOUND,
334 /*inbound_onion=*/false,
335 /*network_key=*/1};
336 nodes[0]->SetCommonVersion(PROTOCOL_VERSION);
337 peerLogic->InitializeNode(*nodes[0], NODE_NETWORK);
338 nodes[0]->fSuccessfullyConnected = true;
339 connman->AddTestNode(*nodes[0]);
340 peerLogic->UnitTestMisbehaving(nodes[0]->GetId()); // Should be discouraged
341 BOOST_CHECK(peerLogic->SendMessages(*nodes[0]));
342
343 BOOST_CHECK(banman->IsDiscouraged(addr[0]));
344 BOOST_CHECK(nodes[0]->fDisconnect);
345 BOOST_CHECK(!banman->IsDiscouraged(other_addr)); // Different address, not discouraged
346
347 nodes[1] = new CNode{id++,
348 /*sock=*/nullptr,
349 addr[1],
350 /*nKeyedNetGroupIn=*/1,
351 /*nLocalHostNonceIn=*/1,
352 CAddress(),
353 /*addrNameIn=*/"",
354 ConnectionType::INBOUND,
355 /*inbound_onion=*/false,
356 /*network_key=*/1};
357 nodes[1]->SetCommonVersion(PROTOCOL_VERSION);
358 peerLogic->InitializeNode(*nodes[1], NODE_NETWORK);
359 nodes[1]->fSuccessfullyConnected = true;
360 connman->AddTestNode(*nodes[1]);
361 BOOST_CHECK(peerLogic->SendMessages(*nodes[1]));
362 // [0] is still discouraged/disconnected.
363 BOOST_CHECK(banman->IsDiscouraged(addr[0]));
364 BOOST_CHECK(nodes[0]->fDisconnect);
365 // [1] is not discouraged/disconnected yet.
366 BOOST_CHECK(!banman->IsDiscouraged(addr[1]));
367 BOOST_CHECK(!nodes[1]->fDisconnect);
368 peerLogic->UnitTestMisbehaving(nodes[1]->GetId());
369 BOOST_CHECK(peerLogic->SendMessages(*nodes[1]));
370 // Expect both [0] and [1] to be discouraged/disconnected now.
371 BOOST_CHECK(banman->IsDiscouraged(addr[0]));
372 BOOST_CHECK(nodes[0]->fDisconnect);
373 BOOST_CHECK(banman->IsDiscouraged(addr[1]));
374 BOOST_CHECK(nodes[1]->fDisconnect);
375
376 // Make sure non-IP peers are discouraged and disconnected properly.
377
378 nodes[2] = new CNode{id++,
379 /*sock=*/nullptr,
380 addr[2],
381 /*nKeyedNetGroupIn=*/1,
382 /*nLocalHostNonceIn=*/1,
383 CAddress(),
384 /*addrNameIn=*/"",
385 ConnectionType::OUTBOUND_FULL_RELAY,
386 /*inbound_onion=*/false,
387 /*network_key=*/2};
388 nodes[2]->SetCommonVersion(PROTOCOL_VERSION);
389 peerLogic->InitializeNode(*nodes[2], NODE_NETWORK);
390 nodes[2]->fSuccessfullyConnected = true;
391 connman->AddTestNode(*nodes[2]);
392 peerLogic->UnitTestMisbehaving(nodes[2]->GetId());
393 BOOST_CHECK(peerLogic->SendMessages(*nodes[2]));
394 BOOST_CHECK(banman->IsDiscouraged(addr[0]));
395 BOOST_CHECK(banman->IsDiscouraged(addr[1]));
396 BOOST_CHECK(banman->IsDiscouraged(addr[2]));
397 BOOST_CHECK(nodes[0]->fDisconnect);
398 BOOST_CHECK(nodes[1]->fDisconnect);
399 BOOST_CHECK(nodes[2]->fDisconnect);
400
401 for (CNode* node : nodes) {
402 peerLogic->FinalizeNode(*node);
403 }
404 connman->ClearTestNodes();
405 }
406
407 BOOST_AUTO_TEST_CASE(DoS_bantime)
408 {
409 LOCK(NetEventsInterface::g_msgproc_mutex);
410
411 auto banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist", nullptr, DEFAULT_MISBEHAVING_BANTIME);
412 auto connman = std::make_unique<CConnman>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman, Params());
413 auto peerLogic = PeerManager::make(*connman, *m_node.addrman, banman.get(), *m_node.chainman, *m_node.mempool, *m_node.warnings, {});
414
415 banman->ClearBanned();
416 const FakeNodeClock clock{}; // keep mocktime constant
417
418 CAddress addr(ip(0xa0b0c001), NODE_NONE);
419 NodeId id{0};
420 CNode dummyNode{id++,
421 /*sock=*/nullptr,
422 addr,
423 /*nKeyedNetGroupIn=*/4,
424 /*nLocalHostNonceIn=*/4,
425 CAddress(),
426 /*addrNameIn=*/"",
427 ConnectionType::INBOUND,
428 /*inbound_onion=*/false,
429 /*network_key=*/1};
430 dummyNode.SetCommonVersion(PROTOCOL_VERSION);
431 peerLogic->InitializeNode(dummyNode, NODE_NETWORK);
432 dummyNode.fSuccessfullyConnected = true;
433
434 peerLogic->UnitTestMisbehaving(dummyNode.GetId());
435 BOOST_CHECK(peerLogic->SendMessages(dummyNode));
436 BOOST_CHECK(banman->IsDiscouraged(addr));
437
438 peerLogic->FinalizeNode(dummyNode);
439 }
440
441 BOOST_AUTO_TEST_SUITE_END()
442