]> git.ipfire.org Git - thirdparty/pdns.git/blob - regression-tests.dnsdist/test_DynBlocks.py
Merge pull request #8795 from omoerbeek/rec-lua-docs-policytag
[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 def doTestRCodeRatio(self, name, rcode, noerrorcount, rcodecount):
448 query = dns.message.make_query(name, 'A', 'IN')
449 response = dns.message.make_response(query)
450 rrset = dns.rrset.from_text(name,
451 60,
452 dns.rdataclass.IN,
453 dns.rdatatype.A,
454 '192.0.2.1')
455 response.answer.append(rrset)
456 expectedResponse = dns.message.make_response(query)
457 expectedResponse.set_rcode(rcode)
458
459 # start with normal responses
460 for _ in range(noerrorcount-1):
461 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
462 receivedQuery.id = query.id
463 self.assertEquals(query, receivedQuery)
464 self.assertEquals(response, receivedResponse)
465
466 # wait for the maintenance function to run
467 time.sleep(2)
468
469 # we should NOT be dropped!
470 (_, receivedResponse) = self.sendUDPQuery(query, response)
471 self.assertEquals(receivedResponse, response)
472
473 # now with rcode!
474 sent = 0
475 allowed = 0
476 for _ in range(rcodecount):
477 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, expectedResponse)
478 sent = sent + 1
479 if receivedQuery:
480 receivedQuery.id = query.id
481 self.assertEquals(query, receivedQuery)
482 self.assertEquals(expectedResponse, receivedResponse)
483 allowed = allowed + 1
484 else:
485 # the query has not reached the responder,
486 # let's clear the response queue
487 self.clearToResponderQueue()
488
489 # we should have been able to send all our queries since the minimum number of queries is set to noerrorcount + rcodecount
490 self.assertGreaterEqual(allowed, rcodecount)
491
492 # wait for the maintenance function to run
493 time.sleep(2)
494
495 # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
496 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
497 self.assertEquals(receivedResponse, None)
498
499 # wait until we are not blocked anymore
500 time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
501
502 # this one should succeed
503 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
504 receivedQuery.id = query.id
505 self.assertEquals(query, receivedQuery)
506 self.assertEquals(response, receivedResponse)
507
508 # again, over TCP this time
509 # start with normal responses
510 for _ in range(noerrorcount-1):
511 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
512 receivedQuery.id = query.id
513 self.assertEquals(query, receivedQuery)
514 self.assertEquals(response, receivedResponse)
515
516 # wait for the maintenance function to run
517 time.sleep(2)
518
519 # we should NOT be dropped!
520 (_, receivedResponse) = self.sendUDPQuery(query, response)
521 self.assertEquals(receivedResponse, response)
522
523 # now with rcode!
524 sent = 0
525 allowed = 0
526 for _ in range(rcodecount):
527 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, expectedResponse)
528 sent = sent + 1
529 if receivedQuery:
530 receivedQuery.id = query.id
531 self.assertEquals(query, receivedQuery)
532 self.assertEquals(expectedResponse, receivedResponse)
533 allowed = allowed + 1
534 else:
535 # the query has not reached the responder,
536 # let's clear the response queue
537 self.clearToResponderQueue()
538
539 # we should have been able to send all our queries since the minimum number of queries is set to noerrorcount + rcodecount
540 self.assertGreaterEqual(allowed, rcodecount)
541
542 # wait for the maintenance function to run
543 time.sleep(2)
544
545 # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
546 (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
547 self.assertEquals(receivedResponse, None)
548
549 # wait until we are not blocked anymore
550 time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
551
552 # this one should succeed
553 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
554 receivedQuery.id = query.id
555 self.assertEquals(query, receivedQuery)
556 self.assertEquals(response, receivedResponse)
557
558 class TestDynBlockQPS(DynBlocksTest):
559
560 _dynBlockQPS = 10
561 _dynBlockPeriod = 2
562 _dynBlockDuration = 5
563 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
564 _config_template = """
565 function maintenance()
566 addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d)
567 end
568 newServer{address="127.0.0.1:%s"}
569 webserver("127.0.0.1:%s", "%s", "%s")
570 """
571
572 def testDynBlocksQRate(self):
573 """
574 Dyn Blocks: QRate
575 """
576 name = 'qrate.dynblocks.tests.powerdns.com.'
577 self.doTestQRate(name)
578
579 class TestDynBlockGroupQPS(DynBlocksTest):
580
581 _dynBlockQPS = 10
582 _dynBlockPeriod = 2
583 _dynBlockDuration = 5
584 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
585 _config_template = """
586 local dbr = dynBlockRulesGroup()
587 dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
588
589 function maintenance()
590 dbr:apply()
591 end
592 newServer{address="127.0.0.1:%s"}
593 webserver("127.0.0.1:%s", "%s", "%s")
594 """
595
596 def testDynBlocksQRate(self):
597 """
598 Dyn Blocks (Group): QRate
599 """
600 name = 'qrate.group.dynblocks.tests.powerdns.com.'
601 self.doTestQRate(name)
602
603
604 class TestDynBlockQPSRefused(DynBlocksTest):
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)
613 end
614 setDynBlocksAction(DNSAction.Refused)
615 newServer{address="127.0.0.1:%s"}
616 """
617
618 def testDynBlocksQRate(self):
619 """
620 Dyn Blocks: QRate refused
621 """
622 name = 'qraterefused.dynblocks.tests.powerdns.com.'
623 self.doTestQRateRCode(name, dns.rcode.REFUSED)
624
625 class TestDynBlockGroupQPSRefused(DynBlocksTest):
626
627 _dynBlockQPS = 10
628 _dynBlockPeriod = 2
629 _dynBlockDuration = 5
630 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
631 _config_template = """
632 local dbr = dynBlockRulesGroup()
633 dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
634
635 function maintenance()
636 dbr:apply()
637 end
638 setDynBlocksAction(DNSAction.Refused)
639 newServer{address="127.0.0.1:%s"}
640 """
641
642 def testDynBlocksQRate(self):
643 """
644 Dyn Blocks (Group): QRate refused
645 """
646 name = 'qraterefused.group.dynblocks.tests.powerdns.com.'
647 self.doTestQRateRCode(name, dns.rcode.REFUSED)
648
649 class TestDynBlockQPSActionRefused(DynBlocksTest):
650
651 _dynBlockQPS = 10
652 _dynBlockPeriod = 2
653 _dynBlockDuration = 5
654 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
655 _config_template = """
656 function maintenance()
657 addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Refused)
658 end
659 setDynBlocksAction(DNSAction.Drop)
660 newServer{address="127.0.0.1:%s"}
661 """
662
663 def testDynBlocksQRate(self):
664 """
665 Dyn Blocks: QRate refused (action)
666 """
667 name = 'qrateactionrefused.dynblocks.tests.powerdns.com.'
668 self.doTestQRateRCode(name, dns.rcode.REFUSED)
669
670 class TestDynBlockQPSActionNXD(DynBlocksTest):
671
672 _dynBlockQPS = 10
673 _dynBlockPeriod = 2
674 _dynBlockDuration = 5
675 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
676 _config_template = """
677 function maintenance()
678 addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Nxdomain)
679 end
680 setDynBlocksAction(DNSAction.Drop)
681 newServer{address="127.0.0.1:%s"}
682 """
683
684 def testDynBlocksQRate(self):
685 """
686 Dyn Blocks: QRate NXD (action)
687 """
688 name = 'qrateactionnxd.dynblocks.tests.powerdns.com.'
689 self.doTestQRateRCode(name, dns.rcode.NXDOMAIN)
690
691 class TestDynBlockGroupQPSActionRefused(DynBlocksTest):
692
693 _dynBlockQPS = 10
694 _dynBlockPeriod = 2
695 _dynBlockDuration = 5
696 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
697 _config_template = """
698 local dbr = dynBlockRulesGroup()
699 dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.Refused)
700
701 function maintenance()
702 dbr:apply()
703 end
704 setDynBlocksAction(DNSAction.Drop)
705 newServer{address="127.0.0.1:%s"}
706 """
707
708 def testDynBlocksQRate(self):
709 """
710 Dyn Blocks (group): QRate refused (action)
711 """
712 name = 'qrateactionrefused.group.dynblocks.tests.powerdns.com.'
713 self.doTestQRateRCode(name, dns.rcode.REFUSED)
714
715 class TestDynBlockQPSActionTruncated(DNSDistTest):
716
717 _dynBlockQPS = 10
718 _dynBlockPeriod = 2
719 _dynBlockDuration = 5
720 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
721 _config_template = """
722 function maintenance()
723 addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Truncate)
724 end
725 setDynBlocksAction(DNSAction.Drop)
726 newServer{address="127.0.0.1:%s"}
727 """
728
729 def testDynBlocksQRate(self):
730 """
731 Dyn Blocks: QRate truncated (action)
732 """
733 name = 'qrateactiontruncated.dynblocks.tests.powerdns.com.'
734 query = dns.message.make_query(name, 'A', 'IN')
735 # dnsdist sets RA = RD for TC responses
736 query.flags &= ~dns.flags.RD
737 response = dns.message.make_response(query)
738 rrset = dns.rrset.from_text(name,
739 60,
740 dns.rdataclass.IN,
741 dns.rdatatype.A,
742 '192.0.2.1')
743 response.answer.append(rrset)
744 truncatedResponse = dns.message.make_response(query)
745 truncatedResponse.flags |= dns.flags.TC
746
747 allowed = 0
748 sent = 0
749 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
750 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
751 sent = sent + 1
752 if receivedQuery:
753 receivedQuery.id = query.id
754 self.assertEquals(query, receivedQuery)
755 self.assertEquals(receivedResponse, response)
756 allowed = allowed + 1
757 else:
758 self.assertEquals(receivedResponse, truncatedResponse)
759 # the query has not reached the responder,
760 # let's clear the response queue
761 self.clearToResponderQueue()
762
763 # we might be already truncated, but we should have been able to send
764 # at least self._dynBlockQPS queries
765 self.assertGreaterEqual(allowed, self._dynBlockQPS)
766
767 if allowed == sent:
768 # wait for the maintenance function to run
769 time.sleep(2)
770
771 # we should now be 'truncated' for up to self._dynBlockDuration + self._dynBlockPeriod
772 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
773 self.assertEquals(receivedResponse, truncatedResponse)
774
775 # check over TCP, which should not be truncated
776 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
777
778 self.assertEquals(query, receivedQuery)
779 self.assertEquals(receivedResponse, response)
780
781 # wait until we are not blocked anymore
782 time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
783
784 # this one should succeed
785 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
786 receivedQuery.id = query.id
787 self.assertEquals(query, receivedQuery)
788 self.assertEquals(response, receivedResponse)
789
790 allowed = 0
791 sent = 0
792 # again, over TCP this time, we should never get truncated!
793 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
794 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
795 sent = sent + 1
796 self.assertEquals(query, receivedQuery)
797 self.assertEquals(receivedResponse, response)
798 receivedQuery.id = query.id
799 allowed = allowed + 1
800
801 self.assertEquals(allowed, sent)
802
803 class TestDynBlockServFails(DynBlocksTest):
804
805 _dynBlockQPS = 10
806 _dynBlockPeriod = 2
807 _dynBlockDuration = 5
808 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
809 _config_template = """
810 function maintenance()
811 addDynBlocks(exceedServFails(%d, %d), "Exceeded servfail rate", %d)
812 end
813 newServer{address="127.0.0.1:%s"}
814 """
815
816 def testDynBlocksServFailRate(self):
817 """
818 Dyn Blocks: Server Failure Rate
819 """
820 name = 'servfailrate.dynblocks.tests.powerdns.com.'
821 self.doTestRCodeRate(name, dns.rcode.SERVFAIL)
822
823 class TestDynBlockWhitelist(DynBlocksTest):
824
825 _dynBlockQPS = 10
826 _dynBlockPeriod = 2
827 _dynBlockDuration = 5
828 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
829 _config_template = """
830 whitelisted = false
831 function maintenance()
832 toBlock = exceedQRate(%d, %d)
833 for addr, count in pairs(toBlock) do
834 if addr:toString() == "127.0.0.1" then
835 whitelisted = true
836 toBlock[addr] = nil
837 end
838 end
839 addDynBlocks(toBlock, "Exceeded query rate", %d)
840 end
841
842 function spoofrule(dq)
843 if (whitelisted)
844 then
845 return DNSAction.Spoof, "192.0.2.42"
846 else
847 return DNSAction.None, ""
848 end
849 end
850 addAction("whitelisted-test.dynblocks.tests.powerdns.com.", LuaAction(spoofrule))
851
852 newServer{address="127.0.0.1:%s"}
853 """
854
855 def testWhitelisted(self):
856 """
857 Dyn Blocks: Whitelisted from the dynamic blocks
858 """
859 name = 'whitelisted.dynblocks.tests.powerdns.com.'
860 query = dns.message.make_query(name, 'A', 'IN')
861 response = dns.message.make_response(query)
862 rrset = dns.rrset.from_text(name,
863 60,
864 dns.rdataclass.IN,
865 dns.rdatatype.A,
866 '192.0.2.1')
867 response.answer.append(rrset)
868
869 allowed = 0
870 sent = 0
871 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
872 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
873 sent = sent + 1
874 if receivedQuery:
875 receivedQuery.id = query.id
876 self.assertEquals(query, receivedQuery)
877 self.assertEquals(response, receivedResponse)
878 allowed = allowed + 1
879 else:
880 # the query has not reached the responder,
881 # let's clear the response queue
882 self.clearToResponderQueue()
883
884 # we should not have been blocked
885 self.assertEqual(allowed, sent)
886
887 # wait for the maintenance function to run
888 time.sleep(2)
889
890 # we should still not be blocked
891 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
892 receivedQuery.id = query.id
893 self.assertEquals(query, receivedQuery)
894 self.assertEquals(receivedResponse, receivedResponse)
895
896 # check that we would have been blocked without the whitelisting
897 name = 'whitelisted-test.dynblocks.tests.powerdns.com.'
898 query = dns.message.make_query(name, 'A', 'IN')
899 # dnsdist set RA = RD for spoofed responses
900 query.flags &= ~dns.flags.RD
901 expectedResponse = dns.message.make_response(query)
902 rrset = dns.rrset.from_text(name,
903 60,
904 dns.rdataclass.IN,
905 dns.rdatatype.A,
906 '192.0.2.42')
907 expectedResponse.answer.append(rrset)
908 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
909 self.assertEquals(receivedResponse, expectedResponse)
910
911 class TestDynBlockGroupServFails(DynBlocksTest):
912
913 _dynBlockQPS = 10
914 _dynBlockPeriod = 2
915 _dynBlockDuration = 5
916 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
917 _config_template = """
918 local dbr = dynBlockRulesGroup()
919 dbr:setRCodeRate(DNSRCode.SERVFAIL, %d, %d, "Exceeded query rate", %d)
920
921 function maintenance()
922 dbr:apply()
923 end
924
925 newServer{address="127.0.0.1:%s"}
926 """
927
928 def testDynBlocksServFailRate(self):
929 """
930 Dyn Blocks (group): Server Failure Rate
931 """
932 name = 'servfailrate.group.dynblocks.tests.powerdns.com.'
933 self.doTestRCodeRate(name, dns.rcode.SERVFAIL)
934
935 class TestDynBlockGroupServFailsRatio(DynBlocksTest):
936
937 _dynBlockPeriod = 2
938 _dynBlockDuration = 5
939 _config_params = ['_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
940 _config_template = """
941 local dbr = dynBlockRulesGroup()
942 dbr:setRCodeRatio(DNSRCode.SERVFAIL, 0.2, %d, "Exceeded query rate", %d, 20)
943
944 function maintenance()
945 dbr:apply()
946 end
947
948 newServer{address="127.0.0.1:%s"}
949 """
950
951 def testDynBlocksServFailRatio(self):
952 """
953 Dyn Blocks (group): Server Failure Ratio
954 """
955 name = 'servfailratio.group.dynblocks.tests.powerdns.com.'
956 self.doTestRCodeRatio(name, dns.rcode.SERVFAIL, 10, 10)
957
958 class TestDynBlockResponseBytes(DynBlocksTest):
959
960 _dynBlockBytesPerSecond = 200
961 _dynBlockPeriod = 2
962 _dynBlockDuration = 5
963 _consoleKey = DNSDistTest.generateConsoleKey()
964 _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
965 _config_params = ['_consoleKeyB64', '_consolePort', '_dynBlockBytesPerSecond', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
966 _config_template = """
967 setKey("%s")
968 controlSocket("127.0.0.1:%s")
969 function maintenance()
970 addDynBlocks(exceedRespByterate(%d, %d), "Exceeded response byterate", %d)
971 end
972 newServer{address="127.0.0.1:%s"}
973 """
974
975 def testDynBlocksResponseByteRate(self):
976 """
977 Dyn Blocks: Response Byte Rate
978 """
979 name = 'responsebyterate.dynblocks.tests.powerdns.com.'
980 self.doTestResponseByteRate(name)
981
982 class TestDynBlockGroupResponseBytes(DynBlocksTest):
983
984 _dynBlockBytesPerSecond = 200
985 _dynBlockPeriod = 2
986 _dynBlockDuration = 5
987 _consoleKey = DNSDistTest.generateConsoleKey()
988 _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
989 _config_params = ['_consoleKeyB64', '_consolePort', '_dynBlockBytesPerSecond', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
990 _config_template = """
991 setKey("%s")
992 controlSocket("127.0.0.1:%s")
993 local dbr = dynBlockRulesGroup()
994 dbr:setResponseByteRate(%d, %d, "Exceeded query rate", %d)
995
996 function maintenance()
997 dbr:apply()
998 end
999
1000 newServer{address="127.0.0.1:%s"}
1001 """
1002
1003 def testDynBlocksResponseByteRate(self):
1004 """
1005 Dyn Blocks (group) : Response Byte Rate
1006 """
1007 name = 'responsebyterate.group.dynblocks.tests.powerdns.com.'
1008 self.doTestResponseByteRate(name)
1009
1010 class TestDynBlockGroupExcluded(DynBlocksTest):
1011
1012 _dynBlockQPS = 10
1013 _dynBlockPeriod = 2
1014 _dynBlockDuration = 5
1015 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
1016 _config_template = """
1017 local dbr = dynBlockRulesGroup()
1018 dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
1019 dbr:excludeRange("127.0.0.1/32")
1020
1021 function maintenance()
1022 dbr:apply()
1023 end
1024
1025 newServer{address="127.0.0.1:%s"}
1026 """
1027
1028 def testExcluded(self):
1029 """
1030 Dyn Blocks (group) : Excluded from the dynamic block rules
1031 """
1032 name = 'excluded.group.dynblocks.tests.powerdns.com.'
1033 query = dns.message.make_query(name, 'A', 'IN')
1034 response = dns.message.make_response(query)
1035 rrset = dns.rrset.from_text(name,
1036 60,
1037 dns.rdataclass.IN,
1038 dns.rdatatype.A,
1039 '192.0.2.1')
1040 response.answer.append(rrset)
1041
1042 allowed = 0
1043 sent = 0
1044 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
1045 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
1046 sent = sent + 1
1047 if receivedQuery:
1048 receivedQuery.id = query.id
1049 self.assertEquals(query, receivedQuery)
1050 self.assertEquals(response, receivedResponse)
1051 allowed = allowed + 1
1052 else:
1053 # the query has not reached the responder,
1054 # let's clear the response queue
1055 self.clearToResponderQueue()
1056
1057 # we should not have been blocked
1058 self.assertEqual(allowed, sent)
1059
1060 # wait for the maintenance function to run
1061 time.sleep(2)
1062
1063 # we should still not be blocked
1064 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
1065 receivedQuery.id = query.id
1066 self.assertEquals(query, receivedQuery)
1067 self.assertEquals(receivedResponse, receivedResponse)
1068
1069 class TestDynBlockGroupNoOp(DynBlocksTest):
1070
1071 _dynBlockQPS = 10
1072 _dynBlockPeriod = 2
1073 _dynBlockDuration = 5
1074 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
1075 _config_template = """
1076 local dbr = dynBlockRulesGroup()
1077 dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.NoOp)
1078
1079 function maintenance()
1080 dbr:apply()
1081 end
1082
1083 newServer{address="127.0.0.1:%s"}
1084 webserver("127.0.0.1:%s", "%s", "%s")
1085 """
1086
1087 def testNoOp(self):
1088 """
1089 Dyn Blocks (group) : NoOp
1090 """
1091 name = 'noop.group.dynblocks.tests.powerdns.com.'
1092 query = dns.message.make_query(name, 'A', 'IN')
1093 response = dns.message.make_response(query)
1094 rrset = dns.rrset.from_text(name,
1095 60,
1096 dns.rdataclass.IN,
1097 dns.rdatatype.A,
1098 '192.0.2.1')
1099 response.answer.append(rrset)
1100
1101 allowed = 0
1102 sent = 0
1103 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
1104 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
1105 sent = sent + 1
1106 if receivedQuery:
1107 receivedQuery.id = query.id
1108 self.assertEquals(query, receivedQuery)
1109 self.assertEquals(response, receivedResponse)
1110 allowed = allowed + 1
1111 else:
1112 # the query has not reached the responder,
1113 # let's clear the response queue
1114 self.clearToResponderQueue()
1115
1116 # a dynamic rule should have been inserted, but the queries should still go on
1117 self.assertEqual(allowed, sent)
1118
1119 # wait for the maintenance function to run
1120 time.sleep(2)
1121
1122 # the rule should still be present, but the queries pass through anyway
1123 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
1124 receivedQuery.id = query.id
1125 self.assertEquals(query, receivedQuery)
1126 self.assertEquals(receivedResponse, receivedResponse)
1127
1128 # check that the rule has been inserted
1129 self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', self._dynBlockDuration - 4, self._dynBlockDuration, 0, sent)
1130
1131 class TestDynBlockGroupWarning(DynBlocksTest):
1132
1133 _dynBlockWarningQPS = 5
1134 _dynBlockQPS = 20
1135 _dynBlockPeriod = 2
1136 _dynBlockDuration = 5
1137 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_dynBlockWarningQPS', '_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
1138 _config_template = """
1139 local dbr = dynBlockRulesGroup()
1140 dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.Drop, %d)
1141
1142 function maintenance()
1143 dbr:apply()
1144 end
1145
1146 newServer{address="127.0.0.1:%s"}
1147 webserver("127.0.0.1:%s", "%s", "%s")
1148 """
1149
1150 def testWarning(self):
1151 """
1152 Dyn Blocks (group) : Warning
1153 """
1154 name = 'warning.group.dynblocks.tests.powerdns.com.'
1155 query = dns.message.make_query(name, 'A', 'IN')
1156 response = dns.message.make_response(query)
1157 rrset = dns.rrset.from_text(name,
1158 60,
1159 dns.rdataclass.IN,
1160 dns.rdatatype.A,
1161 '192.0.2.1')
1162 response.answer.append(rrset)
1163
1164 allowed = 0
1165 sent = 0
1166 for _ in range((self._dynBlockWarningQPS * self._dynBlockPeriod) + 1):
1167 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
1168 sent = sent + 1
1169 if receivedQuery:
1170 receivedQuery.id = query.id
1171 self.assertEquals(query, receivedQuery)
1172 self.assertEquals(response, receivedResponse)
1173 allowed = allowed + 1
1174 else:
1175 # the query has not reached the responder,
1176 # let's clear the response queue
1177 self.clearToResponderQueue()
1178
1179 # a dynamic rule should have been inserted, but the queries should
1180 # still go on because we are still at warning level
1181 self.assertEqual(allowed, sent)
1182
1183 # wait for the maintenance function to run
1184 time.sleep(2)
1185
1186 # the rule should still be present, but the queries pass through anyway
1187 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
1188 receivedQuery.id = query.id
1189 self.assertEquals(query, receivedQuery)
1190 self.assertEquals(receivedResponse, receivedResponse)
1191
1192 # check that the rule has been inserted
1193 self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', self._dynBlockDuration - 4, self._dynBlockDuration, 0, sent)
1194
1195 self.doTestQRate(name)