]> git.ipfire.org Git - thirdparty/pdns.git/blob - regression-tests.dnsdist/test_Advanced.py
Merge pull request #13861 from omoerbeek/rec-rpzloader-tidy
[thirdparty/pdns.git] / regression-tests.dnsdist / test_Advanced.py
1 #!/usr/bin/env python
2 import base64
3 import os
4 import socket
5 import time
6 import unittest
7 import dns
8 from dnsdisttests import DNSDistTest
9
10 class TestAdvancedFixupCase(DNSDistTest):
11
12 _config_template = """
13 truncateTC(true)
14 fixupCase(true)
15 newServer{address="127.0.0.1:%s"}
16 """
17
18 def testAdvancedFixupCase(self):
19 """
20 Advanced: Fixup Case
21
22 Send a query with lower and upper chars,
23 make the backend return a lowercase version,
24 check that dnsdist fixes the response.
25 """
26 name = 'fiXuPCasE.advanced.tests.powerdns.com.'
27 query = dns.message.make_query(name, 'A', 'IN')
28 lowercasequery = dns.message.make_query(name.lower(), 'A', 'IN')
29 response = dns.message.make_response(lowercasequery)
30 expectedResponse = dns.message.make_response(query)
31 rrset = dns.rrset.from_text(name,
32 3600,
33 dns.rdataclass.IN,
34 dns.rdatatype.A,
35 '127.0.0.1')
36 response.answer.append(rrset)
37 expectedResponse.answer.append(rrset)
38
39 for method in ("sendUDPQuery", "sendTCPQuery"):
40 sender = getattr(self, method)
41 (receivedQuery, receivedResponse) = sender(query, response)
42 self.assertTrue(receivedQuery)
43 self.assertTrue(receivedResponse)
44 receivedQuery.id = query.id
45 self.assertEqual(query, receivedQuery)
46 self.assertEqual(expectedResponse, receivedResponse)
47
48 class TestAdvancedACL(DNSDistTest):
49
50 _config_template = """
51 newServer{address="127.0.0.1:%s"}
52 """
53 _acl = ['192.0.2.1/32']
54
55 def testACLBlocked(self):
56 """
57 Advanced: ACL blocked
58
59 Send an A query to "tests.powerdns.com.",
60 we expect no response since 127.0.0.1 is not on the
61 ACL.
62 """
63 name = 'tests.powerdns.com.'
64 query = dns.message.make_query(name, 'A', 'IN')
65
66 for method in ("sendUDPQuery", "sendTCPQuery"):
67 sender = getattr(self, method)
68 (_, receivedResponse) = sender(query, response=None, useQueue=False)
69 self.assertEqual(receivedResponse, None)
70
71 class TestAdvancedStringOnlyServer(DNSDistTest):
72
73 _config_template = """
74 newServer("127.0.0.1:%s")
75 """
76
77 def testAdvancedStringOnlyServer(self):
78 """
79 Advanced: "string-only" server is placed in the default pool
80 """
81 name = 'string-only-server.advanced.tests.powerdns.com.'
82 query = dns.message.make_query(name, 'A', 'IN')
83 response = dns.message.make_response(query)
84 rrset = dns.rrset.from_text(name,
85 3600,
86 dns.rdataclass.IN,
87 dns.rdatatype.A,
88 '192.0.2.1')
89 response.answer.append(rrset)
90
91 for method in ("sendUDPQuery", "sendTCPQuery"):
92 sender = getattr(self, method)
93 (receivedQuery, receivedResponse) = sender(query, response)
94 self.assertTrue(receivedQuery)
95 self.assertTrue(receivedResponse)
96 receivedQuery.id = query.id
97 self.assertEqual(query, receivedQuery)
98 self.assertEqual(response, receivedResponse)
99
100 @unittest.skipIf('SKIP_INCLUDEDIR_TESTS' in os.environ, 'IncludeDir tests are disabled')
101 class TestAdvancedIncludeDir(DNSDistTest):
102
103 _config_template = """
104 -- this directory contains a file allowing includedir.advanced.tests.powerdns.com.
105 includeDirectory('test-include-dir')
106 newServer{address="127.0.0.1:%s"}
107 """
108
109 def testAdvancedIncludeDirAllowed(self):
110 """
111 Advanced: includeDirectory()
112 """
113 name = 'includedir.advanced.tests.powerdns.com.'
114 query = dns.message.make_query(name, 'A', 'IN')
115 response = dns.message.make_response(query)
116 rrset = dns.rrset.from_text(name,
117 3600,
118 dns.rdataclass.IN,
119 dns.rdatatype.A,
120 '192.0.2.1')
121 response.answer.append(rrset)
122
123 for method in ("sendUDPQuery", "sendTCPQuery"):
124 sender = getattr(self, method)
125 (receivedQuery, receivedResponse) = sender(query, response)
126 self.assertTrue(receivedQuery)
127 self.assertTrue(receivedResponse)
128 receivedQuery.id = query.id
129 self.assertEqual(query, receivedQuery)
130 self.assertEqual(response, receivedResponse)
131
132 # this one should be refused
133 name = 'notincludedir.advanced.tests.powerdns.com.'
134 query = dns.message.make_query(name, 'A', 'IN')
135 query.flags &= ~dns.flags.RD
136 expectedResponse = dns.message.make_response(query)
137 expectedResponse.set_rcode(dns.rcode.REFUSED)
138
139 for method in ("sendUDPQuery", "sendTCPQuery"):
140 sender = getattr(self, method)
141 (_, receivedResponse) = sender(query, response=None, useQueue=False)
142 self.assertEqual(receivedResponse, expectedResponse)
143
144 class TestStatNodeRespRingSince(DNSDistTest):
145
146 _consoleKey = DNSDistTest.generateConsoleKey()
147 _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
148 _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort']
149 _config_template = """
150 setKey("%s")
151 controlSocket("127.0.0.1:%s")
152 s1 = newServer{address="127.0.0.1:%s"}
153 s1:setUp()
154 function visitor(node, self, childstat)
155 table.insert(nodesSeen, node.fullname)
156 end
157 """
158
159 def testStatNodeRespRingSince(self):
160 """
161 Advanced: StatNodeRespRing with optional since parameter
162
163 """
164 name = 'statnodesince.advanced.tests.powerdns.com.'
165 query = dns.message.make_query(name, 'A', 'IN')
166 response = dns.message.make_response(query)
167 rrset = dns.rrset.from_text(name,
168 1,
169 dns.rdataclass.IN,
170 dns.rdatatype.A,
171 '127.0.0.1')
172 response.answer.append(rrset)
173
174 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
175 self.assertTrue(receivedQuery)
176 self.assertTrue(receivedResponse)
177 receivedQuery.id = query.id
178 self.assertEqual(query, receivedQuery)
179 self.assertEqual(response, receivedResponse)
180
181 self.sendConsoleCommand("nodesSeen = {}")
182 self.sendConsoleCommand("statNodeRespRing(visitor)")
183 nodes = self.sendConsoleCommand("str = '' for key,value in pairs(nodesSeen) do str = str..value..\"\\n\" end return str")
184 nodes = nodes.strip("\n")
185 self.assertEqual(nodes, """statnodesince.advanced.tests.powerdns.com.
186 advanced.tests.powerdns.com.
187 tests.powerdns.com.
188 powerdns.com.
189 com.""")
190
191 self.sendConsoleCommand("nodesSeen = {}")
192 self.sendConsoleCommand("statNodeRespRing(visitor, 0)")
193 nodes = self.sendConsoleCommand("str = '' for key,value in pairs(nodesSeen) do str = str..value..\"\\n\" end return str")
194 nodes = nodes.strip("\n")
195 self.assertEqual(nodes, """statnodesince.advanced.tests.powerdns.com.
196 advanced.tests.powerdns.com.
197 tests.powerdns.com.
198 powerdns.com.
199 com.""")
200
201 time.sleep(5)
202
203 self.sendConsoleCommand("nodesSeen = {}")
204 self.sendConsoleCommand("statNodeRespRing(visitor)")
205 nodes = self.sendConsoleCommand("str = '' for key,value in pairs(nodesSeen) do str = str..value..\"\\n\" end return str")
206 nodes = nodes.strip("\n")
207 self.assertEqual(nodes, """statnodesince.advanced.tests.powerdns.com.
208 advanced.tests.powerdns.com.
209 tests.powerdns.com.
210 powerdns.com.
211 com.""")
212
213 self.sendConsoleCommand("nodesSeen = {}")
214 self.sendConsoleCommand("statNodeRespRing(visitor, 5)")
215 nodes = self.sendConsoleCommand("str = '' for key,value in pairs(nodesSeen) do str = str..value..\"\\n\" end return str")
216 nodes = nodes.strip("\n")
217 self.assertEqual(nodes, """""")
218
219 self.sendConsoleCommand("nodesSeen = {}")
220 self.sendConsoleCommand("statNodeRespRing(visitor, 10)")
221 nodes = self.sendConsoleCommand("str = '' for key,value in pairs(nodesSeen) do str = str..value..\"\\n\" end return str")
222 nodes = nodes.strip("\n")
223 self.assertEqual(nodes, """statnodesince.advanced.tests.powerdns.com.
224 advanced.tests.powerdns.com.
225 tests.powerdns.com.
226 powerdns.com.
227 com.""")
228
229 class TestAdvancedGetLocalPort(DNSDistTest):
230
231 _config_template = """
232 function answerBasedOnLocalPort(dq)
233 local port = dq.localaddr:getPort()
234 return DNSAction.Spoof, "port-was-"..port..".local-port.advanced.tests.powerdns.com."
235 end
236 addAction("local-port.advanced.tests.powerdns.com.", LuaAction(answerBasedOnLocalPort))
237 newServer{address="127.0.0.1:%s"}
238 """
239
240 def testAdvancedGetLocalPort(self):
241 """
242 Advanced: Return CNAME containing the local port
243 """
244 name = 'local-port.advanced.tests.powerdns.com.'
245 query = dns.message.make_query(name, 'A', 'IN')
246 # dnsdist set RA = RD for spoofed responses
247 query.flags &= ~dns.flags.RD
248
249 response = dns.message.make_response(query)
250 rrset = dns.rrset.from_text(name,
251 60,
252 dns.rdataclass.IN,
253 dns.rdatatype.CNAME,
254 'port-was-{}.local-port.advanced.tests.powerdns.com.'.format(self._dnsDistPort))
255 response.answer.append(rrset)
256
257 for method in ("sendUDPQuery", "sendTCPQuery"):
258 sender = getattr(self, method)
259 (_, receivedResponse) = sender(query, response=None, useQueue=False)
260 self.assertEqual(receivedResponse, response)
261
262 class TestAdvancedGetLocalPortOnAnyBind(DNSDistTest):
263
264 _config_template = """
265 function answerBasedOnLocalPort(dq)
266 local port = dq.localaddr:getPort()
267 return DNSAction.Spoof, "port-was-"..port..".local-port-any.advanced.tests.powerdns.com."
268 end
269 addAction("local-port-any.advanced.tests.powerdns.com.", LuaAction(answerBasedOnLocalPort))
270 newServer{address="127.0.0.1:%d"}
271 """
272 _dnsDistListeningAddr = '0.0.0.0'
273
274 def testAdvancedGetLocalPortOnAnyBind(self):
275 """
276 Advanced: Return CNAME containing the local port for an ANY bind
277 """
278 name = 'local-port-any.advanced.tests.powerdns.com.'
279 query = dns.message.make_query(name, 'A', 'IN')
280 # dnsdist set RA = RD for spoofed responses
281 query.flags &= ~dns.flags.RD
282
283 response = dns.message.make_response(query)
284 rrset = dns.rrset.from_text(name,
285 60,
286 dns.rdataclass.IN,
287 dns.rdatatype.CNAME,
288 'port-was-{}.local-port-any.advanced.tests.powerdns.com.'.format(self._dnsDistPort))
289 response.answer.append(rrset)
290
291 for method in ("sendUDPQuery", "sendTCPQuery"):
292 sender = getattr(self, method)
293 (_, receivedResponse) = sender(query, response=None, useQueue=False)
294 self.assertEqual(receivedResponse, response)
295
296 class TestAdvancedGetLocalAddressOnAnyBind(DNSDistTest):
297
298 _config_template = """
299 function answerBasedOnLocalAddress(dq)
300 local dest = tostring(dq.localaddr)
301 local i, j = string.find(dest, "[0-9.]+")
302 local addr = string.sub(dest, i, j)
303 local dashAddr = string.gsub(addr, "[.]", "-")
304 return DNSAction.Spoof, "address-was-"..dashAddr..".local-address-any.advanced.tests.powerdns.com."
305 end
306 addAction("local-address-any.advanced.tests.powerdns.com.", LuaAction(answerBasedOnLocalAddress))
307 newServer{address="127.0.0.1:%s"}
308 addLocal('0.0.0.0:%d')
309 addLocal('[::]:%d')
310 """
311 _config_params = ['_testServerPort', '_dnsDistPort', '_dnsDistPort']
312 _acl = ['127.0.0.1/32', '::1/128']
313 _skipListeningOnCL = True
314 _verboseMode = True
315
316 def testAdvancedGetLocalAddressOnAnyBind(self):
317 """
318 Advanced: Return CNAME containing the local address for an ANY bind
319 """
320 name = 'local-address-any.advanced.tests.powerdns.com.'
321 query = dns.message.make_query(name, 'A', 'IN')
322 # dnsdist set RA = RD for spoofed responses
323 query.flags &= ~dns.flags.RD
324
325 response = dns.message.make_response(query)
326 rrset = dns.rrset.from_text(name,
327 60,
328 dns.rdataclass.IN,
329 dns.rdatatype.CNAME,
330 'address-was-127-0-0-1.local-address-any.advanced.tests.powerdns.com.')
331 response.answer.append(rrset)
332
333 for method in ("sendUDPQuery", "sendTCPQuery"):
334 sender = getattr(self, method)
335 (_, receivedResponse) = sender(query, response=None, useQueue=False)
336 self.assertEqual(receivedResponse, response)
337
338 # now a bit more tricky, UDP-only IPv4
339 response = dns.message.make_response(query)
340 rrset = dns.rrset.from_text(name,
341 60,
342 dns.rdataclass.IN,
343 dns.rdatatype.CNAME,
344 'address-was-127-0-0-2.local-address-any.advanced.tests.powerdns.com.')
345 response.answer.append(rrset)
346 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
347 sock.settimeout(2.0)
348 sock.connect(('127.0.0.2', self._dnsDistPort))
349 try:
350 query = query.to_wire()
351 sock.send(query)
352 (data, remote) = sock.recvfrom(4096)
353 self.assertEqual(remote[0], '127.0.0.2')
354 except socket.timeout:
355 data = None
356
357 self.assertTrue(data)
358 receivedResponse = dns.message.from_wire(data)
359 self.assertEqual(receivedResponse, response)
360
361 def testAdvancedCheckSourceAddrOnAnyBind(self):
362 """
363 Advanced: Check the source address on responses for an ANY bind
364 """
365 name = 'source-addr-any.advanced.tests.powerdns.com.'
366 query = dns.message.make_query(name, 'A', 'IN')
367 # dnsdist set RA = RD for spoofed responses
368 query.flags &= ~dns.flags.RD
369
370 response = dns.message.make_response(query)
371 rrset = dns.rrset.from_text(name,
372 60,
373 dns.rdataclass.IN,
374 dns.rdatatype.A,
375 '192.0.2.42')
376 response.answer.append(rrset)
377
378 # a bit more tricky, UDP-only IPv4
379 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
380 sock.settimeout(2.0)
381 sock.connect(('127.0.0.2', self._dnsDistPort))
382 self._toResponderQueue.put(response, True, 1.0)
383 try:
384 data = query.to_wire()
385 sock.send(data)
386 (data, remote) = sock.recvfrom(4096)
387 self.assertEqual(remote[0], '127.0.0.2')
388 except socket.timeout:
389 data = None
390
391 self.assertTrue(data)
392 receivedResponse = dns.message.from_wire(data)
393 receivedQuery = self._fromResponderQueue.get(True, 1.0)
394 receivedQuery.id = query.id
395 self.assertEqual(receivedQuery, query)
396 self.assertEqual(receivedResponse, response)
397
398 if 'SKIP_IPV6_TESTS' in os.environ:
399 return
400
401 # a bit more tricky, UDP-only IPv6
402 sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
403 sock.settimeout(2.0)
404 sock.connect(('::1', self._dnsDistPort))
405 self._toResponderQueue.put(response, True, 1.0)
406 try:
407 data = query.to_wire()
408 sock.send(data)
409 (data, remote) = sock.recvfrom(4096)
410 self.assertEqual(remote[0], '::1')
411 except socket.timeout:
412 data = None
413
414 self.assertTrue(data)
415 receivedResponse = dns.message.from_wire(data)
416 receivedQuery = self._fromResponderQueue.get(True, 1.0)
417 receivedQuery.id = query.id
418 self.assertEqual(receivedQuery, query)
419 self.assertEqual(receivedResponse, response)
420
421 class TestAdvancedGetLocalAddressOnNonDefaultLoopbackBind(DNSDistTest):
422 # this one is tricky: on the loopback interface we cannot harvest the destination
423 # address, so we exercise a different code path when we bind on a different address
424 # than the default 127.0.0.1 one
425 _config_template = """
426 newServer{address="127.0.0.1:%s"}
427 addLocal('127.0.1.19:%d')
428 """
429 _config_params = ['_testServerPort', '_dnsDistPort']
430 _acl = ['127.0.0.1/32']
431 _skipListeningOnCL = True
432 _alternateListeningAddr = '127.0.1.19'
433 _alternateListeningPort = DNSDistTest._dnsDistPort
434
435 def testAdvancedCheckSourceAddrOnNonDefaultLoopbackBind(self):
436 """
437 Advanced: Check the source address used to reply on a non-default loopback bind
438 """
439 name = 'source-addr-non-default-loopback.advanced.tests.powerdns.com.'
440 query = dns.message.make_query(name, 'A', 'IN')
441 response = dns.message.make_response(query)
442 rrset = dns.rrset.from_text(name,
443 60,
444 dns.rdataclass.IN,
445 dns.rdatatype.A,
446 '192.0.2.42')
447 response.answer.append(rrset)
448
449 # a bit more tricky, UDP-only IPv4
450 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
451 sock.settimeout(2.0)
452 sock.connect(('127.0.1.19', self._dnsDistPort))
453 self._toResponderQueue.put(response, True, 1.0)
454 try:
455 data = query.to_wire()
456 sock.send(data)
457 (data, remote) = sock.recvfrom(4096)
458 self.assertEqual(remote[0], '127.0.1.19')
459 except socket.timeout:
460 data = None
461
462 self.assertTrue(data)
463 receivedResponse = dns.message.from_wire(data)
464 receivedQuery = self._fromResponderQueue.get(True, 1.0)
465 receivedQuery.id = query.id
466 self.assertEqual(receivedQuery, query)
467 self.assertEqual(receivedResponse, response)
468
469 class TestAdvancedAllowHeaderOnly(DNSDistTest):
470
471 _config_template = """
472 newServer{address="127.0.0.1:%s"}
473 setAllowEmptyResponse(true)
474 """
475
476 def testHeaderOnlyRefused(self):
477 """
478 Advanced: Header-only refused response
479 """
480 name = 'header-only-refused-response.advanced.tests.powerdns.com.'
481 query = dns.message.make_query(name, 'A', 'IN')
482 response = dns.message.make_response(query)
483 response.set_rcode(dns.rcode.REFUSED)
484 response.question = []
485
486 for method in ("sendUDPQuery", "sendTCPQuery"):
487 sender = getattr(self, method)
488 (receivedQuery, receivedResponse) = sender(query, response)
489 self.assertTrue(receivedQuery)
490 receivedQuery.id = query.id
491 self.assertEqual(query, receivedQuery)
492 self.assertEqual(receivedResponse, response)
493
494 def testHeaderOnlyNoErrorResponse(self):
495 """
496 Advanced: Header-only NoError response should be allowed
497 """
498 name = 'header-only-noerror-response.advanced.tests.powerdns.com.'
499 query = dns.message.make_query(name, 'A', 'IN')
500 response = dns.message.make_response(query)
501 response.question = []
502
503 for method in ("sendUDPQuery", "sendTCPQuery"):
504 sender = getattr(self, method)
505 (receivedQuery, receivedResponse) = sender(query, response)
506 self.assertTrue(receivedQuery)
507 receivedQuery.id = query.id
508 self.assertEqual(query, receivedQuery)
509 self.assertEqual(receivedResponse, response)
510
511 def testHeaderOnlyNXDResponse(self):
512 """
513 Advanced: Header-only NXD response should be allowed
514 """
515 name = 'header-only-nxd-response.advanced.tests.powerdns.com.'
516 query = dns.message.make_query(name, 'A', 'IN')
517 response = dns.message.make_response(query)
518 response.set_rcode(dns.rcode.NXDOMAIN)
519 response.question = []
520
521 for method in ("sendUDPQuery", "sendTCPQuery"):
522 sender = getattr(self, method)
523 (receivedQuery, receivedResponse) = sender(query, response)
524 self.assertTrue(receivedQuery)
525 receivedQuery.id = query.id
526 self.assertEqual(query, receivedQuery)
527 self.assertEqual(receivedResponse, response)
528
529 class TestAdvancedDropEmptyQueries(DNSDistTest):
530
531 _config_template = """
532 setDropEmptyQueries(true)
533 newServer{address="127.0.0.1:%s"}
534 """
535
536 def testAdvancedDropEmptyQueries(self):
537 """
538 Advanced: Drop empty queries
539 """
540 name = 'drop-empty-queries.advanced.tests.powerdns.com.'
541 query = dns.message.Message()
542
543 for method in ("sendUDPQuery", "sendTCPQuery"):
544 sender = getattr(self, method)
545 (_, receivedResponse) = sender(query, response=None, useQueue=False)
546 self.assertEqual(receivedResponse, None)
547
548 class TestProtocols(DNSDistTest):
549 _config_template = """
550 function checkUDP(dq)
551 if dq:getProtocol() ~= "Do53 UDP" then
552 return DNSAction.Spoof, '1.2.3.4'
553 end
554 return DNSAction.None
555 end
556
557 function checkTCP(dq)
558 if dq:getProtocol() ~= "Do53 TCP" then
559 return DNSAction.Spoof, '1.2.3.4'
560 end
561 return DNSAction.None
562 end
563
564 addAction("udp.protocols.advanced.tests.powerdns.com.", LuaAction(checkUDP))
565 addAction("tcp.protocols.advanced.tests.powerdns.com.", LuaAction(checkTCP))
566 newServer{address="127.0.0.1:%s"}
567 """
568
569 def testProtocolUDP(self):
570 """
571 Advanced: Test DNSQuestion.Protocol over UDP
572 """
573 name = 'udp.protocols.advanced.tests.powerdns.com.'
574 query = dns.message.make_query(name, 'A', 'IN')
575 response = dns.message.make_response(query)
576
577 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
578 receivedQuery.id = query.id
579 self.assertEqual(receivedQuery, query)
580 self.assertEqual(receivedResponse, response)
581
582 def testProtocolTCP(self):
583 """
584 Advanced: Test DNSQuestion.Protocol over TCP
585 """
586 name = 'tcp.protocols.advanced.tests.powerdns.com.'
587 query = dns.message.make_query(name, 'A', 'IN')
588 response = dns.message.make_response(query)
589
590 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
591 receivedQuery.id = query.id
592 self.assertEqual(receivedQuery, query)
593 self.assertEqual(receivedResponse, response)
594
595 class TestCustomMetrics(DNSDistTest):
596 _config_template = """
597 function custommetrics(dq)
598 initialCounter = getMetric("my-custom-counter")
599 initialGauge = getMetric("my-custom-gauge")
600 incMetric("my-custom-counter")
601 incMetric("my-custom-counter", 41)
602 setMetric("my-custom-gauge", initialGauge + 1.3)
603 if getMetric("my-custom-counter") ~= (initialCounter + 42) or getMetric("my-custom-gauge") ~= (initialGauge + 1.3) then
604 return DNSAction.Spoof, '1.2.3.5'
605 end
606 return DNSAction.Spoof, '4.3.2.1'
607 end
608
609 function declareNewMetric(dq)
610 if declareMetric("new-runtime-metric", "counter", "Metric declaration at runtime should work fine") then
611 return DNSAction.None
612 end
613 return DNSAction.Spoof, '1.2.3.4'
614 end
615
616 declareMetric("my-custom-counter", "counter", "Number of tests run")
617 declareMetric("my-custom-gauge", "gauge", "Temperature of the tests")
618 addAction("declare.metric.advanced.tests.powerdns.com.", LuaAction(declareNewMetric))
619 addAction("operations.metric.advanced.tests.powerdns.com.", LuaAction(custommetrics))
620 newServer{address="127.0.0.1:%s"}
621 """
622
623 def testDeclareAfterConfig(self):
624 """
625 Advanced: Test custom metric declaration after config done
626 """
627 name = 'declare.metric.advanced.tests.powerdns.com.'
628 query = dns.message.make_query(name, 'A', 'IN')
629 response = dns.message.make_response(query)
630
631 for method in ("sendUDPQuery", "sendTCPQuery"):
632 sender = getattr(self, method)
633 (receivedQuery, receivedResponse) = sender(query, response)
634 receivedQuery.id = query.id
635 self.assertEqual(receivedQuery, query)
636 self.assertEqual(receivedResponse, response)
637
638 def testMetricOperations(self):
639 """
640 Advanced: Test basic operations on custom metrics
641 """
642 name = 'operations.metric.advanced.tests.powerdns.com.'
643 query = dns.message.make_query(name, 'A', 'IN')
644 # dnsdist set RA = RD for spoofed responses
645 query.flags &= ~dns.flags.RD
646 response = dns.message.make_response(query)
647 rrset = dns.rrset.from_text(name,
648 60,
649 dns.rdataclass.IN,
650 dns.rdatatype.A,
651 '4.3.2.1')
652 response.answer.append(rrset)
653
654 for method in ("sendUDPQuery", "sendTCPQuery"):
655 sender = getattr(self, method)
656 (_, receivedResponse) = sender(query, response=None, useQueue=False)
657 self.assertEqual(receivedResponse, response)
658
659 class TestDNSQuestionTime(DNSDistTest):
660 _config_template = """
661 local queryTime = nil
662
663 function luaquery(dq)
664 if queryTime then
665 errlog('Error, the time variable is already set')
666 return DNSAction.Drop
667 end
668 queryTime = dq:getQueryTime()
669 local currentTime = getCurrentTime()
670 if queryTime.tv_sec > currentTime.tv_sec then
671 errlog('Error, query time is higher than current time')
672 return DNSAction.Drop
673 end
674 if queryTime.tv_sec == currentTime.tv_sec and queryTime.tv_nsec > currentTime.tv_nsec then
675 errlog('Error, query time NS is higher than current time')
676 return DNSAction.Drop
677 end
678 return DNSAction.None
679 end
680
681 function luaresponse(dr)
682 if queryTime == nil then
683 errlog('Error, the time variable is NOT set')
684 return DNSAction.Drop
685 end
686 local currentTime = getCurrentTime()
687 local queryTimeFromResponse = dr:getQueryTime()
688 if queryTime.tv_sec ~= queryTimeFromResponse.tv_sec or queryTime.tv_nsec ~= queryTimeFromResponse.tv_nsec then
689 errlog('Error, the query time in the response does NOT match the one from the query')
690 return DNSAction.Drop
691 end
692 if queryTime.tv_sec > currentTime.tv_sec then
693 errlog('Error, query time is higher than current time')
694 return DNSAction.Drop
695 end
696 if queryTime.tv_sec == currentTime.tv_sec and queryTime.tv_nsec > currentTime.tv_nsec then
697 errlog('Error, query time (NS) is higher than current time')
698 return DNSAction.Drop
699 end
700
701 queryTime = nil
702 return DNSAction.None
703 end
704
705 addAction(AllRule(), LuaAction(luaquery))
706 addResponseAction(AllRule(), LuaResponseAction(luaresponse))
707 newServer{address="127.0.0.1:%s"}
708 """
709
710 def testQueryTime(self):
711 """
712 Advanced: Test query time
713 """
714 name = 'query.time.advanced.tests.powerdns.com.'
715 query = dns.message.make_query(name, 'A', 'IN')
716 response = dns.message.make_response(query)
717 rrset = dns.rrset.from_text(name,
718 60,
719 dns.rdataclass.IN,
720 dns.rdatatype.A,
721 '4.3.2.1')
722 response.answer.append(rrset)
723
724 for method in ("sendUDPQuery", "sendTCPQuery"):
725 sender = getattr(self, method)
726 (receivedQuery, receivedResponse) = sender(query, response)
727 receivedQuery.id = query.id
728 self.assertEqual(receivedQuery, query)
729 self.assertEqual(receivedResponse, response)
730
731 class TestChangeName(DNSDistTest):
732 _config_template = """
733 local tagName = 'initial-name'
734 function luaChangeNamequery(dq)
735 dq:setTag(tagName, dq.qname:toString())
736 if not dq:changeName(newDNSName('changeName.advanced.tests.dnsdist.org')) then
737 errlog('Error rebasing the query')
738 return DNSAction.Drop
739 end
740 return DNSAction.None
741 end
742
743 function luaChangeNameresponse(dr)
744 local initialName = dr:getTag(tagName)
745 if not dr:changeName(newDNSName(initialName)) then
746 errlog('Error rebasing the response')
747 return DNSAction.Drop
748 end
749 return DNSAction.None
750 end
751
752 addAction('changeName.advanced.tests.powerdns.com', LuaAction(luaChangeNamequery))
753 addResponseAction('changeName.advanced.tests.dnsdist.org', LuaResponseAction(luaChangeNameresponse))
754 newServer{address="127.0.0.1:%s"}
755 """
756
757 def testChangeName(self):
758 """
759 Advanced: ChangeName the query name
760 """
761 name = 'changeName.advanced.tests.powerdns.com.'
762 query = dns.message.make_query(name, 'A', 'IN')
763
764 changedName = 'changeName.advanced.tests.dnsdist.org.'
765 changedQuery = dns.message.make_query(changedName, 'A', 'IN')
766 changedQuery.id = query.id
767
768 response = dns.message.make_response(changedQuery)
769 rrset = dns.rrset.from_text(changedName,
770 60,
771 dns.rdataclass.IN,
772 dns.rdatatype.A,
773 '4.3.2.1')
774 response.answer.append(rrset)
775 rrset = dns.rrset.from_text('sub.sub2.changeName.advanced.tests.dnsdist.org.',
776 60,
777 dns.rdataclass.IN,
778 dns.rdatatype.TXT,
779 'This text contains sub.sub2.changeName.advanced.tests.dnsdist.org.')
780 response.additional.append(rrset)
781
782 expectedResponse = dns.message.make_response(query)
783 rrset = dns.rrset.from_text(name,
784 60,
785 dns.rdataclass.IN,
786 dns.rdatatype.A,
787 '4.3.2.1')
788 expectedResponse.answer.append(rrset)
789 # we only rewrite records if the owner name matches the new target, nothing else
790 rrset = dns.rrset.from_text('sub.sub2.changeName.advanced.tests.dnsdist.org.',
791 60,
792 dns.rdataclass.IN,
793 dns.rdatatype.TXT,
794 'This text contains sub.sub2.changeName.advanced.tests.dnsdist.org.')
795 expectedResponse.additional.append(rrset)
796
797 for method in ("sendUDPQuery", "sendTCPQuery"):
798 sender = getattr(self, method)
799 (receivedQuery, receivedResponse) = sender(query, response)
800 receivedQuery.id = query.id
801 self.assertEqual(receivedQuery, changedQuery)
802 self.assertEqual(receivedResponse, expectedResponse)
803
804 class TestFlagsOnTimeout(DNSDistTest):
805
806 _consoleKey = DNSDistTest.generateConsoleKey()
807 _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
808 _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort']
809 _config_template = """
810 setKey("%s")
811 controlSocket("127.0.0.1:%s")
812 -- this server is not going to answer, resulting in a timeout
813 newServer{address="192.0.2.1:%s"}:setUp()
814 """
815
816 def testFlags(self):
817 """
818 Advanced: Test that we record the correct incoming flags on a timeout
819 """
820 name = 'timeout-flags.advanced.tests.powerdns.com.'
821
822 # first with RD=1
823 query = dns.message.make_query(name, 'A', 'IN')
824 query.id = 42
825 query.flags |= dns.flags.RD
826
827 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
828 self.assertFalse(receivedResponse)
829
830 # then with RD=0
831 query = dns.message.make_query(name, 'A', 'IN')
832 query.id = 84
833 query.flags &= ~dns.flags.RD
834
835 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
836 self.assertFalse(receivedResponse)
837
838 # make sure that the timeouts have been detected and recorded
839 for _ in range(6):
840 content = self.sendConsoleCommand("grepq('')")
841 lines = content.splitlines()
842 if len(lines) == 5:
843 break
844 # and otherwise sleep for a short while
845 time.sleep(1)
846
847 print(lines)
848 self.assertEqual(len(lines), 5)
849 # header line
850 self.assertIn('TC RD AA', lines[0])
851
852 queries = {}
853 timeouts = {}
854
855 for line in lines[1:]:
856 self.assertIn('DoUDP', line)
857 if 'T.O' in line:
858 queryID = int(line.split()[4])
859 timeouts[queryID] = line
860 else:
861 queryID = int(line.split()[3])
862 queries[queryID] = line
863 if queryID == 42:
864 self.assertIn('RD', line)
865 else:
866 self.assertNotIn('RD', line)
867
868 self.assertEqual(len(timeouts), 2)
869 self.assertEqual(len(queries), 2)
870
871 class TestTruncatedUDPLargeAnswers(DNSDistTest):
872 _config_template = """
873 newServer{address="127.0.0.1:%d"}
874 """
875 def testVeryLargeAnswer(self):
876 """
877 Advanced: Check that UDP responses that are too large for our buffer are dismissed
878 """
879 name = 'very-large-answer-dismissed.advanced.tests.powerdns.com.'
880 query = dns.message.make_query(name, 'TXT', 'IN')
881 response = dns.message.make_response(query)
882 # we prepare a large answer
883 content = ''
884 for i in range(31):
885 if len(content) > 0:
886 content = content + ' '
887 content = content + 'A' * 255
888 # pad up to 8192
889 content = content + ' ' + 'B' * 170
890
891 rrset = dns.rrset.from_text(name,
892 3600,
893 dns.rdataclass.IN,
894 dns.rdatatype.TXT,
895 content)
896 response.answer.append(rrset)
897 self.assertEqual(len(response.to_wire()), 8192)
898
899 # TCP should be OK
900 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
901 self.assertTrue(receivedQuery)
902 self.assertTrue(receivedResponse)
903 receivedQuery.id = query.id
904 self.assertEqual(query, receivedQuery)
905 self.assertEqual(receivedResponse, response)
906
907 # UDP should never get an answer, because dnsdist will not be able to get it from the backend
908 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
909 self.assertTrue(receivedQuery)
910 self.assertFalse(receivedResponse)
911 receivedQuery.id = query.id
912 self.assertEqual(query, receivedQuery)