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
103 if dq.queryTime.tv_sec < tm - 1 or dq.queryTime.tv_sec > tm + 1 then
104 pdnslog("queryTime is wrong")
105 dq.rcode = pdns.REFUSED
109 if dq.qtype == pdns.A then
110 dq:addAnswer(pdns.A, '192.0.2.1')
111 elseif dq.qtype == pdns.AAAA then
112 dq:addAnswer(pdns.AAAA, '2001:db8::1')
123 confdir
= os
.path
.join('configs', cls
._confdir
)
124 cls
.createConfigDir(confdir
)
125 cls
.generateRecursorConfig(confdir
)
126 cls
.startRecursor(confdir
, cls
._recursorPort
)
129 def tearDownClass(cls
):
130 cls
.tearDownRecursor()
135 dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.1'),
136 dns
.rrset
.from_text_list(name
, 0, dns
.rdataclass
.IN
, 'TXT', [ name
, 'gettag-qtype-1'])
138 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
139 query
.flags |
= dns
.flags
.CD
140 res
= self
.sendUDPQuery(query
)
141 self
.assertResponseMatches(query
, expected
, res
)
144 name
= 'gettag-tcpa.lua.'
146 dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.1'),
147 dns
.rrset
.from_text_list(name
, 0, dns
.rdataclass
.IN
, 'TXT', [ name
, 'gettag-qtype-1', 'gettag-tcp'])
149 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
150 query
.flags |
= dns
.flags
.CD
151 res
= self
.sendTCPQuery(query
)
152 self
.assertResponseMatches(query
, expected
, res
)
155 name
= 'gettag-aaaa.lua.'
157 dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'AAAA', '2001:db8::1'),
158 dns
.rrset
.from_text_list(name
, 0, dns
.rdataclass
.IN
, 'TXT', [ name
, 'gettag-qtype-28'])
160 query
= dns
.message
.make_query(name
, 'AAAA', want_dnssec
=True)
161 query
.flags |
= dns
.flags
.CD
162 res
= self
.sendUDPQuery(query
)
163 self
.assertResponseMatches(query
, expected
, res
)
166 name
= 'gettag-tcpaaaa.lua.'
168 dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'AAAA', '2001:db8::1'),
169 dns
.rrset
.from_text_list(name
, 0, dns
.rdataclass
.IN
, 'TXT', [ name
, 'gettag-qtype-28', 'gettag-tcp'])
171 query
= dns
.message
.make_query(name
, 'AAAA', want_dnssec
=True)
172 query
.flags |
= dns
.flags
.CD
173 res
= self
.sendTCPQuery(query
)
174 self
.assertResponseMatches(query
, expected
, res
)
176 def testSubnet(self
):
177 name
= 'gettag-subnet.lua.'
178 subnet
= '192.0.2.255'
180 ecso
= clientsubnetoption
.ClientSubnetOption(subnet
, subnetMask
)
182 dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.1'),
183 dns
.rrset
.from_text_list(name
, 0, dns
.rdataclass
.IN
, 'TXT', [name
, 'gettag-qtype-1', 'edns-subnet-' + subnet
+ '/' + str(subnetMask
),
184 'ednsoption-8-count-1', 'ednsoption-8-total-len-8']),
186 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True, options
=[ecso
])
187 query
.flags |
= dns
.flags
.CD
188 res
= self
.sendUDPQuery(query
)
189 self
.assertResponseMatches(query
, expected
, res
)
191 def testEDNSOptions(self
):
192 name
= 'gettag-ednsoptions.lua.'
193 subnet
= '192.0.2.255'
195 ecso
= clientsubnetoption
.ClientSubnetOption(subnet
, subnetMask
)
196 eco1
= cookiesoption
.CookiesOption(b
'deadbeef', b
'deadbeef')
197 eco2
= cookiesoption
.CookiesOption(b
'deadc0de', b
'deadc0de')
200 dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.1'),
201 dns
.rrset
.from_text_list(name
, 0, dns
.rdataclass
.IN
, 'TXT', [name
, 'gettag-qtype-1', 'edns-subnet-' + subnet
+ '/' + str(subnetMask
),
202 'ednsoption-10-count-2', 'ednsoption-10-total-len-32',
203 'ednsoption-8-count-1', 'ednsoption-8-total-len-8'
206 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True, options
=[eco1
,ecso
,eco2
])
207 query
.flags |
= dns
.flags
.CD
208 res
= self
.sendUDPQuery(query
)
209 self
.assertResponseMatches(query
, expected
, res
)
211 class GettagRecursorDistributesQueriesTest(GettagRecursorTest
):
212 _confdir
= 'LuaGettagDistributes'
213 _config_template
= """
214 log-common-errors=yes
215 gettag-needs-edns-options=yes
216 pdns-distributes-queries=yes
220 hooksReactorRunning
= False
222 class UDPHooksResponder(DatagramProtocol
):
224 def datagramReceived(self
, datagram
, address
):
225 request
= dns
.message
.from_wire(datagram
)
227 response
= dns
.message
.make_response(request
)
228 response
.flags |
= dns
.flags
.AA
230 if request
.question
[0].name
== dns
.name
.from_text('nxdomain.luahooks.example.'):
231 soa
= dns
.rrset
.from_text('luahooks.example.', 86400, dns
.rdataclass
.IN
, 'SOA', 'ns.luahooks.example. hostmaster.luahooks.example. 1 3600 3600 3600 1')
232 response
.authority
.append(soa
)
233 response
.set_rcode(dns
.rcode
.NXDOMAIN
)
235 elif request
.question
[0].name
== dns
.name
.from_text('nodata.luahooks.example.'):
236 soa
= dns
.rrset
.from_text('luahooks.example.', 86400, dns
.rdataclass
.IN
, 'SOA', 'ns.luahooks.example. hostmaster.luahooks.example. 1 3600 3600 3600 1')
237 response
.authority
.append(soa
)
239 elif request
.question
[0].name
== dns
.name
.from_text('postresolve.luahooks.example.'):
240 answer
= dns
.rrset
.from_text('postresolve.luahooks.example.', 3600, dns
.rdataclass
.IN
, 'A', '192.0.2.1')
241 response
.answer
.append(answer
)
243 elif request
.question
[0].name
== dns
.name
.from_text('preresolve.luahooks.example.'):
244 answer
= dns
.rrset
.from_text('preresolve.luahooks.example.', 3600, dns
.rdataclass
.IN
, 'A', '192.0.2.2')
245 response
.answer
.append(answer
)
247 self
.transport
.write(response
.to_wire(), address
)
249 class LuaHooksRecursorTest(RecursorTest
):
250 _confdir
= 'LuaHooks'
251 _config_template
= """
252 forward-zones=luahooks.example=%s.23
253 log-common-errors=yes
255 """ % (os
.environ
['PREFIX'])
256 _lua_dns_script_file
= """
258 allowedips = newNMG()
259 allowedips:addMask("%s.0/24")
261 function ipfilter(remoteip, localip, dh)
262 -- allow only 127.0.0.1 and AD=0
263 if allowedips:match(remoteip) and not dh:getAD() then
270 function preresolve(dq)
271 -- test both preresolve hooking and udpQueryResponse
272 if dq.qtype == pdns.A and dq.qname == newDN("preresolve.luahooks.example.") then
273 dq.followupFunction = "udpQueryResponse"
274 dq.udpCallback = "gotUdpResponse"
275 dq.udpQueryDest = newCA("%s.23:53")
276 -- synthesize query, responder should answer with regular answer
277 dq.udpQuery = "\\254\\255\\001\\000\\000\\001\\000\\000\\000\\000\\000\\000\\npreresolve\\008luahooks\\007example\\000\\000\\001\\000\\001"
283 function gotUdpResponse(dq)
284 dq.followupFunction = ""
285 if string.len(dq.udpAnswer) == 61 then
286 dq:addAnswer(pdns.A, "192.0.2.2")
289 dq:addAnswer(pdns.A, "0.0.0.0")
294 if dq.qtype == pdns.AAAA and dq.qname == newDN("nodata.luahooks.example.") then
295 dq:addAnswer(pdns.AAAA, "2001:DB8::1")
302 function nxdomain(dq)
303 if dq.qtype == pdns.A and dq.qname == newDN("nxdomain.luahooks.example.") then
305 dq:addAnswer(pdns.A, "192.0.2.1")
312 function postresolve(dq)
313 if dq.qtype == pdns.A and dq.qname == newDN("postresolve.luahooks.example.") then
314 local records = dq:getRecords()
315 for k,v in pairs(records) do
316 if v.type == pdns.A and v:getContent() == "192.0.2.1" then
317 v:changeContent("192.0.2.42")
321 dq:setRecords(records)
328 function preoutquery(dq)
329 if dq.remoteaddr:equal(newCA("%s.23")) and dq.qname == newDN("preout.luahooks.example.") and dq.qtype == pdns.A then
330 dq.rcode = -3 -- "kill"
337 """ % (os
.environ
['PREFIX'], os
.environ
['PREFIX'], os
.environ
['PREFIX'])
340 def startResponders(cls
):
341 global hooksReactorRunning
342 print("Launching responders..")
344 address
= cls
._PREFIX
+ '.23'
347 if not hooksReactorRunning
:
348 reactor
.listenUDP(port
, UDPHooksResponder(), interface
=address
)
349 hooksReactorRunning
= True
351 if not reactor
.running
:
352 cls
._UDPResponder
= threading
.Thread(name
='UDP Hooks Responder', target
=reactor
.run
, args
=(False,))
353 cls
._UDPResponder
.setDaemon(True)
354 cls
._UDPResponder
.start()
360 cls
.startResponders()
362 confdir
= os
.path
.join('configs', cls
._confdir
)
363 cls
.createConfigDir(confdir
)
365 cls
.generateRecursorConfig(confdir
)
366 cls
.startRecursor(confdir
, cls
._recursorPort
)
368 print("Launching tests..")
371 def tearDownClass(cls
):
372 cls
.tearDownRecursor()
374 def testNoData(self
):
375 expected
= dns
.rrset
.from_text('nodata.luahooks.example.', 3600, dns
.rdataclass
.IN
, 'AAAA', '2001:DB8::1')
376 query
= dns
.message
.make_query('nodata.luahooks.example.', 'AAAA', 'IN')
378 for method
in ("sendUDPQuery", "sendTCPQuery"):
379 sender
= getattr(self
, method
)
381 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
382 self
.assertRRsetInAnswer(res
, expected
)
384 def testVanillaNXD(self
):
385 #expected = dns.rrset.from_text('nxdomain.luahooks.example.', 3600, dns.rdataclass.IN, 'A', '192.0.2.1')
386 query
= dns
.message
.make_query('nxdomain.luahooks.example.', 'AAAA', 'IN')
388 for method
in ("sendUDPQuery", "sendTCPQuery"):
389 sender
= getattr(self
, method
)
391 self
.assertRcodeEqual(res
, dns
.rcode
.NXDOMAIN
)
393 def testHookedNXD(self
):
394 expected
= dns
.rrset
.from_text('nxdomain.luahooks.example.', 3600, dns
.rdataclass
.IN
, 'A', '192.0.2.1')
395 query
= dns
.message
.make_query('nxdomain.luahooks.example.', 'A', 'IN')
397 for method
in ("sendUDPQuery", "sendTCPQuery"):
398 sender
= getattr(self
, method
)
400 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
401 self
.assertRRsetInAnswer(res
, expected
)
403 def testPostResolve(self
):
404 expected
= dns
.rrset
.from_text('postresolve.luahooks.example.', 1, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
405 query
= dns
.message
.make_query('postresolve.luahooks.example.', 'A', 'IN')
407 for method
in ("sendUDPQuery", "sendTCPQuery"):
408 sender
= getattr(self
, method
)
410 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
411 self
.assertRRsetInAnswer(res
, expected
)
412 self
.assertEqual(res
.answer
[0].ttl
, 1)
414 def testPreResolve(self
):
415 expected
= dns
.rrset
.from_text('preresolve.luahooks.example.', 1, dns
.rdataclass
.IN
, 'A', '192.0.2.2')
416 query
= dns
.message
.make_query('preresolve.luahooks.example.', 'A', 'IN')
417 for method
in ("sendUDPQuery", "sendTCPQuery"):
418 sender
= getattr(self
, method
)
420 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
421 self
.assertRRsetInAnswer(res
, expected
)
423 def testIPFilterHeader(self
):
424 query
= dns
.message
.make_query('ipfilter.luahooks.example.', 'A', 'IN')
425 query
.flags |
= dns
.flags
.AD
427 for method
in ("sendUDPQuery", "sendTCPQuery"):
428 sender
= getattr(self
, method
)
430 self
.assertEqual(res
, None)
432 def testPreOutInterceptedQuery(self
):
433 query
= dns
.message
.make_query('preout.luahooks.example.', 'A', 'IN')
435 for method
in ("sendUDPQuery", "sendTCPQuery"):
436 sender
= getattr(self
, method
)
438 self
.assertRcodeEqual(res
, dns
.rcode
.SERVFAIL
)
440 def testPreOutNotInterceptedQuery(self
):
441 query
= dns
.message
.make_query('preout.luahooks.example.', 'AAAA', 'IN')
443 for method
in ("sendUDPQuery", "sendTCPQuery"):
444 sender
= getattr(self
, method
)
446 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
448 class LuaHooksRecursorDistributesTest(LuaHooksRecursorTest
):
449 _confdir
= 'LuaHooksDistributes'
450 _config_template
= """
451 forward-zones=luahooks.example=%s.23
452 log-common-errors=yes
453 pdns-distributes-queries=yes
456 """ % (os
.environ
['PREFIX'])
458 class LuaDNS64Test(RecursorTest
):
459 """Tests the dq.followupAction("getFakeAAAARecords")"""
461 _confdir
= 'lua-dns64'
462 _config_template
= """
464 _lua_dns_script_file
= """
465 prefix = "2001:DB8:64::"
468 if dq.qtype ~= pdns.AAAA then
470 end -- only AAAA records
472 -- don't fake AAAA records if DNSSEC validation failed
473 if dq.validationState == pdns.validationstates.Bogus then
477 dq.followupFunction = "getFakeAAAARecords"
478 dq.followupPrefix = prefix
479 dq.followupName = dq.qname
484 def testAtoAAAA(self
):
486 dns
.rrset
.from_text('ns.secure.example.', 15, dns
.rdataclass
.IN
, 'AAAA', '2001:db8:64::7f00:9')
488 query
= dns
.message
.make_query('ns.secure.example', 'AAAA')
490 for method
in ("sendUDPQuery", "sendTCPQuery"):
491 sender
= getattr(self
, method
)
494 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
495 self
.assertEqual(len(res
.answer
), 1)
496 self
.assertEqual(len(res
.authority
), 0)
497 self
.assertResponseMatches(query
, expected
, res
)
499 def testAtoCNAMEtoAAAA(self
):
501 dns
.rrset
.from_text('cname-to-insecure.secure.example.', 3600, dns
.rdataclass
.IN
, 'CNAME', 'node1.insecure.example.'),
502 dns
.rrset
.from_text('node1.insecure.example.', 3600, dns
.rdataclass
.IN
, 'AAAA', '2001:db8:64::c000:206')
504 query
= dns
.message
.make_query('cname-to-insecure.secure.example.', 'AAAA')
506 for method
in ("sendUDPQuery", "sendTCPQuery"):
507 sender
= getattr(self
, method
)
510 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
511 self
.assertEqual(len(res
.answer
), 2)
512 self
.assertEqual(len(res
.authority
), 0)
513 self
.assertResponseMatches(query
, expected
, res
)
515 class GettagFFIDNS64Test(RecursorTest
):
516 """Tests the interaction between gettag_ffi, RPZ and DNS64:
517 - gettag_ffi will intercept the query and return a NODATA
518 - the RPZ zone will match the name, but the only type is A
519 - DNS64 should kick in, generating an AAAA
522 _confdir
= 'gettagffi-rpz-dns64'
523 _config_template
= """
524 dns64-prefix=64:ff9b::/96
526 _lua_config_file
= """
527 rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz." })
530 _lua_dns_script_file
= """
531 local ffi = require("ffi")
534 typedef struct pdns_ffi_param pdns_ffi_param_t;
536 void pdns_ffi_param_set_rcode(pdns_ffi_param_t* ref, int rcode);
539 function gettag_ffi(obj)
540 -- fake a NODATA (without SOA) no matter the query
541 ffi.C.pdns_ffi_param_set_rcode(obj, 0)
548 def generateRecursorConfig(cls
, confdir
):
549 rpzFilePath
= os
.path
.join(confdir
, 'zone.rpz')
550 with
open(rpzFilePath
, 'w') as rpzZone
:
551 rpzZone
.write("""$ORIGIN zone.rpz.
553 dns64.test.powerdns.com.zone.rpz. 60 IN A 192.0.2.42
554 """.format(soa
=cls
._SOA
))
555 super(GettagFFIDNS64Test
, cls
).generateRecursorConfig(confdir
)
557 def testAtoAAAA(self
):
559 dns
.rrset
.from_text('dns64.test.powerdns.com.', 15, dns
.rdataclass
.IN
, 'AAAA', '64:ff9b::c000:22a')
561 query
= dns
.message
.make_query('dns64.test.powerdns.com.', 'AAAA')
563 for method
in ("sendUDPQuery", "sendTCPQuery"):
564 sender
= getattr(self
, method
)
567 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
568 self
.assertEqual(len(res
.answer
), 1)
569 self
.assertEqual(len(res
.authority
), 0)
570 self
.assertResponseMatches(query
, expected
, res
)
572 class PDNSRandomTest(RecursorTest
):
573 """Tests if pdnsrandom works"""
575 _confdir
= 'pdnsrandom'
576 _config_template
= """
578 _lua_dns_script_file
= """
579 function preresolve (dq)
580 dq.rcode = pdns.NOERROR
581 dq:addAnswer(pdns.TXT, pdnsrandom())
587 def testRandom(self
):
588 query
= dns
.message
.make_query('whatever.example.', 'TXT')
592 ret
= self
.sendUDPQuery(query
)
593 ans
.add(ret
.answer
[0][0])
594 ret
= self
.sendUDPQuery(query
)
595 ans
.add(ret
.answer
[0][0])
597 self
.assertEqual(len(ans
), 2)
600 class PDNSFeaturesTest(RecursorTest
):
601 """Tests if pdns_features works"""
603 _confdir
= 'pdnsfeatures'
604 _config_template
= """
606 _lua_dns_script_file
= """
607 function preresolve (dq)
608 dq.rcode = pdns.NOERROR
609 -- test pdns_features
610 if pdns_features['nonexistent'] ~= nil then
611 print('PDNSFeaturesTest: case 1')
612 dq.rcode = pdns.SERVFAIL
614 if not pdns_features['PR8001_devicename'] then
615 print('PDNSFeaturesTest: case 2')
616 dq.rcode = pdns.SERVFAIL
622 def testFeatures(self
):
623 query
= dns
.message
.make_query('whatever.example.', 'TXT')
624 res
= self
.sendUDPQuery(query
)
626 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
628 class PDNSGeneratingAnswerFromGettagTest(RecursorTest
):
629 """Tests that we can generate answers from gettag"""
631 _confdir
= 'gettaganswers'
632 _config_template
= """
634 _lua_dns_script_file
= """
635 local ffi = require("ffi")
638 typedef struct pdns_ffi_param pdns_ffi_param_t;
645 } pdns_record_place_t;
647 const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref);
648 void pdns_ffi_param_set_rcode(pdns_ffi_param_t* ref, int rcode);
649 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);
652 function gettag_ffi(obj)
653 local qname = ffi.string(ffi.C.pdns_ffi_param_get_qname(obj))
654 if qname == 'gettag-answers.powerdns.com' then
655 ffi.C.pdns_ffi_param_set_rcode(obj, 0)
656 ffi.C.pdns_ffi_param_add_record(obj, nil, pdns.A, 60, '192.0.2.1', 9, 1);
657 ffi.C.pdns_ffi_param_add_record(obj, "not-powerdns.com.", pdns.A, 60, '192.0.2.2', 9, 3);
661 function preresolve (dq)
662 -- refused everything if it comes that far
663 dq.rcode = pdns.REFUSED
668 def testGettag(self
):
669 expectedAnswerRecords
= [
670 dns
.rrset
.from_text('gettag-answers.powerdns.com.', 60, dns
.rdataclass
.IN
, 'A', '192.0.2.1'),
672 expectedAdditionalRecords
= [
673 dns
.rrset
.from_text('not-powerdns.com.', 60, dns
.rdataclass
.IN
, 'A', '192.0.2.2'),
675 query
= dns
.message
.make_query('gettag-answers.powerdns.com.', 'A')
676 res
= self
.sendUDPQuery(query
)
678 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
679 self
.assertEqual(len(res
.answer
), 1)
680 self
.assertEqual(len(res
.authority
), 0)
681 self
.assertEqual(len(res
.additional
), 1)
682 self
.assertEqual(res
.answer
, expectedAnswerRecords
)
683 self
.assertEqual(res
.additional
, expectedAdditionalRecords
)
685 class PDNSValidationStatesTest(RecursorTest
):
686 """Tests that we have access to the validation states from Lua"""
688 _confdir
= 'validation-states-from-lua'
689 _config_template
= """
693 _lua_config_file
= """
696 _lua_dns_script_file
= """
697 function postresolve (dq)
698 if pdns.validationstates.Indeterminate == nil or
699 pdns.validationstates.BogusNoValidDNSKEY == nil or
700 pdns.validationstates.BogusInvalidDenial == nil or
701 pdns.validationstates.BogusUnableToGetDSs == nil or
702 pdns.validationstates.BogusUnableToGetDNSKEYs == nil or
703 pdns.validationstates.BogusSelfSignedDS == nil or
704 pdns.validationstates.BogusNoRRSIG == nil or
705 pdns.validationstates.BogusNoValidRRSIG == nil or
706 pdns.validationstates.BogusMissingNegativeIndication == nil or
707 pdns.validationstates.BogusSignatureNotYetValid == nil or
708 pdns.validationstates.BogusSignatureExpired == nil or
709 pdns.validationstates.BogusUnsupportedDNSKEYAlgo == nil or
710 pdns.validationstates.BogusUnsupportedDSDigestType == nil or
711 pdns.validationstates.BogusNoZoneKeyBitSet == nil or
712 pdns.validationstates.BogusRevokedDNSKEY == nil or
713 pdns.validationstates.BogusInvalidDNSKEYProtocol == nil or
714 pdns.validationstates.Insecure == nil or
715 pdns.validationstates.Secure == nil or
716 pdns.validationstates.Bogus == nil then
717 -- refused if at least one state is not available
718 pdnslog('Missing DNSSEC validation state!')
719 dq.rcode = pdns.REFUSED
722 if dq.qname == newDN('brokendnssec.net.') then
723 if dq.validationState ~= pdns.validationstates.Bogus then
724 pdnslog('DNSSEC validation state should be Bogus!')
725 dq.rcode = pdns.REFUSED
728 if dq.detailedValidationState ~= pdns.validationstates.BogusNoRRSIG then
729 pdnslog('DNSSEC detailed validation state is not valid, got '..dq.detailedValidationState..' and expected '..pdns.validationstates.BogusNoRRSIG)
730 dq.rcode = pdns.REFUSED
733 if not isValidationStateBogus(dq.detailedValidationState) then
734 pdnslog('DNSSEC detailed validation state should be Bogus and is not!')
735 dq.rcode = pdns.REFUSED
743 def testValidationBogus(self
):
744 query
= dns
.message
.make_query('brokendnssec.net.', 'A')
745 res
= self
.sendUDPQuery(query
)
746 self
.assertRcodeEqual(res
, dns
.rcode
.SERVFAIL
)
747 self
.assertEqual(len(res
.answer
), 0)
748 self
.assertEqual(len(res
.authority
), 0)
750 class PolicyEventFilterOnFollowUpTest(RecursorTest
):
751 """Tests the interaction between RPZ and followup queries (dns64, followCNAME)
754 _confdir
= 'policyeventfilter-followup'
755 _config_template
= """
757 _lua_config_file
= """
758 rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz." })
761 _lua_dns_script_file
= """
762 function preresolve(dq)
763 dq:addAnswer(pdns.CNAME, "secure.example.")
764 dq.followupFunction="followCNAMERecords"
765 dq.rcode = pdns.NOERROR
769 function policyEventFilter(event)
770 event.appliedPolicy.policyKind = pdns.policykinds.NoAction
776 def generateRecursorConfig(cls
, confdir
):
777 rpzFilePath
= os
.path
.join(confdir
, 'zone.rpz')
778 with
open(rpzFilePath
, 'w') as rpzZone
:
779 rpzZone
.write("""$ORIGIN zone.rpz.
781 secure.example.zone.rpz. 60 IN A 192.0.2.42
782 """.format(soa
=cls
._SOA
))
783 super(PolicyEventFilterOnFollowUpTest
, cls
).generateRecursorConfig(confdir
)
787 dns
.rrset
.from_text('policyeventfilter-followup.test.powerdns.com.', 15, dns
.rdataclass
.IN
, 'CNAME', 'secure.example.'),
788 dns
.rrset
.from_text('secure.example.', 15, dns
.rdataclass
.IN
, 'A', '192.0.2.17')
790 query
= dns
.message
.make_query('policyeventfilter-followup.test.powerdns.com.', 'A')
792 for method
in ("sendUDPQuery", "sendTCPQuery"):
793 sender
= getattr(self
, method
)
796 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
797 self
.assertEqual(len(res
.answer
), 2)
798 self
.assertEqual(len(res
.authority
), 0)
799 self
.assertResponseMatches(query
, expected
, res
)
801 class PolicyEventFilterOnFollowUpWithNativeDNS64Test(RecursorTest
):
802 """Tests the interaction between followup queries and native dns64
805 _confdir
= 'policyeventfilter-followup-dns64'
806 _config_template
= """
807 dns64-prefix=1234::/96
809 _lua_config_file
= """
811 _lua_dns_script_file
= """
812 function preresolve(dq)
813 dq:addAnswer(pdns.CNAME, "cname.secure.example.")
814 dq.followupFunction="followCNAMERecords"
815 dq.rcode = pdns.NOERROR
823 dns
.rrset
.from_text('mx1.secure.example.', 15, dns
.rdataclass
.IN
, 'CNAME', 'cname.secure.example.'),
824 dns
.rrset
.from_text('cname.secure.example.', 15, dns
.rdataclass
.IN
, 'CNAME', ' host1.secure.example.'),
825 dns
.rrset
.from_text('host1.secure.example.', 15, dns
.rdataclass
.IN
, 'AAAA', '1234::c000:202')
827 query
= dns
.message
.make_query('mx1.secure.example', 'AAAA')
829 for method
in ("sendUDPQuery", "sendTCPQuery"):
830 sender
= getattr(self
, method
)
833 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
834 self
.assertEqual(len(res
.answer
), 3)
835 self
.assertEqual(len(res
.authority
), 0)
836 self
.assertResponseMatches(query
, expected
, res
)
838 class LuaPostResolveFFITest(RecursorTest
):
839 """Tests postresolve_ffi interface"""
841 _confdir
= 'LuaPostResolveFFITest'
842 _config_template
= """
844 _lua_dns_script_file
= """
845 local ffi = require("ffi")
848 typedef struct pdns_postresolve_ffi_handle pdns_postresolve_ffi_handle_t;
852 pdns_record_place_answer = 1,
853 pdns_record_place_authority = 2,
854 pdns_record_place_additional = 3
855 } pdns_record_place_t;
859 pdns_policy_kind_noaction = 0,
860 pdns_policy_kind_drop = 1,
861 pdns_policy_kind_nxdomain = 2,
862 pdns_policy_kind_nodata = 3,
863 pdns_policy_kind_truncate = 4,
864 pdns_policy_kind_custom = 5
865 } pdns_policy_kind_t;
867 typedef struct pdns_ffi_record {
873 pdns_record_place_t place;
877 const char* pdns_postresolve_ffi_handle_get_qname(pdns_postresolve_ffi_handle_t* ref);
878 const char* pdns_postresolve_ffi_handle_get_qname_raw(pdns_postresolve_ffi_handle_t* ref, const char** name, size_t* len);
879 uint16_t pdns_postresolve_ffi_handle_get_qtype(const pdns_postresolve_ffi_handle_t* ref);
880 uint16_t pdns_postresolve_ffi_handle_get_rcode(const pdns_postresolve_ffi_handle_t* ref);
881 void pdns_postresolve_ffi_handle_set_rcode(const pdns_postresolve_ffi_handle_t* ref, uint16_t rcode);
882 void pdns_postresolve_ffi_handle_set_appliedpolicy_kind(pdns_postresolve_ffi_handle_t* ref, pdns_policy_kind_t kind);
883 bool pdns_postresolve_ffi_handle_get_record(pdns_postresolve_ffi_handle_t* ref, unsigned int i, pdns_ffi_record_t *record, bool raw);
884 bool pdns_postresolve_ffi_handle_set_record(pdns_postresolve_ffi_handle_t* ref, unsigned int i, const char* content, size_t contentLen, bool raw);
885 void pdns_postresolve_ffi_handle_clear_records(pdns_postresolve_ffi_handle_t* ref);
886 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);
887 const char* pdns_postresolve_ffi_handle_get_authip(pdns_postresolve_ffi_handle_t* ref);
888 void pdns_postresolve_ffi_handle_get_authip_raw(pdns_postresolve_ffi_handle_t* ref, const void** addr, size_t* addrSize);
893 return (str:gsub('.', function (c) return string.format('%02X', string.byte(c)) end))
896 return (str:gsub('.', function (c) return string.format('%d.', string.byte(c)) end))
899 function postresolve_ffi(ref)
900 local qname = ffi.string(ffi.C.pdns_postresolve_ffi_handle_get_qname(ref))
901 local qtype = ffi.C.pdns_postresolve_ffi_handle_get_qtype(ref)
903 if qname == "example" and qtype == pdns.SOA
905 local addr = ffi.string(ffi.C.pdns_postresolve_ffi_handle_get_authip(ref))
906 if string.sub(addr, -3) ~= ".10" and string.sub(addr, -3) ~= ".18"
908 -- signal error by clearing all
909 ffi.C.pdns_postresolve_ffi_handle_clear_records(ref)
911 local qaddr = ffi.new("const char *[1]")
912 local qlen = ffi.new("size_t [1]")
913 ffi.C.pdns_postresolve_ffi_handle_get_qname_raw(ref, qaddr, qlen)
914 local q = ffi.string(qaddr[0], qlen[0])
915 if tohex(q) ~= "076578616D706C6500"
917 -- pdnslog("Error "..tohex(q))
918 -- signal error by clearing all
919 ffi.C.pdns_postresolve_ffi_handle_clear_records(ref)
921 -- as a bonus check from which auth the data came
922 local addr = ffi.new("const void *[1]")
923 local len = ffi.new("size_t [1]")
924 ffi.C.pdns_postresolve_ffi_handle_get_authip_raw(ref, addr, len)
925 local a = ffi.string(addr[0], len[0])
926 if string.byte(a, 4) ~= 10 and string.byte(a,4) ~= 18
928 -- signal error by clearing all
929 ffi.C.pdns_postresolve_ffi_handle_clear_records(ref)
931 ffi.C.pdns_postresolve_ffi_handle_set_appliedpolicy_kind(ref, "pdns_policy_kind_noaction")
934 if qname == "example" and qtype == pdns.TXT
936 ffi.C.pdns_postresolve_ffi_handle_set_appliedpolicy_kind(ref, "pdns_policy_kind_drop")
939 if qname == "ns1.example" and qtype == pdns.A
941 ffi.C.pdns_postresolve_ffi_handle_set_appliedpolicy_kind(ref, "pdns_policy_kind_nxdomain")
944 if qname == "ns1.example" and qtype == pdns.AAAA
946 ffi.C.pdns_postresolve_ffi_handle_set_appliedpolicy_kind(ref, "pdns_policy_kind_nodata")
949 if qname == "ns2.example" and qtype == pdns.A
951 ffi.C.pdns_postresolve_ffi_handle_set_appliedpolicy_kind(ref, "pdns_policy_kind_truncate")
955 if qname ~= "postresolve_ffi.example" or (qtype ~= pdns.A and qtype ~= pdns.AAAA)
960 local record = ffi.new("pdns_ffi_record_t")
963 while ffi.C.pdns_postresolve_ffi_handle_get_record(ref, i, record, false)
965 local content = ffi.string(record.content, record.content_len)
966 local name = ffi.string(record.name)
967 if name ~= "postresolve_ffi.example"
971 if record.type == pdns.A and content == "1.2.3.4"
973 ffi.C.pdns_postresolve_ffi_handle_set_record(ref, i, "0.1.2.3", 7, false);
974 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);
975 elseif record.type == pdns.AAAA and content == "::1"
977 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);
981 -- loop again using raw
983 while ffi.C.pdns_postresolve_ffi_handle_get_record(ref, i, record, true)
985 local content = ffi.string(record.content, record.content_len)
986 local name = ffi.string(record.name, record.name_len)
987 --pdnslog("R "..tohex(name))
988 if tohex(name) ~= "0F706F73747265736F6C76655F666669076578616D706C6500"
990 ffi.C.pdns_postresolve_ffi_handle_clear_records(ref)
998 def testNOACTION(self
):
999 """ postresolve_ffi: test that we can do a NOACTION for a name and type combo"""
1000 query
= dns
.message
.make_query('example', 'SOA')
1001 res
= self
.sendUDPQuery(query
)
1002 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
1003 self
.assertEqual(len(res
.answer
), 1)
1006 """ postresolve_ffi: test that we can do a DROP for a name and type combo"""
1007 query
= dns
.message
.make_query('example', 'TXT')
1008 res
= self
.sendUDPQuery(query
)
1009 self
.assertEqual(res
, None)
1011 def testNXDOMAIN(self
):
1012 """ postresolve_ffi: test that we can return a NXDOMAIN for a name and type combo"""
1013 query
= dns
.message
.make_query('ns1.example', 'A')
1014 res
= self
.sendUDPQuery(query
)
1015 self
.assertRcodeEqual(res
, dns
.rcode
.NXDOMAIN
)
1016 self
.assertEqual(len(res
.answer
), 0)
1018 def testNODATA(self
):
1019 """ postresolve_ffi: test that we can return a NODATA for a name and type combo"""
1020 query
= dns
.message
.make_query('ns1.example', 'AAAA')
1021 res
= self
.sendUDPQuery(query
)
1022 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
1023 self
.assertEqual(len(res
.answer
), 0)
1025 def testTRUNCATE(self
):
1026 """ postresolve_ffi: test that we can return a truncated for a name and type combo"""
1027 query
= dns
.message
.make_query('ns2.example', 'A')
1028 res
= self
.sendUDPQuery(query
)
1029 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
1030 self
.assertEqual(len(res
.answer
), 0)
1031 self
.assertMessageHasFlags(res
, ['QR', 'TC', 'RD', 'RA'])
1034 def testModifyA(self
):
1035 """postresolve_ffi: test that we can modify A answers"""
1036 expectedAnswerRecords
= [
1037 dns
.rrset
.from_text('postresolve_ffi.example.', 60, dns
.rdataclass
.IN
, 'A', '0.1.2.3', '1.2.3.5'),
1039 expectedAdditionalRecords
= [
1040 dns
.rrset
.from_text('add.postresolve_ffi.example.', 60, dns
.rdataclass
.IN
, 'A', '4.5.6.7'),
1043 query
= dns
.message
.make_query('postresolve_ffi.example', 'A')
1044 res
= self
.sendUDPQuery(query
)
1045 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
1046 self
.assertEqual(len(res
.answer
), 1)
1047 self
.assertEqual(len(res
.authority
), 0)
1048 self
.assertEqual(len(res
.additional
), 1)
1049 self
.assertEqual(res
.answer
, expectedAnswerRecords
)
1050 self
.assertEqual(res
.additional
, expectedAdditionalRecords
)
1052 def testModifyAAAA(self
):
1053 """postresolve_ffi: test that we can modify AAAA answers"""
1054 expectedAnswerRecords
= [
1055 dns
.rrset
.from_text('postresolve_ffi.example.', 60, dns
.rdataclass
.IN
, 'AAAA', '1:203:405:607:809:a0b:c0d:e0f', '::2'),
1057 query
= dns
.message
.make_query('postresolve_ffi.example', 'AAAA')
1058 res
= self
.sendUDPQuery(query
)
1059 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
1060 self
.assertEqual(len(res
.answer
), 1)
1061 self
.assertEqual(len(res
.authority
), 0)
1062 self
.assertEqual(len(res
.additional
), 0)
1063 self
.assertEqual(res
.answer
, expectedAnswerRecords
)