1 import clientsubnetoption
8 from twisted
.internet
.protocol
import DatagramProtocol
9 from twisted
.internet
import reactor
11 from recursortests
import RecursorTest
13 class GettagRecursorTest(RecursorTest
):
14 _confdir
= 'LuaGettag'
15 _config_template
= """
17 gettag-needs-edns-options=yes
19 _lua_dns_script_file
= """
20 function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp)
25 -- make sure we can pass data around to the other hooks
26 data['canary'] = 'from-gettag'
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())
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())
42 if not ednssubnet:empty() then
43 table.insert(tags, 'edns-subnet-'..ednssubnet:toString())
46 for k,v in pairs(ednsoptions) do
47 table.insert(tags, 'ednsoption-'..k..'-count-'..v:count())
49 local values = v:getValues()
50 for j,l in pairs(values) do
53 -- check that the old interface (before 4.2.0) still works
55 if l:len() ~= v.size then
56 table.insert(tags, 'size obtained via the old edns option interface does not match')
58 value = v:getContent()
60 table.insert(tags, 'content obtained via the old edns option interface does not match')
64 table.insert(tags, 'ednsoption-'..k..'-total-len-'..len)
68 table.insert(tags, 'gettag-tcp')
71 -- test that tags are passed to other hooks
72 table.insert(tags, qname:toString())
73 table.insert(tags, 'gettag-qtype-'..qtype)
78 function preresolve(dq)
80 -- test that we are getting the tags set by gettag()
81 -- and also getting the correct qname
83 for _, tag in pairs(dq:getPolicyTags()) do
84 if dq.qname:equal(tag) then
87 dq:addAnswer(pdns.TXT, '"'..tag..'"')
91 pdnslog("not valid tag found")
92 dq.rcode = pdns.REFUSED
96 if dq.data['canary'] ~= 'from-gettag' then
97 pdnslog("did not get any data from gettag")
98 dq.rcode = pdns.REFUSED
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')
116 confdir
= os
.path
.join('configs', cls
._confdir
)
117 cls
.createConfigDir(confdir
)
118 cls
.generateRecursorConfig(confdir
)
119 cls
.startRecursor(confdir
, cls
._recursorPort
)
122 def tearDownClass(cls
):
123 cls
.tearDownRecursor()
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'])
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
)
137 name
= 'gettag-tcpa.lua.'
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'])
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
)
148 name
= 'gettag-aaaa.lua.'
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'])
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
)
159 name
= 'gettag-tcpaaaa.lua.'
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'])
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
)
169 def testSubnet(self
):
170 name
= 'gettag-subnet.lua.'
171 subnet
= '192.0.2.255'
173 ecso
= clientsubnetoption
.ClientSubnetOption(subnet
, subnetMask
)
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']),
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
)
184 def testEDNSOptions(self
):
185 name
= 'gettag-ednsoptions.lua.'
186 subnet
= '192.0.2.255'
188 ecso
= clientsubnetoption
.ClientSubnetOption(subnet
, subnetMask
)
189 eco1
= cookiesoption
.CookiesOption(b
'deadbeef', b
'deadbeef')
190 eco2
= cookiesoption
.CookiesOption(b
'deadc0de', b
'deadc0de')
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'
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
)
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
213 hooksReactorRunning
= False
215 class UDPHooksResponder(DatagramProtocol
):
217 def datagramReceived(self
, datagram
, address
):
218 request
= dns
.message
.from_wire(datagram
)
220 response
= dns
.message
.make_response(request
)
221 response
.flags |
= dns
.flags
.AA
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
)
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
)
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
)
236 self
.transport
.write(response
.to_wire(), address
)
238 class LuaHooksRecursorTest(RecursorTest
):
239 _confdir
= 'LuaHooks'
240 _config_template
= """
241 forward-zones=luahooks.example=%s.23
242 log-common-errors=yes
244 """ % (os
.environ
['PREFIX'])
245 _lua_dns_script_file
= """
247 allowedips = newNMG()
248 allowedips:addMask("%s.0/24")
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
260 if dq.qtype == pdns.AAAA and dq.qname == newDN("nodata.luahooks.example.") then
261 dq:addAnswer(pdns.AAAA, "2001:DB8::1")
268 function nxdomain(dq)
269 if dq.qtype == pdns.A and dq.qname == newDN("nxdomain.luahooks.example.") then
271 dq:addAnswer(pdns.A, "192.0.2.1")
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")
287 dq:setRecords(records)
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"
303 """ % (os
.environ
['PREFIX'], os
.environ
['PREFIX'])
306 def startResponders(cls
):
307 global hooksReactorRunning
308 print("Launching responders..")
310 address
= cls
._PREFIX
+ '.23'
313 if not hooksReactorRunning
:
314 reactor
.listenUDP(port
, UDPHooksResponder(), interface
=address
)
315 hooksReactorRunning
= True
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()
326 cls
.startResponders()
328 confdir
= os
.path
.join('configs', cls
._confdir
)
329 cls
.createConfigDir(confdir
)
331 cls
.generateRecursorConfig(confdir
)
332 cls
.startRecursor(confdir
, cls
._recursorPort
)
334 print("Launching tests..")
337 def tearDownClass(cls
):
338 cls
.tearDownRecursor()
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')
344 for method
in ("sendUDPQuery", "sendTCPQuery"):
345 sender
= getattr(self
, method
)
347 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
348 self
.assertRRsetInAnswer(res
, expected
)
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')
354 for method
in ("sendUDPQuery", "sendTCPQuery"):
355 sender
= getattr(self
, method
)
357 self
.assertRcodeEqual(res
, dns
.rcode
.NXDOMAIN
)
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')
363 for method
in ("sendUDPQuery", "sendTCPQuery"):
364 sender
= getattr(self
, method
)
366 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
367 self
.assertRRsetInAnswer(res
, expected
)
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')
373 for method
in ("sendUDPQuery", "sendTCPQuery"):
374 sender
= getattr(self
, method
)
376 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
377 self
.assertRRsetInAnswer(res
, expected
)
378 self
.assertEqual(res
.answer
[0].ttl
, 1)
380 def testIPFilterHeader(self
):
381 query
= dns
.message
.make_query('ipfilter.luahooks.example.', 'A', 'IN')
382 query
.flags |
= dns
.flags
.AD
384 for method
in ("sendUDPQuery", "sendTCPQuery"):
385 sender
= getattr(self
, method
)
387 self
.assertEqual(res
, None)
389 def testPreOutInterceptedQuery(self
):
390 query
= dns
.message
.make_query('preout.luahooks.example.', 'A', 'IN')
392 for method
in ("sendUDPQuery", "sendTCPQuery"):
393 sender
= getattr(self
, method
)
395 self
.assertRcodeEqual(res
, dns
.rcode
.SERVFAIL
)
397 def testPreOutNotInterceptedQuery(self
):
398 query
= dns
.message
.make_query('preout.luahooks.example.', 'AAAA', 'IN')
400 for method
in ("sendUDPQuery", "sendTCPQuery"):
401 sender
= getattr(self
, method
)
403 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
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
413 """ % (os
.environ
['PREFIX'])
415 class LuaDNS64Test(RecursorTest
):
416 """Tests the dq.followupAction("getFakeAAAARecords")"""
418 _confdir
= 'lua-dns64'
419 _config_template
= """
421 _lua_dns_script_file
= """
422 prefix = "2001:DB8:64::"
425 if dq.qtype ~= pdns.AAAA then
427 end -- only AAAA records
429 -- don't fake AAAA records if DNSSEC validation failed
430 if dq.validationState == pdns.validationstates.Bogus then
434 dq.followupFunction = "getFakeAAAARecords"
435 dq.followupPrefix = prefix
436 dq.followupName = dq.qname
441 def testAtoAAAA(self
):
443 dns
.rrset
.from_text('ns.secure.example.', 15, dns
.rdataclass
.IN
, 'AAAA', '2001:db8:64::7f00:9')
445 query
= dns
.message
.make_query('ns.secure.example', 'AAAA')
447 for method
in ("sendUDPQuery", "sendTCPQuery"):
448 sender
= getattr(self
, method
)
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
)
456 def testAtoCNAMEtoAAAA(self
):
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')
461 query
= dns
.message
.make_query('cname-to-insecure.secure.example.', 'AAAA')
463 for method
in ("sendUDPQuery", "sendTCPQuery"):
464 sender
= getattr(self
, method
)
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
)
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
479 _confdir
= 'gettagffi-rpz-dns64'
480 _config_template
= """
481 dns64-prefix=64:ff9b::/96
483 _lua_config_file
= """
484 rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz." })
487 _lua_dns_script_file
= """
488 local ffi = require("ffi")
491 typedef struct pdns_ffi_param pdns_ffi_param_t;
493 void pdns_ffi_param_set_rcode(pdns_ffi_param_t* ref, int rcode);
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)
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.
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
)
514 def testAtoAAAA(self
):
516 dns
.rrset
.from_text('dns64.test.powerdns.com.', 15, dns
.rdataclass
.IN
, 'AAAA', '64:ff9b::c000:22a')
518 query
= dns
.message
.make_query('dns64.test.powerdns.com.', 'AAAA')
520 for method
in ("sendUDPQuery", "sendTCPQuery"):
521 sender
= getattr(self
, method
)
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
)
529 class PDNSRandomTest(RecursorTest
):
530 """Tests if pdnsrandom works"""
532 _confdir
= 'pdnsrandom'
533 _config_template
= """
535 _lua_dns_script_file
= """
536 function preresolve (dq)
537 dq.rcode = pdns.NOERROR
538 dq:addAnswer(pdns.TXT, pdnsrandom())
543 def testRandom(self
):
544 query
= dns
.message
.make_query('whatever.example.', 'TXT')
548 ret
= self
.sendUDPQuery(query
)
549 ans
.add(ret
.answer
[0][0])
550 ret
= self
.sendUDPQuery(query
)
551 ans
.add(ret
.answer
[0][0])
553 self
.assertEqual(len(ans
), 2)
556 class PDNSFeaturesTest(RecursorTest
):
557 """Tests if pdns_features works"""
559 _confdir
= 'pdnsfeatures'
560 _config_template
= """
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
570 if not pdns_features['PR8001_devicename'] then
571 print('PDNSFeaturesTest: case 2')
572 dq.rcode = pdns.SERVFAIL
578 def testFeatures(self
):
579 query
= dns
.message
.make_query('whatever.example.', 'TXT')
580 res
= self
.sendUDPQuery(query
)
582 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
584 class PDNSGeneratingAnswerFromGettagTest(RecursorTest
):
585 """Tests that we can generate answers from gettag"""
587 _confdir
= 'gettaganswers'
588 _config_template
= """
590 _lua_dns_script_file
= """
591 local ffi = require("ffi")
594 typedef struct pdns_ffi_param pdns_ffi_param_t;
601 } pdns_record_place_t;
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);
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);
617 function preresolve (dq)
618 -- refused everything if it comes that far
619 dq.rcode = pdns.REFUSED
624 def testGettag(self
):
625 expectedAnswerRecords
= [
626 dns
.rrset
.from_text('gettag-answers.powerdns.com.', 60, dns
.rdataclass
.IN
, 'A', '192.0.2.1'),
628 expectedAdditionalRecords
= [
629 dns
.rrset
.from_text('not-powerdns.com.', 60, dns
.rdataclass
.IN
, 'A', '192.0.2.2'),
631 query
= dns
.message
.make_query('gettag-answers.powerdns.com.', 'A')
632 res
= self
.sendUDPQuery(query
)
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
)
641 class PDNSValidationStatesTest(RecursorTest
):
642 """Tests that we have access to the validation states from Lua"""
644 _confdir
= 'validation-states-from-lua'
645 _config_template_default
= """
650 packetcache-servfail-ttl=0
655 log-common-errors=yes
658 _lua_config_file
= """
660 _config_template
= """
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
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
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
699 if not isValidationStateBogus(dq.detailedValidationState) then
700 pdnslog('DNSSEC detailed validation state should be Bogus and is not!')
701 dq.rcode = pdns.REFUSED
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)
716 class PolicyEventFilterOnFollowUpTest(RecursorTest
):
717 """Tests the interaction between RPZ and followup queries (dns64, folliwCNAME)
720 _confdir
= 'policyeventfilter-followup'
721 _config_template
= """
723 _lua_config_file
= """
724 rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz." })
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
735 function policyEventFilter(event)
736 event.appliedPolicy.policyKind = pdns.policykinds.NoAction
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.
747 secure.example.zone.rpz. 60 IN A 192.0.2.42
748 """.format(soa
=cls
._SOA
))
749 super(PolicyEventFilterOnFollowUpTest
, cls
).generateRecursorConfig(confdir
)
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')
756 query
= dns
.message
.make_query('policyeventfilter-followup.test.powerdns.com.', 'A')
758 for method
in ("sendUDPQuery", "sendTCPQuery"):
759 sender
= getattr(self
, method
)
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
)
767 class LuaPostResolveFFITest(RecursorTest
):
768 """Tests postresolve_ffi interface"""
770 _confdir
= 'LuaPostResolveFFITest'
771 _config_template
= """
773 _lua_dns_script_file
= """
774 local ffi = require("ffi")
777 typedef struct pdns_postresolve_ffi_handle pdns_postresolve_ffi_handle_t;
781 pdns_record_place_answer = 1,
782 pdns_record_place_authority = 2,
783 pdns_record_place_additional = 3
784 } pdns_record_place_t;
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;
796 typedef struct pdns_ffi_record {
799 const size_t content_len;
801 pdns_record_place_t place;
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);
819 return (str:gsub('.', function (c) return string.format('%02X', string.byte(c)) end))
822 return (str:gsub('.', function (c) return string.format('%d.', string.byte(c)) end))
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)
829 if qname == "example" and qtype == pdns.SOA
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"
835 -- signal error by clearing all
836 ffi.C.pdns_postresolve_ffi_handle_clear_records(ref)
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
845 -- signal error by clearing all
846 ffi.C.pdns_postresolve_ffi_handle_clear_records(ref)
849 ffi.C.pdns_postresolve_ffi_handle_set_appliedpolicy_kind(ref, "pdns_policy_kind_noaction")
852 if qname == "example" and qtype == pdns.TXT
854 ffi.C.pdns_postresolve_ffi_handle_set_appliedpolicy_kind(ref, "pdns_policy_kind_drop")
857 if qname == "ns1.example" and qtype == pdns.A
859 ffi.C.pdns_postresolve_ffi_handle_set_appliedpolicy_kind(ref, "pdns_policy_kind_nxdomain")
862 if qname == "ns1.example" and qtype == pdns.AAAA
864 ffi.C.pdns_postresolve_ffi_handle_set_appliedpolicy_kind(ref, "pdns_policy_kind_nodata")
867 if qname == "ns2.example" and qtype == pdns.A
869 ffi.C.pdns_postresolve_ffi_handle_set_appliedpolicy_kind(ref, "pdns_policy_kind_truncate")
873 if qname ~= "postresolve_ffi.example" or (qtype ~= pdns.A and qtype ~= pdns.AAAA)
878 local record = ffi.new("pdns_ffi_record_t")
881 while ffi.C.pdns_postresolve_ffi_handle_get_record(ref, i, record, false)
883 local content = ffi.string(record.content, record.content_len)
884 local name = ffi.string(record.name)
885 if name ~= "postresolve_ffi.example"
889 if record.type == pdns.A and content == "1.2.3.4"
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"
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);
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)
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)
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)
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)
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'])
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'),
944 expectedAdditionalRecords
= [
945 dns
.rrset
.from_text('add.postresolve_ffi.example.', 60, dns
.rdataclass
.IN
, 'A', '4.5.6.7'),
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
)
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'),
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
)