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