]>
Commit | Line | Data |
---|---|---|
18e0e4df RG |
1 | import clientsubnetoption |
2 | import cookiesoption | |
3 | import dns | |
4 | import os | |
d19bcbf0 RG |
5 | import threading |
6 | import time | |
7 | ||
8 | from twisted.internet.protocol import DatagramProtocol | |
9 | from twisted.internet import reactor | |
18e0e4df RG |
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 | ||
4ccb484c OM |
102 | local tm = os.time() |
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 | |
106 | return true | |
107 | end | |
108 | ||
18e0e4df RG |
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') | |
113 | end | |
114 | ||
115 | return true | |
116 | end | |
117 | """ | |
118 | ||
119 | @classmethod | |
120 | def setUpClass(cls): | |
121 | ||
122 | cls.setUpSockets() | |
123 | confdir = os.path.join('configs', cls._confdir) | |
124 | cls.createConfigDir(confdir) | |
125 | cls.generateRecursorConfig(confdir) | |
126 | cls.startRecursor(confdir, cls._recursorPort) | |
127 | ||
128 | @classmethod | |
129 | def tearDownClass(cls): | |
130 | cls.tearDownRecursor() | |
131 | ||
18e0e4df RG |
132 | def testA(self): |
133 | name = 'gettag.lua.' | |
134 | expected = [ | |
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']) | |
137 | ] | |
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) | |
142 | ||
143 | def testTCPA(self): | |
144 | name = 'gettag-tcpa.lua.' | |
145 | expected = [ | |
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']) | |
148 | ] | |
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) | |
153 | ||
154 | def testAAAA(self): | |
155 | name = 'gettag-aaaa.lua.' | |
156 | expected = [ | |
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']) | |
159 | ] | |
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) | |
164 | ||
cf12aaef RG |
165 | def testAAAA(self): |
166 | name = 'gettag-tcpaaaa.lua.' | |
167 | expected = [ | |
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']) | |
170 | ] | |
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) | |
175 | ||
18e0e4df RG |
176 | def testSubnet(self): |
177 | name = 'gettag-subnet.lua.' | |
178 | subnet = '192.0.2.255' | |
179 | subnetMask = 32 | |
180 | ecso = clientsubnetoption.ClientSubnetOption(subnet, subnetMask) | |
181 | expected = [ | |
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']), | |
185 | ] | |
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) | |
190 | ||
191 | def testEDNSOptions(self): | |
192 | name = 'gettag-ednsoptions.lua.' | |
193 | subnet = '192.0.2.255' | |
194 | subnetMask = 32 | |
195 | ecso = clientsubnetoption.ClientSubnetOption(subnet, subnetMask) | |
196 | eco1 = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef') | |
197 | eco2 = cookiesoption.CookiesOption(b'deadc0de', b'deadc0de') | |
198 | ||
199 | expected = [ | |
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' | |
204 | ]), | |
205 | ] | |
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) | |
210 | ||
cf12aaef RG |
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 | |
217 | threads=2 | |
218 | """ | |
219 | ||
d19bcbf0 RG |
220 | hooksReactorRunning = False |
221 | ||
222 | class UDPHooksResponder(DatagramProtocol): | |
223 | ||
224 | def datagramReceived(self, datagram, address): | |
225 | request = dns.message.from_wire(datagram) | |
226 | ||
227 | response = dns.message.make_response(request) | |
228 | response.flags |= dns.flags.AA | |
229 | ||
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) | |
234 | ||
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) | |
238 | ||
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) | |
242 | ||
8ca70105 OM |
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) | |
246 | ||
d19bcbf0 RG |
247 | self.transport.write(response.to_wire(), address) |
248 | ||
249 | class LuaHooksRecursorTest(RecursorTest): | |
250 | _confdir = 'LuaHooks' | |
251 | _config_template = """ | |
252 | forward-zones=luahooks.example=%s.23 | |
253 | log-common-errors=yes | |
254 | quiet=no | |
255 | """ % (os.environ['PREFIX']) | |
256 | _lua_dns_script_file = """ | |
257 | ||
258 | allowedips = newNMG() | |
259 | allowedips:addMask("%s.0/24") | |
260 | ||
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 | |
264 | return false | |
265 | end | |
266 | ||
267 | return true | |
268 | end | |
269 | ||
8ca70105 OM |
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" | |
278 | return true | |
279 | end | |
280 | return false | |
281 | end | |
282 | ||
283 | function gotUdpResponse(dq) | |
284 | dq.followupFunction = "" | |
285 | if string.len(dq.udpAnswer) == 61 then | |
286 | dq:addAnswer(pdns.A, "192.0.2.2") | |
287 | return true; | |
288 | end | |
289 | dq:addAnswer(pdns.A, "0.0.0.0") | |
290 | return true | |
291 | end | |
292 | ||
d19bcbf0 RG |
293 | function nodata(dq) |
294 | if dq.qtype == pdns.AAAA and dq.qname == newDN("nodata.luahooks.example.") then | |
295 | dq:addAnswer(pdns.AAAA, "2001:DB8::1") | |
296 | return true | |
297 | end | |
298 | ||
299 | return false | |
300 | end | |
301 | ||
302 | function nxdomain(dq) | |
303 | if dq.qtype == pdns.A and dq.qname == newDN("nxdomain.luahooks.example.") then | |
304 | dq.rcode=0 | |
305 | dq:addAnswer(pdns.A, "192.0.2.1") | |
306 | return true | |
307 | end | |
308 | ||
309 | return false | |
310 | end | |
311 | ||
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") | |
318 | v.ttl=1 | |
319 | end | |
320 | end | |
321 | dq:setRecords(records) | |
322 | return true | |
323 | end | |
324 | ||
325 | return false | |
326 | end | |
327 | ||
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" | |
331 | return true | |
332 | end | |
333 | ||
334 | return false | |
335 | end | |
336 | ||
8ca70105 | 337 | """ % (os.environ['PREFIX'], os.environ['PREFIX'], os.environ['PREFIX']) |
d19bcbf0 RG |
338 | |
339 | @classmethod | |
340 | def startResponders(cls): | |
341 | global hooksReactorRunning | |
342 | print("Launching responders..") | |
343 | ||
344 | address = cls._PREFIX + '.23' | |
345 | port = 53 | |
346 | ||
347 | if not hooksReactorRunning: | |
348 | reactor.listenUDP(port, UDPHooksResponder(), interface=address) | |
349 | hooksReactorRunning = True | |
350 | ||
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() | |
355 | ||
356 | @classmethod | |
357 | def setUpClass(cls): | |
358 | cls.setUpSockets() | |
359 | ||
360 | cls.startResponders() | |
361 | ||
362 | confdir = os.path.join('configs', cls._confdir) | |
363 | cls.createConfigDir(confdir) | |
364 | ||
365 | cls.generateRecursorConfig(confdir) | |
366 | cls.startRecursor(confdir, cls._recursorPort) | |
367 | ||
368 | print("Launching tests..") | |
369 | ||
370 | @classmethod | |
371 | def tearDownClass(cls): | |
372 | cls.tearDownRecursor() | |
373 | ||
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') | |
d19bcbf0 | 377 | |
cf12aaef RG |
378 | for method in ("sendUDPQuery", "sendTCPQuery"): |
379 | sender = getattr(self, method) | |
380 | res = sender(query) | |
381 | self.assertRcodeEqual(res, dns.rcode.NOERROR) | |
382 | self.assertRRsetInAnswer(res, expected) | |
d19bcbf0 RG |
383 | |
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') | |
d19bcbf0 | 387 | |
cf12aaef RG |
388 | for method in ("sendUDPQuery", "sendTCPQuery"): |
389 | sender = getattr(self, method) | |
390 | res = sender(query) | |
391 | self.assertRcodeEqual(res, dns.rcode.NXDOMAIN) | |
d19bcbf0 RG |
392 | |
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') | |
d19bcbf0 | 396 | |
cf12aaef RG |
397 | for method in ("sendUDPQuery", "sendTCPQuery"): |
398 | sender = getattr(self, method) | |
399 | res = sender(query) | |
400 | self.assertRcodeEqual(res, dns.rcode.NOERROR) | |
401 | self.assertRRsetInAnswer(res, expected) | |
d19bcbf0 RG |
402 | |
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') | |
d19bcbf0 | 406 | |
cf12aaef RG |
407 | for method in ("sendUDPQuery", "sendTCPQuery"): |
408 | sender = getattr(self, method) | |
409 | res = sender(query) | |
410 | self.assertRcodeEqual(res, dns.rcode.NOERROR) | |
411 | self.assertRRsetInAnswer(res, expected) | |
412 | self.assertEqual(res.answer[0].ttl, 1) | |
d19bcbf0 | 413 | |
8ca70105 OM |
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) | |
419 | res = sender(query) | |
420 | self.assertRcodeEqual(res, dns.rcode.NOERROR) | |
421 | self.assertRRsetInAnswer(res, expected) | |
422 | ||
d19bcbf0 | 423 | def testIPFilterHeader(self): |
ef2ea4bf | 424 | query = dns.message.make_query('ipfilter.luahooks.example.', 'A', 'IN') |
d19bcbf0 | 425 | query.flags |= dns.flags.AD |
cf12aaef RG |
426 | |
427 | for method in ("sendUDPQuery", "sendTCPQuery"): | |
428 | sender = getattr(self, method) | |
429 | res = sender(query) | |
430 | self.assertEqual(res, None) | |
d19bcbf0 RG |
431 | |
432 | def testPreOutInterceptedQuery(self): | |
433 | query = dns.message.make_query('preout.luahooks.example.', 'A', 'IN') | |
cf12aaef RG |
434 | |
435 | for method in ("sendUDPQuery", "sendTCPQuery"): | |
436 | sender = getattr(self, method) | |
437 | res = sender(query) | |
438 | self.assertRcodeEqual(res, dns.rcode.SERVFAIL) | |
d19bcbf0 RG |
439 | |
440 | def testPreOutNotInterceptedQuery(self): | |
441 | query = dns.message.make_query('preout.luahooks.example.', 'AAAA', 'IN') | |
f3f19ebe | 442 | |
cf12aaef RG |
443 | for method in ("sendUDPQuery", "sendTCPQuery"): |
444 | sender = getattr(self, method) | |
445 | res = sender(query) | |
446 | self.assertRcodeEqual(res, dns.rcode.NOERROR) | |
447 | ||
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 | |
454 | threads=2 | |
455 | quiet=no | |
456 | """ % (os.environ['PREFIX']) | |
f3f19ebe | 457 | |
76b47869 | 458 | class LuaDNS64Test(RecursorTest): |
f3f19ebe PL |
459 | """Tests the dq.followupAction("getFakeAAAARecords")""" |
460 | ||
76b47869 | 461 | _confdir = 'lua-dns64' |
f3f19ebe PL |
462 | _config_template = """ |
463 | """ | |
464 | _lua_dns_script_file = """ | |
465 | prefix = "2001:DB8:64::" | |
466 | ||
467 | function nodata (dq) | |
468 | if dq.qtype ~= pdns.AAAA then | |
469 | return false | |
470 | end -- only AAAA records | |
471 | ||
472 | -- don't fake AAAA records if DNSSEC validation failed | |
473 | if dq.validationState == pdns.validationstates.Bogus then | |
474 | return false | |
475 | end | |
476 | ||
477 | dq.followupFunction = "getFakeAAAARecords" | |
478 | dq.followupPrefix = prefix | |
479 | dq.followupName = dq.qname | |
480 | return true | |
481 | end | |
482 | """ | |
483 | ||
484 | def testAtoAAAA(self): | |
485 | expected = [ | |
486 | dns.rrset.from_text('ns.secure.example.', 15, dns.rdataclass.IN, 'AAAA', '2001:db8:64::7f00:9') | |
487 | ] | |
488 | query = dns.message.make_query('ns.secure.example', 'AAAA') | |
489 | ||
cf12aaef RG |
490 | for method in ("sendUDPQuery", "sendTCPQuery"): |
491 | sender = getattr(self, method) | |
492 | res = sender(query) | |
f3f19ebe | 493 | |
cf12aaef RG |
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) | |
f3f19ebe PL |
498 | |
499 | def testAtoCNAMEtoAAAA(self): | |
500 | expected = [ | |
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') | |
503 | ] | |
504 | query = dns.message.make_query('cname-to-insecure.secure.example.', 'AAAA') | |
505 | ||
cf12aaef RG |
506 | for method in ("sendUDPQuery", "sendTCPQuery"): |
507 | sender = getattr(self, method) | |
508 | res = sender(query) | |
f3f19ebe | 509 | |
cf12aaef RG |
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) | |
2c419831 | 514 | |
76b47869 RG |
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 | |
520 | """ | |
521 | ||
522 | _confdir = 'gettagffi-rpz-dns64' | |
523 | _config_template = """ | |
524 | dns64-prefix=64:ff9b::/96 | |
525 | """ | |
526 | _lua_config_file = """ | |
527 | rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz." }) | |
528 | """ % (_confdir) | |
529 | ||
530 | _lua_dns_script_file = """ | |
531 | local ffi = require("ffi") | |
532 | ||
533 | ffi.cdef[[ | |
534 | typedef struct pdns_ffi_param pdns_ffi_param_t; | |
535 | ||
536 | void pdns_ffi_param_set_rcode(pdns_ffi_param_t* ref, int rcode); | |
537 | ]] | |
538 | ||
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) | |
542 | return {} | |
543 | end | |
544 | """ | |
545 | _auth_zones = [] | |
546 | ||
547 | @classmethod | |
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. | |
552 | @ 3600 IN SOA {soa} | |
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) | |
556 | ||
557 | def testAtoAAAA(self): | |
558 | expected = [ | |
559 | dns.rrset.from_text('dns64.test.powerdns.com.', 15, dns.rdataclass.IN, 'AAAA', '64:ff9b::c000:22a') | |
560 | ] | |
561 | query = dns.message.make_query('dns64.test.powerdns.com.', 'AAAA') | |
562 | ||
563 | for method in ("sendUDPQuery", "sendTCPQuery"): | |
564 | sender = getattr(self, method) | |
565 | res = sender(query) | |
566 | ||
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) | |
2c419831 PL |
571 | |
572 | class PDNSRandomTest(RecursorTest): | |
573 | """Tests if pdnsrandom works""" | |
574 | ||
575 | _confdir = 'pdnsrandom' | |
576 | _config_template = """ | |
577 | """ | |
578 | _lua_dns_script_file = """ | |
579 | function preresolve (dq) | |
580 | dq.rcode = pdns.NOERROR | |
581 | dq:addAnswer(pdns.TXT, pdnsrandom()) | |
d2c1660a | 582 | dq.variable = true |
2c419831 PL |
583 | return true |
584 | end | |
585 | """ | |
586 | ||
587 | def testRandom(self): | |
588 | query = dns.message.make_query('whatever.example.', 'TXT') | |
589 | ||
590 | ans = set() | |
591 | ||
592 | ret = self.sendUDPQuery(query) | |
3d144e24 | 593 | ans.add(ret.answer[0][0]) |
2c419831 | 594 | ret = self.sendUDPQuery(query) |
3d144e24 | 595 | ans.add(ret.answer[0][0]) |
2c419831 PL |
596 | |
597 | self.assertEqual(len(ans), 2) | |
1b2c38ad OM |
598 | |
599 | ||
600 | class PDNSFeaturesTest(RecursorTest): | |
601 | """Tests if pdns_features works""" | |
602 | ||
603 | _confdir = 'pdnsfeatures' | |
604 | _config_template = """ | |
605 | """ | |
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 | |
613 | end | |
614 | if not pdns_features['PR8001_devicename'] then | |
615 | print('PDNSFeaturesTest: case 2') | |
616 | dq.rcode = pdns.SERVFAIL | |
617 | end | |
618 | return true | |
619 | end | |
620 | """ | |
621 | ||
622 | def testFeatures(self): | |
623 | query = dns.message.make_query('whatever.example.', 'TXT') | |
624 | res = self.sendUDPQuery(query) | |
625 | ||
626 | self.assertRcodeEqual(res, dns.rcode.NOERROR) | |
627 | ||
9fbfe39b RG |
628 | class PDNSGeneratingAnswerFromGettagTest(RecursorTest): |
629 | """Tests that we can generate answers from gettag""" | |
630 | ||
631 | _confdir = 'gettaganswers' | |
632 | _config_template = """ | |
633 | """ | |
634 | _lua_dns_script_file = """ | |
635 | local ffi = require("ffi") | |
636 | ||
637 | ffi.cdef[[ | |
638 | typedef struct pdns_ffi_param pdns_ffi_param_t; | |
639 | ||
640 | typedef enum | |
641 | { | |
642 | answer = 1, | |
643 | authority = 2, | |
644 | additional = 3 | |
645 | } pdns_record_place_t; | |
646 | ||
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); | |
650 | ]] | |
651 | ||
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); | |
658 | end | |
659 | end | |
660 | ||
661 | function preresolve (dq) | |
662 | -- refused everything if it comes that far | |
663 | dq.rcode = pdns.REFUSED | |
664 | return true | |
665 | end | |
666 | """ | |
667 | ||
668 | def testGettag(self): | |
669 | expectedAnswerRecords = [ | |
670 | dns.rrset.from_text('gettag-answers.powerdns.com.', 60, dns.rdataclass.IN, 'A', '192.0.2.1'), | |
671 | ] | |
672 | expectedAdditionalRecords = [ | |
673 | dns.rrset.from_text('not-powerdns.com.', 60, dns.rdataclass.IN, 'A', '192.0.2.2'), | |
674 | ] | |
675 | query = dns.message.make_query('gettag-answers.powerdns.com.', 'A') | |
676 | res = self.sendUDPQuery(query) | |
677 | ||
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) | |
4bfebc93 CH |
682 | self.assertEqual(res.answer, expectedAnswerRecords) |
683 | self.assertEqual(res.additional, expectedAdditionalRecords) | |
66c02cb7 RG |
684 | |
685 | class PDNSValidationStatesTest(RecursorTest): | |
686 | """Tests that we have access to the validation states from Lua""" | |
687 | ||
688 | _confdir = 'validation-states-from-lua' | |
9425e4ca | 689 | _config_template = """ |
66c02cb7 | 690 | dnssec=validate |
66c02cb7 RG |
691 | """ |
692 | _roothints = None | |
693 | _lua_config_file = """ | |
694 | """ | |
9425e4ca | 695 | |
66c02cb7 RG |
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 | |
720 | return true | |
721 | end | |
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 | |
726 | return true | |
727 | end | |
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 | |
731 | return true | |
732 | end | |
733 | if not isValidationStateBogus(dq.detailedValidationState) then | |
734 | pdnslog('DNSSEC detailed validation state should be Bogus and is not!') | |
735 | dq.rcode = pdns.REFUSED | |
736 | return true | |
737 | end | |
738 | end | |
739 | return false | |
740 | end | |
741 | """ | |
742 | ||
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) | |
40379147 RG |
749 | |
750 | class PolicyEventFilterOnFollowUpTest(RecursorTest): | |
63ad9c90 | 751 | """Tests the interaction between RPZ and followup queries (dns64, followCNAME) |
40379147 RG |
752 | """ |
753 | ||
754 | _confdir = 'policyeventfilter-followup' | |
755 | _config_template = """ | |
756 | """ | |
757 | _lua_config_file = """ | |
758 | rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz." }) | |
759 | """ % (_confdir) | |
760 | ||
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 | |
766 | return true | |
767 | end | |
768 | ||
769 | function policyEventFilter(event) | |
770 | event.appliedPolicy.policyKind = pdns.policykinds.NoAction | |
771 | return true | |
772 | end | |
773 | """ | |
774 | ||
775 | @classmethod | |
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. | |
780 | @ 3600 IN SOA {soa} | |
781 | secure.example.zone.rpz. 60 IN A 192.0.2.42 | |
782 | """.format(soa=cls._SOA)) | |
783 | super(PolicyEventFilterOnFollowUpTest, cls).generateRecursorConfig(confdir) | |
784 | ||
785 | def testA(self): | |
786 | expected = [ | |
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') | |
789 | ] | |
790 | query = dns.message.make_query('policyeventfilter-followup.test.powerdns.com.', 'A') | |
791 | ||
792 | for method in ("sendUDPQuery", "sendTCPQuery"): | |
793 | sender = getattr(self, method) | |
794 | res = sender(query) | |
795 | ||
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) | |
63ad9c90 OM |
800 | |
801 | class PolicyEventFilterOnFollowUpWithNativeDNS64Test(RecursorTest): | |
802 | """Tests the interaction between followup queries and native dns64 | |
803 | """ | |
804 | ||
805 | _confdir = 'policyeventfilter-followup-dns64' | |
806 | _config_template = """ | |
807 | dns64-prefix=1234::/96 | |
808 | """ | |
809 | _lua_config_file = """ | |
810 | """ | |
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 | |
816 | return true | |
817 | end | |
818 | ||
819 | """ | |
820 | ||
821 | def testAAAA(self): | |
822 | expected = [ | |
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') | |
826 | ] | |
827 | query = dns.message.make_query('mx1.secure.example', 'AAAA') | |
828 | ||
829 | for method in ("sendUDPQuery", "sendTCPQuery"): | |
830 | sender = getattr(self, method) | |
831 | res = sender(query) | |
832 | print(res) | |
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) | |
9996807c OM |
837 | |
838 | class LuaPostResolveFFITest(RecursorTest): | |
839 | """Tests postresolve_ffi interface""" | |
840 | ||
841 | _confdir = 'LuaPostResolveFFITest' | |
842 | _config_template = """ | |
843 | """ | |
844 | _lua_dns_script_file = """ | |
845 | local ffi = require("ffi") | |
846 | ||
847 | ffi.cdef[[ | |
848 | typedef struct pdns_postresolve_ffi_handle pdns_postresolve_ffi_handle_t; | |
849 | ||
850 | typedef enum | |
851 | { | |
852 | pdns_record_place_answer = 1, | |
853 | pdns_record_place_authority = 2, | |
854 | pdns_record_place_additional = 3 | |
855 | } pdns_record_place_t; | |
856 | ||
857 | typedef enum | |
858 | { | |
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; | |
866 | ||
867 | typedef struct pdns_ffi_record { | |
868 | const char* name; | |
e1747b11 | 869 | size_t name_len; |
9996807c | 870 | const char* content; |
e1747b11 | 871 | size_t content_len; |
9996807c OM |
872 | uint32_t ttl; |
873 | pdns_record_place_t place; | |
874 | uint16_t type; | |
875 | } pdns_ffi_record_t; | |
876 | ||
877 | const char* pdns_postresolve_ffi_handle_get_qname(pdns_postresolve_ffi_handle_t* ref); | |
e1747b11 | 878 | const char* pdns_postresolve_ffi_handle_get_qname_raw(pdns_postresolve_ffi_handle_t* ref, const char** name, size_t* len); |
9996807c OM |
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); | |
e1747b11 | 881 | void pdns_postresolve_ffi_handle_set_rcode(const pdns_postresolve_ffi_handle_t* ref, uint16_t rcode); |
9996807c OM |
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); | |
889 | ]] | |
890 | ||
891 | ||
892 | function tohex(str) | |
893 | return (str:gsub('.', function (c) return string.format('%02X', string.byte(c)) end)) | |
894 | end | |
895 | function toA(str) | |
896 | return (str:gsub('.', function (c) return string.format('%d.', string.byte(c)) end)) | |
897 | end | |
898 | ||
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) | |
902 | ||
903 | if qname == "example" and qtype == pdns.SOA | |
904 | then | |
905 | local addr = ffi.string(ffi.C.pdns_postresolve_ffi_handle_get_authip(ref)) | |
9996807c OM |
906 | if string.sub(addr, -3) ~= ".10" and string.sub(addr, -3) ~= ".18" |
907 | then | |
908 | -- signal error by clearing all | |
909 | ffi.C.pdns_postresolve_ffi_handle_clear_records(ref) | |
910 | end | |
984f6bfc OM |
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" | |
916 | then | |
917 | -- pdnslog("Error "..tohex(q)) | |
918 | -- signal error by clearing all | |
919 | ffi.C.pdns_postresolve_ffi_handle_clear_records(ref) | |
920 | end | |
921 | -- as a bonus check from which auth the data came | |
9996807c OM |
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 | |
927 | then | |
928 | -- signal error by clearing all | |
929 | ffi.C.pdns_postresolve_ffi_handle_clear_records(ref) | |
930 | end | |
9996807c OM |
931 | ffi.C.pdns_postresolve_ffi_handle_set_appliedpolicy_kind(ref, "pdns_policy_kind_noaction") |
932 | return true | |
933 | end | |
934 | if qname == "example" and qtype == pdns.TXT | |
935 | then | |
936 | ffi.C.pdns_postresolve_ffi_handle_set_appliedpolicy_kind(ref, "pdns_policy_kind_drop") | |
937 | return true | |
938 | end | |
939 | if qname == "ns1.example" and qtype == pdns.A | |
940 | then | |
941 | ffi.C.pdns_postresolve_ffi_handle_set_appliedpolicy_kind(ref, "pdns_policy_kind_nxdomain") | |
942 | return true | |
943 | end | |
944 | if qname == "ns1.example" and qtype == pdns.AAAA | |
945 | then | |
946 | ffi.C.pdns_postresolve_ffi_handle_set_appliedpolicy_kind(ref, "pdns_policy_kind_nodata") | |
947 | return true | |
948 | end | |
949 | if qname == "ns2.example" and qtype == pdns.A | |
950 | then | |
951 | ffi.C.pdns_postresolve_ffi_handle_set_appliedpolicy_kind(ref, "pdns_policy_kind_truncate") | |
952 | return true | |
953 | end | |
954 | ||
955 | if qname ~= "postresolve_ffi.example" or (qtype ~= pdns.A and qtype ~= pdns.AAAA) | |
956 | then | |
957 | return false | |
958 | end | |
959 | ||
960 | local record = ffi.new("pdns_ffi_record_t") | |
961 | local i = 0 | |
962 | ||
963 | while ffi.C.pdns_postresolve_ffi_handle_get_record(ref, i, record, false) | |
964 | do | |
965 | local content = ffi.string(record.content, record.content_len) | |
966 | local name = ffi.string(record.name) | |
967 | if name ~= "postresolve_ffi.example" | |
968 | then | |
969 | return false | |
970 | end | |
971 | if record.type == pdns.A and content == "1.2.3.4" | |
972 | then | |
973 | ffi.C.pdns_postresolve_ffi_handle_set_record(ref, i, "0.1.2.3", 7, false); | |
9c20dc12 | 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); |
9996807c OM |
975 | elseif record.type == pdns.AAAA and content == "::1" |
976 | then | |
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); | |
978 | end | |
979 | i = i + 1 | |
980 | end | |
984f6bfc OM |
981 | -- loop again using raw |
982 | i = 0 | |
983 | while ffi.C.pdns_postresolve_ffi_handle_get_record(ref, i, record, true) | |
984 | do | |
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" | |
989 | then | |
990 | ffi.C.pdns_postresolve_ffi_handle_clear_records(ref) | |
991 | end | |
992 | i = i + 1 | |
993 | end | |
9996807c OM |
994 | return true |
995 | end | |
996 | """ | |
997 | ||
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) | |
1004 | ||
1005 | def testDROP(self): | |
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) | |
a4d2d116 | 1009 | self.assertEqual(res, None) |
9996807c OM |
1010 | |
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) | |
1017 | ||
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) | |
1024 | ||
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']) | |
1032 | ||
1033 | ||
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'), | |
1038 | ] | |
9c20dc12 OM |
1039 | expectedAdditionalRecords = [ |
1040 | dns.rrset.from_text('add.postresolve_ffi.example.', 60, dns.rdataclass.IN, 'A', '4.5.6.7'), | |
1041 | ] | |
1042 | ||
9996807c OM |
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) | |
9c20dc12 | 1048 | self.assertEqual(len(res.additional), 1) |
9996807c | 1049 | self.assertEqual(res.answer, expectedAnswerRecords) |
9c20dc12 | 1050 | self.assertEqual(res.additional, expectedAdditionalRecords) |
9996807c OM |
1051 | |
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'), | |
1056 | ] | |
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) |