]>
Commit | Line | Data |
---|---|---|
ec5f5c6b | 1 | #!/usr/bin/env python |
26b86deb | 2 | import base64 |
ec5f5c6b | 3 | import os |
e8680f95 | 4 | import socket |
856c35e3 | 5 | import time |
9fe42298 | 6 | import unittest |
b1bec9f0 | 7 | import dns |
ec5f5c6b RG |
8 | from dnsdisttests import DNSDistTest |
9 | ||
10 | class TestAdvancedFixupCase(DNSDistTest): | |
11 | ||
ec5f5c6b RG |
12 | _config_template = """ |
13 | truncateTC(true) | |
14 | fixupCase(true) | |
15 | newServer{address="127.0.0.1:%s"} | |
16 | """ | |
17 | ||
ec5f5c6b RG |
18 | def testAdvancedFixupCase(self): |
19 | """ | |
617dfe22 RG |
20 | Advanced: Fixup Case |
21 | ||
ec5f5c6b RG |
22 | Send a query with lower and upper chars, |
23 | make the backend return a lowercase version, | |
24 | check that dnsdist fixes the response. | |
25 | """ | |
26 | name = 'fiXuPCasE.advanced.tests.powerdns.com.' | |
27 | query = dns.message.make_query(name, 'A', 'IN') | |
28 | lowercasequery = dns.message.make_query(name.lower(), 'A', 'IN') | |
29 | response = dns.message.make_response(lowercasequery) | |
30 | expectedResponse = dns.message.make_response(query) | |
31 | rrset = dns.rrset.from_text(name, | |
32 | 3600, | |
33 | dns.rdataclass.IN, | |
34 | dns.rdatatype.A, | |
35 | '127.0.0.1') | |
36 | response.answer.append(rrset) | |
37 | expectedResponse.answer.append(rrset) | |
38 | ||
6ca2e796 RG |
39 | for method in ("sendUDPQuery", "sendTCPQuery"): |
40 | sender = getattr(self, method) | |
41 | (receivedQuery, receivedResponse) = sender(query, response) | |
42 | self.assertTrue(receivedQuery) | |
43 | self.assertTrue(receivedResponse) | |
44 | receivedQuery.id = query.id | |
4bfebc93 CH |
45 | self.assertEqual(query, receivedQuery) |
46 | self.assertEqual(expectedResponse, receivedResponse) | |
ec5f5c6b | 47 | |
ec5f5c6b RG |
48 | class TestAdvancedACL(DNSDistTest): |
49 | ||
ec5f5c6b RG |
50 | _config_template = """ |
51 | newServer{address="127.0.0.1:%s"} | |
52 | """ | |
18a0e7c6 | 53 | _acl = ['192.0.2.1/32'] |
ec5f5c6b RG |
54 | |
55 | def testACLBlocked(self): | |
56 | """ | |
617dfe22 RG |
57 | Advanced: ACL blocked |
58 | ||
ec5f5c6b RG |
59 | Send an A query to "tests.powerdns.com.", |
60 | we expect no response since 127.0.0.1 is not on the | |
61 | ACL. | |
62 | """ | |
903853f4 | 63 | name = 'tests.powerdns.com.' |
7791f83a | 64 | query = dns.message.make_query(name, 'A', 'IN') |
7791f83a | 65 | |
6ca2e796 RG |
66 | for method in ("sendUDPQuery", "sendTCPQuery"): |
67 | sender = getattr(self, method) | |
68 | (_, receivedResponse) = sender(query, response=None, useQueue=False) | |
4bfebc93 | 69 | self.assertEqual(receivedResponse, None) |
903853f4 | 70 | |
7b9abc20 | 71 | class TestAdvancedStringOnlyServer(DNSDistTest): |
903853f4 RG |
72 | |
73 | _config_template = """ | |
7b9abc20 | 74 | newServer("127.0.0.1:%s") |
903853f4 | 75 | """ |
7791f83a | 76 | |
7b9abc20 | 77 | def testAdvancedStringOnlyServer(self): |
7791f83a | 78 | """ |
7b9abc20 | 79 | Advanced: "string-only" server is placed in the default pool |
7791f83a | 80 | """ |
7b9abc20 | 81 | name = 'string-only-server.advanced.tests.powerdns.com.' |
903853f4 RG |
82 | query = dns.message.make_query(name, 'A', 'IN') |
83 | response = dns.message.make_response(query) | |
7791f83a | 84 | rrset = dns.rrset.from_text(name, |
7b9abc20 | 85 | 3600, |
7791f83a | 86 | dns.rdataclass.IN, |
903853f4 RG |
87 | dns.rdatatype.A, |
88 | '192.0.2.1') | |
89 | response.answer.append(rrset) | |
7791f83a | 90 | |
7b9abc20 RG |
91 | for method in ("sendUDPQuery", "sendTCPQuery"): |
92 | sender = getattr(self, method) | |
93 | (receivedQuery, receivedResponse) = sender(query, response) | |
94 | self.assertTrue(receivedQuery) | |
95 | self.assertTrue(receivedResponse) | |
96 | receivedQuery.id = query.id | |
97 | self.assertEqual(query, receivedQuery) | |
98 | self.assertEqual(response, receivedResponse) | |
7791f83a | 99 | |
7b9abc20 RG |
100 | @unittest.skipIf('SKIP_INCLUDEDIR_TESTS' in os.environ, 'IncludeDir tests are disabled') |
101 | class TestAdvancedIncludeDir(DNSDistTest): | |
e7a1029c RG |
102 | |
103 | _config_template = """ | |
7b9abc20 RG |
104 | -- this directory contains a file allowing includedir.advanced.tests.powerdns.com. |
105 | includeDirectory('test-include-dir') | |
e7a1029c RG |
106 | newServer{address="127.0.0.1:%s"} |
107 | """ | |
e7a1029c | 108 | |
7b9abc20 | 109 | def testAdvancedIncludeDirAllowed(self): |
e7a1029c | 110 | """ |
7b9abc20 RG |
111 | Advanced: includeDirectory() |
112 | """ | |
113 | name = 'includedir.advanced.tests.powerdns.com.' | |
e7a1029c RG |
114 | query = dns.message.make_query(name, 'A', 'IN') |
115 | response = dns.message.make_response(query) | |
116 | rrset = dns.rrset.from_text(name, | |
117 | 3600, | |
118 | dns.rdataclass.IN, | |
119 | dns.rdatatype.A, | |
7b9abc20 | 120 | '192.0.2.1') |
e7a1029c RG |
121 | response.answer.append(rrset) |
122 | ||
6ca2e796 RG |
123 | for method in ("sendUDPQuery", "sendTCPQuery"): |
124 | sender = getattr(self, method) | |
125 | (receivedQuery, receivedResponse) = sender(query, response) | |
126 | self.assertTrue(receivedQuery) | |
127 | self.assertTrue(receivedResponse) | |
128 | receivedQuery.id = query.id | |
4bfebc93 | 129 | self.assertEqual(query, receivedQuery) |
4bfebc93 | 130 | self.assertEqual(response, receivedResponse) |
856c35e3 | 131 | |
7b9abc20 RG |
132 | # this one should be refused |
133 | name = 'notincludedir.advanced.tests.powerdns.com.' | |
8eb84a56 | 134 | query = dns.message.make_query(name, 'A', 'IN') |
8eb84a56 | 135 | query.flags &= ~dns.flags.RD |
7b9abc20 RG |
136 | expectedResponse = dns.message.make_response(query) |
137 | expectedResponse.set_rcode(dns.rcode.REFUSED) | |
8eb84a56 RG |
138 | |
139 | for method in ("sendUDPQuery", "sendTCPQuery"): | |
140 | sender = getattr(self, method) | |
141 | (_, receivedResponse) = sender(query, response=None, useQueue=False) | |
7b9abc20 | 142 | self.assertEqual(receivedResponse, expectedResponse) |
8eb84a56 | 143 | |
7b9abc20 | 144 | class TestStatNodeRespRingSince(DNSDistTest): |
b774d943 | 145 | |
7b9abc20 RG |
146 | _consoleKey = DNSDistTest.generateConsoleKey() |
147 | _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii') | |
148 | _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort'] | |
b774d943 | 149 | _config_template = """ |
7b9abc20 RG |
150 | setKey("%s") |
151 | controlSocket("127.0.0.1:%s") | |
152 | s1 = newServer{address="127.0.0.1:%s"} | |
153 | s1:setUp() | |
154 | function visitor(node, self, childstat) | |
155 | table.insert(nodesSeen, node.fullname) | |
156 | end | |
157 | """ | |
b774d943 | 158 | |
7b9abc20 RG |
159 | def testStatNodeRespRingSince(self): |
160 | """ | |
161 | Advanced: StatNodeRespRing with optional since parameter | |
b774d943 | 162 | |
7b9abc20 RG |
163 | """ |
164 | name = 'statnodesince.advanced.tests.powerdns.com.' | |
165 | query = dns.message.make_query(name, 'A', 'IN') | |
166 | response = dns.message.make_response(query) | |
167 | rrset = dns.rrset.from_text(name, | |
168 | 1, | |
169 | dns.rdataclass.IN, | |
170 | dns.rdatatype.A, | |
171 | '127.0.0.1') | |
172 | response.answer.append(rrset) | |
b774d943 | 173 | |
7b9abc20 RG |
174 | (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) |
175 | self.assertTrue(receivedQuery) | |
176 | self.assertTrue(receivedResponse) | |
177 | receivedQuery.id = query.id | |
178 | self.assertEqual(query, receivedQuery) | |
179 | self.assertEqual(response, receivedResponse) | |
b774d943 | 180 | |
7b9abc20 RG |
181 | self.sendConsoleCommand("nodesSeen = {}") |
182 | self.sendConsoleCommand("statNodeRespRing(visitor)") | |
183 | nodes = self.sendConsoleCommand("str = '' for key,value in pairs(nodesSeen) do str = str..value..\"\\n\" end return str") | |
184 | nodes = nodes.strip("\n") | |
185 | self.assertEqual(nodes, """statnodesince.advanced.tests.powerdns.com. | |
186 | advanced.tests.powerdns.com. | |
187 | tests.powerdns.com. | |
188 | powerdns.com. | |
189 | com.""") | |
b774d943 | 190 | |
7b9abc20 RG |
191 | self.sendConsoleCommand("nodesSeen = {}") |
192 | self.sendConsoleCommand("statNodeRespRing(visitor, 0)") | |
193 | nodes = self.sendConsoleCommand("str = '' for key,value in pairs(nodesSeen) do str = str..value..\"\\n\" end return str") | |
194 | nodes = nodes.strip("\n") | |
195 | self.assertEqual(nodes, """statnodesince.advanced.tests.powerdns.com. | |
196 | advanced.tests.powerdns.com. | |
197 | tests.powerdns.com. | |
198 | powerdns.com. | |
199 | com.""") | |
b774d943 | 200 | |
7b9abc20 | 201 | time.sleep(5) |
b774d943 | 202 | |
7b9abc20 RG |
203 | self.sendConsoleCommand("nodesSeen = {}") |
204 | self.sendConsoleCommand("statNodeRespRing(visitor)") | |
205 | nodes = self.sendConsoleCommand("str = '' for key,value in pairs(nodesSeen) do str = str..value..\"\\n\" end return str") | |
206 | nodes = nodes.strip("\n") | |
207 | self.assertEqual(nodes, """statnodesince.advanced.tests.powerdns.com. | |
208 | advanced.tests.powerdns.com. | |
209 | tests.powerdns.com. | |
210 | powerdns.com. | |
211 | com.""") | |
b774d943 | 212 | |
7b9abc20 RG |
213 | self.sendConsoleCommand("nodesSeen = {}") |
214 | self.sendConsoleCommand("statNodeRespRing(visitor, 5)") | |
215 | nodes = self.sendConsoleCommand("str = '' for key,value in pairs(nodesSeen) do str = str..value..\"\\n\" end return str") | |
216 | nodes = nodes.strip("\n") | |
217 | self.assertEqual(nodes, """""") | |
b774d943 | 218 | |
7b9abc20 RG |
219 | self.sendConsoleCommand("nodesSeen = {}") |
220 | self.sendConsoleCommand("statNodeRespRing(visitor, 10)") | |
221 | nodes = self.sendConsoleCommand("str = '' for key,value in pairs(nodesSeen) do str = str..value..\"\\n\" end return str") | |
222 | nodes = nodes.strip("\n") | |
223 | self.assertEqual(nodes, """statnodesince.advanced.tests.powerdns.com. | |
224 | advanced.tests.powerdns.com. | |
225 | tests.powerdns.com. | |
226 | powerdns.com. | |
227 | com.""") | |
b774d943 | 228 | |
7b9abc20 | 229 | class TestAdvancedGetLocalPort(DNSDistTest): |
b774d943 | 230 | |
7b9abc20 RG |
231 | _config_template = """ |
232 | function answerBasedOnLocalPort(dq) | |
233 | local port = dq.localaddr:getPort() | |
234 | return DNSAction.Spoof, "port-was-"..port..".local-port.advanced.tests.powerdns.com." | |
f7e6a5ce | 235 | end |
7b9abc20 RG |
236 | addAction("local-port.advanced.tests.powerdns.com.", LuaAction(answerBasedOnLocalPort)) |
237 | newServer{address="127.0.0.1:%s"} | |
b774d943 RG |
238 | """ |
239 | ||
7b9abc20 | 240 | def testAdvancedGetLocalPort(self): |
b774d943 | 241 | """ |
7b9abc20 | 242 | Advanced: Return CNAME containing the local port |
b774d943 | 243 | """ |
7b9abc20 | 244 | name = 'local-port.advanced.tests.powerdns.com.' |
b774d943 RG |
245 | query = dns.message.make_query(name, 'A', 'IN') |
246 | # dnsdist set RA = RD for spoofed responses | |
247 | query.flags &= ~dns.flags.RD | |
248 | ||
249 | response = dns.message.make_response(query) | |
250 | rrset = dns.rrset.from_text(name, | |
251 | 60, | |
252 | dns.rdataclass.IN, | |
7b9abc20 RG |
253 | dns.rdatatype.CNAME, |
254 | 'port-was-{}.local-port.advanced.tests.powerdns.com.'.format(self._dnsDistPort)) | |
b774d943 RG |
255 | response.answer.append(rrset) |
256 | ||
257 | for method in ("sendUDPQuery", "sendTCPQuery"): | |
258 | sender = getattr(self, method) | |
259 | (_, receivedResponse) = sender(query, response=None, useQueue=False) | |
4bfebc93 | 260 | self.assertEqual(receivedResponse, response) |
b774d943 | 261 | |
7b9abc20 RG |
262 | class TestAdvancedGetLocalPortOnAnyBind(DNSDistTest): |
263 | ||
264 | _config_template = """ | |
265 | function answerBasedOnLocalPort(dq) | |
266 | local port = dq.localaddr:getPort() | |
267 | return DNSAction.Spoof, "port-was-"..port..".local-port-any.advanced.tests.powerdns.com." | |
268 | end | |
269 | addAction("local-port-any.advanced.tests.powerdns.com.", LuaAction(answerBasedOnLocalPort)) | |
e8680f95 | 270 | newServer{address="127.0.0.1:%d"} |
7b9abc20 | 271 | """ |
e8680f95 | 272 | _dnsDistListeningAddr = '0.0.0.0' |
7b9abc20 RG |
273 | |
274 | def testAdvancedGetLocalPortOnAnyBind(self): | |
b774d943 | 275 | """ |
7b9abc20 | 276 | Advanced: Return CNAME containing the local port for an ANY bind |
b774d943 | 277 | """ |
7b9abc20 RG |
278 | name = 'local-port-any.advanced.tests.powerdns.com.' |
279 | query = dns.message.make_query(name, 'A', 'IN') | |
b774d943 RG |
280 | # dnsdist set RA = RD for spoofed responses |
281 | query.flags &= ~dns.flags.RD | |
282 | ||
283 | response = dns.message.make_response(query) | |
7b9abc20 RG |
284 | rrset = dns.rrset.from_text(name, |
285 | 60, | |
286 | dns.rdataclass.IN, | |
287 | dns.rdatatype.CNAME, | |
288 | 'port-was-{}.local-port-any.advanced.tests.powerdns.com.'.format(self._dnsDistPort)) | |
289 | response.answer.append(rrset) | |
b774d943 RG |
290 | |
291 | for method in ("sendUDPQuery", "sendTCPQuery"): | |
292 | sender = getattr(self, method) | |
293 | (_, receivedResponse) = sender(query, response=None, useQueue=False) | |
4bfebc93 | 294 | self.assertEqual(receivedResponse, response) |
9a872eb7 | 295 | |
7b9abc20 | 296 | class TestAdvancedGetLocalAddressOnAnyBind(DNSDistTest): |
79ac0575 RG |
297 | |
298 | _config_template = """ | |
7b9abc20 RG |
299 | function answerBasedOnLocalAddress(dq) |
300 | local dest = tostring(dq.localaddr) | |
301 | local i, j = string.find(dest, "[0-9.]+") | |
302 | local addr = string.sub(dest, i, j) | |
303 | local dashAddr = string.gsub(addr, "[.]", "-") | |
304 | return DNSAction.Spoof, "address-was-"..dashAddr..".local-address-any.advanced.tests.powerdns.com." | |
305 | end | |
306 | addAction("local-address-any.advanced.tests.powerdns.com.", LuaAction(answerBasedOnLocalAddress)) | |
307 | newServer{address="127.0.0.1:%s"} | |
e8680f95 RG |
308 | addLocal('0.0.0.0:%d') |
309 | addLocal('[::]:%d') | |
79ac0575 | 310 | """ |
e8680f95 RG |
311 | _config_params = ['_testServerPort', '_dnsDistPort', '_dnsDistPort'] |
312 | _acl = ['127.0.0.1/32', '::1/128'] | |
313 | _skipListeningOnCL = True | |
f37871dd | 314 | _verboseMode = True |
79ac0575 | 315 | |
7b9abc20 | 316 | def testAdvancedGetLocalAddressOnAnyBind(self): |
79ac0575 | 317 | """ |
7b9abc20 | 318 | Advanced: Return CNAME containing the local address for an ANY bind |
79ac0575 | 319 | """ |
7b9abc20 | 320 | name = 'local-address-any.advanced.tests.powerdns.com.' |
79ac0575 RG |
321 | query = dns.message.make_query(name, 'A', 'IN') |
322 | # dnsdist set RA = RD for spoofed responses | |
323 | query.flags &= ~dns.flags.RD | |
324 | ||
325 | response = dns.message.make_response(query) | |
326 | rrset = dns.rrset.from_text(name, | |
327 | 60, | |
328 | dns.rdataclass.IN, | |
7b9abc20 RG |
329 | dns.rdatatype.CNAME, |
330 | 'address-was-127-0-0-1.local-address-any.advanced.tests.powerdns.com.') | |
79ac0575 RG |
331 | response.answer.append(rrset) |
332 | ||
333 | for method in ("sendUDPQuery", "sendTCPQuery"): | |
334 | sender = getattr(self, method) | |
335 | (_, receivedResponse) = sender(query, response=None, useQueue=False) | |
336 | self.assertEqual(receivedResponse, response) | |
337 | ||
e8680f95 RG |
338 | # now a bit more tricky, UDP-only IPv4 |
339 | response = dns.message.make_response(query) | |
340 | rrset = dns.rrset.from_text(name, | |
341 | 60, | |
342 | dns.rdataclass.IN, | |
343 | dns.rdatatype.CNAME, | |
344 | 'address-was-127-0-0-2.local-address-any.advanced.tests.powerdns.com.') | |
345 | response.answer.append(rrset) | |
346 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | |
23959718 | 347 | sock.settimeout(2.0) |
e8680f95 RG |
348 | sock.connect(('127.0.0.2', self._dnsDistPort)) |
349 | try: | |
350 | query = query.to_wire() | |
351 | sock.send(query) | |
352 | (data, remote) = sock.recvfrom(4096) | |
630eb526 | 353 | self.assertEqual(remote[0], '127.0.0.2') |
e8680f95 RG |
354 | except socket.timeout: |
355 | data = None | |
356 | ||
357 | self.assertTrue(data) | |
358 | receivedResponse = dns.message.from_wire(data) | |
359 | self.assertEqual(receivedResponse, response) | |
360 | ||
361 | def testAdvancedCheckSourceAddrOnAnyBind(self): | |
362 | """ | |
363 | Advanced: Check the source address on responses for an ANY bind | |
364 | """ | |
365 | name = 'source-addr-any.advanced.tests.powerdns.com.' | |
366 | query = dns.message.make_query(name, 'A', 'IN') | |
367 | # dnsdist set RA = RD for spoofed responses | |
368 | query.flags &= ~dns.flags.RD | |
369 | ||
370 | response = dns.message.make_response(query) | |
371 | rrset = dns.rrset.from_text(name, | |
372 | 60, | |
373 | dns.rdataclass.IN, | |
374 | dns.rdatatype.A, | |
375 | '192.0.2.42') | |
376 | response.answer.append(rrset) | |
377 | ||
378 | # a bit more tricky, UDP-only IPv4 | |
379 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | |
23959718 | 380 | sock.settimeout(2.0) |
e8680f95 RG |
381 | sock.connect(('127.0.0.2', self._dnsDistPort)) |
382 | self._toResponderQueue.put(response, True, 1.0) | |
383 | try: | |
384 | data = query.to_wire() | |
385 | sock.send(data) | |
386 | (data, remote) = sock.recvfrom(4096) | |
630eb526 | 387 | self.assertEqual(remote[0], '127.0.0.2') |
e8680f95 RG |
388 | except socket.timeout: |
389 | data = None | |
390 | ||
391 | self.assertTrue(data) | |
392 | receivedResponse = dns.message.from_wire(data) | |
393 | receivedQuery = self._fromResponderQueue.get(True, 1.0) | |
394 | receivedQuery.id = query.id | |
395 | self.assertEqual(receivedQuery, query) | |
396 | self.assertEqual(receivedResponse, response) | |
397 | ||
7d862cb3 AR |
398 | if 'SKIP_IPV6_TESTS' in os.environ: |
399 | return | |
400 | ||
e8680f95 RG |
401 | # a bit more tricky, UDP-only IPv6 |
402 | sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) | |
23959718 | 403 | sock.settimeout(2.0) |
e8680f95 RG |
404 | sock.connect(('::1', self._dnsDistPort)) |
405 | self._toResponderQueue.put(response, True, 1.0) | |
406 | try: | |
407 | data = query.to_wire() | |
408 | sock.send(data) | |
409 | (data, remote) = sock.recvfrom(4096) | |
630eb526 | 410 | self.assertEqual(remote[0], '::1') |
e8680f95 RG |
411 | except socket.timeout: |
412 | data = None | |
413 | ||
414 | self.assertTrue(data) | |
415 | receivedResponse = dns.message.from_wire(data) | |
f15f802a RG |
416 | receivedQuery = self._fromResponderQueue.get(True, 1.0) |
417 | receivedQuery.id = query.id | |
418 | self.assertEqual(receivedQuery, query) | |
419 | self.assertEqual(receivedResponse, response) | |
420 | ||
421 | class TestAdvancedGetLocalAddressOnNonDefaultLoopbackBind(DNSDistTest): | |
422 | # this one is tricky: on the loopback interface we cannot harvest the destination | |
423 | # address, so we exercise a different code path when we bind on a different address | |
424 | # than the default 127.0.0.1 one | |
425 | _config_template = """ | |
426 | newServer{address="127.0.0.1:%s"} | |
427 | addLocal('127.0.1.19:%d') | |
428 | """ | |
429 | _config_params = ['_testServerPort', '_dnsDistPort'] | |
430 | _acl = ['127.0.0.1/32'] | |
431 | _skipListeningOnCL = True | |
1953ab6c RG |
432 | _alternateListeningAddr = '127.0.1.19' |
433 | _alternateListeningPort = DNSDistTest._dnsDistPort | |
f15f802a RG |
434 | |
435 | def testAdvancedCheckSourceAddrOnNonDefaultLoopbackBind(self): | |
436 | """ | |
437 | Advanced: Check the source address used to reply on a non-default loopback bind | |
438 | """ | |
439 | name = 'source-addr-non-default-loopback.advanced.tests.powerdns.com.' | |
440 | query = dns.message.make_query(name, 'A', 'IN') | |
441 | response = dns.message.make_response(query) | |
442 | rrset = dns.rrset.from_text(name, | |
443 | 60, | |
444 | dns.rdataclass.IN, | |
445 | dns.rdatatype.A, | |
446 | '192.0.2.42') | |
447 | response.answer.append(rrset) | |
448 | ||
449 | # a bit more tricky, UDP-only IPv4 | |
450 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | |
23959718 | 451 | sock.settimeout(2.0) |
f15f802a RG |
452 | sock.connect(('127.0.1.19', self._dnsDistPort)) |
453 | self._toResponderQueue.put(response, True, 1.0) | |
454 | try: | |
455 | data = query.to_wire() | |
456 | sock.send(data) | |
457 | (data, remote) = sock.recvfrom(4096) | |
630eb526 | 458 | self.assertEqual(remote[0], '127.0.1.19') |
f15f802a RG |
459 | except socket.timeout: |
460 | data = None | |
461 | ||
462 | self.assertTrue(data) | |
463 | receivedResponse = dns.message.from_wire(data) | |
e8680f95 RG |
464 | receivedQuery = self._fromResponderQueue.get(True, 1.0) |
465 | receivedQuery.id = query.id | |
466 | self.assertEqual(receivedQuery, query) | |
467 | self.assertEqual(receivedResponse, response) | |
468 | ||
7b9abc20 RG |
469 | class TestAdvancedAllowHeaderOnly(DNSDistTest): |
470 | ||
471 | _config_template = """ | |
472 | newServer{address="127.0.0.1:%s"} | |
473 | setAllowEmptyResponse(true) | |
474 | """ | |
475 | ||
476 | def testHeaderOnlyRefused(self): | |
79ac0575 | 477 | """ |
7b9abc20 | 478 | Advanced: Header-only refused response |
79ac0575 | 479 | """ |
7b9abc20 RG |
480 | name = 'header-only-refused-response.advanced.tests.powerdns.com.' |
481 | query = dns.message.make_query(name, 'A', 'IN') | |
b774d943 RG |
482 | response = dns.message.make_response(query) |
483 | response.set_rcode(dns.rcode.REFUSED) | |
7b9abc20 | 484 | response.question = [] |
b774d943 RG |
485 | |
486 | for method in ("sendUDPQuery", "sendTCPQuery"): | |
487 | sender = getattr(self, method) | |
7b9abc20 RG |
488 | (receivedQuery, receivedResponse) = sender(query, response) |
489 | self.assertTrue(receivedQuery) | |
490 | receivedQuery.id = query.id | |
491 | self.assertEqual(query, receivedQuery) | |
492 | self.assertEqual(receivedResponse, response) | |
493 | ||
494 | def testHeaderOnlyNoErrorResponse(self): | |
495 | """ | |
496 | Advanced: Header-only NoError response should be allowed | |
497 | """ | |
498 | name = 'header-only-noerror-response.advanced.tests.powerdns.com.' | |
499 | query = dns.message.make_query(name, 'A', 'IN') | |
500 | response = dns.message.make_response(query) | |
501 | response.question = [] | |
502 | ||
503 | for method in ("sendUDPQuery", "sendTCPQuery"): | |
504 | sender = getattr(self, method) | |
505 | (receivedQuery, receivedResponse) = sender(query, response) | |
506 | self.assertTrue(receivedQuery) | |
507 | receivedQuery.id = query.id | |
508 | self.assertEqual(query, receivedQuery) | |
509 | self.assertEqual(receivedResponse, response) | |
510 | ||
511 | def testHeaderOnlyNXDResponse(self): | |
512 | """ | |
513 | Advanced: Header-only NXD response should be allowed | |
514 | """ | |
515 | name = 'header-only-nxd-response.advanced.tests.powerdns.com.' | |
516 | query = dns.message.make_query(name, 'A', 'IN') | |
517 | response = dns.message.make_response(query) | |
518 | response.set_rcode(dns.rcode.NXDOMAIN) | |
519 | response.question = [] | |
520 | ||
521 | for method in ("sendUDPQuery", "sendTCPQuery"): | |
522 | sender = getattr(self, method) | |
523 | (receivedQuery, receivedResponse) = sender(query, response) | |
524 | self.assertTrue(receivedQuery) | |
525 | receivedQuery.id = query.id | |
526 | self.assertEqual(query, receivedQuery) | |
4bfebc93 | 527 | self.assertEqual(receivedResponse, response) |
9a872eb7 RG |
528 | |
529 | class TestAdvancedDropEmptyQueries(DNSDistTest): | |
530 | ||
531 | _config_template = """ | |
532 | setDropEmptyQueries(true) | |
533 | newServer{address="127.0.0.1:%s"} | |
534 | """ | |
535 | ||
536 | def testAdvancedDropEmptyQueries(self): | |
537 | """ | |
538 | Advanced: Drop empty queries | |
539 | """ | |
540 | name = 'drop-empty-queries.advanced.tests.powerdns.com.' | |
541 | query = dns.message.Message() | |
542 | ||
543 | for method in ("sendUDPQuery", "sendTCPQuery"): | |
544 | sender = getattr(self, method) | |
545 | (_, receivedResponse) = sender(query, response=None, useQueue=False) | |
4bfebc93 | 546 | self.assertEqual(receivedResponse, None) |
7d808ff4 RG |
547 | |
548 | class TestProtocols(DNSDistTest): | |
549 | _config_template = """ | |
550 | function checkUDP(dq) | |
551 | if dq:getProtocol() ~= "Do53 UDP" then | |
552 | return DNSAction.Spoof, '1.2.3.4' | |
553 | end | |
554 | return DNSAction.None | |
555 | end | |
556 | ||
557 | function checkTCP(dq) | |
558 | if dq:getProtocol() ~= "Do53 TCP" then | |
559 | return DNSAction.Spoof, '1.2.3.4' | |
560 | end | |
561 | return DNSAction.None | |
562 | end | |
563 | ||
564 | addAction("udp.protocols.advanced.tests.powerdns.com.", LuaAction(checkUDP)) | |
565 | addAction("tcp.protocols.advanced.tests.powerdns.com.", LuaAction(checkTCP)) | |
566 | newServer{address="127.0.0.1:%s"} | |
567 | """ | |
568 | ||
569 | def testProtocolUDP(self): | |
570 | """ | |
571 | Advanced: Test DNSQuestion.Protocol over UDP | |
572 | """ | |
573 | name = 'udp.protocols.advanced.tests.powerdns.com.' | |
574 | query = dns.message.make_query(name, 'A', 'IN') | |
575 | response = dns.message.make_response(query) | |
576 | ||
577 | (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) | |
578 | receivedQuery.id = query.id | |
579 | self.assertEqual(receivedQuery, query) | |
580 | self.assertEqual(receivedResponse, response) | |
581 | ||
582 | def testProtocolTCP(self): | |
583 | """ | |
584 | Advanced: Test DNSQuestion.Protocol over TCP | |
585 | """ | |
586 | name = 'tcp.protocols.advanced.tests.powerdns.com.' | |
587 | query = dns.message.make_query(name, 'A', 'IN') | |
588 | response = dns.message.make_response(query) | |
589 | ||
590 | (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) | |
591 | receivedQuery.id = query.id | |
592 | self.assertEqual(receivedQuery, query) | |
593 | self.assertEqual(receivedResponse, response) | |
6211164a CHB |
594 | |
595 | class TestCustomMetrics(DNSDistTest): | |
596 | _config_template = """ | |
597 | function custommetrics(dq) | |
598 | initialCounter = getMetric("my-custom-counter") | |
55361195 | 599 | initialGauge = getMetric("my-custom-gauge") |
6211164a | 600 | incMetric("my-custom-counter") |
54c1bc22 | 601 | incMetric("my-custom-counter", 41) |
6211164a | 602 | setMetric("my-custom-gauge", initialGauge + 1.3) |
54c1bc22 | 603 | if getMetric("my-custom-counter") ~= (initialCounter + 42) or getMetric("my-custom-gauge") ~= (initialGauge + 1.3) then |
6211164a CHB |
604 | return DNSAction.Spoof, '1.2.3.5' |
605 | end | |
606 | return DNSAction.Spoof, '4.3.2.1' | |
607 | end | |
608 | ||
609 | function declareNewMetric(dq) | |
54c1bc22 RG |
610 | if declareMetric("new-runtime-metric", "counter", "Metric declaration at runtime should work fine") then |
611 | return DNSAction.None | |
6211164a | 612 | end |
54c1bc22 | 613 | return DNSAction.Spoof, '1.2.3.4' |
6211164a CHB |
614 | end |
615 | ||
b08586c7 CHB |
616 | declareMetric("my-custom-counter", "counter", "Number of tests run") |
617 | declareMetric("my-custom-gauge", "gauge", "Temperature of the tests") | |
6211164a CHB |
618 | addAction("declare.metric.advanced.tests.powerdns.com.", LuaAction(declareNewMetric)) |
619 | addAction("operations.metric.advanced.tests.powerdns.com.", LuaAction(custommetrics)) | |
620 | newServer{address="127.0.0.1:%s"} | |
621 | """ | |
622 | ||
623 | def testDeclareAfterConfig(self): | |
624 | """ | |
625 | Advanced: Test custom metric declaration after config done | |
626 | """ | |
627 | name = 'declare.metric.advanced.tests.powerdns.com.' | |
628 | query = dns.message.make_query(name, 'A', 'IN') | |
629 | response = dns.message.make_response(query) | |
630 | ||
631 | for method in ("sendUDPQuery", "sendTCPQuery"): | |
632 | sender = getattr(self, method) | |
633 | (receivedQuery, receivedResponse) = sender(query, response) | |
634 | receivedQuery.id = query.id | |
635 | self.assertEqual(receivedQuery, query) | |
636 | self.assertEqual(receivedResponse, response) | |
637 | ||
638 | def testMetricOperations(self): | |
639 | """ | |
640 | Advanced: Test basic operations on custom metrics | |
641 | """ | |
642 | name = 'operations.metric.advanced.tests.powerdns.com.' | |
643 | query = dns.message.make_query(name, 'A', 'IN') | |
644 | # dnsdist set RA = RD for spoofed responses | |
645 | query.flags &= ~dns.flags.RD | |
646 | response = dns.message.make_response(query) | |
647 | rrset = dns.rrset.from_text(name, | |
648 | 60, | |
649 | dns.rdataclass.IN, | |
650 | dns.rdatatype.A, | |
651 | '4.3.2.1') | |
652 | response.answer.append(rrset) | |
653 | ||
654 | for method in ("sendUDPQuery", "sendTCPQuery"): | |
655 | sender = getattr(self, method) | |
656 | (_, receivedResponse) = sender(query, response=None, useQueue=False) | |
657 | self.assertEqual(receivedResponse, response) | |
21e30815 RG |
658 | |
659 | class TestDNSQuestionTime(DNSDistTest): | |
660 | _config_template = """ | |
661 | local queryTime = nil | |
662 | ||
663 | function luaquery(dq) | |
664 | if queryTime then | |
665 | errlog('Error, the time variable is already set') | |
666 | return DNSAction.Drop | |
667 | end | |
668 | queryTime = dq:getQueryTime() | |
669 | local currentTime = getCurrentTime() | |
670 | if queryTime.tv_sec > currentTime.tv_sec then | |
671 | errlog('Error, query time is higher than current time') | |
672 | return DNSAction.Drop | |
673 | end | |
674 | if queryTime.tv_sec == currentTime.tv_sec and queryTime.tv_nsec > currentTime.tv_nsec then | |
675 | errlog('Error, query time NS is higher than current time') | |
676 | return DNSAction.Drop | |
677 | end | |
678 | return DNSAction.None | |
679 | end | |
680 | ||
681 | function luaresponse(dr) | |
682 | if queryTime == nil then | |
683 | errlog('Error, the time variable is NOT set') | |
684 | return DNSAction.Drop | |
685 | end | |
686 | local currentTime = getCurrentTime() | |
687 | local queryTimeFromResponse = dr:getQueryTime() | |
688 | if queryTime.tv_sec ~= queryTimeFromResponse.tv_sec or queryTime.tv_nsec ~= queryTimeFromResponse.tv_nsec then | |
689 | errlog('Error, the query time in the response does NOT match the one from the query') | |
690 | return DNSAction.Drop | |
691 | end | |
692 | if queryTime.tv_sec > currentTime.tv_sec then | |
693 | errlog('Error, query time is higher than current time') | |
694 | return DNSAction.Drop | |
695 | end | |
696 | if queryTime.tv_sec == currentTime.tv_sec and queryTime.tv_nsec > currentTime.tv_nsec then | |
697 | errlog('Error, query time (NS) is higher than current time') | |
698 | return DNSAction.Drop | |
699 | end | |
700 | ||
701 | queryTime = nil | |
702 | return DNSAction.None | |
703 | end | |
704 | ||
705 | addAction(AllRule(), LuaAction(luaquery)) | |
706 | addResponseAction(AllRule(), LuaResponseAction(luaresponse)) | |
707 | newServer{address="127.0.0.1:%s"} | |
708 | """ | |
709 | ||
710 | def testQueryTime(self): | |
711 | """ | |
712 | Advanced: Test query time | |
713 | """ | |
714 | name = 'query.time.advanced.tests.powerdns.com.' | |
715 | query = dns.message.make_query(name, 'A', 'IN') | |
716 | response = dns.message.make_response(query) | |
717 | rrset = dns.rrset.from_text(name, | |
718 | 60, | |
719 | dns.rdataclass.IN, | |
720 | dns.rdatatype.A, | |
721 | '4.3.2.1') | |
722 | response.answer.append(rrset) | |
723 | ||
724 | for method in ("sendUDPQuery", "sendTCPQuery"): | |
725 | sender = getattr(self, method) | |
726 | (receivedQuery, receivedResponse) = sender(query, response) | |
727 | receivedQuery.id = query.id | |
728 | self.assertEqual(receivedQuery, query) | |
729 | self.assertEqual(receivedResponse, response) | |
20790191 | 730 | |
56287dc7 | 731 | class TestChangeName(DNSDistTest): |
20790191 RG |
732 | _config_template = """ |
733 | local tagName = 'initial-name' | |
56287dc7 | 734 | function luaChangeNamequery(dq) |
20790191 | 735 | dq:setTag(tagName, dq.qname:toString()) |
56287dc7 | 736 | if not dq:changeName(newDNSName('changeName.advanced.tests.dnsdist.org')) then |
20790191 RG |
737 | errlog('Error rebasing the query') |
738 | return DNSAction.Drop | |
739 | end | |
740 | return DNSAction.None | |
741 | end | |
742 | ||
56287dc7 | 743 | function luaChangeNameresponse(dr) |
20790191 | 744 | local initialName = dr:getTag(tagName) |
56287dc7 | 745 | if not dr:changeName(newDNSName(initialName)) then |
20790191 RG |
746 | errlog('Error rebasing the response') |
747 | return DNSAction.Drop | |
748 | end | |
749 | return DNSAction.None | |
750 | end | |
751 | ||
56287dc7 RG |
752 | addAction('changeName.advanced.tests.powerdns.com', LuaAction(luaChangeNamequery)) |
753 | addResponseAction('changeName.advanced.tests.dnsdist.org', LuaResponseAction(luaChangeNameresponse)) | |
20790191 RG |
754 | newServer{address="127.0.0.1:%s"} |
755 | """ | |
756 | ||
56287dc7 | 757 | def testChangeName(self): |
20790191 | 758 | """ |
56287dc7 | 759 | Advanced: ChangeName the query name |
20790191 | 760 | """ |
56287dc7 | 761 | name = 'changeName.advanced.tests.powerdns.com.' |
20790191 RG |
762 | query = dns.message.make_query(name, 'A', 'IN') |
763 | ||
56287dc7 RG |
764 | changedName = 'changeName.advanced.tests.dnsdist.org.' |
765 | changedQuery = dns.message.make_query(changedName, 'A', 'IN') | |
766 | changedQuery.id = query.id | |
20790191 | 767 | |
56287dc7 RG |
768 | response = dns.message.make_response(changedQuery) |
769 | rrset = dns.rrset.from_text(changedName, | |
20790191 RG |
770 | 60, |
771 | dns.rdataclass.IN, | |
772 | dns.rdatatype.A, | |
773 | '4.3.2.1') | |
774 | response.answer.append(rrset) | |
56287dc7 | 775 | rrset = dns.rrset.from_text('sub.sub2.changeName.advanced.tests.dnsdist.org.', |
20790191 RG |
776 | 60, |
777 | dns.rdataclass.IN, | |
778 | dns.rdatatype.TXT, | |
56287dc7 | 779 | 'This text contains sub.sub2.changeName.advanced.tests.dnsdist.org.') |
20790191 RG |
780 | response.additional.append(rrset) |
781 | ||
782 | expectedResponse = dns.message.make_response(query) | |
783 | rrset = dns.rrset.from_text(name, | |
784 | 60, | |
785 | dns.rdataclass.IN, | |
786 | dns.rdatatype.A, | |
787 | '4.3.2.1') | |
788 | expectedResponse.answer.append(rrset) | |
789 | # we only rewrite records if the owner name matches the new target, nothing else | |
56287dc7 | 790 | rrset = dns.rrset.from_text('sub.sub2.changeName.advanced.tests.dnsdist.org.', |
20790191 RG |
791 | 60, |
792 | dns.rdataclass.IN, | |
793 | dns.rdatatype.TXT, | |
56287dc7 | 794 | 'This text contains sub.sub2.changeName.advanced.tests.dnsdist.org.') |
20790191 RG |
795 | expectedResponse.additional.append(rrset) |
796 | ||
797 | for method in ("sendUDPQuery", "sendTCPQuery"): | |
798 | sender = getattr(self, method) | |
799 | (receivedQuery, receivedResponse) = sender(query, response) | |
800 | receivedQuery.id = query.id | |
56287dc7 | 801 | self.assertEqual(receivedQuery, changedQuery) |
20790191 | 802 | self.assertEqual(receivedResponse, expectedResponse) |
58a4b9b3 RG |
803 | |
804 | class TestFlagsOnTimeout(DNSDistTest): | |
805 | ||
806 | _consoleKey = DNSDistTest.generateConsoleKey() | |
807 | _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii') | |
808 | _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort'] | |
809 | _config_template = """ | |
810 | setKey("%s") | |
811 | controlSocket("127.0.0.1:%s") | |
812 | -- this server is not going to answer, resulting in a timeout | |
813 | newServer{address="192.0.2.1:%s"}:setUp() | |
814 | """ | |
815 | ||
816 | def testFlags(self): | |
817 | """ | |
818 | Advanced: Test that we record the correct incoming flags on a timeout | |
819 | """ | |
820 | name = 'timeout-flags.advanced.tests.powerdns.com.' | |
821 | ||
822 | # first with RD=1 | |
823 | query = dns.message.make_query(name, 'A', 'IN') | |
824 | query.id = 42 | |
825 | query.flags |= dns.flags.RD | |
826 | ||
827 | (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) | |
828 | self.assertFalse(receivedResponse) | |
829 | ||
830 | # then with RD=0 | |
831 | query = dns.message.make_query(name, 'A', 'IN') | |
832 | query.id = 84 | |
833 | query.flags &= ~dns.flags.RD | |
834 | ||
835 | (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) | |
836 | self.assertFalse(receivedResponse) | |
837 | ||
838 | # make sure that the timeouts have been detected and recorded | |
8604a236 | 839 | for _ in range(6): |
58a4b9b3 RG |
840 | content = self.sendConsoleCommand("grepq('')") |
841 | lines = content.splitlines() | |
842 | if len(lines) == 5: | |
843 | break | |
844 | # and otherwise sleep for a short while | |
845 | time.sleep(1) | |
846 | ||
847 | print(lines) | |
848 | self.assertEqual(len(lines), 5) | |
849 | # header line | |
850 | self.assertIn('TC RD AA', lines[0]) | |
851 | ||
852 | queries = {} | |
853 | timeouts = {} | |
854 | ||
855 | for line in lines[1:]: | |
856 | self.assertIn('DoUDP', line) | |
857 | if 'T.O' in line: | |
858 | queryID = int(line.split()[4]) | |
859 | timeouts[queryID] = line | |
860 | else: | |
861 | queryID = int(line.split()[3]) | |
862 | queries[queryID] = line | |
863 | if queryID == 42: | |
864 | self.assertIn('RD', line) | |
865 | else: | |
866 | self.assertNotIn('RD', line) | |
867 | ||
868 | self.assertEqual(len(timeouts), 2) | |
869 | self.assertEqual(len(queries), 2) | |
17a0b06e RG |
870 | |
871 | class TestTruncatedUDPLargeAnswers(DNSDistTest): | |
872 | _config_template = """ | |
873 | newServer{address="127.0.0.1:%d"} | |
874 | """ | |
875 | def testVeryLargeAnswer(self): | |
876 | """ | |
877 | Advanced: Check that UDP responses that are too large for our buffer are dismissed | |
878 | """ | |
879 | name = 'very-large-answer-dismissed.advanced.tests.powerdns.com.' | |
880 | query = dns.message.make_query(name, 'TXT', 'IN') | |
881 | response = dns.message.make_response(query) | |
882 | # we prepare a large answer | |
883 | content = '' | |
884 | for i in range(31): | |
885 | if len(content) > 0: | |
886 | content = content + ' ' | |
887 | content = content + 'A' * 255 | |
888 | # pad up to 8192 | |
889 | content = content + ' ' + 'B' * 170 | |
890 | ||
891 | rrset = dns.rrset.from_text(name, | |
892 | 3600, | |
893 | dns.rdataclass.IN, | |
894 | dns.rdatatype.TXT, | |
895 | content) | |
896 | response.answer.append(rrset) | |
897 | self.assertEqual(len(response.to_wire()), 8192) | |
898 | ||
899 | # TCP should be OK | |
900 | (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) | |
901 | self.assertTrue(receivedQuery) | |
902 | self.assertTrue(receivedResponse) | |
903 | receivedQuery.id = query.id | |
904 | self.assertEqual(query, receivedQuery) | |
905 | self.assertEqual(receivedResponse, response) | |
906 | ||
907 | # UDP should never get an answer, because dnsdist will not be able to get it from the backend | |
908 | (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) | |
909 | self.assertTrue(receivedQuery) | |
910 | self.assertFalse(receivedResponse) | |
911 | receivedQuery.id = query.id | |
912 | self.assertEqual(query, receivedQuery) |