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 response
= dns
.message
.make_response(query
)
736 rrset
= dns
.rrset
.from_text(name
,
741 response
.answer
.append(rrset
)
742 truncatedResponse
= dns
.message
.make_response(query
)
743 truncatedResponse
.flags |
= dns
.flags
.TC
747 for _
in range((self
._dynBlockQPS
* self
._dynBlockPeriod
) + 1):
748 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
751 receivedQuery
.id = query
.id
752 self
.assertEquals(query
, receivedQuery
)
753 self
.assertEquals(receivedResponse
, response
)
754 allowed
= allowed
+ 1
756 self
.assertEquals(receivedResponse
, truncatedResponse
)
757 # the query has not reached the responder,
758 # let's clear the response queue
759 self
.clearToResponderQueue()
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
)
766 # wait for the maintenance function to run
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
)
773 # check over TCP, which should not be truncated
774 (receivedQuery
, receivedResponse
) = self
.sendTCPQuery(query
, response
)
776 self
.assertEquals(query
, receivedQuery
)
777 self
.assertEquals(receivedResponse
, response
)
779 # wait until we are not blocked anymore
780 time
.sleep(self
._dynBlockDuration
+ self
._dynBlockPeriod
)
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
)
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
)
794 self
.assertEquals(query
, receivedQuery
)
795 self
.assertEquals(receivedResponse
, response
)
796 receivedQuery
.id = query
.id
797 allowed
= allowed
+ 1
799 self
.assertEquals(allowed
, sent
)
801 class TestDynBlockServFails(DynBlocksTest
):
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)
811 newServer{address="127.0.0.1:%s"}
814 def testDynBlocksServFailRate(self
):
816 Dyn Blocks: Server Failure Rate
818 name
= 'servfailrate.dynblocks.tests.powerdns.com.'
819 self
.doTestRCodeRate(name
, dns
.rcode
.SERVFAIL
)
821 class TestDynBlockWhitelist(DynBlocksTest
):
825 _dynBlockDuration
= 5
826 _config_params
= ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
827 _config_template
= """
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
837 addDynBlocks(toBlock, "Exceeded query rate", %d)
840 function spoofrule(dq)
843 return DNSAction.Spoof, "192.0.2.42"
845 return DNSAction.None, ""
848 addAction("whitelisted-test.dynblocks.tests.powerdns.com.", LuaAction(spoofrule))
850 newServer{address="127.0.0.1:%s"}
853 def testWhitelisted(self
):
855 Dyn Blocks: Whitelisted from the dynamic blocks
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
,
865 response
.answer
.append(rrset
)
869 for _
in range((self
._dynBlockQPS
* self
._dynBlockPeriod
) + 1):
870 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
873 receivedQuery
.id = query
.id
874 self
.assertEquals(query
, receivedQuery
)
875 self
.assertEquals(response
, receivedResponse
)
876 allowed
= allowed
+ 1
878 # the query has not reached the responder,
879 # let's clear the response queue
880 self
.clearToResponderQueue()
882 # we should not have been blocked
883 self
.assertEqual(allowed
, sent
)
885 # wait for the maintenance function to run
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
)
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
,
905 expectedResponse
.answer
.append(rrset
)
906 (_
, receivedResponse
) = self
.sendUDPQuery(query
, response
=None, useQueue
=False)
907 self
.assertEquals(receivedResponse
, expectedResponse
)
909 class TestDynBlockGroupServFails(DynBlocksTest
):
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)
919 function maintenance()
923 newServer{address="127.0.0.1:%s"}
926 def testDynBlocksServFailRate(self
):
928 Dyn Blocks (group): Server Failure Rate
930 name
= 'servfailrate.group.dynblocks.tests.powerdns.com.'
931 self
.doTestRCodeRate(name
, dns
.rcode
.SERVFAIL
)
933 class TestDynBlockGroupServFailsRatio(DynBlocksTest
):
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)
942 function maintenance()
946 newServer{address="127.0.0.1:%s"}
949 def testDynBlocksServFailRatio(self
):
951 Dyn Blocks (group): Server Failure Ratio
953 name
= 'servfailratio.group.dynblocks.tests.powerdns.com.'
954 self
.doTestRCodeRatio(name
, dns
.rcode
.SERVFAIL
, 10, 10)
956 class TestDynBlockResponseBytes(DynBlocksTest
):
958 _dynBlockBytesPerSecond
= 200
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
= """
966 controlSocket("127.0.0.1:%s")
967 function maintenance()
968 addDynBlocks(exceedRespByterate(%d, %d), "Exceeded response byterate", %d)
970 newServer{address="127.0.0.1:%s"}
973 def testDynBlocksResponseByteRate(self
):
975 Dyn Blocks: Response Byte Rate
977 name
= 'responsebyterate.dynblocks.tests.powerdns.com.'
978 self
.doTestResponseByteRate(name
)
980 class TestDynBlockGroupResponseBytes(DynBlocksTest
):
982 _dynBlockBytesPerSecond
= 200
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
= """
990 controlSocket("127.0.0.1:%s")
991 local dbr = dynBlockRulesGroup()
992 dbr:setResponseByteRate(%d, %d, "Exceeded query rate", %d)
994 function maintenance()
998 newServer{address="127.0.0.1:%s"}
1001 def testDynBlocksResponseByteRate(self
):
1003 Dyn Blocks (group) : Response Byte Rate
1005 name
= 'responsebyterate.group.dynblocks.tests.powerdns.com.'
1006 self
.doTestResponseByteRate(name
)
1008 class TestDynBlockGroupExcluded(DynBlocksTest
):
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")
1019 function maintenance()
1023 newServer{address="127.0.0.1:%s"}
1026 def testExcluded(self
):
1028 Dyn Blocks (group) : Excluded from the dynamic block rules
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
,
1038 response
.answer
.append(rrset
)
1042 for _
in range((self
._dynBlockQPS
* self
._dynBlockPeriod
) + 1):
1043 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
1046 receivedQuery
.id = query
.id
1047 self
.assertEquals(query
, receivedQuery
)
1048 self
.assertEquals(response
, receivedResponse
)
1049 allowed
= allowed
+ 1
1051 # the query has not reached the responder,
1052 # let's clear the response queue
1053 self
.clearToResponderQueue()
1055 # we should not have been blocked
1056 self
.assertEqual(allowed
, sent
)
1058 # wait for the maintenance function to run
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
)
1067 class TestDynBlockGroupNoOp(DynBlocksTest
):
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)
1077 function maintenance()
1081 newServer{address="127.0.0.1:%s"}
1082 webserver("127.0.0.1:%s", "%s", "%s")
1087 Dyn Blocks (group) : NoOp
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
,
1097 response
.answer
.append(rrset
)
1101 for _
in range((self
._dynBlockQPS
* self
._dynBlockPeriod
) + 1):
1102 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
1105 receivedQuery
.id = query
.id
1106 self
.assertEquals(query
, receivedQuery
)
1107 self
.assertEquals(response
, receivedResponse
)
1108 allowed
= allowed
+ 1
1110 # the query has not reached the responder,
1111 # let's clear the response queue
1112 self
.clearToResponderQueue()
1114 # a dynamic rule should have been inserted, but the queries should still go on
1115 self
.assertEqual(allowed
, sent
)
1117 # wait for the maintenance function to run
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
)
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
)
1129 class TestDynBlockGroupWarning(DynBlocksTest
):
1131 _dynBlockWarningQPS
= 5
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)
1140 function maintenance()
1144 newServer{address="127.0.0.1:%s"}
1145 webserver("127.0.0.1:%s", "%s", "%s")
1148 def testWarning(self
):
1150 Dyn Blocks (group) : Warning
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
,
1160 response
.answer
.append(rrset
)
1164 for _
in range((self
._dynBlockWarningQPS
* self
._dynBlockPeriod
) + 1):
1165 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
1168 receivedQuery
.id = query
.id
1169 self
.assertEquals(query
, receivedQuery
)
1170 self
.assertEquals(response
, receivedResponse
)
1171 allowed
= allowed
+ 1
1173 # the query has not reached the responder,
1174 # let's clear the response queue
1175 self
.clearToResponderQueue()
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
)
1181 # wait for the maintenance function to run
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
)
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
)
1193 self
.doTestQRate(name
)