]> git.ipfire.org Git - thirdparty/pdns.git/blame - regression-tests.dnsdist/test_DynBlocks.py
Merge pull request #6952 from phonedph1/root-nx
[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
e44df0f1 37 def doTestQRate(self, name, testViaAPI=True):
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.'
e44df0f1 462 self.doTestQRate(name)
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.'
e44df0f1 486 self.doTestQRate(name)
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
79ee8ff9
RG
555class 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
d3473b8c 576class TestDynBlockGroupQPSActionRefused(DynBlocksTest):
dc2fd93a
RG
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
600class 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):
d354e773 633 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
dc2fd93a 634 sent = sent + 1
3bef39c3
RG
635 if receivedQuery:
636 receivedQuery.id = query.id
637 self.assertEquals(query, receivedQuery)
dc2fd93a
RG
638 self.assertEquals(receivedResponse, response)
639 allowed = allowed + 1
3bef39c3 640 else:
dc2fd93a 641 self.assertEquals(receivedResponse, truncatedResponse)
3bef39c3
RG
642 # the query has not reached the responder,
643 # let's clear the response queue
98883b8f 644 self.clearToResponderQueue()
3bef39c3 645
dc2fd93a
RG
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)
87b0577d 649
3bef39c3
RG
650 if allowed == sent:
651 # wait for the maintenance function to run
652 time.sleep(2)
d354e773 653
dc2fd93a 654 # we should now be 'truncated' for up to self._dynBlockDuration + self._dynBlockPeriod
d354e773 655 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
dc2fd93a 656 self.assertEquals(receivedResponse, truncatedResponse)
d354e773 657
dc2fd93a
RG
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)
87b0577d 663
3bef39c3 664 # wait until we are not blocked anymore
d354e773
RG
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
3bef39c3
RG
673 allowed = 0
674 sent = 0
dc2fd93a
RG
675 # again, over TCP this time, we should never get truncated!
676 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
d354e773 677 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
dc2fd93a
RG
678 sent = sent + 1
679 self.assertEquals(query, receivedQuery)
680 self.assertEquals(receivedResponse, response)
681 receivedQuery.id = query.id
682 allowed = allowed + 1
3bef39c3 683
dc2fd93a 684 self.assertEquals(allowed, sent)
3bef39c3 685
dc2fd93a 686class TestDynBlockServFails(DynBlocksTest):
d354e773 687
dc2fd93a
RG
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 """
d354e773 698
dc2fd93a
RG
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)
d354e773 705
ad3f984f
RG
706class 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
dc2fd93a
RG
794class 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
818class 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
842class 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)
b718792f 869
477c86a0 870class TestDynBlockGroupExcluded(DynBlocksTest):
b718792f
RG
871
872 _dynBlockQPS = 10
873 _dynBlockPeriod = 2
874 _dynBlockDuration = 5
477c86a0 875 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
b718792f 876 _config_template = """
b718792f
RG
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
477c86a0 917 # we should not have been blocked
b718792f
RG
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)
477c86a0
RG
928
929class 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)
1d3ba133
RG
990
991class 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)