]> git.ipfire.org Git - thirdparty/pdns.git/blob - regression-tests.recursor-dnssec/recursortests.py
Merge pull request #12660 from omoerbeek/rec-regr-threads-pc
[thirdparty/pdns.git] / regression-tests.recursor-dnssec / recursortests.py
1 #!/usr/bin/env python2
2
3 from __future__ import print_function
4 import errno
5 import shutil
6 import os
7 import socket
8 import struct
9 import subprocess
10 import sys
11 import time
12 import unittest
13 import dns
14 import dns.message
15
16 from proxyprotocol import ProxyProtocol
17
18 from eqdnsmessage import AssertEqualDNSMessageMixin
19
20
21 def have_ipv6():
22 """
23 Try to make an IPv6 socket and bind it, if it fails, no ipv6...
24 """
25 try:
26 sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
27 sock.bind(('::1', 56581))
28 sock.close()
29 return True
30 except:
31 return False
32 return False
33
34
35 class RecursorTest(AssertEqualDNSMessageMixin, unittest.TestCase):
36 """
37 Setup all recursors and auths required for the tests
38 """
39
40 _confdir = 'recursor'
41
42 _recursorPort = 5300
43
44 _recursor = None
45
46 _PREFIX = os.environ['PREFIX']
47
48 _config_template_default = """
49 daemon=no
50 trace=yes
51 dont-query=
52 local-address=127.0.0.1
53 packetcache-ttl=15
54 packetcache-servfail-ttl=15
55 max-cache-ttl=15
56 threads=2
57 loglevel=9
58 disable-syslog=yes
59 log-common-errors=yes
60 statistics-interval=0
61 """
62 _config_template = """
63 """
64 _config_params = []
65 _lua_config_file = None
66 _lua_dns_script_file = None
67 _roothints = """
68 . 3600 IN NS ns.root.
69 ns.root. 3600 IN A %s.8
70 ns.root. 3600 IN AAAA ::1
71 """ % _PREFIX
72 _root_DS = "63149 13 1 a59da3f5c1b97fcd5fa2b3b2b0ac91d38a60d33a"
73
74 # The default SOA for zones in the authoritative servers
75 _SOA = "ns1.example.net. hostmaster.example.net. 1 3600 1800 1209600 300"
76
77 # The definitions of the zones on the authoritative servers, the key is the
78 # zonename and the value is the zonefile content. several strings are replaced:
79 # - {soa} => value of _SOA
80 # - {prefix} value of _PREFIX
81 _zones = {
82 'ROOT': """
83 . 3600 IN SOA {soa}
84 . 3600 IN NS ns.root.
85 ns.root. 3600 IN A {prefix}.8
86
87 example. 3600 IN NS ns1.example.
88 example. 3600 IN NS ns2.example.
89 example. 3600 IN DS 53174 13 1 50c9e913818767c236c06c2d8272723cb78cbf26
90
91 ns1.example. 3600 IN A {prefix}.10
92 ns2.example. 3600 IN A {prefix}.18
93 """,
94 'example': """
95 example. 3600 IN SOA {soa}
96 example. 3600 IN NS ns1.example.
97 example. 3600 IN NS ns2.example.
98 ns1.example. 3600 IN A {prefix}.10
99 ns2.example. 3600 IN A {prefix}.18
100
101 secure.example. 3600 IN NS ns.secure.example.
102 secure.example. 3600 IN DS 64723 13 1 53eb985040d3a89bacf29dbddb55a65834706f33
103 ns.secure.example. 3600 IN A {prefix}.9
104
105 cname-secure.example. 3600 IN NS ns.cname-secure.example.
106 cname-secure.example. 3600 IN DS 49148 13 1 a10314452d5ec4d97fcc6d7e275d217261fe790f
107 ns.cname-secure.example. 3600 IN A {prefix}.15
108
109 dname-secure.example. 3600 IN NS ns.dname-secure.example.
110 dname-secure.example. 3600 IN DS 42043 13 2 11c67f46b7c4d5968bc5f6cc944d58377b762bda53ddb4f3a6dbe6faf7a9940f
111 ns.dname-secure.example. 3600 IN A {prefix}.13
112
113 bogus.example. 3600 IN NS ns.bogus.example.
114 bogus.example. 3600 IN DS 65034 13 1 6df3bb50ea538e90eacdd7ae5419730783abb0ee
115 ns.bogus.example. 3600 IN A {prefix}.12
116
117 insecure.example. 3600 IN NS ns.insecure.example.
118 ns.insecure.example. 3600 IN A {prefix}.13
119
120 optout.example. 3600 IN NS ns1.optout.example.
121 optout.example. 3600 IN DS 59332 13 1 e664f886ae1b5df03d918bc1217d22afc29925b9
122 ns1.optout.example. 3600 IN A {prefix}.14
123
124 postresolve_ffi.example. 3600 IN A 1.2.3.4
125 postresolve_ffi.example. 3600 IN A 1.2.3.5
126 postresolve_ffi.example. 3600 IN AAAA ::1
127 postresolve_ffi.example. 3600 IN AAAA ::2
128
129 insecure-formerr.example. 3600 IN NS ns1.insecure-formerr.example.
130 ns1.insecure-formerr.example. 3600 IN A {prefix}.2
131
132 ecs-echo.example. 3600 IN NS ns1.ecs-echo.example.
133 ns1.ecs-echo.example. 3600 IN A {prefix}.21
134
135 islandofsecurity.example. 3600 IN NS ns1.islandofsecurity.example.
136 ns1.islandofsecurity.example. 3600 IN A {prefix}.9
137
138 sortcname.example. 3600 IN CNAME sort
139 sort.example. 3600 IN A 17.38.42.80
140 sort.example. 3600 IN A 192.168.0.1
141 sort.example. 3600 IN A 17.238.240.5
142 sort.example. 3600 IN MX 25 mx
143
144 delay1.example. 3600 IN NS ns1.delay1.example.
145 ns1.delay1.example. 3600 IN A {prefix}.16
146 delay1.example. 3600 IN DS 42043 13 2 7319fa605cf117f36e3de070157577ebb9a05a1d1f963d80eda55b5d6e793eb2
147
148 delay2.example. 3600 IN NS ns1.delay2.example.
149 ns1.delay2.example. 3600 IN A {prefix}.17
150 delay2.example. 3600 IN DS 42043 13 2 60a047b87740c8564c21d5fd34626c10a77a6c41e3b34564230119c2f13937b8
151
152 cname-nxd.example. 3600 IN CNAME cname-nxd-target.example.
153 cname-nxd-target.example. 3600 IN A 192.0.2.100
154 cname-nodata.example. 3600 IN CNAME cname-nodata-target.example.
155 cname-nodata-target.example. 3600 IN A 192.0.2.101
156 cname-custom-a.example. 3600 IN CNAME cname-custom-a-target.example.
157 cname-custom-a-target.example. 3600 IN A 192.0.2.102
158 """,
159 'secure.example': """
160 secure.example. 3600 IN SOA {soa}
161 secure.example. 3600 IN NS ns.secure.example.
162 ns.secure.example. 3600 IN A {prefix}.9
163 secure.example. 3600 IN MX 10 mx1.secure.example.
164 secure.example. 3600 IN MX 20 mx2.secure.example.
165 sub.secure.example. 3600 IN MX 10 mx1.secure.example.
166 sub.secure.example. 3600 IN MX 20 mx2.secure.example.
167
168 naptr.secure.example. 60 IN NAPTR 10 10 "a" "X" "A" s1.secure.example.
169 naptr.secure.example. 60 IN NAPTR 10 10 "s" "Y" "B" service1.secure.example.
170 naptr.secure.example. 60 IN NAPTR 10 10 "s" "Z" "C" service2.secure.example.
171 service1.secure.example. 60 IN SRV 20 100 8080 a.secure.example.
172 service2.secure.example. 60 IN SRV 20 100 8080 b.secure.example.
173
174
175 secure.example. 3600 IN A 192.0.2.17
176 mx1.secure.example. 3600 IN A 192.0.2.18
177 mx2.secure.example. 3600 IN AAAA 1::2
178 s1.secure.example. 3600 IN A 192.0.2.19
179 a.secure.example. 3600 IN A 192.0.2.20
180 a.secure.example. 3600 IN A 192.0.2.22
181 b.secure.example. 3600 IN A 192.0.2.21
182 b.secure.example. 3600 IN AAAA 1::3
183
184 host1.secure.example. 3600 IN A 192.0.2.2
185 cname.secure.example. 3600 IN CNAME host1.secure.example.
186 cname-to-insecure.secure.example. 3600 IN CNAME node1.insecure.example.
187 cname-to-bogus.secure.example. 3600 IN CNAME ted.bogus.example.
188 cname-to-islandofsecurity.secure.example. 3600 IN CNAME node1.islandofsecurity.example.
189
190 host1.sub.secure.example. 3600 IN A 192.0.2.11
191
192 ;; See #4158
193 sub2.secure.example. 3600 IN CNAME doesnotmatter.insecure.example.
194 insecure.sub2.secure.example. 3600 IN NS ns1.insecure.example.
195
196 *.wildcard.secure.example. 3600 IN A 192.0.2.10
197
198 *.cnamewildcard.secure.example. 3600 IN CNAME host1.secure.example.
199
200 *.cnamewildcardnxdomain.secure.example. 3600 IN CNAME doesnotexist.secure.example.
201
202 cname-to-formerr.secure.example. 3600 IN CNAME host1.insecure-formerr.example.
203
204 dname-secure.secure.example. 3600 IN DNAME dname-secure.example.
205 dname-insecure.secure.example. 3600 IN DNAME insecure.example.
206 dname-bogus.secure.example. 3600 IN DNAME bogus.example.
207
208 non-apex-dnskey.secure.example. 3600 IN DNSKEY 257 3 13 CT6AJ4MEOtNDgj0+xLtTLGHf1WbLsKWZI8ONHOt/6q7hTjeWSnY/SGig1dIKZrHg+pJFUSPaxeShv48SYVRKEg==
209 non-apex-dnskey2.secure.example. 3600 IN DNSKEY 256 3 13 CT6AJ4MEOtNDgj0+xLtTLGHf1WbLsKWZI8ONHOt/6q7hTjeWSnY/SGig1dIKZrHg+pJFUSPaxeShv48SYVRKEg==
210 non-apex-dnskey3.secure.example. 3600 IN DNSKEY 256 3 13 DT6AJ4MEOtNDgj0+xLtTLGHf1WbLsKWZI8ONHOt/6q7hTjeWSnY/SGig1dIKZrHg+pJFUSPaxeShv48SYVRKEg==
211 """,
212 'dname-secure.example': """
213 dname-secure.example. 3600 IN SOA {soa}
214 dname-secure.example. 3600 IN NS ns.dname-secure.example.
215 ns.dname-secure.example. 3600 IN A {prefix}.13
216
217 host1.dname-secure.example. IN A 192.0.2.21
218
219 cname-to-secure.dname-secure.example. 3600 IN CNAME host1.secure.example.
220 cname-to-insecure.dname-secure.example. 3600 IN CNAME node1.insecure.example.
221 cname-to-bogus.dname-secure.example. 3600 IN CNAME ted.bogus.example.
222 """,
223 'cname-secure.example': """
224 cname-secure.example. 3600 IN SOA {soa}
225 cname-secure.example. 3600 IN NS ns.cname-secure.example.
226 ns.cname-secure.example. 3600 IN A {prefix}.15
227 cname-secure.example. 3600 IN CNAME secure.example.
228 """,
229 'bogus.example': """
230 bogus.example. 3600 IN SOA {soa}
231 bogus.example. 3600 IN NS ns1.bogus.example.
232 ns1.bogus.example. 3600 IN A {prefix}.12
233 ted.bogus.example. 3600 IN A 192.0.2.1
234 bill.bogus.example. 3600 IN AAAA 2001:db8:12::3
235 """,
236 'insecure.sub2.secure.example': """
237 insecure.sub2.secure.example. 3600 IN SOA {soa}
238 insecure.sub2.secure.example. 3600 IN NS ns1.insecure.example.
239
240 node1.insecure.sub2.secure.example. 3600 IN A 192.0.2.18
241 """,
242 'insecure.example': """
243 insecure.example. 3600 IN SOA {soa}
244 insecure.example. 3600 IN NS ns1.insecure.example.
245 ns1.insecure.example. 3600 IN A {prefix}.13
246
247 node1.insecure.example. 3600 IN A 192.0.2.6
248
249 cname-to-secure.insecure.example. 3600 IN CNAME host1.secure.example.
250
251 dname-to-secure.insecure.example. 3600 IN DNAME dname-secure.example.
252 """,
253 'optout.example': """
254 optout.example. 3600 IN SOA {soa}
255 optout.example. 3600 IN NS ns1.optout.example.
256 ns1.optout.example. 3600 IN A {prefix}.14
257
258 insecure.optout.example. 3600 IN NS ns1.insecure.optout.example.
259 ns1.insecure.optout.example. 3600 IN A {prefix}.15
260
261 secure.optout.example. 3600 IN NS ns1.secure.optout.example.
262 secure.optout.example. 3600 IN DS 64215 13 1 b88284d7a8d8605c398e8942262f97b9a5a31787
263 ns1.secure.optout.example. 3600 IN A {prefix}.15
264 """,
265 'insecure.optout.example': """
266 insecure.optout.example. 3600 IN SOA {soa}
267 insecure.optout.example. 3600 IN NS ns1.insecure.optout.example.
268 ns1.insecure.optout.example. 3600 IN A {prefix}.15
269
270 node1.insecure.optout.example. 3600 IN A 192.0.2.7
271 """,
272 'secure.optout.example': """
273 secure.optout.example. 3600 IN SOA {soa}
274 secure.optout.example. 3600 IN NS ns1.secure.optout.example.
275 ns1.secure.optout.example. 3600 IN A {prefix}.15
276
277 node1.secure.optout.example. 3600 IN A 192.0.2.8
278 """,
279 'islandofsecurity.example': """
280 islandofsecurity.example. 3600 IN SOA {soa}
281 islandofsecurity.example. 3600 IN NS ns1.islandofsecurity.example.
282 ns1.islandofsecurity.example. 3600 IN A {prefix}.9
283
284 node1.islandofsecurity.example. 3600 IN A 192.0.2.20
285 """,
286 'undelegated.secure.example': """
287 undelegated.secure.example. 3600 IN SOA {soa}
288 undelegated.secure.example. 3600 IN NS ns1.undelegated.secure.example.
289
290 node1.undelegated.secure.example. 3600 IN A 192.0.2.21
291 """,
292 'undelegated.insecure.example': """
293 undelegated.insecure.example. 3600 IN SOA {soa}
294 undelegated.insecure.example. 3600 IN NS ns1.undelegated.insecure.example.
295
296 node1.undelegated.insecure.example. 3600 IN A 192.0.2.22
297 """,
298
299 'delay1.example': """
300 delay1.example. 3600 IN SOA {soa}
301 delay1.example. 3600 IN NS n1.delay1.example.
302 ns1.delay1.example. 3600 IN A {prefix}.16
303 8.delay1.example. 3600 IN A 192.0.2.100
304 *.delay1.example. 0 LUA TXT ";" "local socket=require('socket')" "socket.sleep(tonumber(qname:getRawLabels()[1])/10)" "return 'a'"
305 *.delay1.example. 0 LUA AAAA ";" "local socket=require('socket')" "socket.sleep(tonumber(qname:getRawLabels()[1])/10)" "return '1::2'"
306 """,
307
308 'delay2.example': """
309 delay2.example. 3600 IN SOA {soa}
310 delay2.example. 3600 IN NS n1.delay2.example.
311 ns1.delay2.example. 3600 IN A {prefix}.17
312 *.delay2.example. 0 LUA TXT ";" "local socket=require('socket')" "socket.sleep(tonumber(qname:getRawLabels()[1])/10)" "return 'a'"
313 """
314 }
315
316 # The private keys for the zones (note that DS records should go into
317 # the zonecontent in _zones
318 _zone_keys = {
319 'ROOT': """
320 Private-key-format: v1.2
321 Algorithm: 13 (ECDSAP256SHA256)
322 PrivateKey: rhWuEydDz3QaIspSVj683B8Xq5q/ozzA38XUgzD4Fbo=
323 """,
324
325 'example': """
326 Private-key-format: v1.2
327 Algorithm: 13 (ECDSAP256SHA256)
328 PrivateKey: Lt0v0Gol3pRUFM7fDdcy0IWN0O/MnEmVPA+VylL8Y4U=
329 """,
330
331 'secure.example': """
332 Private-key-format: v1.2
333 Algorithm: 13 (ECDSAP256SHA256)
334 PrivateKey: 1G4WRoOFJJXk+fotDCHVORtJmIG2OUhKi8AO2jDPGZA=
335 """,
336
337 'bogus.example': """
338 Private-key-format: v1.2
339 Algorithm: 13 (ECDSAP256SHA256)
340 PrivateKey: f5jV7Q8kd5hDpMWObsuQ6SQda0ftf+JrO3uZwEg6nVw=
341 """,
342
343 'optout.example': """
344 Private-key-format: v1.2
345 Algorithm: 13 (ECDSAP256SHA256)
346 PrivateKey: efmq9G+J4Y2iPnIBRwJiy6Z/nIHSzpsCy/7XHhlS19A=
347 """,
348
349 'secure.optout.example': """
350 Private-key-format: v1.2
351 Algorithm: 13 (ECDSAP256SHA256)
352 PrivateKey: xcNUxt1Knj14A00lKQFDboluiJyM2f7FxpgsQaQ3AQ4=
353 """,
354
355 'islandofsecurity.example': """
356 Private-key-format: v1.2
357 Algorithm: 13 (ECDSAP256SHA256)
358 PrivateKey: o9F5iix8V68tnMcuOaM2Lt8XXhIIY//SgHIHEePk6cM=
359 """,
360
361 'cname-secure.example': """
362 Private-key-format: v1.2
363 Algorithm: 13 (ECDSAP256SHA256)
364 PrivateKey: kvoV/g4IO/tefSro+FLJ5UC7H3BUf0IUtZQSUOfQGyA=
365 """,
366
367 'dname-secure.example': """
368 Private-key-format: v1.2
369 Algorithm: 13 (ECDSAP256SHA256)
370 PrivateKey: Ep9uo6+wwjb4MaOmqq7LHav2FLrjotVOeZg8JT1Qk04=
371 """,
372
373 'delay1.example': """
374 Private-key-format: v1.2
375 Algorithm: 13 (ECDSAP256SHA256)
376 PrivateKey: Ep9uo6+wwjb4MaOmqq7LHav2FLrjotVOeZg8JT1Qk04=
377 """,
378
379 'delay2.example': """
380 Private-key-format: v1.2
381 Algorithm: 13 (ECDSAP256SHA256)
382 PrivateKey: Ep9uo6+wwjb4MaOmqq7LHav2FLrjotVOeZg8JT1Qk04=
383 """
384 }
385
386 # This dict is keyed with the suffix of the IP address and its value
387 # is a list of zones hosted on that IP. Note that delegations should
388 # go into the _zones's zonecontent
389 _auth_zones = {
390 '8': {'threads': 1,
391 'zones': ['ROOT']},
392 '9': {'threads': 1,
393 'zones': ['secure.example', 'islandofsecurity.example']},
394 '10': {'threads': 1,
395 'zones': ['example']},
396
397 # 11 is used by CircleCI provided resolver
398
399 '12': {'threads': 1,
400 'zones': ['bogus.example', 'undelegated.secure.example', 'undelegated.insecure.example']},
401 '13': {'threads': 1,
402 'zones': ['insecure.example', 'insecure.sub2.secure.example', 'dname-secure.example']},
403 '14': {'threads': 1,
404 'zones': ['optout.example']},
405 '15': {'threads': 1,
406 'zones': ['insecure.optout.example', 'secure.optout.example', 'cname-secure.example']},
407 '16': {'threads': 2,
408 'zones': ['delay1.example']},
409 '17': {'threads': 2,
410 'zones': ['delay2.example']},
411 '18': {'threads': 1,
412 'zones': ['example']}
413 }
414 # Other IPs used:
415 # 2: test_Interop.py
416 # 3-7: free?
417 # 19: free?
418 # 20: free?
419 # 21: test_ECS.py
420 # 22: test_EDNSBuffer.py
421 # 23: test_Lua.py
422 # 24: test_RoutingTag.py
423
424 _auth_cmd = ['authbind',
425 os.environ['PDNS']]
426 _auth_env = {}
427 _auths = {}
428
429 @classmethod
430 def createConfigDir(cls, confdir):
431 try:
432 shutil.rmtree(confdir)
433 except OSError as e:
434 if e.errno != errno.ENOENT:
435 raise
436 os.mkdir(confdir, 0o755)
437
438 @classmethod
439 def generateAuthZone(cls, confdir, zonename, zonecontent):
440 with open(os.path.join(confdir, '%s.zone' % zonename), 'w') as zonefile:
441 zonefile.write(zonecontent.format(prefix=cls._PREFIX, soa=cls._SOA))
442
443 @classmethod
444 def generateAuthNamedConf(cls, confdir, zones):
445 with open(os.path.join(confdir, 'named.conf'), 'w') as namedconf:
446 namedconf.write("""
447 options {
448 directory "%s";
449 };""" % confdir)
450 for zonename in zones:
451 zone = '.' if zonename == 'ROOT' else zonename
452
453 namedconf.write("""
454 zone "%s" {
455 type master;
456 file "%s.zone";
457 };""" % (zone, zonename))
458
459 @classmethod
460 def generateAuthConfig(cls, confdir, threads):
461 bind_dnssec_db = os.path.join(confdir, 'bind-dnssec.sqlite3')
462
463 with open(os.path.join(confdir, 'pdns.conf'), 'w') as pdnsconf:
464 pdnsconf.write("""
465 module-dir=../regression-tests/modules
466 launch=bind
467 daemon=no
468 bind-config={confdir}/named.conf
469 bind-dnssec-db={bind_dnssec_db}
470 socket-dir={confdir}
471 cache-ttl=0
472 negquery-cache-ttl=0
473 query-cache-ttl=0
474 log-dns-queries=yes
475 log-dns-details=yes
476 loglevel=9
477 enable-lua-records
478 dname-processing=yes
479 distributor-threads={threads}""".format(confdir=confdir,
480 bind_dnssec_db=bind_dnssec_db,
481 threads=threads))
482
483 pdnsutilCmd = [os.environ['PDNSUTIL'],
484 '--config-dir=%s' % confdir,
485 'create-bind-db',
486 bind_dnssec_db]
487
488 print(' '.join(pdnsutilCmd))
489 try:
490 subprocess.check_output(pdnsutilCmd, stderr=subprocess.STDOUT)
491 except subprocess.CalledProcessError as e:
492 raise AssertionError('%s failed (%d): %s' % (pdnsutilCmd, e.returncode, e.output))
493
494 @classmethod
495 def secureZone(cls, confdir, zonename, key=None):
496 zone = '.' if zonename == 'ROOT' else zonename
497 if not key:
498 pdnsutilCmd = [os.environ['PDNSUTIL'],
499 '--config-dir=%s' % confdir,
500 'secure-zone',
501 zone]
502 else:
503 keyfile = os.path.join(confdir, 'dnssec.key')
504 with open(keyfile, 'w') as fdKeyfile:
505 fdKeyfile.write(key)
506
507 pdnsutilCmd = [os.environ['PDNSUTIL'],
508 '--config-dir=%s' % confdir,
509 'import-zone-key',
510 zone,
511 keyfile,
512 'active',
513 'ksk']
514
515 print(' '.join(pdnsutilCmd))
516 try:
517 subprocess.check_output(pdnsutilCmd, stderr=subprocess.STDOUT)
518 except subprocess.CalledProcessError as e:
519 raise AssertionError('%s failed (%d): %s' % (pdnsutilCmd, e.returncode, e.output))
520
521 @classmethod
522 def generateAllAuthConfig(cls, confdir):
523 if cls._auth_zones:
524 for auth_suffix, zoneinfo in cls._auth_zones.items():
525 threads = zoneinfo['threads']
526 zones = zoneinfo['zones']
527 authconfdir = os.path.join(confdir, 'auth-%s' % auth_suffix)
528
529 os.mkdir(authconfdir)
530
531 cls.generateAuthConfig(authconfdir, threads)
532 cls.generateAuthNamedConf(authconfdir, zones)
533
534 for zone in zones:
535 cls.generateAuthZone(authconfdir,
536 zone,
537 cls._zones[zone])
538 if cls._zone_keys.get(zone, None):
539 cls.secureZone(authconfdir, zone, cls._zone_keys.get(zone))
540
541 @classmethod
542 def startAllAuth(cls, confdir):
543 if cls._auth_zones:
544 for auth_suffix, _ in cls._auth_zones.items():
545 authconfdir = os.path.join(confdir, 'auth-%s' % auth_suffix)
546 ipaddress = cls._PREFIX + '.' + auth_suffix
547 cls.startAuth(authconfdir, ipaddress)
548
549 @classmethod
550 def waitForTCPSocket(cls, ipaddress, port):
551 for try_number in range(0, 100):
552 try:
553 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
554 sock.settimeout(1.0)
555 sock.connect((ipaddress, port))
556 sock.close()
557 return
558 except Exception as err:
559 if err.errno != errno.ECONNREFUSED:
560 print(f'Error occurred: {try_number} {err}', file=sys.stderr)
561 time.sleep(0.1)
562
563 @classmethod
564 def startAuth(cls, confdir, ipaddress):
565 print("Launching pdns_server..")
566 authcmd = list(cls._auth_cmd)
567 authcmd.append('--config-dir=%s' % confdir)
568 ipconfig = ipaddress
569 # auth-8 is the auth serving the root, it gets an ipv6 address
570 if (confdir[-6:] == "auth-8") and have_ipv6():
571 ipconfig += ',::1'
572 authcmd.append('--local-address=%s' % ipconfig)
573 print(' '.join(authcmd))
574
575 logFile = os.path.join(confdir, 'pdns.log')
576 with open(logFile, 'w') as fdLog:
577 cls._auths[ipaddress] = subprocess.Popen(authcmd, close_fds=True,
578 stdout=fdLog, stderr=fdLog,
579 env=cls._auth_env)
580
581 cls.waitForTCPSocket(ipaddress, 53)
582
583 if cls._auths[ipaddress].poll() is not None:
584 print(f"\n*** startAuth log for {logFile} ***")
585 with open(logFile, 'r') as fdLog:
586 print(fdLog.read())
587 print(f"*** End startAuth log for {logFile} ***")
588 raise AssertionError('%s failed (%d)' % (authcmd, cls._auths[ipaddress].returncode))
589
590 @classmethod
591 def generateRecursorConfig(cls, confdir):
592 params = tuple([getattr(cls, param) for param in cls._config_params])
593 if len(params):
594 print(params)
595
596 recursorconf = os.path.join(confdir, 'recursor.conf')
597
598 with open(recursorconf, 'w') as conf:
599 conf.write("# Autogenerated by recursortests.py\n")
600 conf.write(cls._config_template_default)
601 conf.write(cls._config_template % params)
602 conf.write("\n")
603 conf.write("socket-dir=%s\n" % confdir)
604 if cls._lua_config_file or cls._root_DS:
605 luaconfpath = os.path.join(confdir, 'conffile.lua')
606 with open(luaconfpath, 'w') as luaconf:
607 if cls._root_DS:
608 luaconf.write("addTA('.', '%s')\n" % cls._root_DS)
609 if cls._lua_config_file:
610 luaconf.write(cls._lua_config_file)
611 conf.write("lua-config-file=%s\n" % luaconfpath)
612 if cls._lua_dns_script_file:
613 luascriptpath = os.path.join(confdir, 'dnsscript.lua')
614 with open(luascriptpath, 'w') as luascript:
615 luascript.write(cls._lua_dns_script_file)
616 conf.write("lua-dns-script=%s\n" % luascriptpath)
617 if cls._roothints:
618 roothintspath = os.path.join(confdir, 'root.hints')
619 with open(roothintspath, 'w') as roothints:
620 roothints.write(cls._roothints)
621 conf.write("hint-file=%s\n" % roothintspath)
622
623 @classmethod
624 def startResponders(cls):
625 pass
626
627 @classmethod
628 def startRecursor(cls, confdir, port):
629 print("Launching pdns_recursor..")
630 recursorcmd = [os.environ['PDNSRECURSOR'],
631 '--config-dir=%s' % confdir,
632 '--local-port=%s' % port,
633 '--security-poll-suffix=']
634 print(' '.join(recursorcmd))
635
636 logFile = os.path.join(confdir, 'recursor.log')
637 with open(logFile, 'w') as fdLog:
638 cls._recursor = subprocess.Popen(recursorcmd, close_fds=True,
639 stdout=fdLog, stderr=fdLog)
640
641 cls.waitForTCPSocket("127.0.0.1", port)
642
643 if cls._recursor.poll() is not None:
644 print(f"\n*** startRecursor log for {logFile} ***")
645 with open(logFile, 'r') as fdLog:
646 print(fdLog.read())
647 print(f"*** End startRecursor log for {logFile} ***")
648 raise AssertionError('%s failed (%d)' % (recursorcmd, cls._recursor.returncode))
649
650 @classmethod
651 def wipeRecursorCache(cls, confdir, name='.$'):
652 rec_controlCmd = [os.environ['RECCONTROL'],
653 '--config-dir=%s' % confdir,
654 'wipe-cache',
655 name]
656 try:
657 subprocess.check_output(rec_controlCmd, stderr=subprocess.STDOUT)
658 except subprocess.CalledProcessError as e:
659 raise AssertionError('%s failed (%d): %s' % (rec_controlCmd, e.returncode, e.output))
660
661 @classmethod
662 def setUpSockets(cls):
663 print("Setting up UDP socket..")
664 cls._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
665 cls._sock.settimeout(2.0)
666 cls._sock.connect(("127.0.0.1", cls._recursorPort))
667
668 @classmethod
669 def setUpClass(cls):
670 cls.setUpSockets()
671
672 cls.startResponders()
673
674 confdir = os.path.join('configs', cls._confdir)
675 cls.createConfigDir(confdir)
676 cls.generateAllAuthConfig(confdir)
677 cls.startAllAuth(confdir)
678
679 cls.generateRecursorConfig(confdir)
680 cls.startRecursor(confdir, cls._recursorPort)
681
682 print("Launching tests..")
683
684 @classmethod
685 def tearDownClass(cls):
686 cls.tearDownRecursor()
687 cls.tearDownAuth()
688 cls.tearDownResponders()
689
690 @classmethod
691 def tearDownResponders(cls):
692 pass
693
694 @classmethod
695 def killProcess(cls, p):
696 # Don't try to kill it if it's already dead
697 if p.poll() is not None:
698 return
699 try:
700 p.terminate()
701 for count in range(10):
702 x = p.poll()
703 if x is not None:
704 break
705 time.sleep(0.1)
706 if x is None:
707 print("kill...", p, file=sys.stderr)
708 p.kill()
709 p.wait()
710 except OSError as e:
711 # There is a race-condition with the poll() and
712 # kill() statements, when the process is dead on the
713 # kill(), this is fine
714 if e.errno != errno.ESRCH:
715 raise
716
717 @classmethod
718 def tearDownAuth(cls):
719 for _, auth in cls._auths.items():
720 cls.killProcess(auth);
721
722 @classmethod
723 def tearDownRecursor(cls):
724 cls.killProcess(cls._recursor)
725
726 @classmethod
727 def sendUDPQuery(cls, query, timeout=2.0, decode=True, fwparams=dict()):
728 if timeout:
729 cls._sock.settimeout(timeout)
730
731 try:
732 cls._sock.send(query.to_wire())
733 data = cls._sock.recv(4096)
734 except socket.timeout:
735 data = None
736 finally:
737 if timeout:
738 cls._sock.settimeout(None)
739
740 message = None
741 if data:
742 if not decode:
743 return data
744 message = dns.message.from_wire(data, **fwparams)
745 return message
746
747 @classmethod
748 def sendTCPQuery(cls, query, timeout=2.0):
749 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
750 if timeout:
751 sock.settimeout(timeout)
752
753 sock.connect(("127.0.0.1", cls._recursorPort))
754
755 try:
756 wire = query.to_wire()
757 sock.send(struct.pack("!H", len(wire)))
758 sock.send(wire)
759 data = sock.recv(2)
760 if data:
761 (datalen,) = struct.unpack("!H", data)
762 data = sock.recv(datalen)
763 except socket.timeout as e:
764 print("Timeout: %s" % (str(e)))
765 data = None
766 except socket.error as e:
767 print("Network error: %s" % (str(e)))
768 data = None
769 finally:
770 sock.close()
771
772 message = None
773 if data:
774 message = dns.message.from_wire(data)
775 return message
776
777 @classmethod
778 def sendTCPQueries(cls, queries, timeout=2.0):
779 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
780 if timeout:
781 sock.settimeout(timeout)
782
783 sock.connect(("127.0.0.1", cls._recursorPort))
784 data = []
785 try:
786 for query in queries:
787 wire = query.to_wire()
788 sock.send(struct.pack("!H", len(wire)))
789 sock.send(wire)
790 for i in range(len(queries)):
791 try:
792 datalen = sock.recv(2)
793 if datalen:
794 (datalen,) = struct.unpack("!H", datalen)
795 data.append(sock.recv(datalen))
796 except socket.timeout as e:
797 continue
798 except socket.error as e:
799 print("Network error: %s" % (str(e)))
800 data = None
801 finally:
802 sock.close()
803
804 messages = []
805 for d in data:
806 messages.append(dns.message.from_wire(d))
807 return messages
808
809 def setUp(self):
810 # This function is called before every tests
811 super(RecursorTest, self).setUp()
812
813 ## Functions for comparisons
814 def assertMessageHasFlags(self, msg, flags, ednsflags=[]):
815 """Asserts that msg has all the flags from flags set
816
817 @param msg: the dns.message.Message to check
818 @param flags: a list of strings with flag mnemonics (like ['RD', 'RA'])
819 @param ednsflags: a list of strings with edns-flag mnemonics (like ['DO'])"""
820
821 if not isinstance(msg, dns.message.Message):
822 raise TypeError("msg is not a dns.message.Message")
823
824 if isinstance(flags, list):
825 for elem in flags:
826 if not isinstance(elem, str):
827 raise TypeError("flags is not a list of strings")
828 else:
829 raise TypeError("flags is not a list of strings")
830
831 if isinstance(ednsflags, list):
832 for elem in ednsflags:
833 if not isinstance(elem, str):
834 raise TypeError("ednsflags is not a list of strings")
835 else:
836 raise TypeError("ednsflags is not a list of strings")
837
838 msgFlags = dns.flags.to_text(msg.flags).split()
839 missingFlags = [flag for flag in flags if flag not in msgFlags]
840
841 msgEdnsFlags = dns.flags.edns_to_text(msg.ednsflags).split()
842 missingEdnsFlags = [ednsflag for ednsflag in ednsflags if ednsflag not in msgEdnsFlags]
843
844 if len(missingFlags) or len(missingEdnsFlags) or len(msgFlags) > len(flags):
845 raise AssertionError("Expected flags '%s' (EDNS: '%s'), found '%s' (EDNS: '%s') in query %s" %
846 (' '.join(flags), ' '.join(ednsflags),
847 ' '.join(msgFlags), ' '.join(msgEdnsFlags),
848 msg.question[0]))
849
850 def assertMessageIsAuthenticated(self, msg):
851 """Asserts that the message has the AD bit set
852
853 @param msg: the dns.message.Message to check"""
854
855 if not isinstance(msg, dns.message.Message):
856 raise TypeError("msg is not a dns.message.Message")
857
858 msgFlags = dns.flags.to_text(msg.flags)
859 self.assertTrue('AD' in msgFlags, "No AD flag found in the message for %s" % msg.question[0].name)
860
861 def assertRRsetInAnswer(self, msg, rrset):
862 """Asserts the rrset (without comparing TTL) exists in the
863 answer section of msg
864
865 @param msg: the dns.message.Message to check
866 @param rrset: a dns.rrset.RRset object"""
867
868 ret = ''
869 if not isinstance(msg, dns.message.Message):
870 raise TypeError("msg is not a dns.message.Message")
871
872 if not isinstance(rrset, dns.rrset.RRset):
873 raise TypeError("rrset is not a dns.rrset.RRset")
874
875 found = False
876 for ans in msg.answer:
877 ret += "%s\n" % ans.to_text()
878 if ans.match(rrset.name, rrset.rdclass, rrset.rdtype, 0, None):
879 self.assertEqual(ans, rrset, "'%s' != '%s'" % (ans.to_text(), rrset.to_text()))
880 found = True
881
882 if not found:
883 raise AssertionError("RRset not found in answer\n\n%s" % ret)
884
885 def assertRRsetInAdditional(self, msg, rrset):
886 """Asserts the rrset (without comparing TTL) exists in the
887 additional section of msg
888
889 @param msg: the dns.message.Message to check
890 @param rrset: a dns.rrset.RRset object"""
891
892 ret = ''
893 if not isinstance(msg, dns.message.Message):
894 raise TypeError("msg is not a dns.message.Message")
895
896 if not isinstance(rrset, dns.rrset.RRset):
897 raise TypeError("rrset is not a dns.rrset.RRset")
898
899 found = False
900 for ans in msg.additional:
901 ret += "%s\n" % ans.to_text()
902 if ans.match(rrset.name, rrset.rdclass, rrset.rdtype, 0, None):
903 self.assertEqual(ans, rrset, "'%s' != '%s'" % (ans.to_text(), rrset.to_text()))
904 found = True
905
906 if not found:
907 raise AssertionError("RRset not found in additional section\n\n%s" % ret)
908
909 def assertMatchingRRSIGInAnswer(self, msg, coveredRRset, keys=None):
910 """Looks for coveredRRset in the answer section and if there is an RRSIG RRset
911 that covers that RRset. If keys is not None, this function will also try to
912 validate the RRset against the RRSIG
913
914 @param msg: The dns.message.Message to check
915 @param coveredRRset: The RRSet to check for
916 @param keys: a dictionary keyed by dns.name.Name with node or rdataset values to use for validation"""
917
918 if not isinstance(msg, dns.message.Message):
919 raise TypeError("msg is not a dns.message.Message")
920
921 if not isinstance(coveredRRset, dns.rrset.RRset):
922 raise TypeError("coveredRRset is not a dns.rrset.RRset")
923
924 msgRRsigRRSet = None
925 msgRRSet = None
926
927 ret = ''
928 for ans in msg.answer:
929 ret += ans.to_text() + "\n"
930
931 if ans.match(coveredRRset.name, coveredRRset.rdclass, coveredRRset.rdtype, 0, None):
932 msgRRSet = ans
933 if ans.match(coveredRRset.name, dns.rdataclass.IN, dns.rdatatype.RRSIG, coveredRRset.rdtype, None):
934 msgRRsigRRSet = ans
935 if msgRRSet and msgRRsigRRSet:
936 break
937
938 if not msgRRSet:
939 raise AssertionError("RRset for '%s' not found in answer" % msg.question[0].to_text())
940
941 if not msgRRsigRRSet:
942 raise AssertionError("No RRSIGs found in answer for %s:\nFull answer:\n%s" % (msg.question[0].to_text(), ret))
943
944 if keys:
945 try:
946 dns.dnssec.validate(msgRRSet, msgRRsigRRSet.to_rdataset(), keys)
947 except dns.dnssec.ValidationFailure as e:
948 raise AssertionError("Signature validation failed for %s:\n%s" % (msg.question[0].to_text(), e))
949
950 def assertMatchingRRSIGInAdditional(self, msg, coveredRRset, keys=None):
951 """Looks for coveredRRset in the additional section and if there is an RRSIG RRset
952 that covers that RRset. If keys is not None, this function will also try to
953 validate the RRset against the RRSIG
954
955 @param msg: The dns.message.Message to check
956 @param coveredRRset: The RRSet to check for
957 @param keys: a dictionary keyed by dns.name.Name with node or rdataset values to use for validation"""
958
959 if not isinstance(msg, dns.message.Message):
960 raise TypeError("msg is not a dns.message.Message")
961
962 if not isinstance(coveredRRset, dns.rrset.RRset):
963 raise TypeError("coveredRRset is not a dns.rrset.RRset")
964
965 msgRRsigRRSet = None
966 msgRRSet = None
967
968 ret = ''
969 for ans in msg.additional:
970 ret += ans.to_text() + "\n"
971
972 if ans.match(coveredRRset.name, coveredRRset.rdclass, coveredRRset.rdtype, 0, None):
973 msgRRSet = ans
974 if ans.match(coveredRRset.name, dns.rdataclass.IN, dns.rdatatype.RRSIG, coveredRRset.rdtype, None):
975 msgRRsigRRSet = ans
976 if msgRRSet and msgRRsigRRSet:
977 break
978
979 if not msgRRSet:
980 raise AssertionError("RRset for '%s' not found in additional" % msg.question[0].to_text())
981
982 if not msgRRsigRRSet:
983 raise AssertionError("No RRSIGs found in additional for %s:\nFull answer:\n%s" % (msg.question[0].to_text(), ret))
984
985 if keys:
986 try:
987 dns.dnssec.validate(msgRRSet, msgRRsigRRSet.to_rdataset(), keys)
988 except dns.dnssec.ValidationFailure as e:
989 raise AssertionError("Signature validation failed for %s:\n%s" % (msg.question[0].to_text(), e))
990
991 def assertNoRRSIGsInAnswer(self, msg):
992 """Checks if there are _no_ RRSIGs in the answer section of msg"""
993
994 if not isinstance(msg, dns.message.Message):
995 raise TypeError("msg is not a dns.message.Message")
996
997 ret = ""
998 for ans in msg.answer:
999 if ans.rdtype == dns.rdatatype.RRSIG:
1000 ret += ans.name.to_text() + "\n"
1001
1002 if len(ret):
1003 raise AssertionError("RRSIG found in answers for:\n%s" % ret)
1004
1005 def assertAnswerEmpty(self, msg):
1006 self.assertTrue(len(msg.answer) == 0, "Data found in the the answer section for %s:\n%s" % (msg.question[0].to_text(), '\n'.join([i.to_text() for i in msg.answer])))
1007
1008 def assertAdditionalEmpty(self, msg):
1009 self.assertTrue(len(msg.additional) == 0, "Data found in the the additional section for %s:\n%s" % (msg.question[0].to_text(), '\n'.join([i.to_text() for i in msg.additional])))
1010
1011 def assertRcodeEqual(self, msg, rcode):
1012 if not isinstance(msg, dns.message.Message):
1013 raise TypeError("msg is not a dns.message.Message but a %s" % type(msg))
1014
1015 if not isinstance(rcode, int):
1016 if isinstance(rcode, str):
1017 rcode = dns.rcode.from_text(rcode)
1018 else:
1019 raise TypeError("rcode is neither a str nor int")
1020
1021 if msg.rcode() != rcode:
1022 msgRcode = dns.rcode.to_text(msg.rcode())
1023 wantedRcode = dns.rcode.to_text(rcode)
1024
1025 raise AssertionError("Rcode for %s is %s, expected %s." % (msg.question[0].to_text(), msgRcode, wantedRcode))
1026
1027 def assertAuthorityHasSOA(self, msg):
1028 if not isinstance(msg, dns.message.Message):
1029 raise TypeError("msg is not a dns.message.Message but a %s" % type(msg))
1030
1031 found = False
1032 for rrset in msg.authority:
1033 if rrset.rdtype == dns.rdatatype.SOA:
1034 found = True
1035 break
1036
1037 if not found:
1038 raise AssertionError("No SOA record found in the authority section:\n%s" % msg.to_text())
1039
1040 def assertResponseMatches(self, query, expectedRRs, response):
1041 expectedResponse = dns.message.make_response(query)
1042
1043 if query.flags & dns.flags.RD:
1044 expectedResponse.flags |= dns.flags.RA
1045 if query.flags & dns.flags.CD:
1046 expectedResponse.flags |= dns.flags.CD
1047
1048 expectedResponse.answer = expectedRRs
1049 print(expectedResponse)
1050 print(response)
1051 self.assertEqual(response, expectedResponse)
1052
1053 @classmethod
1054 def sendQuery(cls, name, rdtype, useTCP=False):
1055 """Helper function that creates the query"""
1056 msg = dns.message.make_query(name, rdtype, want_dnssec=True)
1057 msg.flags |= dns.flags.AD
1058
1059 if useTCP:
1060 return cls.sendTCPQuery(msg)
1061 return cls.sendUDPQuery(msg)
1062
1063 def createQuery(self, name, rdtype, flags, ednsflags):
1064 """Helper function that creates the query with the specified flags.
1065 The flags need to be strings (no checking is performed atm)"""
1066 msg = dns.message.make_query(name, rdtype)
1067 msg.flags = dns.flags.from_text(flags)
1068 msg.flags += dns.flags.from_text('RD')
1069 msg.use_edns(edns=0, ednsflags=dns.flags.edns_from_text(ednsflags))
1070 return msg
1071
1072 @classmethod
1073 def sendUDPQueryWithProxyProtocol(cls, query, v6, source, destination, sourcePort, destinationPort, values=[], timeout=2.0):
1074 queryPayload = query.to_wire()
1075 ppPayload = ProxyProtocol.getPayload(False, False, v6, source, destination, sourcePort, destinationPort, values)
1076 payload = ppPayload + queryPayload
1077
1078 if timeout:
1079 cls._sock.settimeout(timeout)
1080
1081 try:
1082 cls._sock.send(payload)
1083 data = cls._sock.recv(4096)
1084 except socket.timeout:
1085 data = None
1086 finally:
1087 if timeout:
1088 cls._sock.settimeout(None)
1089
1090 message = None
1091 if data:
1092 message = dns.message.from_wire(data)
1093 return message
1094
1095 @classmethod
1096 def sendTCPQueryWithProxyProtocol(cls, query, v6, source, destination, sourcePort, destinationPort, values=[], timeout=2.0):
1097 queryPayload = query.to_wire()
1098 ppPayload = ProxyProtocol.getPayload(False, False, v6, source, destination, sourcePort, destinationPort, values)
1099
1100 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1101 if timeout:
1102 sock.settimeout(timeout)
1103
1104 sock.connect(("127.0.0.1", cls._recursorPort))
1105
1106 try:
1107 sock.send(ppPayload)
1108 sock.send(struct.pack("!H", len(queryPayload)))
1109 sock.send(queryPayload)
1110 data = sock.recv(2)
1111 if data:
1112 (datalen,) = struct.unpack("!H", data)
1113 data = sock.recv(datalen)
1114 except socket.timeout as e:
1115 print("Timeout: %s" % (str(e)))
1116 data = None
1117 except socket.error as e:
1118 print("Network error: %s" % (str(e)))
1119 data = None
1120 finally:
1121 sock.close()
1122
1123 message = None
1124 if data:
1125 message = dns.message.from_wire(data)
1126 return message