]> git.ipfire.org Git - thirdparty/pdns.git/blob - regression-tests.recursor-dnssec/test_Lua.py
Add use of ffi.C.pdns_postresolve_ffi_handle_add_record in test
[thirdparty/pdns.git] / regression-tests.recursor-dnssec / test_Lua.py
1 import clientsubnetoption
2 import cookiesoption
3 import dns
4 import os
5 import threading
6 import time
7
8 from twisted.internet.protocol import DatagramProtocol
9 from twisted.internet import reactor
10
11 from recursortests import RecursorTest
12
13 class GettagRecursorTest(RecursorTest):
14 _confdir = 'LuaGettag'
15 _config_template = """
16 log-common-errors=yes
17 gettag-needs-edns-options=yes
18 """
19 _lua_dns_script_file = """
20 function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp)
21
22 local tags = {}
23 local data = {}
24
25 -- make sure we can pass data around to the other hooks
26 data['canary'] = 'from-gettag'
27
28 -- test that the remote addr is valid
29 if remote:toString() ~= '127.0.0.1' then
30 pdnslog("invalid remote")
31 table.insert(tags, 'invalid remote '..remote:toString())
32 return 1, tags, data
33 end
34
35 -- test that the local addr is valid
36 if localip:toString() ~= '127.0.0.1' then
37 pdnslog("invalid local")
38 table.insert(tags, 'invalid local '..localip:toString())
39 return 1, tags, data
40 end
41
42 if not ednssubnet:empty() then
43 table.insert(tags, 'edns-subnet-'..ednssubnet:toString())
44 end
45
46 for k,v in pairs(ednsoptions) do
47 table.insert(tags, 'ednsoption-'..k..'-count-'..v:count())
48 local len = 0
49 local values = v:getValues()
50 for j,l in pairs(values) do
51 len = len + l:len()
52
53 -- check that the old interface (before 4.2.0) still works
54 if j == 0 then
55 if l:len() ~= v.size then
56 table.insert(tags, 'size obtained via the old edns option interface does not match')
57 end
58 value = v:getContent()
59 if value ~= l then
60 table.insert(tags, 'content obtained via the old edns option interface does not match')
61 end
62 end
63 end
64 table.insert(tags, 'ednsoption-'..k..'-total-len-'..len)
65 end
66
67 if tcp then
68 table.insert(tags, 'gettag-tcp')
69 end
70
71 -- test that tags are passed to other hooks
72 table.insert(tags, qname:toString())
73 table.insert(tags, 'gettag-qtype-'..qtype)
74
75 return 0, tags, data
76 end
77
78 function preresolve(dq)
79
80 -- test that we are getting the tags set by gettag()
81 -- and also getting the correct qname
82 local found = false
83 for _, tag in pairs(dq:getPolicyTags()) do
84 if dq.qname:equal(tag) then
85 found = true
86 end
87 dq:addAnswer(pdns.TXT, '"'..tag..'"')
88 end
89
90 if not found then
91 pdnslog("not valid tag found")
92 dq.rcode = pdns.REFUSED
93 return true
94 end
95
96 if dq.data['canary'] ~= 'from-gettag' then
97 pdnslog("did not get any data from gettag")
98 dq.rcode = pdns.REFUSED
99 return true
100 end
101
102 if dq.qtype == pdns.A then
103 dq:addAnswer(pdns.A, '192.0.2.1')
104 elseif dq.qtype == pdns.AAAA then
105 dq:addAnswer(pdns.AAAA, '2001:db8::1')
106 end
107
108 return true
109 end
110 """
111
112 @classmethod
113 def setUpClass(cls):
114
115 cls.setUpSockets()
116 confdir = os.path.join('configs', cls._confdir)
117 cls.createConfigDir(confdir)
118 cls.generateRecursorConfig(confdir)
119 cls.startRecursor(confdir, cls._recursorPort)
120
121 @classmethod
122 def tearDownClass(cls):
123 cls.tearDownRecursor()
124
125 def testA(self):
126 name = 'gettag.lua.'
127 expected = [
128 dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.1'),
129 dns.rrset.from_text_list(name, 0, dns.rdataclass.IN, 'TXT', [ name, 'gettag-qtype-1'])
130 ]
131 query = dns.message.make_query(name, 'A', want_dnssec=True)
132 query.flags |= dns.flags.CD
133 res = self.sendUDPQuery(query)
134 self.assertResponseMatches(query, expected, res)
135
136 def testTCPA(self):
137 name = 'gettag-tcpa.lua.'
138 expected = [
139 dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.1'),
140 dns.rrset.from_text_list(name, 0, dns.rdataclass.IN, 'TXT', [ name, 'gettag-qtype-1', 'gettag-tcp'])
141 ]
142 query = dns.message.make_query(name, 'A', want_dnssec=True)
143 query.flags |= dns.flags.CD
144 res = self.sendTCPQuery(query)
145 self.assertResponseMatches(query, expected, res)
146
147 def testAAAA(self):
148 name = 'gettag-aaaa.lua.'
149 expected = [
150 dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'AAAA', '2001:db8::1'),
151 dns.rrset.from_text_list(name, 0, dns.rdataclass.IN, 'TXT', [ name, 'gettag-qtype-28'])
152 ]
153 query = dns.message.make_query(name, 'AAAA', want_dnssec=True)
154 query.flags |= dns.flags.CD
155 res = self.sendUDPQuery(query)
156 self.assertResponseMatches(query, expected, res)
157
158 def testAAAA(self):
159 name = 'gettag-tcpaaaa.lua.'
160 expected = [
161 dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'AAAA', '2001:db8::1'),
162 dns.rrset.from_text_list(name, 0, dns.rdataclass.IN, 'TXT', [ name, 'gettag-qtype-28', 'gettag-tcp'])
163 ]
164 query = dns.message.make_query(name, 'AAAA', want_dnssec=True)
165 query.flags |= dns.flags.CD
166 res = self.sendTCPQuery(query)
167 self.assertResponseMatches(query, expected, res)
168
169 def testSubnet(self):
170 name = 'gettag-subnet.lua.'
171 subnet = '192.0.2.255'
172 subnetMask = 32
173 ecso = clientsubnetoption.ClientSubnetOption(subnet, subnetMask)
174 expected = [
175 dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.1'),
176 dns.rrset.from_text_list(name, 0, dns.rdataclass.IN, 'TXT', [name, 'gettag-qtype-1', 'edns-subnet-' + subnet + '/' + str(subnetMask),
177 'ednsoption-8-count-1', 'ednsoption-8-total-len-8']),
178 ]
179 query = dns.message.make_query(name, 'A', want_dnssec=True, options=[ecso])
180 query.flags |= dns.flags.CD
181 res = self.sendUDPQuery(query)
182 self.assertResponseMatches(query, expected, res)
183
184 def testEDNSOptions(self):
185 name = 'gettag-ednsoptions.lua.'
186 subnet = '192.0.2.255'
187 subnetMask = 32
188 ecso = clientsubnetoption.ClientSubnetOption(subnet, subnetMask)
189 eco1 = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
190 eco2 = cookiesoption.CookiesOption(b'deadc0de', b'deadc0de')
191
192 expected = [
193 dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.1'),
194 dns.rrset.from_text_list(name, 0, dns.rdataclass.IN, 'TXT', [name, 'gettag-qtype-1', 'edns-subnet-' + subnet + '/' + str(subnetMask),
195 'ednsoption-10-count-2', 'ednsoption-10-total-len-32',
196 'ednsoption-8-count-1', 'ednsoption-8-total-len-8'
197 ]),
198 ]
199 query = dns.message.make_query(name, 'A', want_dnssec=True, options=[eco1,ecso,eco2])
200 query.flags |= dns.flags.CD
201 res = self.sendUDPQuery(query)
202 self.assertResponseMatches(query, expected, res)
203
204 class GettagRecursorDistributesQueriesTest(GettagRecursorTest):
205 _confdir = 'LuaGettagDistributes'
206 _config_template = """
207 log-common-errors=yes
208 gettag-needs-edns-options=yes
209 pdns-distributes-queries=yes
210 threads=2
211 """
212
213 hooksReactorRunning = False
214
215 class UDPHooksResponder(DatagramProtocol):
216
217 def datagramReceived(self, datagram, address):
218 request = dns.message.from_wire(datagram)
219
220 response = dns.message.make_response(request)
221 response.flags |= dns.flags.AA
222
223 if request.question[0].name == dns.name.from_text('nxdomain.luahooks.example.'):
224 soa = dns.rrset.from_text('luahooks.example.', 86400, dns.rdataclass.IN, 'SOA', 'ns.luahooks.example. hostmaster.luahooks.example. 1 3600 3600 3600 1')
225 response.authority.append(soa)
226 response.set_rcode(dns.rcode.NXDOMAIN)
227
228 elif request.question[0].name == dns.name.from_text('nodata.luahooks.example.'):
229 soa = dns.rrset.from_text('luahooks.example.', 86400, dns.rdataclass.IN, 'SOA', 'ns.luahooks.example. hostmaster.luahooks.example. 1 3600 3600 3600 1')
230 response.authority.append(soa)
231
232 elif request.question[0].name == dns.name.from_text('postresolve.luahooks.example.'):
233 answer = dns.rrset.from_text('postresolve.luahooks.example.', 3600, dns.rdataclass.IN, 'A', '192.0.2.1')
234 response.answer.append(answer)
235
236 self.transport.write(response.to_wire(), address)
237
238 class LuaHooksRecursorTest(RecursorTest):
239 _confdir = 'LuaHooks'
240 _config_template = """
241 forward-zones=luahooks.example=%s.23
242 log-common-errors=yes
243 quiet=no
244 """ % (os.environ['PREFIX'])
245 _lua_dns_script_file = """
246
247 allowedips = newNMG()
248 allowedips:addMask("%s.0/24")
249
250 function ipfilter(remoteip, localip, dh)
251 -- allow only 127.0.0.1 and AD=0
252 if allowedips:match(remoteip) and not dh:getAD() then
253 return false
254 end
255
256 return true
257 end
258
259 function nodata(dq)
260 if dq.qtype == pdns.AAAA and dq.qname == newDN("nodata.luahooks.example.") then
261 dq:addAnswer(pdns.AAAA, "2001:DB8::1")
262 return true
263 end
264
265 return false
266 end
267
268 function nxdomain(dq)
269 if dq.qtype == pdns.A and dq.qname == newDN("nxdomain.luahooks.example.") then
270 dq.rcode=0
271 dq:addAnswer(pdns.A, "192.0.2.1")
272 return true
273 end
274
275 return false
276 end
277
278 function postresolve(dq)
279 if dq.qtype == pdns.A and dq.qname == newDN("postresolve.luahooks.example.") then
280 local records = dq:getRecords()
281 for k,v in pairs(records) do
282 if v.type == pdns.A and v:getContent() == "192.0.2.1" then
283 v:changeContent("192.0.2.42")
284 v.ttl=1
285 end
286 end
287 dq:setRecords(records)
288 return true
289 end
290
291 return false
292 end
293
294 function preoutquery(dq)
295 if dq.remoteaddr:equal(newCA("%s.23")) and dq.qname == newDN("preout.luahooks.example.") and dq.qtype == pdns.A then
296 dq.rcode = -3 -- "kill"
297 return true
298 end
299
300 return false
301 end
302
303 """ % (os.environ['PREFIX'], os.environ['PREFIX'])
304
305 @classmethod
306 def startResponders(cls):
307 global hooksReactorRunning
308 print("Launching responders..")
309
310 address = cls._PREFIX + '.23'
311 port = 53
312
313 if not hooksReactorRunning:
314 reactor.listenUDP(port, UDPHooksResponder(), interface=address)
315 hooksReactorRunning = True
316
317 if not reactor.running:
318 cls._UDPResponder = threading.Thread(name='UDP Hooks Responder', target=reactor.run, args=(False,))
319 cls._UDPResponder.setDaemon(True)
320 cls._UDPResponder.start()
321
322 @classmethod
323 def setUpClass(cls):
324 cls.setUpSockets()
325
326 cls.startResponders()
327
328 confdir = os.path.join('configs', cls._confdir)
329 cls.createConfigDir(confdir)
330
331 cls.generateRecursorConfig(confdir)
332 cls.startRecursor(confdir, cls._recursorPort)
333
334 print("Launching tests..")
335
336 @classmethod
337 def tearDownClass(cls):
338 cls.tearDownRecursor()
339
340 def testNoData(self):
341 expected = dns.rrset.from_text('nodata.luahooks.example.', 3600, dns.rdataclass.IN, 'AAAA', '2001:DB8::1')
342 query = dns.message.make_query('nodata.luahooks.example.', 'AAAA', 'IN')
343
344 for method in ("sendUDPQuery", "sendTCPQuery"):
345 sender = getattr(self, method)
346 res = sender(query)
347 self.assertRcodeEqual(res, dns.rcode.NOERROR)
348 self.assertRRsetInAnswer(res, expected)
349
350 def testVanillaNXD(self):
351 #expected = dns.rrset.from_text('nxdomain.luahooks.example.', 3600, dns.rdataclass.IN, 'A', '192.0.2.1')
352 query = dns.message.make_query('nxdomain.luahooks.example.', 'AAAA', 'IN')
353
354 for method in ("sendUDPQuery", "sendTCPQuery"):
355 sender = getattr(self, method)
356 res = sender(query)
357 self.assertRcodeEqual(res, dns.rcode.NXDOMAIN)
358
359 def testHookedNXD(self):
360 expected = dns.rrset.from_text('nxdomain.luahooks.example.', 3600, dns.rdataclass.IN, 'A', '192.0.2.1')
361 query = dns.message.make_query('nxdomain.luahooks.example.', 'A', 'IN')
362
363 for method in ("sendUDPQuery", "sendTCPQuery"):
364 sender = getattr(self, method)
365 res = sender(query)
366 self.assertRcodeEqual(res, dns.rcode.NOERROR)
367 self.assertRRsetInAnswer(res, expected)
368
369 def testPostResolve(self):
370 expected = dns.rrset.from_text('postresolve.luahooks.example.', 1, dns.rdataclass.IN, 'A', '192.0.2.42')
371 query = dns.message.make_query('postresolve.luahooks.example.', 'A', 'IN')
372
373 for method in ("sendUDPQuery", "sendTCPQuery"):
374 sender = getattr(self, method)
375 res = sender(query)
376 self.assertRcodeEqual(res, dns.rcode.NOERROR)
377 self.assertRRsetInAnswer(res, expected)
378 self.assertEqual(res.answer[0].ttl, 1)
379
380 def testIPFilterHeader(self):
381 query = dns.message.make_query('ipfilter.luahooks.example.', 'A', 'IN')
382 query.flags |= dns.flags.AD
383
384 for method in ("sendUDPQuery", "sendTCPQuery"):
385 sender = getattr(self, method)
386 res = sender(query)
387 self.assertEqual(res, None)
388
389 def testPreOutInterceptedQuery(self):
390 query = dns.message.make_query('preout.luahooks.example.', 'A', 'IN')
391
392 for method in ("sendUDPQuery", "sendTCPQuery"):
393 sender = getattr(self, method)
394 res = sender(query)
395 self.assertRcodeEqual(res, dns.rcode.SERVFAIL)
396
397 def testPreOutNotInterceptedQuery(self):
398 query = dns.message.make_query('preout.luahooks.example.', 'AAAA', 'IN')
399
400 for method in ("sendUDPQuery", "sendTCPQuery"):
401 sender = getattr(self, method)
402 res = sender(query)
403 self.assertRcodeEqual(res, dns.rcode.NOERROR)
404
405 class LuaHooksRecursorDistributesTest(LuaHooksRecursorTest):
406 _confdir = 'LuaHooksDistributes'
407 _config_template = """
408 forward-zones=luahooks.example=%s.23
409 log-common-errors=yes
410 pdns-distributes-queries=yes
411 threads=2
412 quiet=no
413 """ % (os.environ['PREFIX'])
414
415 class LuaDNS64Test(RecursorTest):
416 """Tests the dq.followupAction("getFakeAAAARecords")"""
417
418 _confdir = 'lua-dns64'
419 _config_template = """
420 """
421 _lua_dns_script_file = """
422 prefix = "2001:DB8:64::"
423
424 function nodata (dq)
425 if dq.qtype ~= pdns.AAAA then
426 return false
427 end -- only AAAA records
428
429 -- don't fake AAAA records if DNSSEC validation failed
430 if dq.validationState == pdns.validationstates.Bogus then
431 return false
432 end
433
434 dq.followupFunction = "getFakeAAAARecords"
435 dq.followupPrefix = prefix
436 dq.followupName = dq.qname
437 return true
438 end
439 """
440
441 def testAtoAAAA(self):
442 expected = [
443 dns.rrset.from_text('ns.secure.example.', 15, dns.rdataclass.IN, 'AAAA', '2001:db8:64::7f00:9')
444 ]
445 query = dns.message.make_query('ns.secure.example', 'AAAA')
446
447 for method in ("sendUDPQuery", "sendTCPQuery"):
448 sender = getattr(self, method)
449 res = sender(query)
450
451 self.assertRcodeEqual(res, dns.rcode.NOERROR)
452 self.assertEqual(len(res.answer), 1)
453 self.assertEqual(len(res.authority), 0)
454 self.assertResponseMatches(query, expected, res)
455
456 def testAtoCNAMEtoAAAA(self):
457 expected = [
458 dns.rrset.from_text('cname-to-insecure.secure.example.', 3600, dns.rdataclass.IN, 'CNAME', 'node1.insecure.example.'),
459 dns.rrset.from_text('node1.insecure.example.', 3600, dns.rdataclass.IN, 'AAAA', '2001:db8:64::c000:206')
460 ]
461 query = dns.message.make_query('cname-to-insecure.secure.example.', 'AAAA')
462
463 for method in ("sendUDPQuery", "sendTCPQuery"):
464 sender = getattr(self, method)
465 res = sender(query)
466
467 self.assertRcodeEqual(res, dns.rcode.NOERROR)
468 self.assertEqual(len(res.answer), 2)
469 self.assertEqual(len(res.authority), 0)
470 self.assertResponseMatches(query, expected, res)
471
472 class GettagFFIDNS64Test(RecursorTest):
473 """Tests the interaction between gettag_ffi, RPZ and DNS64:
474 - gettag_ffi will intercept the query and return a NODATA
475 - the RPZ zone will match the name, but the only type is A
476 - DNS64 should kick in, generating an AAAA
477 """
478
479 _confdir = 'gettagffi-rpz-dns64'
480 _config_template = """
481 dns64-prefix=64:ff9b::/96
482 """
483 _lua_config_file = """
484 rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz." })
485 """ % (_confdir)
486
487 _lua_dns_script_file = """
488 local ffi = require("ffi")
489
490 ffi.cdef[[
491 typedef struct pdns_ffi_param pdns_ffi_param_t;
492
493 void pdns_ffi_param_set_rcode(pdns_ffi_param_t* ref, int rcode);
494 ]]
495
496 function gettag_ffi(obj)
497 -- fake a NODATA (without SOA) no matter the query
498 ffi.C.pdns_ffi_param_set_rcode(obj, 0)
499 return {}
500 end
501 """
502 _auth_zones = []
503
504 @classmethod
505 def generateRecursorConfig(cls, confdir):
506 rpzFilePath = os.path.join(confdir, 'zone.rpz')
507 with open(rpzFilePath, 'w') as rpzZone:
508 rpzZone.write("""$ORIGIN zone.rpz.
509 @ 3600 IN SOA {soa}
510 dns64.test.powerdns.com.zone.rpz. 60 IN A 192.0.2.42
511 """.format(soa=cls._SOA))
512 super(GettagFFIDNS64Test, cls).generateRecursorConfig(confdir)
513
514 def testAtoAAAA(self):
515 expected = [
516 dns.rrset.from_text('dns64.test.powerdns.com.', 15, dns.rdataclass.IN, 'AAAA', '64:ff9b::c000:22a')
517 ]
518 query = dns.message.make_query('dns64.test.powerdns.com.', 'AAAA')
519
520 for method in ("sendUDPQuery", "sendTCPQuery"):
521 sender = getattr(self, method)
522 res = sender(query)
523
524 self.assertRcodeEqual(res, dns.rcode.NOERROR)
525 self.assertEqual(len(res.answer), 1)
526 self.assertEqual(len(res.authority), 0)
527 self.assertResponseMatches(query, expected, res)
528
529 class PDNSRandomTest(RecursorTest):
530 """Tests if pdnsrandom works"""
531
532 _confdir = 'pdnsrandom'
533 _config_template = """
534 """
535 _lua_dns_script_file = """
536 function preresolve (dq)
537 dq.rcode = pdns.NOERROR
538 dq:addAnswer(pdns.TXT, pdnsrandom())
539 return true
540 end
541 """
542
543 def testRandom(self):
544 query = dns.message.make_query('whatever.example.', 'TXT')
545
546 ans = set()
547
548 ret = self.sendUDPQuery(query)
549 ans.add(ret.answer[0][0])
550 ret = self.sendUDPQuery(query)
551 ans.add(ret.answer[0][0])
552
553 self.assertEqual(len(ans), 2)
554
555
556 class PDNSFeaturesTest(RecursorTest):
557 """Tests if pdns_features works"""
558
559 _confdir = 'pdnsfeatures'
560 _config_template = """
561 """
562 _lua_dns_script_file = """
563 function preresolve (dq)
564 dq.rcode = pdns.NOERROR
565 -- test pdns_features
566 if pdns_features['nonexistent'] ~= nil then
567 print('PDNSFeaturesTest: case 1')
568 dq.rcode = pdns.SERVFAIL
569 end
570 if not pdns_features['PR8001_devicename'] then
571 print('PDNSFeaturesTest: case 2')
572 dq.rcode = pdns.SERVFAIL
573 end
574 return true
575 end
576 """
577
578 def testFeatures(self):
579 query = dns.message.make_query('whatever.example.', 'TXT')
580 res = self.sendUDPQuery(query)
581
582 self.assertRcodeEqual(res, dns.rcode.NOERROR)
583
584 class PDNSGeneratingAnswerFromGettagTest(RecursorTest):
585 """Tests that we can generate answers from gettag"""
586
587 _confdir = 'gettaganswers'
588 _config_template = """
589 """
590 _lua_dns_script_file = """
591 local ffi = require("ffi")
592
593 ffi.cdef[[
594 typedef struct pdns_ffi_param pdns_ffi_param_t;
595
596 typedef enum
597 {
598 answer = 1,
599 authority = 2,
600 additional = 3
601 } pdns_record_place_t;
602
603 const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref);
604 void pdns_ffi_param_set_rcode(pdns_ffi_param_t* ref, int rcode);
605 bool pdns_ffi_param_add_record(pdns_ffi_param_t *ref, const char* name, uint16_t type, uint32_t ttl, const char* content, size_t contentSize, pdns_record_place_t place);
606 ]]
607
608 function gettag_ffi(obj)
609 local qname = ffi.string(ffi.C.pdns_ffi_param_get_qname(obj))
610 if qname == 'gettag-answers.powerdns.com' then
611 ffi.C.pdns_ffi_param_set_rcode(obj, 0)
612 ffi.C.pdns_ffi_param_add_record(obj, nil, pdns.A, 60, '192.0.2.1', 9, 1);
613 ffi.C.pdns_ffi_param_add_record(obj, "not-powerdns.com.", pdns.A, 60, '192.0.2.2', 9, 3);
614 end
615 end
616
617 function preresolve (dq)
618 -- refused everything if it comes that far
619 dq.rcode = pdns.REFUSED
620 return true
621 end
622 """
623
624 def testGettag(self):
625 expectedAnswerRecords = [
626 dns.rrset.from_text('gettag-answers.powerdns.com.', 60, dns.rdataclass.IN, 'A', '192.0.2.1'),
627 ]
628 expectedAdditionalRecords = [
629 dns.rrset.from_text('not-powerdns.com.', 60, dns.rdataclass.IN, 'A', '192.0.2.2'),
630 ]
631 query = dns.message.make_query('gettag-answers.powerdns.com.', 'A')
632 res = self.sendUDPQuery(query)
633
634 self.assertRcodeEqual(res, dns.rcode.NOERROR)
635 self.assertEqual(len(res.answer), 1)
636 self.assertEqual(len(res.authority), 0)
637 self.assertEqual(len(res.additional), 1)
638 self.assertEqual(res.answer, expectedAnswerRecords)
639 self.assertEqual(res.additional, expectedAdditionalRecords)
640
641 class PDNSValidationStatesTest(RecursorTest):
642 """Tests that we have access to the validation states from Lua"""
643
644 _confdir = 'validation-states-from-lua'
645 _config_template_default = """
646 dnssec=validate
647 daemon=no
648 trace=yes
649 packetcache-ttl=0
650 packetcache-servfail-ttl=0
651 max-cache-ttl=15
652 threads=1
653 loglevel=9
654 disable-syslog=yes
655 log-common-errors=yes
656 """
657 _roothints = None
658 _lua_config_file = """
659 """
660 _config_template = """
661 """
662 _lua_dns_script_file = """
663 function postresolve (dq)
664 if pdns.validationstates.Indeterminate == nil or
665 pdns.validationstates.BogusNoValidDNSKEY == nil or
666 pdns.validationstates.BogusInvalidDenial == nil or
667 pdns.validationstates.BogusUnableToGetDSs == nil or
668 pdns.validationstates.BogusUnableToGetDNSKEYs == nil or
669 pdns.validationstates.BogusSelfSignedDS == nil or
670 pdns.validationstates.BogusNoRRSIG == nil or
671 pdns.validationstates.BogusNoValidRRSIG == nil or
672 pdns.validationstates.BogusMissingNegativeIndication == nil or
673 pdns.validationstates.BogusSignatureNotYetValid == nil or
674 pdns.validationstates.BogusSignatureExpired == nil or
675 pdns.validationstates.BogusUnsupportedDNSKEYAlgo == nil or
676 pdns.validationstates.BogusUnsupportedDSDigestType == nil or
677 pdns.validationstates.BogusNoZoneKeyBitSet == nil or
678 pdns.validationstates.BogusRevokedDNSKEY == nil or
679 pdns.validationstates.BogusInvalidDNSKEYProtocol == nil or
680 pdns.validationstates.Insecure == nil or
681 pdns.validationstates.Secure == nil or
682 pdns.validationstates.Bogus == nil then
683 -- refused if at least one state is not available
684 pdnslog('Missing DNSSEC validation state!')
685 dq.rcode = pdns.REFUSED
686 return true
687 end
688 if dq.qname == newDN('brokendnssec.net.') then
689 if dq.validationState ~= pdns.validationstates.Bogus then
690 pdnslog('DNSSEC validation state should be Bogus!')
691 dq.rcode = pdns.REFUSED
692 return true
693 end
694 if dq.detailedValidationState ~= pdns.validationstates.BogusNoRRSIG then
695 pdnslog('DNSSEC detailed validation state is not valid, got '..dq.detailedValidationState..' and expected '..pdns.validationstates.BogusNoRRSIG)
696 dq.rcode = pdns.REFUSED
697 return true
698 end
699 if not isValidationStateBogus(dq.detailedValidationState) then
700 pdnslog('DNSSEC detailed validation state should be Bogus and is not!')
701 dq.rcode = pdns.REFUSED
702 return true
703 end
704 end
705 return false
706 end
707 """
708
709 def testValidationBogus(self):
710 query = dns.message.make_query('brokendnssec.net.', 'A')
711 res = self.sendUDPQuery(query)
712 self.assertRcodeEqual(res, dns.rcode.SERVFAIL)
713 self.assertEqual(len(res.answer), 0)
714 self.assertEqual(len(res.authority), 0)
715
716 class PolicyEventFilterOnFollowUpTest(RecursorTest):
717 """Tests the interaction between RPZ and followup queries (dns64, folliwCNAME)
718 """
719
720 _confdir = 'policyeventfilter-followup'
721 _config_template = """
722 """
723 _lua_config_file = """
724 rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz." })
725 """ % (_confdir)
726
727 _lua_dns_script_file = """
728 function preresolve(dq)
729 dq:addAnswer(pdns.CNAME, "secure.example.")
730 dq.followupFunction="followCNAMERecords"
731 dq.rcode = pdns.NOERROR
732 return true
733 end
734
735 function policyEventFilter(event)
736 event.appliedPolicy.policyKind = pdns.policykinds.NoAction
737 return true
738 end
739 """
740
741 @classmethod
742 def generateRecursorConfig(cls, confdir):
743 rpzFilePath = os.path.join(confdir, 'zone.rpz')
744 with open(rpzFilePath, 'w') as rpzZone:
745 rpzZone.write("""$ORIGIN zone.rpz.
746 @ 3600 IN SOA {soa}
747 secure.example.zone.rpz. 60 IN A 192.0.2.42
748 """.format(soa=cls._SOA))
749 super(PolicyEventFilterOnFollowUpTest, cls).generateRecursorConfig(confdir)
750
751 def testA(self):
752 expected = [
753 dns.rrset.from_text('policyeventfilter-followup.test.powerdns.com.', 15, dns.rdataclass.IN, 'CNAME', 'secure.example.'),
754 dns.rrset.from_text('secure.example.', 15, dns.rdataclass.IN, 'A', '192.0.2.17')
755 ]
756 query = dns.message.make_query('policyeventfilter-followup.test.powerdns.com.', 'A')
757
758 for method in ("sendUDPQuery", "sendTCPQuery"):
759 sender = getattr(self, method)
760 res = sender(query)
761
762 self.assertRcodeEqual(res, dns.rcode.NOERROR)
763 self.assertEqual(len(res.answer), 2)
764 self.assertEqual(len(res.authority), 0)
765 self.assertResponseMatches(query, expected, res)
766
767 class LuaPostResolveFFITest(RecursorTest):
768 """Tests postresolve_ffi interface"""
769
770 _confdir = 'LuaPostResolveFFITest'
771 _config_template = """
772 """
773 _lua_dns_script_file = """
774 local ffi = require("ffi")
775
776 ffi.cdef[[
777 typedef struct pdns_postresolve_ffi_handle pdns_postresolve_ffi_handle_t;
778
779 typedef enum
780 {
781 pdns_record_place_answer = 1,
782 pdns_record_place_authority = 2,
783 pdns_record_place_additional = 3
784 } pdns_record_place_t;
785
786 typedef enum
787 {
788 pdns_policy_kind_noaction = 0,
789 pdns_policy_kind_drop = 1,
790 pdns_policy_kind_nxdomain = 2,
791 pdns_policy_kind_nodata = 3,
792 pdns_policy_kind_truncate = 4,
793 pdns_policy_kind_custom = 5
794 } pdns_policy_kind_t;
795
796 typedef struct pdns_ffi_record {
797 const char* name;
798 const char* content;
799 const size_t content_len;
800 uint32_t ttl;
801 pdns_record_place_t place;
802 uint16_t type;
803 } pdns_ffi_record_t;
804
805 const char* pdns_postresolve_ffi_handle_get_qname(pdns_postresolve_ffi_handle_t* ref);
806 uint16_t pdns_postresolve_ffi_handle_get_qtype(const pdns_postresolve_ffi_handle_t* ref);
807 uint16_t pdns_postresolve_ffi_handle_get_rcode(const pdns_postresolve_ffi_handle_t* ref);
808 void pdns_postresolve_ffi_handle_set_appliedpolicy_kind(pdns_postresolve_ffi_handle_t* ref, pdns_policy_kind_t kind);
809 bool pdns_postresolve_ffi_handle_get_record(pdns_postresolve_ffi_handle_t* ref, unsigned int i, pdns_ffi_record_t *record, bool raw);
810 bool pdns_postresolve_ffi_handle_set_record(pdns_postresolve_ffi_handle_t* ref, unsigned int i, const char* content, size_t contentLen, bool raw);
811 void pdns_postresolve_ffi_handle_clear_records(pdns_postresolve_ffi_handle_t* ref);
812 bool pdns_postresolve_ffi_handle_add_record(pdns_postresolve_ffi_handle_t* ref, const char* name, uint16_t type, uint32_t ttl, const char* content, size_t contentLen, pdns_record_place_t place, bool raw);
813 const char* pdns_postresolve_ffi_handle_get_authip(pdns_postresolve_ffi_handle_t* ref);
814 void pdns_postresolve_ffi_handle_get_authip_raw(pdns_postresolve_ffi_handle_t* ref, const void** addr, size_t* addrSize);
815 ]]
816
817
818 function tohex(str)
819 return (str:gsub('.', function (c) return string.format('%02X', string.byte(c)) end))
820 end
821 function toA(str)
822 return (str:gsub('.', function (c) return string.format('%d.', string.byte(c)) end))
823 end
824
825 function postresolve_ffi(ref)
826 local qname = ffi.string(ffi.C.pdns_postresolve_ffi_handle_get_qname(ref))
827 local qtype = ffi.C.pdns_postresolve_ffi_handle_get_qtype(ref)
828
829 if qname == "example" and qtype == pdns.SOA
830 then
831 local addr = ffi.string(ffi.C.pdns_postresolve_ffi_handle_get_authip(ref))
832 pdnslog("XXXX "..addr)
833 if string.sub(addr, -3) ~= ".10" and string.sub(addr, -3) ~= ".18"
834 then
835 -- signal error by clearing all
836 ffi.C.pdns_postresolve_ffi_handle_clear_records(ref)
837 end
838 -- as a bonug check from which auth the data came
839 local addr = ffi.new("const void *[1]")
840 local len = ffi.new("size_t [1]")
841 ffi.C.pdns_postresolve_ffi_handle_get_authip_raw(ref, addr, len)
842 local a = ffi.string(addr[0], len[0])
843 if string.byte(a, 4) ~= 10 and string.byte(a,4) ~= 18
844 then
845 -- signal error by clearing all
846 ffi.C.pdns_postresolve_ffi_handle_clear_records(ref)
847 end
848
849 ffi.C.pdns_postresolve_ffi_handle_set_appliedpolicy_kind(ref, "pdns_policy_kind_noaction")
850 return true
851 end
852 if qname == "example" and qtype == pdns.TXT
853 then
854 ffi.C.pdns_postresolve_ffi_handle_set_appliedpolicy_kind(ref, "pdns_policy_kind_drop")
855 return true
856 end
857 if qname == "ns1.example" and qtype == pdns.A
858 then
859 ffi.C.pdns_postresolve_ffi_handle_set_appliedpolicy_kind(ref, "pdns_policy_kind_nxdomain")
860 return true
861 end
862 if qname == "ns1.example" and qtype == pdns.AAAA
863 then
864 ffi.C.pdns_postresolve_ffi_handle_set_appliedpolicy_kind(ref, "pdns_policy_kind_nodata")
865 return true
866 end
867 if qname == "ns2.example" and qtype == pdns.A
868 then
869 ffi.C.pdns_postresolve_ffi_handle_set_appliedpolicy_kind(ref, "pdns_policy_kind_truncate")
870 return true
871 end
872
873 if qname ~= "postresolve_ffi.example" or (qtype ~= pdns.A and qtype ~= pdns.AAAA)
874 then
875 return false
876 end
877
878 local record = ffi.new("pdns_ffi_record_t")
879 local i = 0
880
881 while ffi.C.pdns_postresolve_ffi_handle_get_record(ref, i, record, false)
882 do
883 local content = ffi.string(record.content, record.content_len)
884 local name = ffi.string(record.name)
885 if name ~= "postresolve_ffi.example"
886 then
887 return false
888 end
889 if record.type == pdns.A and content == "1.2.3.4"
890 then
891 ffi.C.pdns_postresolve_ffi_handle_set_record(ref, i, "0.1.2.3", 7, false);
892 ffi.C.pdns_postresolve_ffi_handle_add_record(ref, "add."..name, pdns.A, record.ttl, '4.5.6.7', 7, "pdns_record_place_additional", false);
893 elseif record.type == pdns.AAAA and content == "::1"
894 then
895 ffi.C.pdns_postresolve_ffi_handle_set_record(ref, i, "\\0\\1\\2\\3\\4\\5\\6\\7\\8\\9\\10\\11\\12\\13\\14\\15", 16, true);
896 end
897 i = i + 1
898 end
899 return true
900 end
901 """
902
903 def testNOACTION(self):
904 """ postresolve_ffi: test that we can do a NOACTION for a name and type combo"""
905 query = dns.message.make_query('example', 'SOA')
906 res = self.sendUDPQuery(query)
907 self.assertRcodeEqual(res, dns.rcode.NOERROR)
908 self.assertEqual(len(res.answer), 1)
909
910 def testDROP(self):
911 """ postresolve_ffi: test that we can do a DROP for a name and type combo"""
912 query = dns.message.make_query('example', 'TXT')
913 res = self.sendUDPQuery(query)
914 self.assertEquals(res, None)
915
916 def testNXDOMAIN(self):
917 """ postresolve_ffi: test that we can return a NXDOMAIN for a name and type combo"""
918 query = dns.message.make_query('ns1.example', 'A')
919 res = self.sendUDPQuery(query)
920 self.assertRcodeEqual(res, dns.rcode.NXDOMAIN)
921 self.assertEqual(len(res.answer), 0)
922
923 def testNODATA(self):
924 """ postresolve_ffi: test that we can return a NODATA for a name and type combo"""
925 query = dns.message.make_query('ns1.example', 'AAAA')
926 res = self.sendUDPQuery(query)
927 self.assertRcodeEqual(res, dns.rcode.NOERROR)
928 self.assertEqual(len(res.answer), 0)
929
930 def testTRUNCATE(self):
931 """ postresolve_ffi: test that we can return a truncated for a name and type combo"""
932 query = dns.message.make_query('ns2.example', 'A')
933 res = self.sendUDPQuery(query)
934 self.assertRcodeEqual(res, dns.rcode.NOERROR)
935 self.assertEqual(len(res.answer), 0)
936 self.assertMessageHasFlags(res, ['QR', 'TC', 'RD', 'RA'])
937
938
939 def testModifyA(self):
940 """postresolve_ffi: test that we can modify A answers"""
941 expectedAnswerRecords = [
942 dns.rrset.from_text('postresolve_ffi.example.', 60, dns.rdataclass.IN, 'A', '0.1.2.3', '1.2.3.5'),
943 ]
944 expectedAdditionalRecords = [
945 dns.rrset.from_text('add.postresolve_ffi.example.', 60, dns.rdataclass.IN, 'A', '4.5.6.7'),
946 ]
947
948 query = dns.message.make_query('postresolve_ffi.example', 'A')
949 res = self.sendUDPQuery(query)
950 self.assertRcodeEqual(res, dns.rcode.NOERROR)
951 self.assertEqual(len(res.answer), 1)
952 self.assertEqual(len(res.authority), 0)
953 self.assertEqual(len(res.additional), 1)
954 self.assertEqual(res.answer, expectedAnswerRecords)
955 self.assertEqual(res.additional, expectedAdditionalRecords)
956
957 def testModifyAAAA(self):
958 """postresolve_ffi: test that we can modify AAAA answers"""
959 expectedAnswerRecords = [
960 dns.rrset.from_text('postresolve_ffi.example.', 60, dns.rdataclass.IN, 'AAAA', '1:203:405:607:809:a0b:c0d:e0f', '::2'),
961 ]
962 query = dns.message.make_query('postresolve_ffi.example', 'AAAA')
963 res = self.sendUDPQuery(query)
964 self.assertRcodeEqual(res, dns.rcode.NOERROR)
965 self.assertEqual(len(res.answer), 1)
966 self.assertEqual(len(res.authority), 0)
967 self.assertEqual(len(res.additional), 0)
968 self.assertEqual(res.answer, expectedAnswerRecords)