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