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