]> git.ipfire.org Git - thirdparty/pdns.git/blame - regression-tests.dnsdist/test_DynBlocks.py
dnsdist: Add DNSAction.NoOp to debug Dynamic Blocks
[thirdparty/pdns.git] / regression-tests.dnsdist / test_DynBlocks.py
CommitLineData
d354e773 1#!/usr/bin/env python
87b0577d 2import base64
d3473b8c
RG
3import json
4import requests
d354e773
RG
5import time
6import dns
b4f23783 7from dnsdisttests import DNSDistTest, range
d354e773 8
dc2fd93a 9class DynBlocksTest(DNSDistTest):
d354e773 10
d3473b8c
RG
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]
477c86a0 28 for key in ['reason', 'seconds', 'blocks', 'action']:
d3473b8c
RG
29 self.assertIn(key, values)
30
31 self.assertEqual(values['reason'], reason)
b8753918 32 self.assertGreaterEqual(values['seconds'], minSeconds)
d3473b8c
RG
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=False):
d354e773
RG
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
3bef39c3
RG
47 allowed = 0
48 sent = 0
b4f23783 49 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
d354e773 50 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
3bef39c3
RG
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
98883b8f 60 self.clearToResponderQueue()
3bef39c3
RG
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)
d354e773
RG
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
d3473b8c
RG
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
3bef39c3 77 # wait until we are not blocked anymore
d354e773
RG
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
3bef39c3
RG
87 allowed = 0
88 sent = 0
b4f23783 89 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
d354e773 90 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
3bef39c3
RG
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
98883b8f 100 self.clearToResponderQueue()
3bef39c3
RG
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)
d354e773
RG
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
3bef39c3 114 # wait until we are not blocked anymore
d354e773
RG
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
dc2fd93a 123 def doTestQRateRCode(self, name, rcode):
d354e773
RG
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)
dc2fd93a
RG
132 expectedResponse = dns.message.make_response(query)
133 expectedResponse.set_rcode(rcode)
d354e773 134
3bef39c3
RG
135 allowed = 0
136 sent = 0
b4f23783 137 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
d354e773 138 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
7b925432
RG
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:
dc2fd93a 146 self.assertEquals(receivedResponse, expectedResponse)
7b925432
RG
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
dc2fd93a 159 # we should now be 'rcode' for up to self._dynBlockDuration + self._dynBlockPeriod
7b925432 160 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
dc2fd93a 161 self.assertEquals(receivedResponse, expectedResponse)
7b925432
RG
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
b4f23783 175 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
7b925432
RG
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:
dc2fd93a 184 self.assertEquals(receivedResponse, expectedResponse)
7b925432
RG
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
dc2fd93a 197 # we should now be 'rcode' for up to self._dynBlockDuration + self._dynBlockPeriod
7b925432 198 (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
dc2fd93a 199 self.assertEquals(receivedResponse, expectedResponse)
7b925432
RG
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
dc2fd93a 210 def doTestResponseByteRate(self, name):
7b925432
RG
211 query = dns.message.make_query(name, 'A', 'IN')
212 response = dns.message.make_response(query)
dc2fd93a
RG
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'))
7b925432
RG
223
224 allowed = 0
225 sent = 0
dc2fd93a
RG
226
227 print(time.time())
228
229 for _ in range(int(self._dynBlockBytesPerSecond * 5 / len(response.to_wire()))):
7b925432 230 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
dc2fd93a 231 sent = sent + len(response.to_wire())
3bef39c3
RG
232 if receivedQuery:
233 receivedQuery.id = query.id
234 self.assertEquals(query, receivedQuery)
dc2fd93a
RG
235 self.assertEquals(response, receivedResponse)
236 allowed = allowed + len(response.to_wire())
3bef39c3 237 else:
3bef39c3
RG
238 # the query has not reached the responder,
239 # let's clear the response queue
98883b8f 240 self.clearToResponderQueue()
dc2fd93a
RG
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
3bef39c3
RG
245
246 # we might be already blocked, but we should have been able to send
dc2fd93a
RG
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())
3bef39c3
RG
256
257 if allowed == sent:
258 # wait for the maintenance function to run
dc2fd93a 259 print("Waiting for the maintenance function to run")
3bef39c3 260 time.sleep(2)
d354e773 261
dc2fd93a
RG
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
d354e773 267 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
dc2fd93a
RG
268 self.assertEquals(receivedResponse, None)
269
270 print(self.sendConsoleCommand("showDynBlocks()"))
271 print(self.sendConsoleCommand("grepq(\"\")"))
272 print(time.time())
d354e773 273
3bef39c3 274 # wait until we are not blocked anymore
d354e773
RG
275 time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
276
dc2fd93a
RG
277 print(self.sendConsoleCommand("showDynBlocks()"))
278 print(self.sendConsoleCommand("grepq(\"\")"))
279 print(time.time())
280
d354e773
RG
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
dc2fd93a 287 # again, over TCP this time
3bef39c3
RG
288 allowed = 0
289 sent = 0
dc2fd93a 290 for _ in range(int(self._dynBlockBytesPerSecond * 5 / len(response.to_wire()))):
d354e773 291 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
dc2fd93a 292 sent = sent + len(response.to_wire())
3bef39c3
RG
293 if receivedQuery:
294 receivedQuery.id = query.id
295 self.assertEquals(query, receivedQuery)
dc2fd93a
RG
296 self.assertEquals(response, receivedResponse)
297 allowed = allowed + len(response.to_wire())
3bef39c3 298 else:
3bef39c3
RG
299 # the query has not reached the responder,
300 # let's clear the response queue
98883b8f 301 self.clearToResponderQueue()
dc2fd93a
RG
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
3bef39c3
RG
306
307 # we might be already blocked, but we should have been able to send
dc2fd93a
RG
308 # at least self._dynBlockBytesPerSecond bytes
309 self.assertGreaterEqual(allowed, self._dynBlockBytesPerSecond)
3bef39c3
RG
310
311 if allowed == sent:
312 # wait for the maintenance function to run
313 time.sleep(2)
d354e773 314
dc2fd93a 315 # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
d354e773 316 (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
dc2fd93a 317 self.assertEquals(receivedResponse, None)
d354e773 318
3bef39c3 319 # wait until we are not blocked anymore
d354e773
RG
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
dc2fd93a 328 def doTestRCodeRate(self, name, rcode):
d354e773
RG
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)
dc2fd93a
RG
337 expectedResponse = dns.message.make_response(query)
338 expectedResponse.set_rcode(rcode)
d354e773
RG
339
340 # start with normal responses
b4f23783 341 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
d354e773
RG
342 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
343 receivedQuery.id = query.id
344 self.assertEquals(query, receivedQuery)
345 self.assertEquals(response, receivedResponse)
346
3bef39c3
RG
347 # wait for the maintenance function to run
348 time.sleep(2)
d354e773
RG
349
350 # we should NOT be dropped!
351 (_, receivedResponse) = self.sendUDPQuery(query, response)
352 self.assertEquals(receivedResponse, response)
353
dc2fd93a 354 # now with rcode!
3bef39c3
RG
355 sent = 0
356 allowed = 0
b4f23783 357 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
dc2fd93a 358 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, expectedResponse)
3bef39c3
RG
359 sent = sent + 1
360 if receivedQuery:
361 receivedQuery.id = query.id
362 self.assertEquals(query, receivedQuery)
dc2fd93a 363 self.assertEquals(expectedResponse, receivedResponse)
3bef39c3
RG
364 allowed = allowed + 1
365 else:
366 # the query has not reached the responder,
367 # let's clear the response queue
98883b8f 368 self.clearToResponderQueue()
3bef39c3
RG
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)
d354e773
RG
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
3bef39c3 382 # wait until we are not blocked anymore
d354e773
RG
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
b4f23783 393 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
d354e773
RG
394 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
395 receivedQuery.id = query.id
396 self.assertEquals(query, receivedQuery)
397 self.assertEquals(response, receivedResponse)
398
3bef39c3
RG
399 # wait for the maintenance function to run
400 time.sleep(2)
d354e773
RG
401
402 # we should NOT be dropped!
403 (_, receivedResponse) = self.sendUDPQuery(query, response)
404 self.assertEquals(receivedResponse, response)
405
dc2fd93a 406 # now with rcode!
3bef39c3
RG
407 sent = 0
408 allowed = 0
b4f23783 409 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
dc2fd93a 410 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, expectedResponse)
3bef39c3
RG
411 sent = sent + 1
412 if receivedQuery:
413 receivedQuery.id = query.id
414 self.assertEquals(query, receivedQuery)
dc2fd93a 415 self.assertEquals(expectedResponse, receivedResponse)
3bef39c3
RG
416 allowed = allowed + 1
417 else:
418 # the query has not reached the responder,
419 # let's clear the response queue
98883b8f 420 self.clearToResponderQueue()
3bef39c3
RG
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)
d354e773
RG
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
3bef39c3 434 # wait until we are not blocked anymore
d354e773
RG
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
dc2fd93a 443class TestDynBlockQPS(DynBlocksTest):
d354e773 444
dc2fd93a 445 _dynBlockQPS = 10
d354e773
RG
446 _dynBlockPeriod = 2
447 _dynBlockDuration = 5
d3473b8c 448 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
d354e773
RG
449 _config_template = """
450 function maintenance()
dc2fd93a 451 addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d)
d354e773
RG
452 end
453 newServer{address="127.0.0.1:%s"}
d3473b8c 454 webserver("127.0.0.1:%s", "%s", "%s")
d354e773
RG
455 """
456
dc2fd93a 457 def testDynBlocksQRate(self):
d354e773 458 """
dc2fd93a 459 Dyn Blocks: QRate
d354e773 460 """
dc2fd93a 461 name = 'qrate.dynblocks.tests.powerdns.com.'
d3473b8c 462 self.doTestQRate(name, testViaAPI=True)
d354e773 463
dc2fd93a 464class TestDynBlockGroupQPS(DynBlocksTest):
87b0577d 465
dc2fd93a
RG
466 _dynBlockQPS = 10
467 _dynBlockPeriod = 2
468 _dynBlockDuration = 5
d3473b8c 469 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
dc2fd93a
RG
470 _config_template = """
471 local dbr = dynBlockRulesGroup()
472 dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
87b0577d 473
dc2fd93a
RG
474 function maintenance()
475 dbr:apply()
476 end
477 newServer{address="127.0.0.1:%s"}
d3473b8c 478 webserver("127.0.0.1:%s", "%s", "%s")
dc2fd93a
RG
479 """
480
481 def testDynBlocksQRate(self):
482 """
483 Dyn Blocks (Group): QRate
484 """
485 name = 'qrate.group.dynblocks.tests.powerdns.com.'
d3473b8c 486 self.doTestQRate(name, testViaAPI=True)
dc2fd93a
RG
487
488
489class 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
510class 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
534class 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
d3473b8c 555class TestDynBlockGroupQPSActionRefused(DynBlocksTest):
dc2fd93a
RG
556
557 _dynBlockQPS = 10
558 _dynBlockPeriod = 2
559 _dynBlockDuration = 5
560 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
561 _config_template = """
562 local dbr = dynBlockRulesGroup()
563 dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.Refused)
564
565 function maintenance()
566 dbr:apply()
567 end
568 setDynBlocksAction(DNSAction.Drop)
569 newServer{address="127.0.0.1:%s"}
570 """
571
572 def testDynBlocksQRate(self):
573 """
574 Dyn Blocks (group): QRate refused (action)
575 """
576 name = 'qrateactionrefused.group.dynblocks.tests.powerdns.com.'
577 self.doTestQRateRCode(name, dns.rcode.REFUSED)
578
579class TestDynBlockQPSActionTruncated(DNSDistTest):
580
581 _dynBlockQPS = 10
582 _dynBlockPeriod = 2
583 _dynBlockDuration = 5
584 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
585 _config_template = """
586 function maintenance()
587 addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Truncate)
588 end
589 setDynBlocksAction(DNSAction.Drop)
590 newServer{address="127.0.0.1:%s"}
591 """
592
593 def testDynBlocksQRate(self):
594 """
595 Dyn Blocks: QRate truncated (action)
596 """
597 name = 'qrateactiontruncated.dynblocks.tests.powerdns.com.'
598 query = dns.message.make_query(name, 'A', 'IN')
599 response = dns.message.make_response(query)
600 rrset = dns.rrset.from_text(name,
601 60,
602 dns.rdataclass.IN,
603 dns.rdatatype.A,
604 '192.0.2.1')
605 response.answer.append(rrset)
606 truncatedResponse = dns.message.make_response(query)
607 truncatedResponse.flags |= dns.flags.TC
608
609 allowed = 0
610 sent = 0
611 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
d354e773 612 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
dc2fd93a 613 sent = sent + 1
3bef39c3
RG
614 if receivedQuery:
615 receivedQuery.id = query.id
616 self.assertEquals(query, receivedQuery)
dc2fd93a
RG
617 self.assertEquals(receivedResponse, response)
618 allowed = allowed + 1
3bef39c3 619 else:
dc2fd93a 620 self.assertEquals(receivedResponse, truncatedResponse)
3bef39c3
RG
621 # the query has not reached the responder,
622 # let's clear the response queue
98883b8f 623 self.clearToResponderQueue()
3bef39c3 624
dc2fd93a
RG
625 # we might be already truncated, but we should have been able to send
626 # at least self._dynBlockQPS queries
627 self.assertGreaterEqual(allowed, self._dynBlockQPS)
87b0577d 628
3bef39c3
RG
629 if allowed == sent:
630 # wait for the maintenance function to run
631 time.sleep(2)
d354e773 632
dc2fd93a 633 # we should now be 'truncated' for up to self._dynBlockDuration + self._dynBlockPeriod
d354e773 634 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
dc2fd93a 635 self.assertEquals(receivedResponse, truncatedResponse)
d354e773 636
dc2fd93a
RG
637 # check over TCP, which should not be truncated
638 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
639
640 self.assertEquals(query, receivedQuery)
641 self.assertEquals(receivedResponse, response)
87b0577d 642
3bef39c3 643 # wait until we are not blocked anymore
d354e773
RG
644 time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
645
646 # this one should succeed
647 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
648 receivedQuery.id = query.id
649 self.assertEquals(query, receivedQuery)
650 self.assertEquals(response, receivedResponse)
651
3bef39c3
RG
652 allowed = 0
653 sent = 0
dc2fd93a
RG
654 # again, over TCP this time, we should never get truncated!
655 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
d354e773 656 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
dc2fd93a
RG
657 sent = sent + 1
658 self.assertEquals(query, receivedQuery)
659 self.assertEquals(receivedResponse, response)
660 receivedQuery.id = query.id
661 allowed = allowed + 1
3bef39c3 662
dc2fd93a 663 self.assertEquals(allowed, sent)
3bef39c3 664
dc2fd93a 665class TestDynBlockServFails(DynBlocksTest):
d354e773 666
dc2fd93a
RG
667 _dynBlockQPS = 10
668 _dynBlockPeriod = 2
669 _dynBlockDuration = 5
670 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
671 _config_template = """
672 function maintenance()
673 addDynBlocks(exceedServFails(%d, %d), "Exceeded servfail rate", %d)
674 end
675 newServer{address="127.0.0.1:%s"}
676 """
d354e773 677
dc2fd93a
RG
678 def testDynBlocksServFailRate(self):
679 """
680 Dyn Blocks: Server Failure Rate
681 """
682 name = 'servfailrate.dynblocks.tests.powerdns.com.'
683 self.doTestRCodeRate(name, dns.rcode.SERVFAIL)
d354e773 684
ad3f984f
RG
685class TestDynBlockWhitelist(DynBlocksTest):
686
687 _dynBlockQPS = 10
688 _dynBlockPeriod = 2
689 _dynBlockDuration = 5
690 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
691 _config_template = """
692 whitelisted = false
693 function maintenance()
694 toBlock = exceedQRate(%d, %d)
695 for addr, count in pairs(toBlock) do
696 if addr:toString() == "127.0.0.1" then
697 whitelisted = true
698 toBlock[addr] = nil
699 end
700 end
701 addDynBlocks(toBlock, "Exceeded query rate", %d)
702 end
703
704 function spoofrule(dq)
705 if (whitelisted)
706 then
707 return DNSAction.Spoof, "192.0.2.42"
708 else
709 return DNSAction.None, ""
710 end
711 end
712 addAction("whitelisted-test.dynblocks.tests.powerdns.com.", LuaAction(spoofrule))
713
714 newServer{address="127.0.0.1:%s"}
715 """
716
717 def testWhitelisted(self):
718 """
719 Dyn Blocks: Whitelisted from the dynamic blocks
720 """
721 name = 'whitelisted.dynblocks.tests.powerdns.com.'
722 query = dns.message.make_query(name, 'A', 'IN')
723 response = dns.message.make_response(query)
724 rrset = dns.rrset.from_text(name,
725 60,
726 dns.rdataclass.IN,
727 dns.rdatatype.A,
728 '192.0.2.1')
729 response.answer.append(rrset)
730
731 allowed = 0
732 sent = 0
733 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
734 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
735 sent = sent + 1
736 if receivedQuery:
737 receivedQuery.id = query.id
738 self.assertEquals(query, receivedQuery)
739 self.assertEquals(response, receivedResponse)
740 allowed = allowed + 1
741 else:
742 # the query has not reached the responder,
743 # let's clear the response queue
744 self.clearToResponderQueue()
745
746 # we should not have been blocked
747 self.assertEqual(allowed, sent)
748
749 # wait for the maintenance function to run
750 time.sleep(2)
751
752 # we should still not be blocked
753 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
754 receivedQuery.id = query.id
755 self.assertEquals(query, receivedQuery)
756 self.assertEquals(receivedResponse, receivedResponse)
757
758 # check that we would have been blocked without the whitelisting
759 name = 'whitelisted-test.dynblocks.tests.powerdns.com.'
760 query = dns.message.make_query(name, 'A', 'IN')
761 # dnsdist set RA = RD for spoofed responses
762 query.flags &= ~dns.flags.RD
763 expectedResponse = dns.message.make_response(query)
764 rrset = dns.rrset.from_text(name,
765 60,
766 dns.rdataclass.IN,
767 dns.rdatatype.A,
768 '192.0.2.42')
769 expectedResponse.answer.append(rrset)
770 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
771 self.assertEquals(receivedResponse, expectedResponse)
772
dc2fd93a
RG
773class TestDynBlockGroupServFails(DynBlocksTest):
774
775 _dynBlockQPS = 10
776 _dynBlockPeriod = 2
777 _dynBlockDuration = 5
778 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
779 _config_template = """
780 local dbr = dynBlockRulesGroup()
781 dbr:setRCodeRate(dnsdist.SERVFAIL, %d, %d, "Exceeded query rate", %d)
782
783 function maintenance()
784 dbr:apply()
785 end
786
787 newServer{address="127.0.0.1:%s"}
788 """
789
790 def testDynBlocksServFailRate(self):
791 """
792 Dyn Blocks (group): Server Failure Rate
793 """
794 name = 'servfailrate.group.dynblocks.tests.powerdns.com.'
795 self.doTestRCodeRate(name, dns.rcode.SERVFAIL)
796
797class TestDynBlockResponseBytes(DynBlocksTest):
798
799 _dynBlockBytesPerSecond = 200
800 _dynBlockPeriod = 2
801 _dynBlockDuration = 5
802 _consoleKey = DNSDistTest.generateConsoleKey()
803 _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
804 _config_params = ['_consoleKeyB64', '_consolePort', '_dynBlockBytesPerSecond', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
805 _config_template = """
806 setKey("%s")
807 controlSocket("127.0.0.1:%s")
808 function maintenance()
809 addDynBlocks(exceedRespByterate(%d, %d), "Exceeded response byterate", %d)
810 end
811 newServer{address="127.0.0.1:%s"}
812 """
813
814 def testDynBlocksResponseByteRate(self):
815 """
816 Dyn Blocks: Response Byte Rate
817 """
818 name = 'responsebyterate.dynblocks.tests.powerdns.com.'
819 self.doTestResponseByteRate(name)
820
821class TestDynBlockGroupResponseBytes(DynBlocksTest):
822
823 _dynBlockBytesPerSecond = 200
824 _dynBlockPeriod = 2
825 _dynBlockDuration = 5
826 _consoleKey = DNSDistTest.generateConsoleKey()
827 _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
828 _config_params = ['_consoleKeyB64', '_consolePort', '_dynBlockBytesPerSecond', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
829 _config_template = """
830 setKey("%s")
831 controlSocket("127.0.0.1:%s")
832 local dbr = dynBlockRulesGroup()
833 dbr:setResponseByteRate(%d, %d, "Exceeded query rate", %d)
834
835 function maintenance()
836 dbr:apply()
837 end
838
839 newServer{address="127.0.0.1:%s"}
840 """
841
842 def testDynBlocksResponseByteRate(self):
843 """
844 Dyn Blocks (group) : Response Byte Rate
845 """
846 name = 'responsebyterate.group.dynblocks.tests.powerdns.com.'
847 self.doTestResponseByteRate(name)
b718792f 848
477c86a0 849class TestDynBlockGroupExcluded(DynBlocksTest):
b718792f
RG
850
851 _dynBlockQPS = 10
852 _dynBlockPeriod = 2
853 _dynBlockDuration = 5
477c86a0 854 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
b718792f 855 _config_template = """
b718792f
RG
856 local dbr = dynBlockRulesGroup()
857 dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
858 dbr:excludeRange("127.0.0.1/32")
859
860 function maintenance()
861 dbr:apply()
862 end
863
864 newServer{address="127.0.0.1:%s"}
865 """
866
867 def testExcluded(self):
868 """
869 Dyn Blocks (group) : Excluded from the dynamic block rules
870 """
871 name = 'excluded.group.dynblocks.tests.powerdns.com.'
872 query = dns.message.make_query(name, 'A', 'IN')
873 response = dns.message.make_response(query)
874 rrset = dns.rrset.from_text(name,
875 60,
876 dns.rdataclass.IN,
877 dns.rdatatype.A,
878 '192.0.2.1')
879 response.answer.append(rrset)
880
881 allowed = 0
882 sent = 0
883 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
884 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
885 sent = sent + 1
886 if receivedQuery:
887 receivedQuery.id = query.id
888 self.assertEquals(query, receivedQuery)
889 self.assertEquals(response, receivedResponse)
890 allowed = allowed + 1
891 else:
892 # the query has not reached the responder,
893 # let's clear the response queue
894 self.clearToResponderQueue()
895
477c86a0 896 # we should not have been blocked
b718792f
RG
897 self.assertEqual(allowed, sent)
898
899 # wait for the maintenance function to run
900 time.sleep(2)
901
902 # we should still not be blocked
903 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
904 receivedQuery.id = query.id
905 self.assertEquals(query, receivedQuery)
906 self.assertEquals(receivedResponse, receivedResponse)
477c86a0
RG
907
908class TestDynBlockGroupNoOp(DynBlocksTest):
909
910 _dynBlockQPS = 10
911 _dynBlockPeriod = 2
912 _dynBlockDuration = 5
913 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
914 _config_template = """
915 local dbr = dynBlockRulesGroup()
916 dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.NoOp)
917
918 function maintenance()
919 dbr:apply()
920 end
921
922 newServer{address="127.0.0.1:%s"}
923 webserver("127.0.0.1:%s", "%s", "%s")
924 """
925
926 def testNoOp(self):
927 """
928 Dyn Blocks (group) : NoOp
929 """
930 name = 'noop.group.dynblocks.tests.powerdns.com.'
931 query = dns.message.make_query(name, 'A', 'IN')
932 response = dns.message.make_response(query)
933 rrset = dns.rrset.from_text(name,
934 60,
935 dns.rdataclass.IN,
936 dns.rdatatype.A,
937 '192.0.2.1')
938 response.answer.append(rrset)
939
940 allowed = 0
941 sent = 0
942 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
943 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
944 sent = sent + 1
945 if receivedQuery:
946 receivedQuery.id = query.id
947 self.assertEquals(query, receivedQuery)
948 self.assertEquals(response, receivedResponse)
949 allowed = allowed + 1
950 else:
951 # the query has not reached the responder,
952 # let's clear the response queue
953 self.clearToResponderQueue()
954
955 # a dynamic rule should have been inserted, but the queries should still go on
956 self.assertEqual(allowed, sent)
957
958 # wait for the maintenance function to run
959 time.sleep(2)
960
961 # the rule should still be present, but the queries pass through anyway
962 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
963 receivedQuery.id = query.id
964 self.assertEquals(query, receivedQuery)
965 self.assertEquals(receivedResponse, receivedResponse)
966
967 # check that the rule has been inserted
968 self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', self._dynBlockDuration - 4, self._dynBlockDuration, 0, sent)