7 from dnsdisttests
import DNSDistTest
13 class DynBlocksTest(DNSDistTest
):
17 _webServerBasicAuthPassword
= 'secret'
18 _webServerAPIKey
= 'apisecret'
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
)
25 self
.assertEquals(r
.status_code
, 200)
28 self
.assertIsNotNone(content
)
29 self
.assertIn(range, content
)
31 values
= content
[range]
32 for key
in ['reason', 'seconds', 'blocks', 'action']:
33 self
.assertIn(key
, values
)
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
)
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
,
49 response
.answer
.append(rrset
)
53 for _
in range((self
._dynBlockQPS
* self
._dynBlockPeriod
) + 1):
54 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
57 receivedQuery
.id = query
.id
58 self
.assertEquals(query
, receivedQuery
)
59 self
.assertEquals(response
, receivedResponse
)
62 # the query has not reached the responder,
63 # let's clear the response queue
64 self
.clearToResponderQueue()
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
)
71 # wait for the maintenance function to run
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)
79 self
.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', self
._dynBlockDuration
- 4, self
._dynBlockDuration
, (sent
-allowed
)+1, (sent
-allowed
)+1)
81 # wait until we are not blocked anymore
82 time
.sleep(self
._dynBlockDuration
+ self
._dynBlockPeriod
)
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
)
90 # again, over TCP this time
93 for _
in range((self
._dynBlockQPS
* self
._dynBlockPeriod
) + 1):
94 (receivedQuery
, receivedResponse
) = self
.sendTCPQuery(query
, response
)
97 receivedQuery
.id = query
.id
98 self
.assertEquals(query
, receivedQuery
)
99 self
.assertEquals(response
, receivedResponse
)
100 allowed
= allowed
+ 1
102 # the query has not reached the responder,
103 # let's clear the response queue
104 self
.clearToResponderQueue()
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
)
111 # wait for the maintenance function to run
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)
118 # wait until we are not blocked anymore
119 time
.sleep(self
._dynBlockDuration
+ self
._dynBlockPeriod
)
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
)
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
,
135 response
.answer
.append(rrset
)
136 expectedResponse
= dns
.message
.make_response(query
)
137 expectedResponse
.set_rcode(rcode
)
141 for _
in range((self
._dynBlockQPS
* self
._dynBlockPeriod
) + 1):
142 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
145 receivedQuery
.id = query
.id
146 self
.assertEquals(query
, receivedQuery
)
147 self
.assertEquals(receivedResponse
, response
)
148 allowed
= allowed
+ 1
150 self
.assertEquals(receivedResponse
, expectedResponse
)
151 # the query has not reached the responder,
152 # let's clear the response queue
153 self
.clearToResponderQueue()
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
)
160 # wait for the maintenance function to run
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
)
167 # wait until we are not blocked anymore
168 time
.sleep(self
._dynBlockDuration
+ self
._dynBlockPeriod
)
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
)
178 # again, over TCP this time
179 for _
in range((self
._dynBlockQPS
* self
._dynBlockPeriod
) + 1):
180 (receivedQuery
, receivedResponse
) = self
.sendTCPQuery(query
, response
)
183 receivedQuery
.id = query
.id
184 self
.assertEquals(query
, receivedQuery
)
185 self
.assertEquals(receivedResponse
, response
)
186 allowed
= allowed
+ 1
188 self
.assertEquals(receivedResponse
, expectedResponse
)
189 # the query has not reached the responder,
190 # let's clear the response queue
191 self
.clearToResponderQueue()
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
)
198 # wait for the maintenance function to run
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
)
205 # wait until we are not blocked anymore
206 time
.sleep(self
._dynBlockDuration
+ self
._dynBlockPeriod
)
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
)
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
,
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
,
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())
237 receivedQuery
.id = query
.id
238 self
.assertEquals(query
, receivedQuery
)
239 self
.assertEquals(response
, receivedResponse
)
240 allowed
= allowed
+ len(response
.to_wire())
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
250 # we might be already blocked, but we should have been able to send
251 # at least self._dynBlockBytesPerSecond bytes
255 self
.assertGreaterEqual(allowed
, self
._dynBlockBytesPerSecond
)
257 print(self
.sendConsoleCommand("showDynBlocks()"))
258 print(self
.sendConsoleCommand("grepq(\"\")"))
262 # wait for the maintenance function to run
263 print("Waiting for the maintenance function to run")
266 print(self
.sendConsoleCommand("showDynBlocks()"))
267 print(self
.sendConsoleCommand("grepq(\"\")"))
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)
274 print(self
.sendConsoleCommand("showDynBlocks()"))
275 print(self
.sendConsoleCommand("grepq(\"\")"))
278 # wait until we are not blocked anymore
279 time
.sleep(self
._dynBlockDuration
+ self
._dynBlockPeriod
)
281 print(self
.sendConsoleCommand("showDynBlocks()"))
282 print(self
.sendConsoleCommand("grepq(\"\")"))
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
)
291 # again, over TCP this time
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())
298 receivedQuery
.id = query
.id
299 self
.assertEquals(query
, receivedQuery
)
300 self
.assertEquals(response
, receivedResponse
)
301 allowed
= allowed
+ len(response
.to_wire())
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
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
)
316 # wait for the maintenance function to run
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)
323 # wait until we are not blocked anymore
324 time
.sleep(self
._dynBlockDuration
+ self
._dynBlockPeriod
)
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
)
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
,
340 response
.answer
.append(rrset
)
341 expectedResponse
= dns
.message
.make_response(query
)
342 expectedResponse
.set_rcode(rcode
)
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
)
351 # wait for the maintenance function to run
354 # we should NOT be dropped!
355 (_
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
356 self
.assertEquals(receivedResponse
, response
)
361 for _
in range((self
._dynBlockQPS
* self
._dynBlockPeriod
) + 1):
362 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, expectedResponse
)
365 receivedQuery
.id = query
.id
366 self
.assertEquals(query
, receivedQuery
)
367 self
.assertEquals(expectedResponse
, receivedResponse
)
368 allowed
= allowed
+ 1
370 # the query has not reached the responder,
371 # let's clear the response queue
372 self
.clearToResponderQueue()
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
)
379 # wait for the maintenance function to run
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)
386 # wait until we are not blocked anymore
387 time
.sleep(self
._dynBlockDuration
+ self
._dynBlockPeriod
)
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
)
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
)
403 # wait for the maintenance function to run
406 # we should NOT be dropped!
407 (_
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
408 self
.assertEquals(receivedResponse
, response
)
413 for _
in range((self
._dynBlockQPS
* self
._dynBlockPeriod
) + 1):
414 (receivedQuery
, receivedResponse
) = self
.sendTCPQuery(query
, expectedResponse
)
417 receivedQuery
.id = query
.id
418 self
.assertEquals(query
, receivedQuery
)
419 self
.assertEquals(expectedResponse
, receivedResponse
)
420 allowed
= allowed
+ 1
422 # the query has not reached the responder,
423 # let's clear the response queue
424 self
.clearToResponderQueue()
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
)
431 # wait for the maintenance function to run
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)
438 # wait until we are not blocked anymore
439 time
.sleep(self
._dynBlockDuration
+ self
._dynBlockPeriod
)
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
)
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
,
455 response
.answer
.append(rrset
)
456 expectedResponse
= dns
.message
.make_response(query
)
457 expectedResponse
.set_rcode(rcode
)
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
)
466 # wait for the maintenance function to run
469 # we should NOT be dropped!
470 (_
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
471 self
.assertEquals(receivedResponse
, response
)
476 for _
in range(rcodecount
):
477 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, expectedResponse
)
480 receivedQuery
.id = query
.id
481 self
.assertEquals(query
, receivedQuery
)
482 self
.assertEquals(expectedResponse
, receivedResponse
)
483 allowed
= allowed
+ 1
485 # the query has not reached the responder,
486 # let's clear the response queue
487 self
.clearToResponderQueue()
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
)
492 # wait for the maintenance function to run
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)
499 # wait until we are not blocked anymore
500 time
.sleep(self
._dynBlockDuration
+ self
._dynBlockPeriod
)
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
)
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
)
516 # wait for the maintenance function to run
519 # we should NOT be dropped!
520 (_
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
521 self
.assertEquals(receivedResponse
, response
)
526 for _
in range(rcodecount
):
527 (receivedQuery
, receivedResponse
) = self
.sendTCPQuery(query
, expectedResponse
)
530 receivedQuery
.id = query
.id
531 self
.assertEquals(query
, receivedQuery
)
532 self
.assertEquals(expectedResponse
, receivedResponse
)
533 allowed
= allowed
+ 1
535 # the query has not reached the responder,
536 # let's clear the response queue
537 self
.clearToResponderQueue()
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
)
542 # wait for the maintenance function to run
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)
549 # wait until we are not blocked anymore
550 time
.sleep(self
._dynBlockDuration
+ self
._dynBlockPeriod
)
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
)
558 class TestDynBlockQPS(DynBlocksTest
):
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)
568 newServer{address="127.0.0.1:%s"}
569 webserver("127.0.0.1:%s", "%s", "%s")
572 def testDynBlocksQRate(self
):
576 name
= 'qrate.dynblocks.tests.powerdns.com.'
577 self
.doTestQRate(name
)
579 class TestDynBlockGroupQPS(DynBlocksTest
):
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)
589 function maintenance()
592 newServer{address="127.0.0.1:%s"}
593 webserver("127.0.0.1:%s", "%s", "%s")
596 def testDynBlocksQRate(self
):
598 Dyn Blocks (Group): QRate
600 name
= 'qrate.group.dynblocks.tests.powerdns.com.'
601 self
.doTestQRate(name
)
604 class TestDynBlockQPSRefused(DynBlocksTest
):
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)
614 setDynBlocksAction(DNSAction.Refused)
615 newServer{address="127.0.0.1:%s"}
618 def testDynBlocksQRate(self
):
620 Dyn Blocks: QRate refused
622 name
= 'qraterefused.dynblocks.tests.powerdns.com.'
623 self
.doTestQRateRCode(name
, dns
.rcode
.REFUSED
)
625 class TestDynBlockGroupQPSRefused(DynBlocksTest
):
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)
635 function maintenance()
638 setDynBlocksAction(DNSAction.Refused)
639 newServer{address="127.0.0.1:%s"}
642 def testDynBlocksQRate(self
):
644 Dyn Blocks (Group): QRate refused
646 name
= 'qraterefused.group.dynblocks.tests.powerdns.com.'
647 self
.doTestQRateRCode(name
, dns
.rcode
.REFUSED
)
649 class TestDynBlockQPSActionRefused(DynBlocksTest
):
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)
659 setDynBlocksAction(DNSAction.Drop)
660 newServer{address="127.0.0.1:%s"}
663 def testDynBlocksQRate(self
):
665 Dyn Blocks: QRate refused (action)
667 name
= 'qrateactionrefused.dynblocks.tests.powerdns.com.'
668 self
.doTestQRateRCode(name
, dns
.rcode
.REFUSED
)
670 class TestDynBlockQPSActionNXD(DynBlocksTest
):
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)
680 setDynBlocksAction(DNSAction.Drop)
681 newServer{address="127.0.0.1:%s"}
684 def testDynBlocksQRate(self
):
686 Dyn Blocks: QRate NXD (action)
688 name
= 'qrateactionnxd.dynblocks.tests.powerdns.com.'
689 self
.doTestQRateRCode(name
, dns
.rcode
.NXDOMAIN
)
691 class TestDynBlockGroupQPSActionRefused(DynBlocksTest
):
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)
701 function maintenance()
704 setDynBlocksAction(DNSAction.Drop)
705 newServer{address="127.0.0.1:%s"}
708 def testDynBlocksQRate(self
):
710 Dyn Blocks (group): QRate refused (action)
712 name
= 'qrateactionrefused.group.dynblocks.tests.powerdns.com.'
713 self
.doTestQRateRCode(name
, dns
.rcode
.REFUSED
)
715 class TestDynBlockQPSActionTruncated(DNSDistTest
):
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)
725 setDynBlocksAction(DNSAction.Drop)
726 newServer{address="127.0.0.1:%s"}
729 def testDynBlocksQRate(self
):
731 Dyn Blocks: QRate truncated (action)
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
,
743 response
.answer
.append(rrset
)
744 truncatedResponse
= dns
.message
.make_response(query
)
745 truncatedResponse
.flags |
= dns
.flags
.TC
749 for _
in range((self
._dynBlockQPS
* self
._dynBlockPeriod
) + 1):
750 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
753 receivedQuery
.id = query
.id
754 self
.assertEquals(query
, receivedQuery
)
755 self
.assertEquals(receivedResponse
, response
)
756 allowed
= allowed
+ 1
758 self
.assertEquals(receivedResponse
, truncatedResponse
)
759 # the query has not reached the responder,
760 # let's clear the response queue
761 self
.clearToResponderQueue()
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
)
768 # wait for the maintenance function to run
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
)
775 # check over TCP, which should not be truncated
776 (receivedQuery
, receivedResponse
) = self
.sendTCPQuery(query
, response
)
778 self
.assertEquals(query
, receivedQuery
)
779 self
.assertEquals(receivedResponse
, response
)
781 # wait until we are not blocked anymore
782 time
.sleep(self
._dynBlockDuration
+ self
._dynBlockPeriod
)
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
)
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
)
796 self
.assertEquals(query
, receivedQuery
)
797 self
.assertEquals(receivedResponse
, response
)
798 receivedQuery
.id = query
.id
799 allowed
= allowed
+ 1
801 self
.assertEquals(allowed
, sent
)
803 class TestDynBlockServFails(DynBlocksTest
):
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)
813 newServer{address="127.0.0.1:%s"}
816 def testDynBlocksServFailRate(self
):
818 Dyn Blocks: Server Failure Rate
820 name
= 'servfailrate.dynblocks.tests.powerdns.com.'
821 self
.doTestRCodeRate(name
, dns
.rcode
.SERVFAIL
)
823 class TestDynBlockWhitelist(DynBlocksTest
):
827 _dynBlockDuration
= 5
828 _config_params
= ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
829 _config_template
= """
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
839 addDynBlocks(toBlock, "Exceeded query rate", %d)
842 function spoofrule(dq)
845 return DNSAction.Spoof, "192.0.2.42"
847 return DNSAction.None, ""
850 addAction("whitelisted-test.dynblocks.tests.powerdns.com.", LuaAction(spoofrule))
852 newServer{address="127.0.0.1:%s"}
855 def testWhitelisted(self
):
857 Dyn Blocks: Whitelisted from the dynamic blocks
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
,
867 response
.answer
.append(rrset
)
871 for _
in range((self
._dynBlockQPS
* self
._dynBlockPeriod
) + 1):
872 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
875 receivedQuery
.id = query
.id
876 self
.assertEquals(query
, receivedQuery
)
877 self
.assertEquals(response
, receivedResponse
)
878 allowed
= allowed
+ 1
880 # the query has not reached the responder,
881 # let's clear the response queue
882 self
.clearToResponderQueue()
884 # we should not have been blocked
885 self
.assertEqual(allowed
, sent
)
887 # wait for the maintenance function to run
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
)
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
,
907 expectedResponse
.answer
.append(rrset
)
908 (_
, receivedResponse
) = self
.sendUDPQuery(query
, response
=None, useQueue
=False)
909 self
.assertEquals(receivedResponse
, expectedResponse
)
911 class TestDynBlockGroupServFails(DynBlocksTest
):
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)
921 function maintenance()
925 newServer{address="127.0.0.1:%s"}
928 def testDynBlocksServFailRate(self
):
930 Dyn Blocks (group): Server Failure Rate
932 name
= 'servfailrate.group.dynblocks.tests.powerdns.com.'
933 self
.doTestRCodeRate(name
, dns
.rcode
.SERVFAIL
)
935 class TestDynBlockGroupServFailsRatio(DynBlocksTest
):
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)
944 function maintenance()
948 newServer{address="127.0.0.1:%s"}
951 def testDynBlocksServFailRatio(self
):
953 Dyn Blocks (group): Server Failure Ratio
955 name
= 'servfailratio.group.dynblocks.tests.powerdns.com.'
956 self
.doTestRCodeRatio(name
, dns
.rcode
.SERVFAIL
, 10, 10)
958 class TestDynBlockResponseBytes(DynBlocksTest
):
960 _dynBlockBytesPerSecond
= 200
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
= """
968 controlSocket("127.0.0.1:%s")
969 function maintenance()
970 addDynBlocks(exceedRespByterate(%d, %d), "Exceeded response byterate", %d)
972 newServer{address="127.0.0.1:%s"}
975 def testDynBlocksResponseByteRate(self
):
977 Dyn Blocks: Response Byte Rate
979 name
= 'responsebyterate.dynblocks.tests.powerdns.com.'
980 self
.doTestResponseByteRate(name
)
982 class TestDynBlockGroupResponseBytes(DynBlocksTest
):
984 _dynBlockBytesPerSecond
= 200
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
= """
992 controlSocket("127.0.0.1:%s")
993 local dbr = dynBlockRulesGroup()
994 dbr:setResponseByteRate(%d, %d, "Exceeded query rate", %d)
996 function maintenance()
1000 newServer{address="127.0.0.1:%s"}
1003 def testDynBlocksResponseByteRate(self
):
1005 Dyn Blocks (group) : Response Byte Rate
1007 name
= 'responsebyterate.group.dynblocks.tests.powerdns.com.'
1008 self
.doTestResponseByteRate(name
)
1010 class TestDynBlockGroupExcluded(DynBlocksTest
):
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")
1021 function maintenance()
1025 newServer{address="127.0.0.1:%s"}
1028 def testExcluded(self
):
1030 Dyn Blocks (group) : Excluded from the dynamic block rules
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
,
1040 response
.answer
.append(rrset
)
1044 for _
in range((self
._dynBlockQPS
* self
._dynBlockPeriod
) + 1):
1045 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
1048 receivedQuery
.id = query
.id
1049 self
.assertEquals(query
, receivedQuery
)
1050 self
.assertEquals(response
, receivedResponse
)
1051 allowed
= allowed
+ 1
1053 # the query has not reached the responder,
1054 # let's clear the response queue
1055 self
.clearToResponderQueue()
1057 # we should not have been blocked
1058 self
.assertEqual(allowed
, sent
)
1060 # wait for the maintenance function to run
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
)
1069 class TestDynBlockGroupNoOp(DynBlocksTest
):
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)
1079 function maintenance()
1083 newServer{address="127.0.0.1:%s"}
1084 webserver("127.0.0.1:%s", "%s", "%s")
1089 Dyn Blocks (group) : NoOp
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
,
1099 response
.answer
.append(rrset
)
1103 for _
in range((self
._dynBlockQPS
* self
._dynBlockPeriod
) + 1):
1104 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
1107 receivedQuery
.id = query
.id
1108 self
.assertEquals(query
, receivedQuery
)
1109 self
.assertEquals(response
, receivedResponse
)
1110 allowed
= allowed
+ 1
1112 # the query has not reached the responder,
1113 # let's clear the response queue
1114 self
.clearToResponderQueue()
1116 # a dynamic rule should have been inserted, but the queries should still go on
1117 self
.assertEqual(allowed
, sent
)
1119 # wait for the maintenance function to run
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
)
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
)
1131 class TestDynBlockGroupWarning(DynBlocksTest
):
1133 _dynBlockWarningQPS
= 5
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)
1142 function maintenance()
1146 newServer{address="127.0.0.1:%s"}
1147 webserver("127.0.0.1:%s", "%s", "%s")
1150 def testWarning(self
):
1152 Dyn Blocks (group) : Warning
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
,
1162 response
.answer
.append(rrset
)
1166 for _
in range((self
._dynBlockWarningQPS
* self
._dynBlockPeriod
) + 1):
1167 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
1170 receivedQuery
.id = query
.id
1171 self
.assertEquals(query
, receivedQuery
)
1172 self
.assertEquals(response
, receivedResponse
)
1173 allowed
= allowed
+ 1
1175 # the query has not reached the responder,
1176 # let's clear the response queue
1177 self
.clearToResponderQueue()
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
)
1183 # wait for the maintenance function to run
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
)
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
)
1195 self
.doTestQRate(name
)