]> git.ipfire.org Git - thirdparty/pdns.git/blob - regression-tests.dnsdist/test_DynBlocks.py
Merge pull request #8701 from pieterlexis/remote-support-also-notify
[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 response = dns.message.make_response(query)
736 rrset = dns.rrset.from_text(name,
737 60,
738 dns.rdataclass.IN,
739 dns.rdatatype.A,
740 '192.0.2.1')
741 response.answer.append(rrset)
742 truncatedResponse = dns.message.make_response(query)
743 truncatedResponse.flags |= dns.flags.TC
744
745 allowed = 0
746 sent = 0
747 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
748 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
749 sent = sent + 1
750 if receivedQuery:
751 receivedQuery.id = query.id
752 self.assertEquals(query, receivedQuery)
753 self.assertEquals(receivedResponse, response)
754 allowed = allowed + 1
755 else:
756 self.assertEquals(receivedResponse, truncatedResponse)
757 # the query has not reached the responder,
758 # let's clear the response queue
759 self.clearToResponderQueue()
760
761 # we might be already truncated, but we should have been able to send
762 # at least self._dynBlockQPS queries
763 self.assertGreaterEqual(allowed, self._dynBlockQPS)
764
765 if allowed == sent:
766 # wait for the maintenance function to run
767 time.sleep(2)
768
769 # we should now be 'truncated' for up to self._dynBlockDuration + self._dynBlockPeriod
770 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
771 self.assertEquals(receivedResponse, truncatedResponse)
772
773 # check over TCP, which should not be truncated
774 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
775
776 self.assertEquals(query, receivedQuery)
777 self.assertEquals(receivedResponse, response)
778
779 # wait until we are not blocked anymore
780 time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
781
782 # this one should succeed
783 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
784 receivedQuery.id = query.id
785 self.assertEquals(query, receivedQuery)
786 self.assertEquals(response, receivedResponse)
787
788 allowed = 0
789 sent = 0
790 # again, over TCP this time, we should never get truncated!
791 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
792 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
793 sent = sent + 1
794 self.assertEquals(query, receivedQuery)
795 self.assertEquals(receivedResponse, response)
796 receivedQuery.id = query.id
797 allowed = allowed + 1
798
799 self.assertEquals(allowed, sent)
800
801 class TestDynBlockServFails(DynBlocksTest):
802
803 _dynBlockQPS = 10
804 _dynBlockPeriod = 2
805 _dynBlockDuration = 5
806 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
807 _config_template = """
808 function maintenance()
809 addDynBlocks(exceedServFails(%d, %d), "Exceeded servfail rate", %d)
810 end
811 newServer{address="127.0.0.1:%s"}
812 """
813
814 def testDynBlocksServFailRate(self):
815 """
816 Dyn Blocks: Server Failure Rate
817 """
818 name = 'servfailrate.dynblocks.tests.powerdns.com.'
819 self.doTestRCodeRate(name, dns.rcode.SERVFAIL)
820
821 class TestDynBlockWhitelist(DynBlocksTest):
822
823 _dynBlockQPS = 10
824 _dynBlockPeriod = 2
825 _dynBlockDuration = 5
826 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
827 _config_template = """
828 whitelisted = false
829 function maintenance()
830 toBlock = exceedQRate(%d, %d)
831 for addr, count in pairs(toBlock) do
832 if addr:toString() == "127.0.0.1" then
833 whitelisted = true
834 toBlock[addr] = nil
835 end
836 end
837 addDynBlocks(toBlock, "Exceeded query rate", %d)
838 end
839
840 function spoofrule(dq)
841 if (whitelisted)
842 then
843 return DNSAction.Spoof, "192.0.2.42"
844 else
845 return DNSAction.None, ""
846 end
847 end
848 addAction("whitelisted-test.dynblocks.tests.powerdns.com.", LuaAction(spoofrule))
849
850 newServer{address="127.0.0.1:%s"}
851 """
852
853 def testWhitelisted(self):
854 """
855 Dyn Blocks: Whitelisted from the dynamic blocks
856 """
857 name = 'whitelisted.dynblocks.tests.powerdns.com.'
858 query = dns.message.make_query(name, 'A', 'IN')
859 response = dns.message.make_response(query)
860 rrset = dns.rrset.from_text(name,
861 60,
862 dns.rdataclass.IN,
863 dns.rdatatype.A,
864 '192.0.2.1')
865 response.answer.append(rrset)
866
867 allowed = 0
868 sent = 0
869 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
870 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
871 sent = sent + 1
872 if receivedQuery:
873 receivedQuery.id = query.id
874 self.assertEquals(query, receivedQuery)
875 self.assertEquals(response, receivedResponse)
876 allowed = allowed + 1
877 else:
878 # the query has not reached the responder,
879 # let's clear the response queue
880 self.clearToResponderQueue()
881
882 # we should not have been blocked
883 self.assertEqual(allowed, sent)
884
885 # wait for the maintenance function to run
886 time.sleep(2)
887
888 # we should still not be blocked
889 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
890 receivedQuery.id = query.id
891 self.assertEquals(query, receivedQuery)
892 self.assertEquals(receivedResponse, receivedResponse)
893
894 # check that we would have been blocked without the whitelisting
895 name = 'whitelisted-test.dynblocks.tests.powerdns.com.'
896 query = dns.message.make_query(name, 'A', 'IN')
897 # dnsdist set RA = RD for spoofed responses
898 query.flags &= ~dns.flags.RD
899 expectedResponse = dns.message.make_response(query)
900 rrset = dns.rrset.from_text(name,
901 60,
902 dns.rdataclass.IN,
903 dns.rdatatype.A,
904 '192.0.2.42')
905 expectedResponse.answer.append(rrset)
906 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
907 self.assertEquals(receivedResponse, expectedResponse)
908
909 class TestDynBlockGroupServFails(DynBlocksTest):
910
911 _dynBlockQPS = 10
912 _dynBlockPeriod = 2
913 _dynBlockDuration = 5
914 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
915 _config_template = """
916 local dbr = dynBlockRulesGroup()
917 dbr:setRCodeRate(DNSRCode.SERVFAIL, %d, %d, "Exceeded query rate", %d)
918
919 function maintenance()
920 dbr:apply()
921 end
922
923 newServer{address="127.0.0.1:%s"}
924 """
925
926 def testDynBlocksServFailRate(self):
927 """
928 Dyn Blocks (group): Server Failure Rate
929 """
930 name = 'servfailrate.group.dynblocks.tests.powerdns.com.'
931 self.doTestRCodeRate(name, dns.rcode.SERVFAIL)
932
933 class TestDynBlockGroupServFailsRatio(DynBlocksTest):
934
935 _dynBlockPeriod = 2
936 _dynBlockDuration = 5
937 _config_params = ['_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
938 _config_template = """
939 local dbr = dynBlockRulesGroup()
940 dbr:setRCodeRatio(DNSRCode.SERVFAIL, 0.2, %d, "Exceeded query rate", %d, 20)
941
942 function maintenance()
943 dbr:apply()
944 end
945
946 newServer{address="127.0.0.1:%s"}
947 """
948
949 def testDynBlocksServFailRatio(self):
950 """
951 Dyn Blocks (group): Server Failure Ratio
952 """
953 name = 'servfailratio.group.dynblocks.tests.powerdns.com.'
954 self.doTestRCodeRatio(name, dns.rcode.SERVFAIL, 10, 10)
955
956 class TestDynBlockResponseBytes(DynBlocksTest):
957
958 _dynBlockBytesPerSecond = 200
959 _dynBlockPeriod = 2
960 _dynBlockDuration = 5
961 _consoleKey = DNSDistTest.generateConsoleKey()
962 _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
963 _config_params = ['_consoleKeyB64', '_consolePort', '_dynBlockBytesPerSecond', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
964 _config_template = """
965 setKey("%s")
966 controlSocket("127.0.0.1:%s")
967 function maintenance()
968 addDynBlocks(exceedRespByterate(%d, %d), "Exceeded response byterate", %d)
969 end
970 newServer{address="127.0.0.1:%s"}
971 """
972
973 def testDynBlocksResponseByteRate(self):
974 """
975 Dyn Blocks: Response Byte Rate
976 """
977 name = 'responsebyterate.dynblocks.tests.powerdns.com.'
978 self.doTestResponseByteRate(name)
979
980 class TestDynBlockGroupResponseBytes(DynBlocksTest):
981
982 _dynBlockBytesPerSecond = 200
983 _dynBlockPeriod = 2
984 _dynBlockDuration = 5
985 _consoleKey = DNSDistTest.generateConsoleKey()
986 _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
987 _config_params = ['_consoleKeyB64', '_consolePort', '_dynBlockBytesPerSecond', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
988 _config_template = """
989 setKey("%s")
990 controlSocket("127.0.0.1:%s")
991 local dbr = dynBlockRulesGroup()
992 dbr:setResponseByteRate(%d, %d, "Exceeded query rate", %d)
993
994 function maintenance()
995 dbr:apply()
996 end
997
998 newServer{address="127.0.0.1:%s"}
999 """
1000
1001 def testDynBlocksResponseByteRate(self):
1002 """
1003 Dyn Blocks (group) : Response Byte Rate
1004 """
1005 name = 'responsebyterate.group.dynblocks.tests.powerdns.com.'
1006 self.doTestResponseByteRate(name)
1007
1008 class TestDynBlockGroupExcluded(DynBlocksTest):
1009
1010 _dynBlockQPS = 10
1011 _dynBlockPeriod = 2
1012 _dynBlockDuration = 5
1013 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
1014 _config_template = """
1015 local dbr = dynBlockRulesGroup()
1016 dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
1017 dbr:excludeRange("127.0.0.1/32")
1018
1019 function maintenance()
1020 dbr:apply()
1021 end
1022
1023 newServer{address="127.0.0.1:%s"}
1024 """
1025
1026 def testExcluded(self):
1027 """
1028 Dyn Blocks (group) : Excluded from the dynamic block rules
1029 """
1030 name = 'excluded.group.dynblocks.tests.powerdns.com.'
1031 query = dns.message.make_query(name, 'A', 'IN')
1032 response = dns.message.make_response(query)
1033 rrset = dns.rrset.from_text(name,
1034 60,
1035 dns.rdataclass.IN,
1036 dns.rdatatype.A,
1037 '192.0.2.1')
1038 response.answer.append(rrset)
1039
1040 allowed = 0
1041 sent = 0
1042 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
1043 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
1044 sent = sent + 1
1045 if receivedQuery:
1046 receivedQuery.id = query.id
1047 self.assertEquals(query, receivedQuery)
1048 self.assertEquals(response, receivedResponse)
1049 allowed = allowed + 1
1050 else:
1051 # the query has not reached the responder,
1052 # let's clear the response queue
1053 self.clearToResponderQueue()
1054
1055 # we should not have been blocked
1056 self.assertEqual(allowed, sent)
1057
1058 # wait for the maintenance function to run
1059 time.sleep(2)
1060
1061 # we should still not be blocked
1062 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
1063 receivedQuery.id = query.id
1064 self.assertEquals(query, receivedQuery)
1065 self.assertEquals(receivedResponse, receivedResponse)
1066
1067 class TestDynBlockGroupNoOp(DynBlocksTest):
1068
1069 _dynBlockQPS = 10
1070 _dynBlockPeriod = 2
1071 _dynBlockDuration = 5
1072 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
1073 _config_template = """
1074 local dbr = dynBlockRulesGroup()
1075 dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.NoOp)
1076
1077 function maintenance()
1078 dbr:apply()
1079 end
1080
1081 newServer{address="127.0.0.1:%s"}
1082 webserver("127.0.0.1:%s", "%s", "%s")
1083 """
1084
1085 def testNoOp(self):
1086 """
1087 Dyn Blocks (group) : NoOp
1088 """
1089 name = 'noop.group.dynblocks.tests.powerdns.com.'
1090 query = dns.message.make_query(name, 'A', 'IN')
1091 response = dns.message.make_response(query)
1092 rrset = dns.rrset.from_text(name,
1093 60,
1094 dns.rdataclass.IN,
1095 dns.rdatatype.A,
1096 '192.0.2.1')
1097 response.answer.append(rrset)
1098
1099 allowed = 0
1100 sent = 0
1101 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
1102 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
1103 sent = sent + 1
1104 if receivedQuery:
1105 receivedQuery.id = query.id
1106 self.assertEquals(query, receivedQuery)
1107 self.assertEquals(response, receivedResponse)
1108 allowed = allowed + 1
1109 else:
1110 # the query has not reached the responder,
1111 # let's clear the response queue
1112 self.clearToResponderQueue()
1113
1114 # a dynamic rule should have been inserted, but the queries should still go on
1115 self.assertEqual(allowed, sent)
1116
1117 # wait for the maintenance function to run
1118 time.sleep(2)
1119
1120 # the rule should still be present, but the queries pass through anyway
1121 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
1122 receivedQuery.id = query.id
1123 self.assertEquals(query, receivedQuery)
1124 self.assertEquals(receivedResponse, receivedResponse)
1125
1126 # check that the rule has been inserted
1127 self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', self._dynBlockDuration - 4, self._dynBlockDuration, 0, sent)
1128
1129 class TestDynBlockGroupWarning(DynBlocksTest):
1130
1131 _dynBlockWarningQPS = 5
1132 _dynBlockQPS = 20
1133 _dynBlockPeriod = 2
1134 _dynBlockDuration = 5
1135 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_dynBlockWarningQPS', '_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
1136 _config_template = """
1137 local dbr = dynBlockRulesGroup()
1138 dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.Drop, %d)
1139
1140 function maintenance()
1141 dbr:apply()
1142 end
1143
1144 newServer{address="127.0.0.1:%s"}
1145 webserver("127.0.0.1:%s", "%s", "%s")
1146 """
1147
1148 def testWarning(self):
1149 """
1150 Dyn Blocks (group) : Warning
1151 """
1152 name = 'warning.group.dynblocks.tests.powerdns.com.'
1153 query = dns.message.make_query(name, 'A', 'IN')
1154 response = dns.message.make_response(query)
1155 rrset = dns.rrset.from_text(name,
1156 60,
1157 dns.rdataclass.IN,
1158 dns.rdatatype.A,
1159 '192.0.2.1')
1160 response.answer.append(rrset)
1161
1162 allowed = 0
1163 sent = 0
1164 for _ in range((self._dynBlockWarningQPS * self._dynBlockPeriod) + 1):
1165 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
1166 sent = sent + 1
1167 if receivedQuery:
1168 receivedQuery.id = query.id
1169 self.assertEquals(query, receivedQuery)
1170 self.assertEquals(response, receivedResponse)
1171 allowed = allowed + 1
1172 else:
1173 # the query has not reached the responder,
1174 # let's clear the response queue
1175 self.clearToResponderQueue()
1176
1177 # a dynamic rule should have been inserted, but the queries should
1178 # still go on because we are still at warning level
1179 self.assertEqual(allowed, sent)
1180
1181 # wait for the maintenance function to run
1182 time.sleep(2)
1183
1184 # the rule should still be present, but the queries pass through anyway
1185 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
1186 receivedQuery.id = query.id
1187 self.assertEquals(query, receivedQuery)
1188 self.assertEquals(receivedResponse, receivedResponse)
1189
1190 # check that the rule has been inserted
1191 self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', self._dynBlockDuration - 4, self._dynBlockDuration, 0, sent)
1192
1193 self.doTestQRate(name)