]> git.ipfire.org Git - thirdparty/pdns.git/blob - regression-tests.dnsdist/dnsdistDynBlockTests.py
Merge pull request #12908 from wwijkander/master
[thirdparty/pdns.git] / regression-tests.dnsdist / dnsdistDynBlockTests.py
1 #!/usr/bin/env python
2 import time
3 import requests
4 import dns
5 from dnsdisttests import DNSDistTest, pickAvailablePort
6
7 _maintenanceWaitTime = 2
8
9 def waitForMaintenanceToRun():
10 time.sleep(_maintenanceWaitTime)
11
12 class DynBlocksTest(DNSDistTest):
13
14 _webTimeout = 2.0
15 _webServerPort = pickAvailablePort()
16 _webServerBasicAuthPassword = 'secret'
17 _webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
18 _webServerAPIKey = 'apisecret'
19 _webServerAPIKeyHashed = '$scrypt$ln=10,p=1,r=8$9v8JxDfzQVyTpBkTbkUqYg==$bDQzAOHeK1G9UvTPypNhrX48w974ZXbFPtRKS34+aso='
20 _dynBlockQPS = 10
21 _dynBlockPeriod = 2
22 # this needs to be greater than maintenanceWaitTime
23 _dynBlockDuration = _maintenanceWaitTime + 2
24 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
25
26 def doTestDynBlockViaAPI(self, ipRange, reason, minSeconds, maxSeconds, minBlocks, maxBlocks):
27 headers = {'x-api-key': self._webServerAPIKey}
28 url = 'http://127.0.0.1:' + str(self._webServerPort) + '/jsonstat?command=dynblocklist'
29 r = requests.get(url, headers=headers, timeout=self._webTimeout)
30 self.assertTrue(r)
31 self.assertEqual(r.status_code, 200)
32
33 content = r.json()
34 self.assertIsNotNone(content)
35 self.assertIn(ipRange, content)
36
37 values = content[ipRange]
38 for key in ['reason', 'seconds', 'blocks', 'action']:
39 self.assertIn(key, values)
40
41 self.assertEqual(values['reason'], reason)
42 self.assertGreaterEqual(values['seconds'], minSeconds)
43 self.assertLessEqual(values['seconds'], maxSeconds)
44 self.assertGreaterEqual(values['blocks'], minBlocks)
45 self.assertLessEqual(values['blocks'], maxBlocks)
46
47 def doTestQRate(self, name, testViaAPI=True):
48 query = dns.message.make_query(name, 'A', 'IN')
49 response = dns.message.make_response(query)
50 rrset = dns.rrset.from_text(name,
51 60,
52 dns.rdataclass.IN,
53 dns.rdatatype.A,
54 '192.0.2.1')
55 response.answer.append(rrset)
56
57 allowed = 0
58 sent = 0
59 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
60 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
61 sent = sent + 1
62 if receivedQuery:
63 receivedQuery.id = query.id
64 self.assertEqual(query, receivedQuery)
65 self.assertEqual(response, receivedResponse)
66 allowed = allowed + 1
67 else:
68 # the query has not reached the responder,
69 # let's clear the response queue
70 self.clearToResponderQueue()
71
72 # we might be already blocked, but we should have been able to send
73 # at least self._dynBlockQPS queries
74 self.assertGreaterEqual(allowed, self._dynBlockQPS)
75
76 if allowed == sent:
77 waitForMaintenanceToRun()
78
79 # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
80 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False, timeout=1)
81 self.assertEqual(receivedResponse, None)
82
83 if testViaAPI:
84 self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', 1, self._dynBlockDuration, (sent-allowed)+1, (sent-allowed)+1)
85
86 # wait until we are not blocked anymore
87 time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
88
89 # this one should succeed
90 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
91 receivedQuery.id = query.id
92 self.assertEqual(query, receivedQuery)
93 self.assertEqual(response, receivedResponse)
94
95 # again, over TCP this time
96 allowed = 0
97 sent = 0
98 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
99 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
100 sent = sent + 1
101 if receivedQuery:
102 receivedQuery.id = query.id
103 self.assertEqual(query, receivedQuery)
104 self.assertEqual(response, receivedResponse)
105 allowed = allowed + 1
106 else:
107 # the query has not reached the responder,
108 # let's clear the response queue
109 self.clearToResponderQueue()
110
111 # we might be already blocked, but we should have been able to send
112 # at least self._dynBlockQPS queries
113 self.assertGreaterEqual(allowed, self._dynBlockQPS)
114
115 if allowed == sent:
116 waitForMaintenanceToRun()
117
118 # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
119 (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
120 self.assertEqual(receivedResponse, None)
121
122 # wait until we are not blocked anymore
123 time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
124
125 # this one should succeed
126 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
127 receivedQuery.id = query.id
128 self.assertEqual(query, receivedQuery)
129 self.assertEqual(response, receivedResponse)
130
131 def doTestQRateRCode(self, name, rcode):
132 query = dns.message.make_query(name, 'A', 'IN')
133 response = dns.message.make_response(query)
134 rrset = dns.rrset.from_text(name,
135 60,
136 dns.rdataclass.IN,
137 dns.rdatatype.A,
138 '192.0.2.1')
139 response.answer.append(rrset)
140 expectedResponse = dns.message.make_response(query)
141 expectedResponse.set_rcode(rcode)
142
143 allowed = 0
144 sent = 0
145 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
146 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
147 sent = sent + 1
148 if receivedQuery:
149 receivedQuery.id = query.id
150 self.assertEqual(query, receivedQuery)
151 self.assertEqual(receivedResponse, response)
152 allowed = allowed + 1
153 else:
154 self.assertEqual(receivedResponse, expectedResponse)
155 # the query has not reached the responder,
156 # let's clear the response queue
157 self.clearToResponderQueue()
158
159 # we might be already blocked, but we should have been able to send
160 # at least self._dynBlockQPS queries
161 self.assertGreaterEqual(allowed, self._dynBlockQPS)
162
163 if allowed == sent:
164 waitForMaintenanceToRun()
165
166 # we should now be 'rcode' for up to self._dynBlockDuration + self._dynBlockPeriod
167 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
168 self.assertEqual(receivedResponse, expectedResponse)
169
170 # wait until we are not blocked anymore
171 time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
172
173 # this one should succeed
174 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
175 receivedQuery.id = query.id
176 self.assertEqual(query, receivedQuery)
177 self.assertEqual(response, receivedResponse)
178
179 allowed = 0
180 sent = 0
181 # again, over TCP this time
182 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
183 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
184 sent = sent + 1
185 if receivedQuery:
186 receivedQuery.id = query.id
187 self.assertEqual(query, receivedQuery)
188 self.assertEqual(receivedResponse, response)
189 allowed = allowed + 1
190 else:
191 self.assertEqual(receivedResponse, expectedResponse)
192 # the query has not reached the responder,
193 # let's clear the response queue
194 self.clearToResponderQueue()
195
196 # we might be already blocked, but we should have been able to send
197 # at least self._dynBlockQPS queries
198 self.assertGreaterEqual(allowed, self._dynBlockQPS)
199
200 if allowed == sent:
201 waitForMaintenanceToRun()
202
203 # we should now be 'rcode' for up to self._dynBlockDuration + self._dynBlockPeriod
204 (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
205 self.assertEqual(receivedResponse, expectedResponse)
206
207 # wait until we are not blocked anymore
208 time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
209
210 # this one should succeed
211 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
212 receivedQuery.id = query.id
213 self.assertEqual(query, receivedQuery)
214 self.assertEqual(response, receivedResponse)
215
216 def doTestResponseByteRate(self, name, dynBlockBytesPerSecond):
217 query = dns.message.make_query(name, 'A', 'IN')
218 response = dns.message.make_response(query)
219 response.answer.append(dns.rrset.from_text_list(name,
220 60,
221 dns.rdataclass.IN,
222 dns.rdatatype.A,
223 ['192.0.2.1', '192.0.2.2', '192.0.2.3', '192.0.2.4']))
224 response.answer.append(dns.rrset.from_text(name,
225 60,
226 dns.rdataclass.IN,
227 dns.rdatatype.AAAA,
228 '2001:DB8::1'))
229
230 allowed = 0
231 sent = 0
232
233 print(time.time())
234
235 for _ in range(int(dynBlockBytesPerSecond * 5 / len(response.to_wire()))):
236 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
237 sent = sent + len(response.to_wire())
238 if receivedQuery:
239 receivedQuery.id = query.id
240 self.assertEqual(query, receivedQuery)
241 self.assertEqual(response, receivedResponse)
242 allowed = allowed + len(response.to_wire())
243 else:
244 # the query has not reached the responder,
245 # let's clear the response queue
246 self.clearToResponderQueue()
247 # and stop right there, otherwise we might
248 # wait for so long that the dynblock is gone
249 # by the time we finished
250 break
251
252 # we might be already blocked, but we should have been able to send
253 # at least dynBlockBytesPerSecond bytes
254 print(allowed)
255 print(sent)
256 print(time.time())
257 self.assertGreaterEqual(allowed, dynBlockBytesPerSecond)
258
259 print(self.sendConsoleCommand("showDynBlocks()"))
260 print(self.sendConsoleCommand("grepq(\"\")"))
261 print(time.time())
262
263 if allowed == sent:
264 print("Waiting for the maintenance function to run")
265 waitForMaintenanceToRun()
266
267 print(self.sendConsoleCommand("showDynBlocks()"))
268 print(self.sendConsoleCommand("grepq(\"\")"))
269 print(time.time())
270
271 # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
272 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False, timeout=1)
273 self.assertEqual(receivedResponse, None)
274
275 print(self.sendConsoleCommand("showDynBlocks()"))
276 print(self.sendConsoleCommand("grepq(\"\")"))
277 print(time.time())
278
279 # wait until we are not blocked anymore
280 time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
281
282 print(self.sendConsoleCommand("showDynBlocks()"))
283 print(self.sendConsoleCommand("grepq(\"\")"))
284 print(time.time())
285
286 # this one should succeed
287 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
288 receivedQuery.id = query.id
289 self.assertEqual(query, receivedQuery)
290 self.assertEqual(response, receivedResponse)
291
292 # again, over TCP this time
293 allowed = 0
294 sent = 0
295 for _ in range(int(dynBlockBytesPerSecond * 5 / len(response.to_wire()))):
296 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
297 sent = sent + len(response.to_wire())
298 if receivedQuery:
299 receivedQuery.id = query.id
300 self.assertEqual(query, receivedQuery)
301 self.assertEqual(response, receivedResponse)
302 allowed = allowed + len(response.to_wire())
303 else:
304 # the query has not reached the responder,
305 # let's clear the response queue
306 self.clearToResponderQueue()
307 # and stop right there, otherwise we might
308 # wait for so long that the dynblock is gone
309 # by the time we finished
310 break
311
312 # we might be already blocked, but we should have been able to send
313 # at least dynBlockBytesPerSecond bytes
314 self.assertGreaterEqual(allowed, dynBlockBytesPerSecond)
315
316 if allowed == sent:
317 waitForMaintenanceToRun()
318
319 # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
320 (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
321 self.assertEqual(receivedResponse, None)
322
323 # wait until we are not blocked anymore
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.assertEqual(query, receivedQuery)
330 self.assertEqual(response, receivedResponse)
331
332 def doTestRCodeRate(self, name, rcode):
333 query = dns.message.make_query(name, 'A', 'IN')
334 response = dns.message.make_response(query)
335 rrset = dns.rrset.from_text(name,
336 60,
337 dns.rdataclass.IN,
338 dns.rdatatype.A,
339 '192.0.2.1')
340 response.answer.append(rrset)
341 expectedResponse = dns.message.make_response(query)
342 expectedResponse.set_rcode(rcode)
343
344 # start with normal responses
345 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
346 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
347 receivedQuery.id = query.id
348 self.assertEqual(query, receivedQuery)
349 self.assertEqual(response, receivedResponse)
350
351 waitForMaintenanceToRun()
352
353 # we should NOT be dropped!
354 (_, receivedResponse) = self.sendUDPQuery(query, response)
355 self.assertEqual(receivedResponse, response)
356
357 # now with rcode!
358 sent = 0
359 allowed = 0
360 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
361 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, expectedResponse)
362 sent = sent + 1
363 if receivedQuery:
364 receivedQuery.id = query.id
365 self.assertEqual(query, receivedQuery)
366 self.assertEqual(expectedResponse, receivedResponse)
367 allowed = allowed + 1
368 else:
369 # the query has not reached the responder,
370 # let's clear the response queue
371 self.clearToResponderQueue()
372
373 # we might be already blocked, but we should have been able to send
374 # at least self._dynBlockQPS queries
375 self.assertGreaterEqual(allowed, self._dynBlockQPS)
376
377 if allowed == sent:
378 waitForMaintenanceToRun()
379
380 # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
381 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False, timeout=1)
382 self.assertEqual(receivedResponse, None)
383
384 # wait until we are not blocked anymore
385 time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
386
387 # this one should succeed
388 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
389 receivedQuery.id = query.id
390 self.assertEqual(query, receivedQuery)
391 self.assertEqual(response, receivedResponse)
392
393 # again, over TCP this time
394 # start with normal responses
395 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
396 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
397 receivedQuery.id = query.id
398 self.assertEqual(query, receivedQuery)
399 self.assertEqual(response, receivedResponse)
400
401 waitForMaintenanceToRun()
402
403 # we should NOT be dropped!
404 (_, receivedResponse) = self.sendUDPQuery(query, response)
405 self.assertEqual(receivedResponse, response)
406
407 # now with rcode!
408 sent = 0
409 allowed = 0
410 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
411 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, expectedResponse)
412 sent = sent + 1
413 if receivedQuery:
414 receivedQuery.id = query.id
415 self.assertEqual(query, receivedQuery)
416 self.assertEqual(expectedResponse, receivedResponse)
417 allowed = allowed + 1
418 else:
419 # the query has not reached the responder,
420 # let's clear the response queue
421 self.clearToResponderQueue()
422
423 # we might be already blocked, but we should have been able to send
424 # at least self._dynBlockQPS queries
425 self.assertGreaterEqual(allowed, self._dynBlockQPS)
426
427 if allowed == sent:
428 waitForMaintenanceToRun()
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.assertEqual(receivedResponse, None)
433
434 # wait until we are not blocked anymore
435 time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
436
437 # this one should succeed
438 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
439 receivedQuery.id = query.id
440 self.assertEqual(query, receivedQuery)
441 self.assertEqual(response, receivedResponse)
442
443 def doTestRCodeRatio(self, name, rcode, noerrorcount, rcodecount):
444 query = dns.message.make_query(name, 'A', 'IN')
445 response = dns.message.make_response(query)
446 rrset = dns.rrset.from_text(name,
447 60,
448 dns.rdataclass.IN,
449 dns.rdatatype.A,
450 '192.0.2.1')
451 response.answer.append(rrset)
452 expectedResponse = dns.message.make_response(query)
453 expectedResponse.set_rcode(rcode)
454
455 # start with normal responses
456 for _ in range(noerrorcount-1):
457 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
458 receivedQuery.id = query.id
459 self.assertEqual(query, receivedQuery)
460 self.assertEqual(response, receivedResponse)
461
462 waitForMaintenanceToRun()
463
464 # we should NOT be dropped!
465 (_, receivedResponse) = self.sendUDPQuery(query, response)
466 self.assertEqual(receivedResponse, response)
467
468 # now with rcode!
469 sent = 0
470 allowed = 0
471 for _ in range(rcodecount):
472 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, expectedResponse)
473 sent = sent + 1
474 if receivedQuery:
475 receivedQuery.id = query.id
476 self.assertEqual(query, receivedQuery)
477 self.assertEqual(expectedResponse, receivedResponse)
478 allowed = allowed + 1
479 else:
480 # the query has not reached the responder,
481 # let's clear the response queue
482 self.clearToResponderQueue()
483
484 # we should have been able to send all our queries since the minimum number of queries is set to noerrorcount + rcodecount
485 self.assertGreaterEqual(allowed, rcodecount)
486
487 waitForMaintenanceToRun()
488
489 # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
490 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False, timeout=1)
491 self.assertEqual(receivedResponse, None)
492
493 # wait until we are not blocked anymore
494 time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
495
496 # this one should succeed
497 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
498 receivedQuery.id = query.id
499 self.assertEqual(query, receivedQuery)
500 self.assertEqual(response, receivedResponse)
501
502 # again, over TCP this time
503 # start with normal responses
504 for _ in range(noerrorcount-1):
505 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
506 receivedQuery.id = query.id
507 self.assertEqual(query, receivedQuery)
508 self.assertEqual(response, receivedResponse)
509
510 waitForMaintenanceToRun()
511
512 # we should NOT be dropped!
513 (_, receivedResponse) = self.sendUDPQuery(query, response)
514 self.assertEqual(receivedResponse, response)
515
516 # now with rcode!
517 sent = 0
518 allowed = 0
519 for _ in range(rcodecount):
520 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, expectedResponse)
521 sent = sent + 1
522 if receivedQuery:
523 receivedQuery.id = query.id
524 self.assertEqual(query, receivedQuery)
525 self.assertEqual(expectedResponse, receivedResponse)
526 allowed = allowed + 1
527 else:
528 # the query has not reached the responder,
529 # let's clear the response queue
530 self.clearToResponderQueue()
531
532 # we should have been able to send all our queries since the minimum number of queries is set to noerrorcount + rcodecount
533 self.assertGreaterEqual(allowed, rcodecount)
534
535 waitForMaintenanceToRun()
536
537 # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
538 (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
539 self.assertEqual(receivedResponse, None)
540
541 # wait until we are not blocked anymore
542 time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
543
544 # this one should succeed
545 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
546 receivedQuery.id = query.id
547 self.assertEqual(query, receivedQuery)
548 self.assertEqual(response, receivedResponse)