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