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