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