]> git.ipfire.org Git - thirdparty/pdns.git/blob - regression-tests.dnsdist/test_Advanced.py
Merge pull request #12955 from rgacogne/ddist-fix-doc-codeblocks
[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
315 def testAdvancedGetLocalAddressOnAnyBind(self):
316 """
317 Advanced: Return CNAME containing the local address for an ANY bind
318 """
319 name = 'local-address-any.advanced.tests.powerdns.com.'
320 query = dns.message.make_query(name, 'A', 'IN')
321 # dnsdist set RA = RD for spoofed responses
322 query.flags &= ~dns.flags.RD
323
324 response = dns.message.make_response(query)
325 rrset = dns.rrset.from_text(name,
326 60,
327 dns.rdataclass.IN,
328 dns.rdatatype.CNAME,
329 'address-was-127-0-0-1.local-address-any.advanced.tests.powerdns.com.')
330 response.answer.append(rrset)
331
332 for method in ("sendUDPQuery", "sendTCPQuery"):
333 sender = getattr(self, method)
334 (_, receivedResponse) = sender(query, response=None, useQueue=False)
335 self.assertEqual(receivedResponse, response)
336
337 # now a bit more tricky, UDP-only IPv4
338 response = dns.message.make_response(query)
339 rrset = dns.rrset.from_text(name,
340 60,
341 dns.rdataclass.IN,
342 dns.rdatatype.CNAME,
343 'address-was-127-0-0-2.local-address-any.advanced.tests.powerdns.com.')
344 response.answer.append(rrset)
345 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
346 sock.settimeout(1.0)
347 sock.connect(('127.0.0.2', self._dnsDistPort))
348 try:
349 query = query.to_wire()
350 sock.send(query)
351 (data, remote) = sock.recvfrom(4096)
352 self.assertEquals(remote[0], '127.0.0.2')
353 except socket.timeout:
354 data = None
355
356 self.assertTrue(data)
357 receivedResponse = dns.message.from_wire(data)
358 self.assertEqual(receivedResponse, response)
359
360 def testAdvancedCheckSourceAddrOnAnyBind(self):
361 """
362 Advanced: Check the source address on responses for an ANY bind
363 """
364 name = 'source-addr-any.advanced.tests.powerdns.com.'
365 query = dns.message.make_query(name, 'A', 'IN')
366 # dnsdist set RA = RD for spoofed responses
367 query.flags &= ~dns.flags.RD
368
369 response = dns.message.make_response(query)
370 rrset = dns.rrset.from_text(name,
371 60,
372 dns.rdataclass.IN,
373 dns.rdatatype.A,
374 '192.0.2.42')
375 response.answer.append(rrset)
376
377 # a bit more tricky, UDP-only IPv4
378 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
379 sock.settimeout(1.0)
380 sock.connect(('127.0.0.2', self._dnsDistPort))
381 self._toResponderQueue.put(response, True, 1.0)
382 try:
383 data = query.to_wire()
384 sock.send(data)
385 (data, remote) = sock.recvfrom(4096)
386 self.assertEquals(remote[0], '127.0.0.2')
387 except socket.timeout:
388 data = None
389
390 self.assertTrue(data)
391 receivedResponse = dns.message.from_wire(data)
392 receivedQuery = self._fromResponderQueue.get(True, 1.0)
393 receivedQuery.id = query.id
394 self.assertEqual(receivedQuery, query)
395 self.assertEqual(receivedResponse, response)
396
397 if 'SKIP_IPV6_TESTS' in os.environ:
398 return
399
400 # a bit more tricky, UDP-only IPv6
401 sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
402 sock.settimeout(1.0)
403 sock.connect(('::1', self._dnsDistPort))
404 self._toResponderQueue.put(response, True, 1.0)
405 try:
406 data = query.to_wire()
407 sock.send(data)
408 (data, remote) = sock.recvfrom(4096)
409 self.assertEquals(remote[0], '::1')
410 except socket.timeout:
411 data = None
412
413 self.assertTrue(data)
414 receivedResponse = dns.message.from_wire(data)
415 receivedQuery = self._fromResponderQueue.get(True, 1.0)
416 receivedQuery.id = query.id
417 self.assertEqual(receivedQuery, query)
418 self.assertEqual(receivedResponse, response)
419
420 class TestAdvancedGetLocalAddressOnNonDefaultLoopbackBind(DNSDistTest):
421 # this one is tricky: on the loopback interface we cannot harvest the destination
422 # address, so we exercise a different code path when we bind on a different address
423 # than the default 127.0.0.1 one
424 _config_template = """
425 newServer{address="127.0.0.1:%s"}
426 addLocal('127.0.1.19:%d')
427 """
428 _config_params = ['_testServerPort', '_dnsDistPort']
429 _acl = ['127.0.0.1/32']
430 _skipListeningOnCL = True
431 _alternateListeningAddr = '127.0.1.19'
432 _alternateListeningPort = DNSDistTest._dnsDistPort
433
434 def testAdvancedCheckSourceAddrOnNonDefaultLoopbackBind(self):
435 """
436 Advanced: Check the source address used to reply on a non-default loopback bind
437 """
438 name = 'source-addr-non-default-loopback.advanced.tests.powerdns.com.'
439 query = dns.message.make_query(name, 'A', 'IN')
440 response = dns.message.make_response(query)
441 rrset = dns.rrset.from_text(name,
442 60,
443 dns.rdataclass.IN,
444 dns.rdatatype.A,
445 '192.0.2.42')
446 response.answer.append(rrset)
447
448 # a bit more tricky, UDP-only IPv4
449 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
450 sock.settimeout(1.0)
451 sock.connect(('127.0.1.19', self._dnsDistPort))
452 self._toResponderQueue.put(response, True, 1.0)
453 try:
454 data = query.to_wire()
455 sock.send(data)
456 (data, remote) = sock.recvfrom(4096)
457 self.assertEquals(remote[0], '127.0.1.19')
458 except socket.timeout:
459 data = None
460
461 self.assertTrue(data)
462 receivedResponse = dns.message.from_wire(data)
463 receivedQuery = self._fromResponderQueue.get(True, 1.0)
464 receivedQuery.id = query.id
465 self.assertEqual(receivedQuery, query)
466 self.assertEqual(receivedResponse, response)
467
468 class TestAdvancedAllowHeaderOnly(DNSDistTest):
469
470 _config_template = """
471 newServer{address="127.0.0.1:%s"}
472 setAllowEmptyResponse(true)
473 """
474
475 def testHeaderOnlyRefused(self):
476 """
477 Advanced: Header-only refused response
478 """
479 name = 'header-only-refused-response.advanced.tests.powerdns.com.'
480 query = dns.message.make_query(name, 'A', 'IN')
481 response = dns.message.make_response(query)
482 response.set_rcode(dns.rcode.REFUSED)
483 response.question = []
484
485 for method in ("sendUDPQuery", "sendTCPQuery"):
486 sender = getattr(self, method)
487 (receivedQuery, receivedResponse) = sender(query, response)
488 self.assertTrue(receivedQuery)
489 receivedQuery.id = query.id
490 self.assertEqual(query, receivedQuery)
491 self.assertEqual(receivedResponse, response)
492
493 def testHeaderOnlyNoErrorResponse(self):
494 """
495 Advanced: Header-only NoError response should be allowed
496 """
497 name = 'header-only-noerror-response.advanced.tests.powerdns.com.'
498 query = dns.message.make_query(name, 'A', 'IN')
499 response = dns.message.make_response(query)
500 response.question = []
501
502 for method in ("sendUDPQuery", "sendTCPQuery"):
503 sender = getattr(self, method)
504 (receivedQuery, receivedResponse) = sender(query, response)
505 self.assertTrue(receivedQuery)
506 receivedQuery.id = query.id
507 self.assertEqual(query, receivedQuery)
508 self.assertEqual(receivedResponse, response)
509
510 def testHeaderOnlyNXDResponse(self):
511 """
512 Advanced: Header-only NXD response should be allowed
513 """
514 name = 'header-only-nxd-response.advanced.tests.powerdns.com.'
515 query = dns.message.make_query(name, 'A', 'IN')
516 response = dns.message.make_response(query)
517 response.set_rcode(dns.rcode.NXDOMAIN)
518 response.question = []
519
520 for method in ("sendUDPQuery", "sendTCPQuery"):
521 sender = getattr(self, method)
522 (receivedQuery, receivedResponse) = sender(query, response)
523 self.assertTrue(receivedQuery)
524 receivedQuery.id = query.id
525 self.assertEqual(query, receivedQuery)
526 self.assertEqual(receivedResponse, response)
527
528 class TestAdvancedDropEmptyQueries(DNSDistTest):
529
530 _config_template = """
531 setDropEmptyQueries(true)
532 newServer{address="127.0.0.1:%s"}
533 """
534
535 def testAdvancedDropEmptyQueries(self):
536 """
537 Advanced: Drop empty queries
538 """
539 name = 'drop-empty-queries.advanced.tests.powerdns.com.'
540 query = dns.message.Message()
541
542 for method in ("sendUDPQuery", "sendTCPQuery"):
543 sender = getattr(self, method)
544 (_, receivedResponse) = sender(query, response=None, useQueue=False)
545 self.assertEqual(receivedResponse, None)
546
547 class TestProtocols(DNSDistTest):
548 _config_template = """
549 function checkUDP(dq)
550 if dq:getProtocol() ~= "Do53 UDP" then
551 return DNSAction.Spoof, '1.2.3.4'
552 end
553 return DNSAction.None
554 end
555
556 function checkTCP(dq)
557 if dq:getProtocol() ~= "Do53 TCP" then
558 return DNSAction.Spoof, '1.2.3.4'
559 end
560 return DNSAction.None
561 end
562
563 addAction("udp.protocols.advanced.tests.powerdns.com.", LuaAction(checkUDP))
564 addAction("tcp.protocols.advanced.tests.powerdns.com.", LuaAction(checkTCP))
565 newServer{address="127.0.0.1:%s"}
566 """
567
568 def testProtocolUDP(self):
569 """
570 Advanced: Test DNSQuestion.Protocol over UDP
571 """
572 name = 'udp.protocols.advanced.tests.powerdns.com.'
573 query = dns.message.make_query(name, 'A', 'IN')
574 response = dns.message.make_response(query)
575
576 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
577 receivedQuery.id = query.id
578 self.assertEqual(receivedQuery, query)
579 self.assertEqual(receivedResponse, response)
580
581 def testProtocolTCP(self):
582 """
583 Advanced: Test DNSQuestion.Protocol over TCP
584 """
585 name = 'tcp.protocols.advanced.tests.powerdns.com.'
586 query = dns.message.make_query(name, 'A', 'IN')
587 response = dns.message.make_response(query)
588
589 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
590 receivedQuery.id = query.id
591 self.assertEqual(receivedQuery, query)
592 self.assertEqual(receivedResponse, response)
593
594 class TestCustomMetrics(DNSDistTest):
595 _config_template = """
596 function custommetrics(dq)
597 initialCounter = getMetric("my-custom-counter")
598 initialGauge = getMetric("my-custom-gauge")
599 incMetric("my-custom-counter")
600 incMetric("my-custom-counter", 41)
601 setMetric("my-custom-gauge", initialGauge + 1.3)
602 if getMetric("my-custom-counter") ~= (initialCounter + 42) or getMetric("my-custom-gauge") ~= (initialGauge + 1.3) then
603 return DNSAction.Spoof, '1.2.3.5'
604 end
605 return DNSAction.Spoof, '4.3.2.1'
606 end
607
608 function declareNewMetric(dq)
609 if declareMetric("new-runtime-metric", "counter", "Metric declaration at runtime should work fine") then
610 return DNSAction.None
611 end
612 return DNSAction.Spoof, '1.2.3.4'
613 end
614
615 declareMetric("my-custom-counter", "counter", "Number of tests run")
616 declareMetric("my-custom-gauge", "gauge", "Temperature of the tests")
617 addAction("declare.metric.advanced.tests.powerdns.com.", LuaAction(declareNewMetric))
618 addAction("operations.metric.advanced.tests.powerdns.com.", LuaAction(custommetrics))
619 newServer{address="127.0.0.1:%s"}
620 """
621
622 def testDeclareAfterConfig(self):
623 """
624 Advanced: Test custom metric declaration after config done
625 """
626 name = 'declare.metric.advanced.tests.powerdns.com.'
627 query = dns.message.make_query(name, 'A', 'IN')
628 response = dns.message.make_response(query)
629
630 for method in ("sendUDPQuery", "sendTCPQuery"):
631 sender = getattr(self, method)
632 (receivedQuery, receivedResponse) = sender(query, response)
633 receivedQuery.id = query.id
634 self.assertEqual(receivedQuery, query)
635 self.assertEqual(receivedResponse, response)
636
637 def testMetricOperations(self):
638 """
639 Advanced: Test basic operations on custom metrics
640 """
641 name = 'operations.metric.advanced.tests.powerdns.com.'
642 query = dns.message.make_query(name, 'A', 'IN')
643 # dnsdist set RA = RD for spoofed responses
644 query.flags &= ~dns.flags.RD
645 response = dns.message.make_response(query)
646 rrset = dns.rrset.from_text(name,
647 60,
648 dns.rdataclass.IN,
649 dns.rdatatype.A,
650 '4.3.2.1')
651 response.answer.append(rrset)
652
653 for method in ("sendUDPQuery", "sendTCPQuery"):
654 sender = getattr(self, method)
655 (_, receivedResponse) = sender(query, response=None, useQueue=False)
656 self.assertEqual(receivedResponse, response)
657
658 class TestDNSQuestionTime(DNSDistTest):
659 _config_template = """
660 local queryTime = nil
661
662 function luaquery(dq)
663 if queryTime then
664 errlog('Error, the time variable is already set')
665 return DNSAction.Drop
666 end
667 queryTime = dq:getQueryTime()
668 local currentTime = getCurrentTime()
669 if queryTime.tv_sec > currentTime.tv_sec then
670 errlog('Error, query time is higher than current time')
671 return DNSAction.Drop
672 end
673 if queryTime.tv_sec == currentTime.tv_sec and queryTime.tv_nsec > currentTime.tv_nsec then
674 errlog('Error, query time NS is higher than current time')
675 return DNSAction.Drop
676 end
677 return DNSAction.None
678 end
679
680 function luaresponse(dr)
681 if queryTime == nil then
682 errlog('Error, the time variable is NOT set')
683 return DNSAction.Drop
684 end
685 local currentTime = getCurrentTime()
686 local queryTimeFromResponse = dr:getQueryTime()
687 if queryTime.tv_sec ~= queryTimeFromResponse.tv_sec or queryTime.tv_nsec ~= queryTimeFromResponse.tv_nsec then
688 errlog('Error, the query time in the response does NOT match the one from the query')
689 return DNSAction.Drop
690 end
691 if queryTime.tv_sec > currentTime.tv_sec then
692 errlog('Error, query time is higher than current time')
693 return DNSAction.Drop
694 end
695 if queryTime.tv_sec == currentTime.tv_sec and queryTime.tv_nsec > currentTime.tv_nsec then
696 errlog('Error, query time (NS) is higher than current time')
697 return DNSAction.Drop
698 end
699
700 queryTime = nil
701 return DNSAction.None
702 end
703
704 addAction(AllRule(), LuaAction(luaquery))
705 addResponseAction(AllRule(), LuaResponseAction(luaresponse))
706 newServer{address="127.0.0.1:%s"}
707 """
708
709 def testQueryTime(self):
710 """
711 Advanced: Test query time
712 """
713 name = 'query.time.advanced.tests.powerdns.com.'
714 query = dns.message.make_query(name, 'A', 'IN')
715 response = dns.message.make_response(query)
716 rrset = dns.rrset.from_text(name,
717 60,
718 dns.rdataclass.IN,
719 dns.rdatatype.A,
720 '4.3.2.1')
721 response.answer.append(rrset)
722
723 for method in ("sendUDPQuery", "sendTCPQuery"):
724 sender = getattr(self, method)
725 (receivedQuery, receivedResponse) = sender(query, response)
726 receivedQuery.id = query.id
727 self.assertEqual(receivedQuery, query)
728 self.assertEqual(receivedResponse, response)
729
730 class TestChangeName(DNSDistTest):
731 _config_template = """
732 local tagName = 'initial-name'
733 function luaChangeNamequery(dq)
734 dq:setTag(tagName, dq.qname:toString())
735 if not dq:changeName(newDNSName('changeName.advanced.tests.dnsdist.org')) then
736 errlog('Error rebasing the query')
737 return DNSAction.Drop
738 end
739 return DNSAction.None
740 end
741
742 function luaChangeNameresponse(dr)
743 local initialName = dr:getTag(tagName)
744 if not dr:changeName(newDNSName(initialName)) then
745 errlog('Error rebasing the response')
746 return DNSAction.Drop
747 end
748 return DNSAction.None
749 end
750
751 addAction('changeName.advanced.tests.powerdns.com', LuaAction(luaChangeNamequery))
752 addResponseAction('changeName.advanced.tests.dnsdist.org', LuaResponseAction(luaChangeNameresponse))
753 newServer{address="127.0.0.1:%s"}
754 """
755
756 def testChangeName(self):
757 """
758 Advanced: ChangeName the query name
759 """
760 name = 'changeName.advanced.tests.powerdns.com.'
761 query = dns.message.make_query(name, 'A', 'IN')
762
763 changedName = 'changeName.advanced.tests.dnsdist.org.'
764 changedQuery = dns.message.make_query(changedName, 'A', 'IN')
765 changedQuery.id = query.id
766
767 response = dns.message.make_response(changedQuery)
768 rrset = dns.rrset.from_text(changedName,
769 60,
770 dns.rdataclass.IN,
771 dns.rdatatype.A,
772 '4.3.2.1')
773 response.answer.append(rrset)
774 rrset = dns.rrset.from_text('sub.sub2.changeName.advanced.tests.dnsdist.org.',
775 60,
776 dns.rdataclass.IN,
777 dns.rdatatype.TXT,
778 'This text contains sub.sub2.changeName.advanced.tests.dnsdist.org.')
779 response.additional.append(rrset)
780
781 expectedResponse = dns.message.make_response(query)
782 rrset = dns.rrset.from_text(name,
783 60,
784 dns.rdataclass.IN,
785 dns.rdatatype.A,
786 '4.3.2.1')
787 expectedResponse.answer.append(rrset)
788 # we only rewrite records if the owner name matches the new target, nothing else
789 rrset = dns.rrset.from_text('sub.sub2.changeName.advanced.tests.dnsdist.org.',
790 60,
791 dns.rdataclass.IN,
792 dns.rdatatype.TXT,
793 'This text contains sub.sub2.changeName.advanced.tests.dnsdist.org.')
794 expectedResponse.additional.append(rrset)
795
796 for method in ("sendUDPQuery", "sendTCPQuery"):
797 sender = getattr(self, method)
798 (receivedQuery, receivedResponse) = sender(query, response)
799 receivedQuery.id = query.id
800 self.assertEqual(receivedQuery, changedQuery)
801 self.assertEqual(receivedResponse, expectedResponse)
802
803 class TestFlagsOnTimeout(DNSDistTest):
804
805 _consoleKey = DNSDistTest.generateConsoleKey()
806 _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
807 _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort']
808 _config_template = """
809 setKey("%s")
810 controlSocket("127.0.0.1:%s")
811 -- this server is not going to answer, resulting in a timeout
812 newServer{address="192.0.2.1:%s"}:setUp()
813 """
814
815 def testFlags(self):
816 """
817 Advanced: Test that we record the correct incoming flags on a timeout
818 """
819 name = 'timeout-flags.advanced.tests.powerdns.com.'
820
821 # first with RD=1
822 query = dns.message.make_query(name, 'A', 'IN')
823 query.id = 42
824 query.flags |= dns.flags.RD
825
826 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
827 self.assertFalse(receivedResponse)
828
829 # then with RD=0
830 query = dns.message.make_query(name, 'A', 'IN')
831 query.id = 84
832 query.flags &= ~dns.flags.RD
833
834 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
835 self.assertFalse(receivedResponse)
836
837 # make sure that the timeouts have been detected and recorded
838 for _ in range(6):
839 content = self.sendConsoleCommand("grepq('')")
840 lines = content.splitlines()
841 if len(lines) == 5:
842 break
843 # and otherwise sleep for a short while
844 time.sleep(1)
845
846 print(lines)
847 self.assertEqual(len(lines), 5)
848 # header line
849 self.assertIn('TC RD AA', lines[0])
850
851 queries = {}
852 timeouts = {}
853
854 for line in lines[1:]:
855 self.assertIn('DoUDP', line)
856 if 'T.O' in line:
857 queryID = int(line.split()[4])
858 timeouts[queryID] = line
859 else:
860 queryID = int(line.split()[3])
861 queries[queryID] = line
862 if queryID == 42:
863 self.assertIn('RD', line)
864 else:
865 self.assertNotIn('RD', line)
866
867 self.assertEqual(len(timeouts), 2)
868 self.assertEqual(len(queries), 2)