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