]> git.ipfire.org Git - thirdparty/pdns.git/blame - regression-tests.recursor-dnssec/test_Lua.py
Merge pull request #7260 from spirillen/patch-1
[thirdparty/pdns.git] / regression-tests.recursor-dnssec / test_Lua.py
CommitLineData
18e0e4df
RG
1import clientsubnetoption
2import cookiesoption
3import dns
4import os
d19bcbf0
RG
5import threading
6import time
7
8from twisted.internet.protocol import DatagramProtocol
9from twisted.internet import reactor
18e0e4df
RG
10
11from recursortests import RecursorTest
12
13class GettagRecursorTest(RecursorTest):
14 _confdir = 'LuaGettag'
15 _config_template = """
16 log-common-errors=yes
17 gettag-needs-edns-options=yes
18 """
19 _lua_dns_script_file = """
20 function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp)
21
22 local tags = {}
23 local data = {}
24
25 -- make sure we can pass data around to the other hooks
26 data['canary'] = 'from-gettag'
27
28 -- test that the remote addr is valid
29 if remote:toString() ~= '127.0.0.1' then
30 pdnslog("invalid remote")
31 table.insert(tags, 'invalid remote '..remote:toString())
32 return 1, tags, data
33 end
34
35 -- test that the local addr is valid
36 if localip:toString() ~= '127.0.0.1' then
37 pdnslog("invalid local")
38 table.insert(tags, 'invalid local '..localip:toString())
39 return 1, tags, data
40 end
41
42 if not ednssubnet:empty() then
43 table.insert(tags, 'edns-subnet-'..ednssubnet:toString())
44 end
45
46 for k,v in pairs(ednsoptions) do
47 table.insert(tags, 'ednsoption-'..k..'-count-'..v:count())
48 local len = 0
49 local values = v:getValues()
50 for j,l in pairs(values) do
51 len = len + l:len()
52
53 -- check that the old interface (before 4.2.0) still works
54 if j == 0 then
55 if l:len() ~= v.size then
56 table.insert(tags, 'size obtained via the old edns option interface does not match')
57 end
58 value = v:getContent()
59 if value ~= l then
60 table.insert(tags, 'content obtained via the old edns option interface does not match')
61 end
62 end
63 end
64 table.insert(tags, 'ednsoption-'..k..'-total-len-'..len)
65 end
66
67 if tcp then
68 table.insert(tags, 'gettag-tcp')
69 end
70
71 -- test that tags are passed to other hooks
72 table.insert(tags, qname:toString())
73 table.insert(tags, 'gettag-qtype-'..qtype)
74
75 return 0, tags, data
76 end
77
78 function preresolve(dq)
79
80 -- test that we are getting the tags set by gettag()
81 -- and also getting the correct qname
82 local found = false
83 for _, tag in pairs(dq:getPolicyTags()) do
84 if dq.qname:equal(tag) then
85 found = true
86 end
87 dq:addAnswer(pdns.TXT, '"'..tag..'"')
88 end
89
90 if not found then
91 pdnslog("not valid tag found")
92 dq.rcode = pdns.REFUSED
93 return true
94 end
95
96 if dq.data['canary'] ~= 'from-gettag' then
97 pdnslog("did not get any data from gettag")
98 dq.rcode = pdns.REFUSED
99 return true
100 end
101
102 if dq.qtype == pdns.A then
103 dq:addAnswer(pdns.A, '192.0.2.1')
104 elseif dq.qtype == pdns.AAAA then
105 dq:addAnswer(pdns.AAAA, '2001:db8::1')
106 end
107
108 return true
109 end
110 """
111
112 @classmethod
113 def setUpClass(cls):
114
115 cls.setUpSockets()
116 confdir = os.path.join('configs', cls._confdir)
117 cls.createConfigDir(confdir)
118 cls.generateRecursorConfig(confdir)
119 cls.startRecursor(confdir, cls._recursorPort)
120
121 @classmethod
122 def tearDownClass(cls):
123 cls.tearDownRecursor()
124
125 def assertResponseMatches(self, query, expectedRRs, response):
126 expectedResponse = dns.message.make_response(query)
127
128 if query.flags & dns.flags.RD:
129 expectedResponse.flags |= dns.flags.RA
130 if query.flags & dns.flags.CD:
131 expectedResponse.flags |= dns.flags.CD
132
133 expectedResponse.answer = expectedRRs
134 print(expectedResponse)
135 print(response)
136 self.assertEquals(response, expectedResponse)
137
138 def testA(self):
139 name = 'gettag.lua.'
140 expected = [
141 dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.1'),
142 dns.rrset.from_text_list(name, 0, dns.rdataclass.IN, 'TXT', [ name, 'gettag-qtype-1'])
143 ]
144 query = dns.message.make_query(name, 'A', want_dnssec=True)
145 query.flags |= dns.flags.CD
146 res = self.sendUDPQuery(query)
147 self.assertResponseMatches(query, expected, res)
148
149 def testTCPA(self):
150 name = 'gettag-tcpa.lua.'
151 expected = [
152 dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.1'),
153 dns.rrset.from_text_list(name, 0, dns.rdataclass.IN, 'TXT', [ name, 'gettag-qtype-1', 'gettag-tcp'])
154 ]
155 query = dns.message.make_query(name, 'A', want_dnssec=True)
156 query.flags |= dns.flags.CD
157 res = self.sendTCPQuery(query)
158 self.assertResponseMatches(query, expected, res)
159
160 def testAAAA(self):
161 name = 'gettag-aaaa.lua.'
162 expected = [
163 dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'AAAA', '2001:db8::1'),
164 dns.rrset.from_text_list(name, 0, dns.rdataclass.IN, 'TXT', [ name, 'gettag-qtype-28'])
165 ]
166 query = dns.message.make_query(name, 'AAAA', want_dnssec=True)
167 query.flags |= dns.flags.CD
168 res = self.sendUDPQuery(query)
169 self.assertResponseMatches(query, expected, res)
170
171 def testSubnet(self):
172 name = 'gettag-subnet.lua.'
173 subnet = '192.0.2.255'
174 subnetMask = 32
175 ecso = clientsubnetoption.ClientSubnetOption(subnet, subnetMask)
176 expected = [
177 dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.1'),
178 dns.rrset.from_text_list(name, 0, dns.rdataclass.IN, 'TXT', [name, 'gettag-qtype-1', 'edns-subnet-' + subnet + '/' + str(subnetMask),
179 'ednsoption-8-count-1', 'ednsoption-8-total-len-8']),
180 ]
181 query = dns.message.make_query(name, 'A', want_dnssec=True, options=[ecso])
182 query.flags |= dns.flags.CD
183 res = self.sendUDPQuery(query)
184 self.assertResponseMatches(query, expected, res)
185
186 def testEDNSOptions(self):
187 name = 'gettag-ednsoptions.lua.'
188 subnet = '192.0.2.255'
189 subnetMask = 32
190 ecso = clientsubnetoption.ClientSubnetOption(subnet, subnetMask)
191 eco1 = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
192 eco2 = cookiesoption.CookiesOption(b'deadc0de', b'deadc0de')
193
194 expected = [
195 dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.1'),
196 dns.rrset.from_text_list(name, 0, dns.rdataclass.IN, 'TXT', [name, 'gettag-qtype-1', 'edns-subnet-' + subnet + '/' + str(subnetMask),
197 'ednsoption-10-count-2', 'ednsoption-10-total-len-32',
198 'ednsoption-8-count-1', 'ednsoption-8-total-len-8'
199 ]),
200 ]
201 query = dns.message.make_query(name, 'A', want_dnssec=True, options=[eco1,ecso,eco2])
202 query.flags |= dns.flags.CD
203 res = self.sendUDPQuery(query)
204 self.assertResponseMatches(query, expected, res)
205
d19bcbf0
RG
206hooksReactorRunning = False
207
208class UDPHooksResponder(DatagramProtocol):
209
210 def datagramReceived(self, datagram, address):
211 request = dns.message.from_wire(datagram)
212
213 response = dns.message.make_response(request)
214 response.flags |= dns.flags.AA
215
216 if request.question[0].name == dns.name.from_text('nxdomain.luahooks.example.'):
217 soa = dns.rrset.from_text('luahooks.example.', 86400, dns.rdataclass.IN, 'SOA', 'ns.luahooks.example. hostmaster.luahooks.example. 1 3600 3600 3600 1')
218 response.authority.append(soa)
219 response.set_rcode(dns.rcode.NXDOMAIN)
220
221 elif request.question[0].name == dns.name.from_text('nodata.luahooks.example.'):
222 soa = dns.rrset.from_text('luahooks.example.', 86400, dns.rdataclass.IN, 'SOA', 'ns.luahooks.example. hostmaster.luahooks.example. 1 3600 3600 3600 1')
223 response.authority.append(soa)
224
225 elif request.question[0].name == dns.name.from_text('postresolve.luahooks.example.'):
226 answer = dns.rrset.from_text('postresolve.luahooks.example.', 3600, dns.rdataclass.IN, 'A', '192.0.2.1')
227 response.answer.append(answer)
228
229 self.transport.write(response.to_wire(), address)
230
231class LuaHooksRecursorTest(RecursorTest):
232 _confdir = 'LuaHooks'
233 _config_template = """
234forward-zones=luahooks.example=%s.23
235log-common-errors=yes
236quiet=no
237 """ % (os.environ['PREFIX'])
238 _lua_dns_script_file = """
239
240 allowedips = newNMG()
241 allowedips:addMask("%s.0/24")
242
243 function ipfilter(remoteip, localip, dh)
244 -- allow only 127.0.0.1 and AD=0
245 if allowedips:match(remoteip) and not dh:getAD() then
246 return false
247 end
248
249 return true
250 end
251
252 function nodata(dq)
253 if dq.qtype == pdns.AAAA and dq.qname == newDN("nodata.luahooks.example.") then
254 dq:addAnswer(pdns.AAAA, "2001:DB8::1")
255 return true
256 end
257
258 return false
259 end
260
261 function nxdomain(dq)
262 if dq.qtype == pdns.A and dq.qname == newDN("nxdomain.luahooks.example.") then
263 dq.rcode=0
264 dq:addAnswer(pdns.A, "192.0.2.1")
265 return true
266 end
267
268 return false
269 end
270
271 function postresolve(dq)
272 if dq.qtype == pdns.A and dq.qname == newDN("postresolve.luahooks.example.") then
273 local records = dq:getRecords()
274 for k,v in pairs(records) do
275 if v.type == pdns.A and v:getContent() == "192.0.2.1" then
276 v:changeContent("192.0.2.42")
277 v.ttl=1
278 end
279 end
280 dq:setRecords(records)
281 return true
282 end
283
284 return false
285 end
286
287 function preoutquery(dq)
288 if dq.remoteaddr:equal(newCA("%s.23")) and dq.qname == newDN("preout.luahooks.example.") and dq.qtype == pdns.A then
289 dq.rcode = -3 -- "kill"
290 return true
291 end
292
293 return false
294 end
295
296 """ % (os.environ['PREFIX'], os.environ['PREFIX'])
297
298 @classmethod
299 def startResponders(cls):
300 global hooksReactorRunning
301 print("Launching responders..")
302
303 address = cls._PREFIX + '.23'
304 port = 53
305
306 if not hooksReactorRunning:
307 reactor.listenUDP(port, UDPHooksResponder(), interface=address)
308 hooksReactorRunning = True
309
310 if not reactor.running:
311 cls._UDPResponder = threading.Thread(name='UDP Hooks Responder', target=reactor.run, args=(False,))
312 cls._UDPResponder.setDaemon(True)
313 cls._UDPResponder.start()
314
315 @classmethod
316 def setUpClass(cls):
317 cls.setUpSockets()
318
319 cls.startResponders()
320
321 confdir = os.path.join('configs', cls._confdir)
322 cls.createConfigDir(confdir)
323
324 cls.generateRecursorConfig(confdir)
325 cls.startRecursor(confdir, cls._recursorPort)
326
327 print("Launching tests..")
328
329 @classmethod
330 def tearDownClass(cls):
331 cls.tearDownRecursor()
332
333 def testNoData(self):
334 expected = dns.rrset.from_text('nodata.luahooks.example.', 3600, dns.rdataclass.IN, 'AAAA', '2001:DB8::1')
335 query = dns.message.make_query('nodata.luahooks.example.', 'AAAA', 'IN')
336 res = self.sendUDPQuery(query)
337
338 self.assertRcodeEqual(res, dns.rcode.NOERROR)
339 self.assertRRsetInAnswer(res, expected)
340
341 def testVanillaNXD(self):
342 #expected = dns.rrset.from_text('nxdomain.luahooks.example.', 3600, dns.rdataclass.IN, 'A', '192.0.2.1')
343 query = dns.message.make_query('nxdomain.luahooks.example.', 'AAAA', 'IN')
344 res = self.sendUDPQuery(query)
345
346 self.assertRcodeEqual(res, dns.rcode.NXDOMAIN)
347
348 def testHookedNXD(self):
349 expected = dns.rrset.from_text('nxdomain.luahooks.example.', 3600, dns.rdataclass.IN, 'A', '192.0.2.1')
350 query = dns.message.make_query('nxdomain.luahooks.example.', 'A', 'IN')
351 res = self.sendUDPQuery(query)
352
353 self.assertRcodeEqual(res, dns.rcode.NOERROR)
354 self.assertRRsetInAnswer(res, expected)
355
356 def testPostResolve(self):
357 expected = dns.rrset.from_text('postresolve.luahooks.example.', 1, dns.rdataclass.IN, 'A', '192.0.2.42')
358 query = dns.message.make_query('postresolve.luahooks.example.', 'A', 'IN')
359 res = self.sendUDPQuery(query)
360
361 self.assertRcodeEqual(res, dns.rcode.NOERROR)
362 self.assertRRsetInAnswer(res, expected)
363 self.assertEqual(res.answer[0].ttl, 1)
364
365 def testIPFilterHeader(self):
366 query = dns.message.make_query('ipfiler.luahooks.example.', 'A', 'IN')
367 query.flags |= dns.flags.AD
368 res = self.sendUDPQuery(query)
369 self.assertEqual(res, None)
370
371 def testPreOutInterceptedQuery(self):
372 query = dns.message.make_query('preout.luahooks.example.', 'A', 'IN')
373 res = self.sendUDPQuery(query)
374 self.assertRcodeEqual(res, dns.rcode.SERVFAIL)
375
376 def testPreOutNotInterceptedQuery(self):
377 query = dns.message.make_query('preout.luahooks.example.', 'AAAA', 'IN')
378 res = self.sendUDPQuery(query)
379 self.assertRcodeEqual(res, dns.rcode.NOERROR)