]>
Commit | Line | Data |
---|---|---|
d354e773 | 1 | #!/usr/bin/env python |
87b0577d | 2 | import base64 |
d3473b8c RG |
3 | import json |
4 | import requests | |
d354e773 RG |
5 | import time |
6 | import dns | |
06b0e003 RG |
7 | from dnsdisttests import DNSDistTest |
8 | try: | |
9 | range = xrange | |
10 | except NameError: | |
11 | pass | |
d354e773 | 12 | |
dc2fd93a | 13 | class 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 | ||
94c38f24 RG |
447 | def doTestRCodeRatio(self, name, rcode, noerrorcount, rcodecount): |
448 | query = dns.message.make_query(name, 'A', 'IN') | |
449 | response = dns.message.make_response(query) | |
450 | rrset = dns.rrset.from_text(name, | |
451 | 60, | |
452 | dns.rdataclass.IN, | |
453 | dns.rdatatype.A, | |
454 | '192.0.2.1') | |
455 | response.answer.append(rrset) | |
456 | expectedResponse = dns.message.make_response(query) | |
457 | expectedResponse.set_rcode(rcode) | |
458 | ||
459 | # start with normal responses | |
460 | for _ in range(noerrorcount-1): | |
461 | (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) | |
462 | receivedQuery.id = query.id | |
463 | self.assertEquals(query, receivedQuery) | |
464 | self.assertEquals(response, receivedResponse) | |
465 | ||
466 | # wait for the maintenance function to run | |
467 | time.sleep(2) | |
468 | ||
469 | # we should NOT be dropped! | |
470 | (_, receivedResponse) = self.sendUDPQuery(query, response) | |
471 | self.assertEquals(receivedResponse, response) | |
472 | ||
473 | # now with rcode! | |
474 | sent = 0 | |
475 | allowed = 0 | |
476 | for _ in range(rcodecount): | |
477 | (receivedQuery, receivedResponse) = self.sendUDPQuery(query, expectedResponse) | |
478 | sent = sent + 1 | |
479 | if receivedQuery: | |
480 | receivedQuery.id = query.id | |
481 | self.assertEquals(query, receivedQuery) | |
482 | self.assertEquals(expectedResponse, receivedResponse) | |
483 | allowed = allowed + 1 | |
484 | else: | |
485 | # the query has not reached the responder, | |
486 | # let's clear the response queue | |
487 | self.clearToResponderQueue() | |
488 | ||
489 | # we should have been able to send all our queries since the minimum number of queries is set to noerrorcount + rcodecount | |
490 | self.assertGreaterEqual(allowed, rcodecount) | |
491 | ||
492 | # wait for the maintenance function to run | |
493 | time.sleep(2) | |
494 | ||
495 | # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod | |
496 | (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) | |
497 | self.assertEquals(receivedResponse, None) | |
498 | ||
499 | # wait until we are not blocked anymore | |
500 | time.sleep(self._dynBlockDuration + self._dynBlockPeriod) | |
501 | ||
502 | # this one should succeed | |
503 | (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) | |
504 | receivedQuery.id = query.id | |
505 | self.assertEquals(query, receivedQuery) | |
506 | self.assertEquals(response, receivedResponse) | |
507 | ||
508 | # again, over TCP this time | |
509 | # start with normal responses | |
510 | for _ in range(noerrorcount-1): | |
511 | (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) | |
512 | receivedQuery.id = query.id | |
513 | self.assertEquals(query, receivedQuery) | |
514 | self.assertEquals(response, receivedResponse) | |
515 | ||
516 | # wait for the maintenance function to run | |
517 | time.sleep(2) | |
518 | ||
519 | # we should NOT be dropped! | |
520 | (_, receivedResponse) = self.sendUDPQuery(query, response) | |
521 | self.assertEquals(receivedResponse, response) | |
522 | ||
523 | # now with rcode! | |
524 | sent = 0 | |
525 | allowed = 0 | |
526 | for _ in range(rcodecount): | |
527 | (receivedQuery, receivedResponse) = self.sendTCPQuery(query, expectedResponse) | |
528 | sent = sent + 1 | |
529 | if receivedQuery: | |
530 | receivedQuery.id = query.id | |
531 | self.assertEquals(query, receivedQuery) | |
532 | self.assertEquals(expectedResponse, receivedResponse) | |
533 | allowed = allowed + 1 | |
534 | else: | |
535 | # the query has not reached the responder, | |
536 | # let's clear the response queue | |
537 | self.clearToResponderQueue() | |
538 | ||
539 | # we should have been able to send all our queries since the minimum number of queries is set to noerrorcount + rcodecount | |
540 | self.assertGreaterEqual(allowed, rcodecount) | |
541 | ||
542 | # wait for the maintenance function to run | |
543 | time.sleep(2) | |
544 | ||
545 | # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod | |
546 | (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False) | |
547 | self.assertEquals(receivedResponse, None) | |
548 | ||
549 | # wait until we are not blocked anymore | |
550 | time.sleep(self._dynBlockDuration + self._dynBlockPeriod) | |
551 | ||
552 | # this one should succeed | |
553 | (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) | |
554 | receivedQuery.id = query.id | |
555 | self.assertEquals(query, receivedQuery) | |
556 | self.assertEquals(response, receivedResponse) | |
557 | ||
dc2fd93a | 558 | class TestDynBlockQPS(DynBlocksTest): |
d354e773 | 559 | |
dc2fd93a | 560 | _dynBlockQPS = 10 |
d354e773 RG |
561 | _dynBlockPeriod = 2 |
562 | _dynBlockDuration = 5 | |
d3473b8c | 563 | _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey'] |
d354e773 RG |
564 | _config_template = """ |
565 | function maintenance() | |
dc2fd93a | 566 | addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d) |
d354e773 RG |
567 | end |
568 | newServer{address="127.0.0.1:%s"} | |
d3473b8c | 569 | webserver("127.0.0.1:%s", "%s", "%s") |
d354e773 RG |
570 | """ |
571 | ||
dc2fd93a | 572 | def testDynBlocksQRate(self): |
d354e773 | 573 | """ |
dc2fd93a | 574 | Dyn Blocks: QRate |
d354e773 | 575 | """ |
dc2fd93a | 576 | name = 'qrate.dynblocks.tests.powerdns.com.' |
e44df0f1 | 577 | self.doTestQRate(name) |
d354e773 | 578 | |
dc2fd93a | 579 | class TestDynBlockGroupQPS(DynBlocksTest): |
87b0577d | 580 | |
dc2fd93a RG |
581 | _dynBlockQPS = 10 |
582 | _dynBlockPeriod = 2 | |
583 | _dynBlockDuration = 5 | |
d3473b8c | 584 | _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey'] |
dc2fd93a RG |
585 | _config_template = """ |
586 | local dbr = dynBlockRulesGroup() | |
587 | dbr:setQueryRate(%d, %d, "Exceeded query rate", %d) | |
87b0577d | 588 | |
dc2fd93a RG |
589 | function maintenance() |
590 | dbr:apply() | |
591 | end | |
592 | newServer{address="127.0.0.1:%s"} | |
d3473b8c | 593 | webserver("127.0.0.1:%s", "%s", "%s") |
dc2fd93a RG |
594 | """ |
595 | ||
596 | def testDynBlocksQRate(self): | |
597 | """ | |
598 | Dyn Blocks (Group): QRate | |
599 | """ | |
600 | name = 'qrate.group.dynblocks.tests.powerdns.com.' | |
e44df0f1 | 601 | self.doTestQRate(name) |
dc2fd93a RG |
602 | |
603 | ||
604 | class TestDynBlockQPSRefused(DynBlocksTest): | |
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) | |
613 | end | |
614 | setDynBlocksAction(DNSAction.Refused) | |
615 | newServer{address="127.0.0.1:%s"} | |
616 | """ | |
617 | ||
618 | def testDynBlocksQRate(self): | |
619 | """ | |
620 | Dyn Blocks: QRate refused | |
621 | """ | |
622 | name = 'qraterefused.dynblocks.tests.powerdns.com.' | |
623 | self.doTestQRateRCode(name, dns.rcode.REFUSED) | |
624 | ||
625 | class TestDynBlockGroupQPSRefused(DynBlocksTest): | |
626 | ||
627 | _dynBlockQPS = 10 | |
628 | _dynBlockPeriod = 2 | |
629 | _dynBlockDuration = 5 | |
630 | _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort'] | |
631 | _config_template = """ | |
632 | local dbr = dynBlockRulesGroup() | |
633 | dbr:setQueryRate(%d, %d, "Exceeded query rate", %d) | |
634 | ||
635 | function maintenance() | |
636 | dbr:apply() | |
637 | end | |
638 | setDynBlocksAction(DNSAction.Refused) | |
639 | newServer{address="127.0.0.1:%s"} | |
640 | """ | |
641 | ||
642 | def testDynBlocksQRate(self): | |
643 | """ | |
644 | Dyn Blocks (Group): QRate refused | |
645 | """ | |
646 | name = 'qraterefused.group.dynblocks.tests.powerdns.com.' | |
647 | self.doTestQRateRCode(name, dns.rcode.REFUSED) | |
648 | ||
649 | class TestDynBlockQPSActionRefused(DynBlocksTest): | |
650 | ||
651 | _dynBlockQPS = 10 | |
652 | _dynBlockPeriod = 2 | |
653 | _dynBlockDuration = 5 | |
654 | _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort'] | |
655 | _config_template = """ | |
656 | function maintenance() | |
657 | addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Refused) | |
658 | end | |
659 | setDynBlocksAction(DNSAction.Drop) | |
660 | newServer{address="127.0.0.1:%s"} | |
661 | """ | |
662 | ||
663 | def testDynBlocksQRate(self): | |
664 | """ | |
665 | Dyn Blocks: QRate refused (action) | |
666 | """ | |
667 | name = 'qrateactionrefused.dynblocks.tests.powerdns.com.' | |
668 | self.doTestQRateRCode(name, dns.rcode.REFUSED) | |
669 | ||
79ee8ff9 RG |
670 | class TestDynBlockQPSActionNXD(DynBlocksTest): |
671 | ||
672 | _dynBlockQPS = 10 | |
673 | _dynBlockPeriod = 2 | |
674 | _dynBlockDuration = 5 | |
675 | _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort'] | |
676 | _config_template = """ | |
677 | function maintenance() | |
678 | addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Nxdomain) | |
679 | end | |
680 | setDynBlocksAction(DNSAction.Drop) | |
681 | newServer{address="127.0.0.1:%s"} | |
682 | """ | |
683 | ||
684 | def testDynBlocksQRate(self): | |
685 | """ | |
686 | Dyn Blocks: QRate NXD (action) | |
687 | """ | |
688 | name = 'qrateactionnxd.dynblocks.tests.powerdns.com.' | |
689 | self.doTestQRateRCode(name, dns.rcode.NXDOMAIN) | |
690 | ||
d3473b8c | 691 | class TestDynBlockGroupQPSActionRefused(DynBlocksTest): |
dc2fd93a RG |
692 | |
693 | _dynBlockQPS = 10 | |
694 | _dynBlockPeriod = 2 | |
695 | _dynBlockDuration = 5 | |
696 | _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort'] | |
697 | _config_template = """ | |
698 | local dbr = dynBlockRulesGroup() | |
699 | dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.Refused) | |
700 | ||
701 | function maintenance() | |
702 | dbr:apply() | |
703 | end | |
704 | setDynBlocksAction(DNSAction.Drop) | |
705 | newServer{address="127.0.0.1:%s"} | |
706 | """ | |
707 | ||
708 | def testDynBlocksQRate(self): | |
709 | """ | |
710 | Dyn Blocks (group): QRate refused (action) | |
711 | """ | |
712 | name = 'qrateactionrefused.group.dynblocks.tests.powerdns.com.' | |
713 | self.doTestQRateRCode(name, dns.rcode.REFUSED) | |
714 | ||
715 | class TestDynBlockQPSActionTruncated(DNSDistTest): | |
716 | ||
717 | _dynBlockQPS = 10 | |
718 | _dynBlockPeriod = 2 | |
719 | _dynBlockDuration = 5 | |
720 | _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort'] | |
721 | _config_template = """ | |
722 | function maintenance() | |
723 | addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Truncate) | |
724 | end | |
725 | setDynBlocksAction(DNSAction.Drop) | |
726 | newServer{address="127.0.0.1:%s"} | |
727 | """ | |
728 | ||
729 | def testDynBlocksQRate(self): | |
730 | """ | |
731 | Dyn Blocks: QRate truncated (action) | |
732 | """ | |
733 | name = 'qrateactiontruncated.dynblocks.tests.powerdns.com.' | |
734 | query = dns.message.make_query(name, 'A', 'IN') | |
955b9377 RG |
735 | # dnsdist sets RA = RD for TC responses |
736 | query.flags &= ~dns.flags.RD | |
dc2fd93a RG |
737 | response = dns.message.make_response(query) |
738 | rrset = dns.rrset.from_text(name, | |
739 | 60, | |
740 | dns.rdataclass.IN, | |
741 | dns.rdatatype.A, | |
742 | '192.0.2.1') | |
743 | response.answer.append(rrset) | |
744 | truncatedResponse = dns.message.make_response(query) | |
745 | truncatedResponse.flags |= dns.flags.TC | |
746 | ||
747 | allowed = 0 | |
748 | sent = 0 | |
749 | for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1): | |
d354e773 | 750 | (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) |
dc2fd93a | 751 | sent = sent + 1 |
3bef39c3 RG |
752 | if receivedQuery: |
753 | receivedQuery.id = query.id | |
754 | self.assertEquals(query, receivedQuery) | |
dc2fd93a RG |
755 | self.assertEquals(receivedResponse, response) |
756 | allowed = allowed + 1 | |
3bef39c3 | 757 | else: |
dc2fd93a | 758 | self.assertEquals(receivedResponse, truncatedResponse) |
3bef39c3 RG |
759 | # the query has not reached the responder, |
760 | # let's clear the response queue | |
98883b8f | 761 | self.clearToResponderQueue() |
3bef39c3 | 762 | |
dc2fd93a RG |
763 | # we might be already truncated, but we should have been able to send |
764 | # at least self._dynBlockQPS queries | |
765 | self.assertGreaterEqual(allowed, self._dynBlockQPS) | |
87b0577d | 766 | |
3bef39c3 RG |
767 | if allowed == sent: |
768 | # wait for the maintenance function to run | |
769 | time.sleep(2) | |
d354e773 | 770 | |
dc2fd93a | 771 | # we should now be 'truncated' for up to self._dynBlockDuration + self._dynBlockPeriod |
d354e773 | 772 | (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) |
dc2fd93a | 773 | self.assertEquals(receivedResponse, truncatedResponse) |
d354e773 | 774 | |
dc2fd93a RG |
775 | # check over TCP, which should not be truncated |
776 | (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) | |
777 | ||
778 | self.assertEquals(query, receivedQuery) | |
779 | self.assertEquals(receivedResponse, response) | |
87b0577d | 780 | |
3bef39c3 | 781 | # wait until we are not blocked anymore |
d354e773 RG |
782 | time.sleep(self._dynBlockDuration + self._dynBlockPeriod) |
783 | ||
784 | # this one should succeed | |
785 | (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) | |
786 | receivedQuery.id = query.id | |
787 | self.assertEquals(query, receivedQuery) | |
788 | self.assertEquals(response, receivedResponse) | |
789 | ||
3bef39c3 RG |
790 | allowed = 0 |
791 | sent = 0 | |
dc2fd93a RG |
792 | # again, over TCP this time, we should never get truncated! |
793 | for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1): | |
d354e773 | 794 | (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) |
dc2fd93a RG |
795 | sent = sent + 1 |
796 | self.assertEquals(query, receivedQuery) | |
797 | self.assertEquals(receivedResponse, response) | |
798 | receivedQuery.id = query.id | |
799 | allowed = allowed + 1 | |
3bef39c3 | 800 | |
dc2fd93a | 801 | self.assertEquals(allowed, sent) |
3bef39c3 | 802 | |
dc2fd93a | 803 | class TestDynBlockServFails(DynBlocksTest): |
d354e773 | 804 | |
dc2fd93a RG |
805 | _dynBlockQPS = 10 |
806 | _dynBlockPeriod = 2 | |
807 | _dynBlockDuration = 5 | |
808 | _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort'] | |
809 | _config_template = """ | |
810 | function maintenance() | |
811 | addDynBlocks(exceedServFails(%d, %d), "Exceeded servfail rate", %d) | |
812 | end | |
813 | newServer{address="127.0.0.1:%s"} | |
814 | """ | |
d354e773 | 815 | |
dc2fd93a RG |
816 | def testDynBlocksServFailRate(self): |
817 | """ | |
818 | Dyn Blocks: Server Failure Rate | |
819 | """ | |
820 | name = 'servfailrate.dynblocks.tests.powerdns.com.' | |
821 | self.doTestRCodeRate(name, dns.rcode.SERVFAIL) | |
d354e773 | 822 | |
ad3f984f RG |
823 | class TestDynBlockWhitelist(DynBlocksTest): |
824 | ||
825 | _dynBlockQPS = 10 | |
826 | _dynBlockPeriod = 2 | |
827 | _dynBlockDuration = 5 | |
828 | _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort'] | |
829 | _config_template = """ | |
830 | whitelisted = false | |
831 | function maintenance() | |
832 | toBlock = exceedQRate(%d, %d) | |
833 | for addr, count in pairs(toBlock) do | |
834 | if addr:toString() == "127.0.0.1" then | |
835 | whitelisted = true | |
836 | toBlock[addr] = nil | |
837 | end | |
838 | end | |
839 | addDynBlocks(toBlock, "Exceeded query rate", %d) | |
840 | end | |
841 | ||
842 | function spoofrule(dq) | |
843 | if (whitelisted) | |
844 | then | |
845 | return DNSAction.Spoof, "192.0.2.42" | |
846 | else | |
847 | return DNSAction.None, "" | |
848 | end | |
849 | end | |
850 | addAction("whitelisted-test.dynblocks.tests.powerdns.com.", LuaAction(spoofrule)) | |
851 | ||
852 | newServer{address="127.0.0.1:%s"} | |
853 | """ | |
854 | ||
855 | def testWhitelisted(self): | |
856 | """ | |
857 | Dyn Blocks: Whitelisted from the dynamic blocks | |
858 | """ | |
859 | name = 'whitelisted.dynblocks.tests.powerdns.com.' | |
860 | query = dns.message.make_query(name, 'A', 'IN') | |
861 | response = dns.message.make_response(query) | |
862 | rrset = dns.rrset.from_text(name, | |
863 | 60, | |
864 | dns.rdataclass.IN, | |
865 | dns.rdatatype.A, | |
866 | '192.0.2.1') | |
867 | response.answer.append(rrset) | |
868 | ||
869 | allowed = 0 | |
870 | sent = 0 | |
871 | for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1): | |
872 | (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) | |
873 | sent = sent + 1 | |
874 | if receivedQuery: | |
875 | receivedQuery.id = query.id | |
876 | self.assertEquals(query, receivedQuery) | |
877 | self.assertEquals(response, receivedResponse) | |
878 | allowed = allowed + 1 | |
879 | else: | |
880 | # the query has not reached the responder, | |
881 | # let's clear the response queue | |
882 | self.clearToResponderQueue() | |
883 | ||
884 | # we should not have been blocked | |
885 | self.assertEqual(allowed, sent) | |
886 | ||
887 | # wait for the maintenance function to run | |
888 | time.sleep(2) | |
889 | ||
890 | # we should still not be blocked | |
891 | (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) | |
892 | receivedQuery.id = query.id | |
893 | self.assertEquals(query, receivedQuery) | |
894 | self.assertEquals(receivedResponse, receivedResponse) | |
895 | ||
896 | # check that we would have been blocked without the whitelisting | |
897 | name = 'whitelisted-test.dynblocks.tests.powerdns.com.' | |
898 | query = dns.message.make_query(name, 'A', 'IN') | |
899 | # dnsdist set RA = RD for spoofed responses | |
900 | query.flags &= ~dns.flags.RD | |
901 | expectedResponse = dns.message.make_response(query) | |
902 | rrset = dns.rrset.from_text(name, | |
903 | 60, | |
904 | dns.rdataclass.IN, | |
905 | dns.rdatatype.A, | |
906 | '192.0.2.42') | |
907 | expectedResponse.answer.append(rrset) | |
908 | (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) | |
909 | self.assertEquals(receivedResponse, expectedResponse) | |
910 | ||
dc2fd93a RG |
911 | class TestDynBlockGroupServFails(DynBlocksTest): |
912 | ||
913 | _dynBlockQPS = 10 | |
914 | _dynBlockPeriod = 2 | |
915 | _dynBlockDuration = 5 | |
916 | _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort'] | |
917 | _config_template = """ | |
918 | local dbr = dynBlockRulesGroup() | |
d3ec24f9 | 919 | dbr:setRCodeRate(DNSRCode.SERVFAIL, %d, %d, "Exceeded query rate", %d) |
dc2fd93a RG |
920 | |
921 | function maintenance() | |
922 | dbr:apply() | |
923 | end | |
924 | ||
925 | newServer{address="127.0.0.1:%s"} | |
926 | """ | |
927 | ||
928 | def testDynBlocksServFailRate(self): | |
929 | """ | |
930 | Dyn Blocks (group): Server Failure Rate | |
931 | """ | |
932 | name = 'servfailrate.group.dynblocks.tests.powerdns.com.' | |
933 | self.doTestRCodeRate(name, dns.rcode.SERVFAIL) | |
934 | ||
94c38f24 RG |
935 | class TestDynBlockGroupServFailsRatio(DynBlocksTest): |
936 | ||
937 | _dynBlockPeriod = 2 | |
938 | _dynBlockDuration = 5 | |
939 | _config_params = ['_dynBlockPeriod', '_dynBlockDuration', '_testServerPort'] | |
940 | _config_template = """ | |
941 | local dbr = dynBlockRulesGroup() | |
942 | dbr:setRCodeRatio(DNSRCode.SERVFAIL, 0.2, %d, "Exceeded query rate", %d, 20) | |
943 | ||
944 | function maintenance() | |
945 | dbr:apply() | |
946 | end | |
947 | ||
948 | newServer{address="127.0.0.1:%s"} | |
949 | """ | |
950 | ||
951 | def testDynBlocksServFailRatio(self): | |
952 | """ | |
953 | Dyn Blocks (group): Server Failure Ratio | |
954 | """ | |
955 | name = 'servfailratio.group.dynblocks.tests.powerdns.com.' | |
956 | self.doTestRCodeRatio(name, dns.rcode.SERVFAIL, 10, 10) | |
957 | ||
dc2fd93a RG |
958 | class TestDynBlockResponseBytes(DynBlocksTest): |
959 | ||
960 | _dynBlockBytesPerSecond = 200 | |
961 | _dynBlockPeriod = 2 | |
962 | _dynBlockDuration = 5 | |
963 | _consoleKey = DNSDistTest.generateConsoleKey() | |
964 | _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii') | |
965 | _config_params = ['_consoleKeyB64', '_consolePort', '_dynBlockBytesPerSecond', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort'] | |
966 | _config_template = """ | |
967 | setKey("%s") | |
968 | controlSocket("127.0.0.1:%s") | |
969 | function maintenance() | |
970 | addDynBlocks(exceedRespByterate(%d, %d), "Exceeded response byterate", %d) | |
971 | end | |
972 | newServer{address="127.0.0.1:%s"} | |
973 | """ | |
974 | ||
975 | def testDynBlocksResponseByteRate(self): | |
976 | """ | |
977 | Dyn Blocks: Response Byte Rate | |
978 | """ | |
979 | name = 'responsebyterate.dynblocks.tests.powerdns.com.' | |
980 | self.doTestResponseByteRate(name) | |
981 | ||
982 | class TestDynBlockGroupResponseBytes(DynBlocksTest): | |
983 | ||
984 | _dynBlockBytesPerSecond = 200 | |
985 | _dynBlockPeriod = 2 | |
986 | _dynBlockDuration = 5 | |
987 | _consoleKey = DNSDistTest.generateConsoleKey() | |
988 | _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii') | |
989 | _config_params = ['_consoleKeyB64', '_consolePort', '_dynBlockBytesPerSecond', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort'] | |
990 | _config_template = """ | |
991 | setKey("%s") | |
992 | controlSocket("127.0.0.1:%s") | |
993 | local dbr = dynBlockRulesGroup() | |
994 | dbr:setResponseByteRate(%d, %d, "Exceeded query rate", %d) | |
995 | ||
996 | function maintenance() | |
997 | dbr:apply() | |
998 | end | |
999 | ||
1000 | newServer{address="127.0.0.1:%s"} | |
1001 | """ | |
1002 | ||
1003 | def testDynBlocksResponseByteRate(self): | |
1004 | """ | |
1005 | Dyn Blocks (group) : Response Byte Rate | |
1006 | """ | |
1007 | name = 'responsebyterate.group.dynblocks.tests.powerdns.com.' | |
1008 | self.doTestResponseByteRate(name) | |
b718792f | 1009 | |
477c86a0 | 1010 | class TestDynBlockGroupExcluded(DynBlocksTest): |
b718792f RG |
1011 | |
1012 | _dynBlockQPS = 10 | |
1013 | _dynBlockPeriod = 2 | |
1014 | _dynBlockDuration = 5 | |
477c86a0 | 1015 | _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort'] |
b718792f | 1016 | _config_template = """ |
b718792f RG |
1017 | local dbr = dynBlockRulesGroup() |
1018 | dbr:setQueryRate(%d, %d, "Exceeded query rate", %d) | |
1019 | dbr:excludeRange("127.0.0.1/32") | |
1020 | ||
1021 | function maintenance() | |
1022 | dbr:apply() | |
1023 | end | |
1024 | ||
1025 | newServer{address="127.0.0.1:%s"} | |
1026 | """ | |
1027 | ||
1028 | def testExcluded(self): | |
1029 | """ | |
1030 | Dyn Blocks (group) : Excluded from the dynamic block rules | |
1031 | """ | |
1032 | name = 'excluded.group.dynblocks.tests.powerdns.com.' | |
1033 | query = dns.message.make_query(name, 'A', 'IN') | |
1034 | response = dns.message.make_response(query) | |
1035 | rrset = dns.rrset.from_text(name, | |
1036 | 60, | |
1037 | dns.rdataclass.IN, | |
1038 | dns.rdatatype.A, | |
1039 | '192.0.2.1') | |
1040 | response.answer.append(rrset) | |
1041 | ||
1042 | allowed = 0 | |
1043 | sent = 0 | |
1044 | for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1): | |
1045 | (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) | |
1046 | sent = sent + 1 | |
1047 | if receivedQuery: | |
1048 | receivedQuery.id = query.id | |
1049 | self.assertEquals(query, receivedQuery) | |
1050 | self.assertEquals(response, receivedResponse) | |
1051 | allowed = allowed + 1 | |
1052 | else: | |
1053 | # the query has not reached the responder, | |
1054 | # let's clear the response queue | |
1055 | self.clearToResponderQueue() | |
1056 | ||
477c86a0 | 1057 | # we should not have been blocked |
b718792f RG |
1058 | self.assertEqual(allowed, sent) |
1059 | ||
1060 | # wait for the maintenance function to run | |
1061 | time.sleep(2) | |
1062 | ||
1063 | # we should still not be blocked | |
1064 | (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) | |
1065 | receivedQuery.id = query.id | |
1066 | self.assertEquals(query, receivedQuery) | |
1067 | self.assertEquals(receivedResponse, receivedResponse) | |
477c86a0 RG |
1068 | |
1069 | class TestDynBlockGroupNoOp(DynBlocksTest): | |
1070 | ||
1071 | _dynBlockQPS = 10 | |
1072 | _dynBlockPeriod = 2 | |
1073 | _dynBlockDuration = 5 | |
1074 | _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey'] | |
1075 | _config_template = """ | |
1076 | local dbr = dynBlockRulesGroup() | |
1077 | dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.NoOp) | |
1078 | ||
1079 | function maintenance() | |
1080 | dbr:apply() | |
1081 | end | |
1082 | ||
1083 | newServer{address="127.0.0.1:%s"} | |
1084 | webserver("127.0.0.1:%s", "%s", "%s") | |
1085 | """ | |
1086 | ||
1087 | def testNoOp(self): | |
1088 | """ | |
1089 | Dyn Blocks (group) : NoOp | |
1090 | """ | |
1091 | name = 'noop.group.dynblocks.tests.powerdns.com.' | |
1092 | query = dns.message.make_query(name, 'A', 'IN') | |
1093 | response = dns.message.make_response(query) | |
1094 | rrset = dns.rrset.from_text(name, | |
1095 | 60, | |
1096 | dns.rdataclass.IN, | |
1097 | dns.rdatatype.A, | |
1098 | '192.0.2.1') | |
1099 | response.answer.append(rrset) | |
1100 | ||
1101 | allowed = 0 | |
1102 | sent = 0 | |
1103 | for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1): | |
1104 | (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) | |
1105 | sent = sent + 1 | |
1106 | if receivedQuery: | |
1107 | receivedQuery.id = query.id | |
1108 | self.assertEquals(query, receivedQuery) | |
1109 | self.assertEquals(response, receivedResponse) | |
1110 | allowed = allowed + 1 | |
1111 | else: | |
1112 | # the query has not reached the responder, | |
1113 | # let's clear the response queue | |
1114 | self.clearToResponderQueue() | |
1115 | ||
1116 | # a dynamic rule should have been inserted, but the queries should still go on | |
1117 | self.assertEqual(allowed, sent) | |
1118 | ||
1119 | # wait for the maintenance function to run | |
1120 | time.sleep(2) | |
1121 | ||
1122 | # the rule should still be present, but the queries pass through anyway | |
1123 | (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) | |
1124 | receivedQuery.id = query.id | |
1125 | self.assertEquals(query, receivedQuery) | |
1126 | self.assertEquals(receivedResponse, receivedResponse) | |
1127 | ||
1128 | # check that the rule has been inserted | |
1129 | self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', self._dynBlockDuration - 4, self._dynBlockDuration, 0, sent) | |
1d3ba133 RG |
1130 | |
1131 | class TestDynBlockGroupWarning(DynBlocksTest): | |
1132 | ||
1133 | _dynBlockWarningQPS = 5 | |
1134 | _dynBlockQPS = 20 | |
1135 | _dynBlockPeriod = 2 | |
1136 | _dynBlockDuration = 5 | |
1137 | _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_dynBlockWarningQPS', '_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey'] | |
1138 | _config_template = """ | |
1139 | local dbr = dynBlockRulesGroup() | |
1140 | dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.Drop, %d) | |
1141 | ||
1142 | function maintenance() | |
1143 | dbr:apply() | |
1144 | end | |
1145 | ||
1146 | newServer{address="127.0.0.1:%s"} | |
1147 | webserver("127.0.0.1:%s", "%s", "%s") | |
1148 | """ | |
1149 | ||
1150 | def testWarning(self): | |
1151 | """ | |
1152 | Dyn Blocks (group) : Warning | |
1153 | """ | |
1154 | name = 'warning.group.dynblocks.tests.powerdns.com.' | |
1155 | query = dns.message.make_query(name, 'A', 'IN') | |
1156 | response = dns.message.make_response(query) | |
1157 | rrset = dns.rrset.from_text(name, | |
1158 | 60, | |
1159 | dns.rdataclass.IN, | |
1160 | dns.rdatatype.A, | |
1161 | '192.0.2.1') | |
1162 | response.answer.append(rrset) | |
1163 | ||
1164 | allowed = 0 | |
1165 | sent = 0 | |
1166 | for _ in range((self._dynBlockWarningQPS * self._dynBlockPeriod) + 1): | |
1167 | (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) | |
1168 | sent = sent + 1 | |
1169 | if receivedQuery: | |
1170 | receivedQuery.id = query.id | |
1171 | self.assertEquals(query, receivedQuery) | |
1172 | self.assertEquals(response, receivedResponse) | |
1173 | allowed = allowed + 1 | |
1174 | else: | |
1175 | # the query has not reached the responder, | |
1176 | # let's clear the response queue | |
1177 | self.clearToResponderQueue() | |
1178 | ||
1179 | # a dynamic rule should have been inserted, but the queries should | |
1180 | # still go on because we are still at warning level | |
1181 | self.assertEqual(allowed, sent) | |
1182 | ||
1183 | # wait for the maintenance function to run | |
1184 | time.sleep(2) | |
1185 | ||
1186 | # the rule should still be present, but the queries pass through anyway | |
1187 | (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) | |
1188 | receivedQuery.id = query.id | |
1189 | self.assertEquals(query, receivedQuery) | |
1190 | self.assertEquals(receivedResponse, receivedResponse) | |
1191 | ||
1192 | # check that the rule has been inserted | |
1193 | self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', self._dynBlockDuration - 4, self._dynBlockDuration, 0, sent) | |
1194 | ||
1195 | self.doTestQRate(name) |