]> git.ipfire.org Git - thirdparty/pdns.git/blob - regression-tests.dnsdist/test_DynBlocks.py
a5768b5fb835ddc96c2630296db1541497d4bedf
[thirdparty/pdns.git] / regression-tests.dnsdist / test_DynBlocks.py
1 #!/usr/bin/env python
2 import base64
3 import json
4 import requests
5 import time
6 import dns
7 from dnsdisttests import DNSDistTest
8 try:
9 range = xrange
10 except NameError:
11 pass
12
13 class DynBlocksTest(DNSDistTest):
14
15 _webTimeout = 2.0
16 _webServerPort = 8083
17 _webServerBasicAuthPassword = 'secret'
18 _webServerAPIKey = 'apisecret'
19
20 def doTestDynBlockViaAPI(self, range, reason, minSeconds, maxSeconds, minBlocks, maxBlocks):
21 headers = {'x-api-key': self._webServerAPIKey}
22 url = 'http://127.0.0.1:' + str(self._webServerPort) + '/jsonstat?command=dynblocklist'
23 r = requests.get(url, headers=headers, timeout=self._webTimeout)
24 self.assertTrue(r)
25 self.assertEquals(r.status_code, 200)
26
27 content = r.json()
28 self.assertIsNotNone(content)
29 self.assertIn(range, content)
30
31 values = content[range]
32 for key in ['reason', 'seconds', 'blocks', 'action']:
33 self.assertIn(key, values)
34
35 self.assertEqual(values['reason'], reason)
36 self.assertGreaterEqual(values['seconds'], minSeconds)
37 self.assertLessEqual(values['seconds'], maxSeconds)
38 self.assertGreaterEqual(values['blocks'], minBlocks)
39 self.assertLessEqual(values['blocks'], maxBlocks)
40
41 def doTestQRate(self, name, testViaAPI=True):
42 query = dns.message.make_query(name, 'A', 'IN')
43 response = dns.message.make_response(query)
44 rrset = dns.rrset.from_text(name,
45 60,
46 dns.rdataclass.IN,
47 dns.rdatatype.A,
48 '192.0.2.1')
49 response.answer.append(rrset)
50
51 allowed = 0
52 sent = 0
53 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
54 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
55 sent = sent + 1
56 if receivedQuery:
57 receivedQuery.id = query.id
58 self.assertEquals(query, receivedQuery)
59 self.assertEquals(response, receivedResponse)
60 allowed = allowed + 1
61 else:
62 # the query has not reached the responder,
63 # let's clear the response queue
64 self.clearToResponderQueue()
65
66 # we might be already blocked, but we should have been able to send
67 # at least self._dynBlockQPS queries
68 self.assertGreaterEqual(allowed, self._dynBlockQPS)
69
70 if allowed == sent:
71 # wait for the maintenance function to run
72 time.sleep(2)
73
74 # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
75 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
76 self.assertEquals(receivedResponse, None)
77
78 if testViaAPI:
79 self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', self._dynBlockDuration - 4, self._dynBlockDuration, (sent-allowed)+1, (sent-allowed)+1)
80
81 # wait until we are not blocked anymore
82 time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
83
84 # this one should succeed
85 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
86 receivedQuery.id = query.id
87 self.assertEquals(query, receivedQuery)
88 self.assertEquals(response, receivedResponse)
89
90 # again, over TCP this time
91 allowed = 0
92 sent = 0
93 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
94 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
95 sent = sent + 1
96 if receivedQuery:
97 receivedQuery.id = query.id
98 self.assertEquals(query, receivedQuery)
99 self.assertEquals(response, receivedResponse)
100 allowed = allowed + 1
101 else:
102 # the query has not reached the responder,
103 # let's clear the response queue
104 self.clearToResponderQueue()
105
106 # we might be already blocked, but we should have been able to send
107 # at least self._dynBlockQPS queries
108 self.assertGreaterEqual(allowed, self._dynBlockQPS)
109
110 if allowed == sent:
111 # wait for the maintenance function to run
112 time.sleep(2)
113
114 # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
115 (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
116 self.assertEquals(receivedResponse, None)
117
118 # wait until we are not blocked anymore
119 time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
120
121 # this one should succeed
122 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
123 receivedQuery.id = query.id
124 self.assertEquals(query, receivedQuery)
125 self.assertEquals(response, receivedResponse)
126
127 def doTestQRateRCode(self, name, rcode):
128 query = dns.message.make_query(name, 'A', 'IN')
129 response = dns.message.make_response(query)
130 rrset = dns.rrset.from_text(name,
131 60,
132 dns.rdataclass.IN,
133 dns.rdatatype.A,
134 '192.0.2.1')
135 response.answer.append(rrset)
136 expectedResponse = dns.message.make_response(query)
137 expectedResponse.set_rcode(rcode)
138
139 allowed = 0
140 sent = 0
141 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
142 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
143 sent = sent + 1
144 if receivedQuery:
145 receivedQuery.id = query.id
146 self.assertEquals(query, receivedQuery)
147 self.assertEquals(receivedResponse, response)
148 allowed = allowed + 1
149 else:
150 self.assertEquals(receivedResponse, expectedResponse)
151 # the query has not reached the responder,
152 # let's clear the response queue
153 self.clearToResponderQueue()
154
155 # we might be already blocked, but we should have been able to send
156 # at least self._dynBlockQPS queries
157 self.assertGreaterEqual(allowed, self._dynBlockQPS)
158
159 if allowed == sent:
160 # wait for the maintenance function to run
161 time.sleep(2)
162
163 # we should now be 'rcode' for up to self._dynBlockDuration + self._dynBlockPeriod
164 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
165 self.assertEquals(receivedResponse, expectedResponse)
166
167 # wait until we are not blocked anymore
168 time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
169
170 # this one should succeed
171 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
172 receivedQuery.id = query.id
173 self.assertEquals(query, receivedQuery)
174 self.assertEquals(response, receivedResponse)
175
176 allowed = 0
177 sent = 0
178 # again, over TCP this time
179 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
180 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
181 sent = sent + 1
182 if receivedQuery:
183 receivedQuery.id = query.id
184 self.assertEquals(query, receivedQuery)
185 self.assertEquals(receivedResponse, response)
186 allowed = allowed + 1
187 else:
188 self.assertEquals(receivedResponse, expectedResponse)
189 # the query has not reached the responder,
190 # let's clear the response queue
191 self.clearToResponderQueue()
192
193 # we might be already blocked, but we should have been able to send
194 # at least self._dynBlockQPS queries
195 self.assertGreaterEqual(allowed, self._dynBlockQPS)
196
197 if allowed == sent:
198 # wait for the maintenance function to run
199 time.sleep(2)
200
201 # we should now be 'rcode' for up to self._dynBlockDuration + self._dynBlockPeriod
202 (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
203 self.assertEquals(receivedResponse, expectedResponse)
204
205 # wait until we are not blocked anymore
206 time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
207
208 # this one should succeed
209 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
210 receivedQuery.id = query.id
211 self.assertEquals(query, receivedQuery)
212 self.assertEquals(response, receivedResponse)
213
214 def doTestResponseByteRate(self, name):
215 query = dns.message.make_query(name, 'A', 'IN')
216 response = dns.message.make_response(query)
217 response.answer.append(dns.rrset.from_text_list(name,
218 60,
219 dns.rdataclass.IN,
220 dns.rdatatype.A,
221 ['192.0.2.1', '192.0.2.2', '192.0.2.3', '192.0.2.4']))
222 response.answer.append(dns.rrset.from_text(name,
223 60,
224 dns.rdataclass.IN,
225 dns.rdatatype.AAAA,
226 '2001:DB8::1'))
227
228 allowed = 0
229 sent = 0
230
231 print(time.time())
232
233 for _ in range(int(self._dynBlockBytesPerSecond * 5 / len(response.to_wire()))):
234 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
235 sent = sent + len(response.to_wire())
236 if receivedQuery:
237 receivedQuery.id = query.id
238 self.assertEquals(query, receivedQuery)
239 self.assertEquals(response, receivedResponse)
240 allowed = allowed + len(response.to_wire())
241 else:
242 # the query has not reached the responder,
243 # let's clear the response queue
244 self.clearToResponderQueue()
245 # and stop right there, otherwise we might
246 # wait for so long that the dynblock is gone
247 # by the time we finished
248 break
249
250 # we might be already blocked, but we should have been able to send
251 # at least self._dynBlockBytesPerSecond bytes
252 print(allowed)
253 print(sent)
254 print(time.time())
255 self.assertGreaterEqual(allowed, self._dynBlockBytesPerSecond)
256
257 print(self.sendConsoleCommand("showDynBlocks()"))
258 print(self.sendConsoleCommand("grepq(\"\")"))
259 print(time.time())
260
261 if allowed == sent:
262 # wait for the maintenance function to run
263 print("Waiting for the maintenance function to run")
264 time.sleep(2)
265
266 print(self.sendConsoleCommand("showDynBlocks()"))
267 print(self.sendConsoleCommand("grepq(\"\")"))
268 print(time.time())
269
270 # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
271 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
272 self.assertEquals(receivedResponse, None)
273
274 print(self.sendConsoleCommand("showDynBlocks()"))
275 print(self.sendConsoleCommand("grepq(\"\")"))
276 print(time.time())
277
278 # wait until we are not blocked anymore
279 time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
280
281 print(self.sendConsoleCommand("showDynBlocks()"))
282 print(self.sendConsoleCommand("grepq(\"\")"))
283 print(time.time())
284
285 # this one should succeed
286 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
287 receivedQuery.id = query.id
288 self.assertEquals(query, receivedQuery)
289 self.assertEquals(response, receivedResponse)
290
291 # again, over TCP this time
292 allowed = 0
293 sent = 0
294 for _ in range(int(self._dynBlockBytesPerSecond * 5 / len(response.to_wire()))):
295 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
296 sent = sent + len(response.to_wire())
297 if receivedQuery:
298 receivedQuery.id = query.id
299 self.assertEquals(query, receivedQuery)
300 self.assertEquals(response, receivedResponse)
301 allowed = allowed + len(response.to_wire())
302 else:
303 # the query has not reached the responder,
304 # let's clear the response queue
305 self.clearToResponderQueue()
306 # and stop right there, otherwise we might
307 # wait for so long that the dynblock is gone
308 # by the time we finished
309 break
310
311 # we might be already blocked, but we should have been able to send
312 # at least self._dynBlockBytesPerSecond bytes
313 self.assertGreaterEqual(allowed, self._dynBlockBytesPerSecond)
314
315 if allowed == sent:
316 # wait for the maintenance function to run
317 time.sleep(2)
318
319 # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
320 (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
321 self.assertEquals(receivedResponse, None)
322
323 # wait until we are not blocked anymore
324 time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
325
326 # this one should succeed
327 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
328 receivedQuery.id = query.id
329 self.assertEquals(query, receivedQuery)
330 self.assertEquals(response, receivedResponse)
331
332 def doTestRCodeRate(self, name, rcode):
333 query = dns.message.make_query(name, 'A', 'IN')
334 response = dns.message.make_response(query)
335 rrset = dns.rrset.from_text(name,
336 60,
337 dns.rdataclass.IN,
338 dns.rdatatype.A,
339 '192.0.2.1')
340 response.answer.append(rrset)
341 expectedResponse = dns.message.make_response(query)
342 expectedResponse.set_rcode(rcode)
343
344 # start with normal responses
345 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
346 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
347 receivedQuery.id = query.id
348 self.assertEquals(query, receivedQuery)
349 self.assertEquals(response, receivedResponse)
350
351 # wait for the maintenance function to run
352 time.sleep(2)
353
354 # we should NOT be dropped!
355 (_, receivedResponse) = self.sendUDPQuery(query, response)
356 self.assertEquals(receivedResponse, response)
357
358 # now with rcode!
359 sent = 0
360 allowed = 0
361 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
362 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, expectedResponse)
363 sent = sent + 1
364 if receivedQuery:
365 receivedQuery.id = query.id
366 self.assertEquals(query, receivedQuery)
367 self.assertEquals(expectedResponse, receivedResponse)
368 allowed = allowed + 1
369 else:
370 # the query has not reached the responder,
371 # let's clear the response queue
372 self.clearToResponderQueue()
373
374 # we might be already blocked, but we should have been able to send
375 # at least self._dynBlockQPS queries
376 self.assertGreaterEqual(allowed, self._dynBlockQPS)
377
378 if allowed == sent:
379 # wait for the maintenance function to run
380 time.sleep(2)
381
382 # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
383 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
384 self.assertEquals(receivedResponse, None)
385
386 # wait until we are not blocked anymore
387 time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
388
389 # this one should succeed
390 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
391 receivedQuery.id = query.id
392 self.assertEquals(query, receivedQuery)
393 self.assertEquals(response, receivedResponse)
394
395 # again, over TCP this time
396 # start with normal responses
397 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
398 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
399 receivedQuery.id = query.id
400 self.assertEquals(query, receivedQuery)
401 self.assertEquals(response, receivedResponse)
402
403 # wait for the maintenance function to run
404 time.sleep(2)
405
406 # we should NOT be dropped!
407 (_, receivedResponse) = self.sendUDPQuery(query, response)
408 self.assertEquals(receivedResponse, response)
409
410 # now with rcode!
411 sent = 0
412 allowed = 0
413 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
414 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, expectedResponse)
415 sent = sent + 1
416 if receivedQuery:
417 receivedQuery.id = query.id
418 self.assertEquals(query, receivedQuery)
419 self.assertEquals(expectedResponse, receivedResponse)
420 allowed = allowed + 1
421 else:
422 # the query has not reached the responder,
423 # let's clear the response queue
424 self.clearToResponderQueue()
425
426 # we might be already blocked, but we should have been able to send
427 # at least self._dynBlockQPS queries
428 self.assertGreaterEqual(allowed, self._dynBlockQPS)
429
430 if allowed == sent:
431 # wait for the maintenance function to run
432 time.sleep(2)
433
434 # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
435 (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
436 self.assertEquals(receivedResponse, None)
437
438 # wait until we are not blocked anymore
439 time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
440
441 # this one should succeed
442 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
443 receivedQuery.id = query.id
444 self.assertEquals(query, receivedQuery)
445 self.assertEquals(response, receivedResponse)
446
447 class TestDynBlockQPS(DynBlocksTest):
448
449 _dynBlockQPS = 10
450 _dynBlockPeriod = 2
451 _dynBlockDuration = 5
452 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
453 _config_template = """
454 function maintenance()
455 addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d)
456 end
457 newServer{address="127.0.0.1:%s"}
458 webserver("127.0.0.1:%s", "%s", "%s")
459 """
460
461 def testDynBlocksQRate(self):
462 """
463 Dyn Blocks: QRate
464 """
465 name = 'qrate.dynblocks.tests.powerdns.com.'
466 self.doTestQRate(name)
467
468 class TestDynBlockGroupQPS(DynBlocksTest):
469
470 _dynBlockQPS = 10
471 _dynBlockPeriod = 2
472 _dynBlockDuration = 5
473 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
474 _config_template = """
475 local dbr = dynBlockRulesGroup()
476 dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
477
478 function maintenance()
479 dbr:apply()
480 end
481 newServer{address="127.0.0.1:%s"}
482 webserver("127.0.0.1:%s", "%s", "%s")
483 """
484
485 def testDynBlocksQRate(self):
486 """
487 Dyn Blocks (Group): QRate
488 """
489 name = 'qrate.group.dynblocks.tests.powerdns.com.'
490 self.doTestQRate(name)
491
492
493 class TestDynBlockQPSRefused(DynBlocksTest):
494
495 _dynBlockQPS = 10
496 _dynBlockPeriod = 2
497 _dynBlockDuration = 5
498 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
499 _config_template = """
500 function maintenance()
501 addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d)
502 end
503 setDynBlocksAction(DNSAction.Refused)
504 newServer{address="127.0.0.1:%s"}
505 """
506
507 def testDynBlocksQRate(self):
508 """
509 Dyn Blocks: QRate refused
510 """
511 name = 'qraterefused.dynblocks.tests.powerdns.com.'
512 self.doTestQRateRCode(name, dns.rcode.REFUSED)
513
514 class TestDynBlockGroupQPSRefused(DynBlocksTest):
515
516 _dynBlockQPS = 10
517 _dynBlockPeriod = 2
518 _dynBlockDuration = 5
519 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
520 _config_template = """
521 local dbr = dynBlockRulesGroup()
522 dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
523
524 function maintenance()
525 dbr:apply()
526 end
527 setDynBlocksAction(DNSAction.Refused)
528 newServer{address="127.0.0.1:%s"}
529 """
530
531 def testDynBlocksQRate(self):
532 """
533 Dyn Blocks (Group): QRate refused
534 """
535 name = 'qraterefused.group.dynblocks.tests.powerdns.com.'
536 self.doTestQRateRCode(name, dns.rcode.REFUSED)
537
538 class TestDynBlockQPSActionRefused(DynBlocksTest):
539
540 _dynBlockQPS = 10
541 _dynBlockPeriod = 2
542 _dynBlockDuration = 5
543 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
544 _config_template = """
545 function maintenance()
546 addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Refused)
547 end
548 setDynBlocksAction(DNSAction.Drop)
549 newServer{address="127.0.0.1:%s"}
550 """
551
552 def testDynBlocksQRate(self):
553 """
554 Dyn Blocks: QRate refused (action)
555 """
556 name = 'qrateactionrefused.dynblocks.tests.powerdns.com.'
557 self.doTestQRateRCode(name, dns.rcode.REFUSED)
558
559 class TestDynBlockQPSActionNXD(DynBlocksTest):
560
561 _dynBlockQPS = 10
562 _dynBlockPeriod = 2
563 _dynBlockDuration = 5
564 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
565 _config_template = """
566 function maintenance()
567 addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Nxdomain)
568 end
569 setDynBlocksAction(DNSAction.Drop)
570 newServer{address="127.0.0.1:%s"}
571 """
572
573 def testDynBlocksQRate(self):
574 """
575 Dyn Blocks: QRate NXD (action)
576 """
577 name = 'qrateactionnxd.dynblocks.tests.powerdns.com.'
578 self.doTestQRateRCode(name, dns.rcode.NXDOMAIN)
579
580 class TestDynBlockGroupQPSActionRefused(DynBlocksTest):
581
582 _dynBlockQPS = 10
583 _dynBlockPeriod = 2
584 _dynBlockDuration = 5
585 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
586 _config_template = """
587 local dbr = dynBlockRulesGroup()
588 dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.Refused)
589
590 function maintenance()
591 dbr:apply()
592 end
593 setDynBlocksAction(DNSAction.Drop)
594 newServer{address="127.0.0.1:%s"}
595 """
596
597 def testDynBlocksQRate(self):
598 """
599 Dyn Blocks (group): QRate refused (action)
600 """
601 name = 'qrateactionrefused.group.dynblocks.tests.powerdns.com.'
602 self.doTestQRateRCode(name, dns.rcode.REFUSED)
603
604 class TestDynBlockQPSActionTruncated(DNSDistTest):
605
606 _dynBlockQPS = 10
607 _dynBlockPeriod = 2
608 _dynBlockDuration = 5
609 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
610 _config_template = """
611 function maintenance()
612 addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Truncate)
613 end
614 setDynBlocksAction(DNSAction.Drop)
615 newServer{address="127.0.0.1:%s"}
616 """
617
618 def testDynBlocksQRate(self):
619 """
620 Dyn Blocks: QRate truncated (action)
621 """
622 name = 'qrateactiontruncated.dynblocks.tests.powerdns.com.'
623 query = dns.message.make_query(name, 'A', 'IN')
624 response = dns.message.make_response(query)
625 rrset = dns.rrset.from_text(name,
626 60,
627 dns.rdataclass.IN,
628 dns.rdatatype.A,
629 '192.0.2.1')
630 response.answer.append(rrset)
631 truncatedResponse = dns.message.make_response(query)
632 truncatedResponse.flags |= dns.flags.TC
633
634 allowed = 0
635 sent = 0
636 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
637 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
638 sent = sent + 1
639 if receivedQuery:
640 receivedQuery.id = query.id
641 self.assertEquals(query, receivedQuery)
642 self.assertEquals(receivedResponse, response)
643 allowed = allowed + 1
644 else:
645 self.assertEquals(receivedResponse, truncatedResponse)
646 # the query has not reached the responder,
647 # let's clear the response queue
648 self.clearToResponderQueue()
649
650 # we might be already truncated, but we should have been able to send
651 # at least self._dynBlockQPS queries
652 self.assertGreaterEqual(allowed, self._dynBlockQPS)
653
654 if allowed == sent:
655 # wait for the maintenance function to run
656 time.sleep(2)
657
658 # we should now be 'truncated' for up to self._dynBlockDuration + self._dynBlockPeriod
659 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
660 self.assertEquals(receivedResponse, truncatedResponse)
661
662 # check over TCP, which should not be truncated
663 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
664
665 self.assertEquals(query, receivedQuery)
666 self.assertEquals(receivedResponse, response)
667
668 # wait until we are not blocked anymore
669 time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
670
671 # this one should succeed
672 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
673 receivedQuery.id = query.id
674 self.assertEquals(query, receivedQuery)
675 self.assertEquals(response, receivedResponse)
676
677 allowed = 0
678 sent = 0
679 # again, over TCP this time, we should never get truncated!
680 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
681 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
682 sent = sent + 1
683 self.assertEquals(query, receivedQuery)
684 self.assertEquals(receivedResponse, response)
685 receivedQuery.id = query.id
686 allowed = allowed + 1
687
688 self.assertEquals(allowed, sent)
689
690 class TestDynBlockServFails(DynBlocksTest):
691
692 _dynBlockQPS = 10
693 _dynBlockPeriod = 2
694 _dynBlockDuration = 5
695 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
696 _config_template = """
697 function maintenance()
698 addDynBlocks(exceedServFails(%d, %d), "Exceeded servfail rate", %d)
699 end
700 newServer{address="127.0.0.1:%s"}
701 """
702
703 def testDynBlocksServFailRate(self):
704 """
705 Dyn Blocks: Server Failure Rate
706 """
707 name = 'servfailrate.dynblocks.tests.powerdns.com.'
708 self.doTestRCodeRate(name, dns.rcode.SERVFAIL)
709
710 class TestDynBlockWhitelist(DynBlocksTest):
711
712 _dynBlockQPS = 10
713 _dynBlockPeriod = 2
714 _dynBlockDuration = 5
715 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
716 _config_template = """
717 whitelisted = false
718 function maintenance()
719 toBlock = exceedQRate(%d, %d)
720 for addr, count in pairs(toBlock) do
721 if addr:toString() == "127.0.0.1" then
722 whitelisted = true
723 toBlock[addr] = nil
724 end
725 end
726 addDynBlocks(toBlock, "Exceeded query rate", %d)
727 end
728
729 function spoofrule(dq)
730 if (whitelisted)
731 then
732 return DNSAction.Spoof, "192.0.2.42"
733 else
734 return DNSAction.None, ""
735 end
736 end
737 addAction("whitelisted-test.dynblocks.tests.powerdns.com.", LuaAction(spoofrule))
738
739 newServer{address="127.0.0.1:%s"}
740 """
741
742 def testWhitelisted(self):
743 """
744 Dyn Blocks: Whitelisted from the dynamic blocks
745 """
746 name = 'whitelisted.dynblocks.tests.powerdns.com.'
747 query = dns.message.make_query(name, 'A', 'IN')
748 response = dns.message.make_response(query)
749 rrset = dns.rrset.from_text(name,
750 60,
751 dns.rdataclass.IN,
752 dns.rdatatype.A,
753 '192.0.2.1')
754 response.answer.append(rrset)
755
756 allowed = 0
757 sent = 0
758 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
759 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
760 sent = sent + 1
761 if receivedQuery:
762 receivedQuery.id = query.id
763 self.assertEquals(query, receivedQuery)
764 self.assertEquals(response, receivedResponse)
765 allowed = allowed + 1
766 else:
767 # the query has not reached the responder,
768 # let's clear the response queue
769 self.clearToResponderQueue()
770
771 # we should not have been blocked
772 self.assertEqual(allowed, sent)
773
774 # wait for the maintenance function to run
775 time.sleep(2)
776
777 # we should still not be blocked
778 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
779 receivedQuery.id = query.id
780 self.assertEquals(query, receivedQuery)
781 self.assertEquals(receivedResponse, receivedResponse)
782
783 # check that we would have been blocked without the whitelisting
784 name = 'whitelisted-test.dynblocks.tests.powerdns.com.'
785 query = dns.message.make_query(name, 'A', 'IN')
786 # dnsdist set RA = RD for spoofed responses
787 query.flags &= ~dns.flags.RD
788 expectedResponse = dns.message.make_response(query)
789 rrset = dns.rrset.from_text(name,
790 60,
791 dns.rdataclass.IN,
792 dns.rdatatype.A,
793 '192.0.2.42')
794 expectedResponse.answer.append(rrset)
795 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
796 self.assertEquals(receivedResponse, expectedResponse)
797
798 class TestDynBlockGroupServFails(DynBlocksTest):
799
800 _dynBlockQPS = 10
801 _dynBlockPeriod = 2
802 _dynBlockDuration = 5
803 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
804 _config_template = """
805 local dbr = dynBlockRulesGroup()
806 dbr:setRCodeRate(dnsdist.SERVFAIL, %d, %d, "Exceeded query rate", %d)
807
808 function maintenance()
809 dbr:apply()
810 end
811
812 newServer{address="127.0.0.1:%s"}
813 """
814
815 def testDynBlocksServFailRate(self):
816 """
817 Dyn Blocks (group): Server Failure Rate
818 """
819 name = 'servfailrate.group.dynblocks.tests.powerdns.com.'
820 self.doTestRCodeRate(name, dns.rcode.SERVFAIL)
821
822 class TestDynBlockResponseBytes(DynBlocksTest):
823
824 _dynBlockBytesPerSecond = 200
825 _dynBlockPeriod = 2
826 _dynBlockDuration = 5
827 _consoleKey = DNSDistTest.generateConsoleKey()
828 _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
829 _config_params = ['_consoleKeyB64', '_consolePort', '_dynBlockBytesPerSecond', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
830 _config_template = """
831 setKey("%s")
832 controlSocket("127.0.0.1:%s")
833 function maintenance()
834 addDynBlocks(exceedRespByterate(%d, %d), "Exceeded response byterate", %d)
835 end
836 newServer{address="127.0.0.1:%s"}
837 """
838
839 def testDynBlocksResponseByteRate(self):
840 """
841 Dyn Blocks: Response Byte Rate
842 """
843 name = 'responsebyterate.dynblocks.tests.powerdns.com.'
844 self.doTestResponseByteRate(name)
845
846 class TestDynBlockGroupResponseBytes(DynBlocksTest):
847
848 _dynBlockBytesPerSecond = 200
849 _dynBlockPeriod = 2
850 _dynBlockDuration = 5
851 _consoleKey = DNSDistTest.generateConsoleKey()
852 _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
853 _config_params = ['_consoleKeyB64', '_consolePort', '_dynBlockBytesPerSecond', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
854 _config_template = """
855 setKey("%s")
856 controlSocket("127.0.0.1:%s")
857 local dbr = dynBlockRulesGroup()
858 dbr:setResponseByteRate(%d, %d, "Exceeded query rate", %d)
859
860 function maintenance()
861 dbr:apply()
862 end
863
864 newServer{address="127.0.0.1:%s"}
865 """
866
867 def testDynBlocksResponseByteRate(self):
868 """
869 Dyn Blocks (group) : Response Byte Rate
870 """
871 name = 'responsebyterate.group.dynblocks.tests.powerdns.com.'
872 self.doTestResponseByteRate(name)
873
874 class TestDynBlockGroupExcluded(DynBlocksTest):
875
876 _dynBlockQPS = 10
877 _dynBlockPeriod = 2
878 _dynBlockDuration = 5
879 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
880 _config_template = """
881 local dbr = dynBlockRulesGroup()
882 dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
883 dbr:excludeRange("127.0.0.1/32")
884
885 function maintenance()
886 dbr:apply()
887 end
888
889 newServer{address="127.0.0.1:%s"}
890 """
891
892 def testExcluded(self):
893 """
894 Dyn Blocks (group) : Excluded from the dynamic block rules
895 """
896 name = 'excluded.group.dynblocks.tests.powerdns.com.'
897 query = dns.message.make_query(name, 'A', 'IN')
898 response = dns.message.make_response(query)
899 rrset = dns.rrset.from_text(name,
900 60,
901 dns.rdataclass.IN,
902 dns.rdatatype.A,
903 '192.0.2.1')
904 response.answer.append(rrset)
905
906 allowed = 0
907 sent = 0
908 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
909 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
910 sent = sent + 1
911 if receivedQuery:
912 receivedQuery.id = query.id
913 self.assertEquals(query, receivedQuery)
914 self.assertEquals(response, receivedResponse)
915 allowed = allowed + 1
916 else:
917 # the query has not reached the responder,
918 # let's clear the response queue
919 self.clearToResponderQueue()
920
921 # we should not have been blocked
922 self.assertEqual(allowed, sent)
923
924 # wait for the maintenance function to run
925 time.sleep(2)
926
927 # we should still not be blocked
928 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
929 receivedQuery.id = query.id
930 self.assertEquals(query, receivedQuery)
931 self.assertEquals(receivedResponse, receivedResponse)
932
933 class TestDynBlockGroupNoOp(DynBlocksTest):
934
935 _dynBlockQPS = 10
936 _dynBlockPeriod = 2
937 _dynBlockDuration = 5
938 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
939 _config_template = """
940 local dbr = dynBlockRulesGroup()
941 dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.NoOp)
942
943 function maintenance()
944 dbr:apply()
945 end
946
947 newServer{address="127.0.0.1:%s"}
948 webserver("127.0.0.1:%s", "%s", "%s")
949 """
950
951 def testNoOp(self):
952 """
953 Dyn Blocks (group) : NoOp
954 """
955 name = 'noop.group.dynblocks.tests.powerdns.com.'
956 query = dns.message.make_query(name, 'A', 'IN')
957 response = dns.message.make_response(query)
958 rrset = dns.rrset.from_text(name,
959 60,
960 dns.rdataclass.IN,
961 dns.rdatatype.A,
962 '192.0.2.1')
963 response.answer.append(rrset)
964
965 allowed = 0
966 sent = 0
967 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
968 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
969 sent = sent + 1
970 if receivedQuery:
971 receivedQuery.id = query.id
972 self.assertEquals(query, receivedQuery)
973 self.assertEquals(response, receivedResponse)
974 allowed = allowed + 1
975 else:
976 # the query has not reached the responder,
977 # let's clear the response queue
978 self.clearToResponderQueue()
979
980 # a dynamic rule should have been inserted, but the queries should still go on
981 self.assertEqual(allowed, sent)
982
983 # wait for the maintenance function to run
984 time.sleep(2)
985
986 # the rule should still be present, but the queries pass through anyway
987 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
988 receivedQuery.id = query.id
989 self.assertEquals(query, receivedQuery)
990 self.assertEquals(receivedResponse, receivedResponse)
991
992 # check that the rule has been inserted
993 self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', self._dynBlockDuration - 4, self._dynBlockDuration, 0, sent)
994
995 class TestDynBlockGroupWarning(DynBlocksTest):
996
997 _dynBlockWarningQPS = 5
998 _dynBlockQPS = 20
999 _dynBlockPeriod = 2
1000 _dynBlockDuration = 5
1001 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_dynBlockWarningQPS', '_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
1002 _config_template = """
1003 local dbr = dynBlockRulesGroup()
1004 dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.Drop, %d)
1005
1006 function maintenance()
1007 dbr:apply()
1008 end
1009
1010 newServer{address="127.0.0.1:%s"}
1011 webserver("127.0.0.1:%s", "%s", "%s")
1012 """
1013
1014 def testWarning(self):
1015 """
1016 Dyn Blocks (group) : Warning
1017 """
1018 name = 'warning.group.dynblocks.tests.powerdns.com.'
1019 query = dns.message.make_query(name, 'A', 'IN')
1020 response = dns.message.make_response(query)
1021 rrset = dns.rrset.from_text(name,
1022 60,
1023 dns.rdataclass.IN,
1024 dns.rdatatype.A,
1025 '192.0.2.1')
1026 response.answer.append(rrset)
1027
1028 allowed = 0
1029 sent = 0
1030 for _ in range((self._dynBlockWarningQPS * self._dynBlockPeriod) + 1):
1031 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
1032 sent = sent + 1
1033 if receivedQuery:
1034 receivedQuery.id = query.id
1035 self.assertEquals(query, receivedQuery)
1036 self.assertEquals(response, receivedResponse)
1037 allowed = allowed + 1
1038 else:
1039 # the query has not reached the responder,
1040 # let's clear the response queue
1041 self.clearToResponderQueue()
1042
1043 # a dynamic rule should have been inserted, but the queries should
1044 # still go on because we are still at warning level
1045 self.assertEqual(allowed, sent)
1046
1047 # wait for the maintenance function to run
1048 time.sleep(2)
1049
1050 # the rule should still be present, but the queries pass through anyway
1051 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
1052 receivedQuery.id = query.id
1053 self.assertEquals(query, receivedQuery)
1054 self.assertEquals(receivedResponse, receivedResponse)
1055
1056 # check that the rule has been inserted
1057 self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', self._dynBlockDuration - 4, self._dynBlockDuration, 0, sent)
1058
1059 self.doTestQRate(name)