]> git.ipfire.org Git - thirdparty/pdns.git/blob - regression-tests.recursor-dnssec/recursortests.py
b2b-migrate did not open a transaction, breaking it for lmdb
[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 bogus.example. 3600 IN NS ns.bogus.example.
89 bogus.example. 3600 IN DS 65034 13 1 6df3bb50ea538e90eacdd7ae5419730783abb0ee
90 ns.bogus.example. 3600 IN A {prefix}.12
91
92 insecure.example. 3600 IN NS ns.insecure.example.
93 ns.insecure.example. 3600 IN A {prefix}.13
94
95 optout.example. 3600 IN NS ns1.optout.example.
96 optout.example. 3600 IN DS 59332 13 1 e664f886ae1b5df03d918bc1217d22afc29925b9
97 ns1.optout.example. 3600 IN A {prefix}.14
98
99 insecure-formerr.example. 3600 IN NS ns1.insecure-formerr.example.
100 ns1.insecure-formerr.example. 3600 IN A {prefix}.2
101
102 ecs-echo.example. 3600 IN NS ns1.ecs-echo.example.
103 ns1.ecs-echo.example. 3600 IN A {prefix}.21
104
105 islandofsecurity.example. 3600 IN NS ns1.islandofsecurity.example.
106 ns1.islandofsecurity.example. 3600 IN A {prefix}.9
107
108 sortcname.example. 3600 IN CNAME sort
109 sort.example. 3600 IN A 17.38.42.80
110 sort.example. 3600 IN A 192.168.0.1
111 sort.example. 3600 IN A 17.238.240.5
112 sort.example. 3600 IN MX 25 mx
113 """,
114 'secure.example': """
115 secure.example. 3600 IN SOA {soa}
116 secure.example. 3600 IN NS ns.secure.example.
117 ns.secure.example. 3600 IN A {prefix}.9
118
119 secure.example. 3600 IN A 192.0.2.17
120
121 host1.secure.example. 3600 IN A 192.0.2.2
122 cname.secure.example. 3600 IN CNAME host1.secure.example.
123 cname-to-insecure.secure.example. 3600 IN CNAME node1.insecure.example.
124 cname-to-bogus.secure.example. 3600 IN CNAME ted.bogus.example.
125 cname-to-islandofsecurity.secure.example. 3600 IN CNAME node1.islandofsecurity.example.
126
127 host1.sub.secure.example. 3600 IN A 192.0.2.11
128
129 ;; See #4158
130 sub2.secure.example. 3600 IN CNAME doesnotmatter.insecure.example.
131 insecure.sub2.secure.example. 3600 IN NS ns1.insecure.example.
132
133 *.wildcard.secure.example. 3600 IN A 192.0.2.10
134
135 *.cnamewildcard.secure.example. 3600 IN CNAME host1.secure.example.
136
137 *.cnamewildcardnxdomain.secure.example. 3600 IN CNAME doesntexist.secure.example.
138
139 cname-to-formerr.secure.example. 3600 IN CNAME host1.insecure-formerr.example.
140 """,
141 'cname-secure.example': """
142 cname-secure.example. 3600 IN SOA {soa}
143 cname-secure.example. 3600 IN NS ns.cname-secure.example.
144 ns.cname-secure.example. 3600 IN A {prefix}.15
145 cname-secure.example. 3600 IN CNAME secure.example.
146 """,
147 'bogus.example': """
148 bogus.example. 3600 IN SOA {soa}
149 bogus.example. 3600 IN NS ns1.bogus.example.
150 ns1.bogus.example. 3600 IN A {prefix}.12
151 ted.bogus.example. 3600 IN A 192.0.2.1
152 bill.bogus.example. 3600 IN AAAA 2001:db8:12::3
153 """,
154 'insecure.sub2.secure.example': """
155 insecure.sub2.secure.example. 3600 IN SOA {soa}
156 insecure.sub2.secure.example. 3600 IN NS ns1.insecure.example.
157
158 node1.insecure.sub2.secure.example. 3600 IN A 192.0.2.18
159 """,
160 'insecure.example': """
161 insecure.example. 3600 IN SOA {soa}
162 insecure.example. 3600 IN NS ns1.insecure.example.
163 ns1.insecure.example. 3600 IN A {prefix}.13
164
165 node1.insecure.example. 3600 IN A 192.0.2.6
166
167 cname-to-secure.insecure.example. 3600 IN CNAME host1.secure.example.
168 """,
169 'optout.example': """
170 optout.example. 3600 IN SOA {soa}
171 optout.example. 3600 IN NS ns1.optout.example.
172 ns1.optout.example. 3600 IN A {prefix}.14
173
174 insecure.optout.example. 3600 IN NS ns1.insecure.optout.example.
175 ns1.insecure.optout.example. 3600 IN A {prefix}.15
176
177 secure.optout.example. 3600 IN NS ns1.secure.optout.example.
178 secure.optout.example. 3600 IN DS 64215 13 1 b88284d7a8d8605c398e8942262f97b9a5a31787
179 ns1.secure.optout.example. 3600 IN A {prefix}.15
180 """,
181 'insecure.optout.example': """
182 insecure.optout.example. 3600 IN SOA {soa}
183 insecure.optout.example. 3600 IN NS ns1.insecure.optout.example.
184 ns1.insecure.optout.example. 3600 IN A {prefix}.15
185
186 node1.insecure.optout.example. 3600 IN A 192.0.2.7
187 """,
188 'secure.optout.example': """
189 secure.optout.example. 3600 IN SOA {soa}
190 secure.optout.example. 3600 IN NS ns1.secure.optout.example.
191 ns1.secure.optout.example. 3600 IN A {prefix}.15
192
193 node1.secure.optout.example. 3600 IN A 192.0.2.8
194 """,
195 'islandofsecurity.example': """
196 islandofsecurity.example. 3600 IN SOA {soa}
197 islandofsecurity.example. 3600 IN NS ns1.islandofsecurity.example.
198 ns1.islandofsecurity.example. 3600 IN A {prefix}.9
199
200 node1.islandofsecurity.example. 3600 IN A 192.0.2.20
201 """,
202 'undelegated.secure.example': """
203 undelegated.secure.example. 3600 IN SOA {soa}
204 undelegated.secure.example. 3600 IN NS ns1.undelegated.secure.example.
205
206 node1.undelegated.secure.example. 3600 IN A 192.0.2.21
207 """,
208 'undelegated.insecure.example': """
209 undelegated.insecure.example. 3600 IN SOA {soa}
210 undelegated.insecure.example. 3600 IN NS ns1.undelegated.insecure.example.
211
212 node1.undelegated.insecure.example. 3600 IN A 192.0.2.22
213 """,
214 }
215
216 # The private keys for the zones (note that DS records should go into
217 # the zonecontent in _zones
218 _zone_keys = {
219 'ROOT': """
220 Private-key-format: v1.2
221 Algorithm: 13 (ECDSAP256SHA256)
222 PrivateKey: rhWuEydDz3QaIspSVj683B8Xq5q/ozzA38XUgzD4Fbo=
223 """,
224
225 'example': """
226 Private-key-format: v1.2
227 Algorithm: 13 (ECDSAP256SHA256)
228 PrivateKey: Lt0v0Gol3pRUFM7fDdcy0IWN0O/MnEmVPA+VylL8Y4U=
229 """,
230
231 'secure.example': """
232 Private-key-format: v1.2
233 Algorithm: 13 (ECDSAP256SHA256)
234 PrivateKey: 1G4WRoOFJJXk+fotDCHVORtJmIG2OUhKi8AO2jDPGZA=
235 """,
236
237 'bogus.example': """
238 Private-key-format: v1.2
239 Algorithm: 13 (ECDSAP256SHA256)
240 PrivateKey: f5jV7Q8kd5hDpMWObsuQ6SQda0ftf+JrO3uZwEg6nVw=
241 """,
242
243 'optout.example': """
244 Private-key-format: v1.2
245 Algorithm: 13 (ECDSAP256SHA256)
246 PrivateKey: efmq9G+J4Y2iPnIBRwJiy6Z/nIHSzpsCy/7XHhlS19A=
247 """,
248
249 'secure.optout.example': """
250 Private-key-format: v1.2
251 Algorithm: 13 (ECDSAP256SHA256)
252 PrivateKey: xcNUxt1Knj14A00lKQFDboluiJyM2f7FxpgsQaQ3AQ4=
253 """,
254
255 'islandofsecurity.example': """
256 Private-key-format: v1.2
257 Algorithm: 13 (ECDSAP256SHA256)
258 PrivateKey: o9F5iix8V68tnMcuOaM2Lt8XXhIIY//SgHIHEePk6cM=
259 """,
260
261 'cname-secure.example': """
262 Private-key-format: v1.2
263 Algorithm: 13 (ECDSAP256SHA256)
264 PrivateKey: kvoV/g4IO/tefSro+FLJ5UC7H3BUf0IUtZQSUOfQGyA=
265 """
266 }
267
268 # This dict is keyed with the suffix of the IP address and its value
269 # is a list of zones hosted on that IP. Note that delegations should
270 # go into the _zones's zonecontent
271 _auth_zones = {
272 '8': ['ROOT'],
273 '9': ['secure.example', 'islandofsecurity.example'],
274 '10': ['example'],
275 '11': ['example'],
276 '12': ['bogus.example', 'undelegated.secure.example', 'undelegated.insecure.example'],
277 '13': ['insecure.example', 'insecure.sub2.secure.example'],
278 '14': ['optout.example'],
279 '15': ['insecure.optout.example', 'secure.optout.example', 'cname-secure.example']
280 }
281
282 _auth_cmd = ['authbind',
283 os.environ['PDNS']]
284 _auth_env = {}
285 _auths = {}
286
287 @classmethod
288 def createConfigDir(cls, confdir):
289 try:
290 shutil.rmtree(confdir)
291 except OSError as e:
292 if e.errno != errno.ENOENT:
293 raise
294 os.mkdir(confdir, 0o755)
295
296 @classmethod
297 def generateAuthZone(cls, confdir, zonename, zonecontent):
298 with open(os.path.join(confdir, '%s.zone' % zonename), 'w') as zonefile:
299 zonefile.write(zonecontent.format(prefix=cls._PREFIX, soa=cls._SOA))
300
301 @classmethod
302 def generateAuthNamedConf(cls, confdir, zones):
303 with open(os.path.join(confdir, 'named.conf'), 'w') as namedconf:
304 namedconf.write("""
305 options {
306 directory "%s";
307 };""" % confdir)
308 for zonename in zones:
309 zone = '.' if zonename == 'ROOT' else zonename
310
311 namedconf.write("""
312 zone "%s" {
313 type master;
314 file "%s.zone";
315 };""" % (zone, zonename))
316
317 @classmethod
318 def generateAuthConfig(cls, confdir):
319 bind_dnssec_db = os.path.join(confdir, 'bind-dnssec.sqlite3')
320
321 with open(os.path.join(confdir, 'pdns.conf'), 'w') as pdnsconf:
322 pdnsconf.write("""
323 module-dir=../regression-tests/modules
324 launch=bind
325 daemon=no
326 local-ipv6=
327 bind-config={confdir}/named.conf
328 bind-dnssec-db={bind_dnssec_db}
329 socket-dir={confdir}
330 cache-ttl=0
331 negquery-cache-ttl=0
332 query-cache-ttl=0
333 log-dns-queries=yes
334 log-dns-details=yes
335 loglevel=9
336 distributor-threads=1""".format(confdir=confdir,
337 bind_dnssec_db=bind_dnssec_db))
338
339 pdnsutilCmd = [os.environ['PDNSUTIL'],
340 '--config-dir=%s' % confdir,
341 'create-bind-db',
342 bind_dnssec_db]
343
344 print(' '.join(pdnsutilCmd))
345 try:
346 subprocess.check_output(pdnsutilCmd, stderr=subprocess.STDOUT)
347 except subprocess.CalledProcessError as e:
348 raise AssertionError('%s failed (%d): %s' % (pdnsutilCmd, e.returncode, e.output))
349
350 @classmethod
351 def secureZone(cls, confdir, zonename, key=None):
352 zone = '.' if zonename == 'ROOT' else zonename
353 if not key:
354 pdnsutilCmd = [os.environ['PDNSUTIL'],
355 '--config-dir=%s' % confdir,
356 'secure-zone',
357 zone]
358 else:
359 keyfile = os.path.join(confdir, 'dnssec.key')
360 with open(keyfile, 'w') as fdKeyfile:
361 fdKeyfile.write(key)
362
363 pdnsutilCmd = [os.environ['PDNSUTIL'],
364 '--config-dir=%s' % confdir,
365 'import-zone-key',
366 zone,
367 keyfile,
368 'active',
369 'ksk']
370
371 print(' '.join(pdnsutilCmd))
372 try:
373 subprocess.check_output(pdnsutilCmd, stderr=subprocess.STDOUT)
374 except subprocess.CalledProcessError as e:
375 raise AssertionError('%s failed (%d): %s' % (pdnsutilCmd, e.returncode, e.output))
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("addTA('.', '%s')\n" % 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._lua_dns_script_file:
452 luascriptpath = os.path.join(confdir, 'dnsscript.lua')
453 with open(luascriptpath, 'w') as luascript:
454 luascript.write(cls._lua_dns_script_file)
455 conf.write("lua-dns-script=%s\n" % luascriptpath)
456 if cls._roothints:
457 roothintspath = os.path.join(confdir, 'root.hints')
458 with open(roothintspath, 'w') as roothints:
459 roothints.write(cls._roothints)
460 conf.write("hint-file=%s\n" % roothintspath)
461
462 @classmethod
463 def startResponders(cls):
464 pass
465
466 @classmethod
467 def startRecursor(cls, confdir, port):
468 print("Launching pdns_recursor..")
469 recursorcmd = [os.environ['PDNSRECURSOR'],
470 '--config-dir=%s' % confdir,
471 '--local-port=%s' % port,
472 '--security-poll-suffix=']
473 print(' '.join(recursorcmd))
474
475 logFile = os.path.join(confdir, 'recursor.log')
476 with open(logFile, 'w') as fdLog:
477 cls._recursor = subprocess.Popen(recursorcmd, close_fds=True,
478 stdout=fdLog, stderr=fdLog)
479
480 if 'PDNSRECURSOR_FAST_TESTS' in os.environ:
481 delay = 0.5
482 else:
483 delay = cls._recursorStartupDelay
484
485 time.sleep(delay)
486
487 if cls._recursor.poll() is not None:
488 try:
489 cls._recursor.kill()
490 except OSError as e:
491 if e.errno != errno.ESRCH:
492 raise
493 with open(logFile, 'r') as fdLog:
494 print(fdLog.read())
495 sys.exit(cls._recursor.returncode)
496
497 @classmethod
498 def wipeRecursorCache(cls, confdir):
499 rec_controlCmd = [os.environ['RECCONTROL'],
500 '--config-dir=%s' % confdir,
501 'wipe-cache',
502 '.$']
503 try:
504 subprocess.check_output(rec_controlCmd, stderr=subprocess.STDOUT)
505 except subprocess.CalledProcessError as e:
506 raise AssertionError('%s failed (%d): %s' % (rec_controlCmd, e.returncode, e.output))
507
508 @classmethod
509 def setUpSockets(cls):
510 print("Setting up UDP socket..")
511 cls._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
512 cls._sock.settimeout(2.0)
513 cls._sock.connect(("127.0.0.1", cls._recursorPort))
514
515 @classmethod
516 def setUpClass(cls):
517 cls.setUpSockets()
518
519 cls.startResponders()
520
521 confdir = os.path.join('configs', cls._confdir)
522 cls.createConfigDir(confdir)
523 cls.generateAllAuthConfig(confdir)
524 cls.startAllAuth(confdir)
525
526 cls.generateRecursorConfig(confdir)
527 cls.startRecursor(confdir, cls._recursorPort)
528
529 print("Launching tests..")
530
531 @classmethod
532 def tearDownClass(cls):
533 cls.tearDownRecursor()
534 cls.tearDownAuth()
535 cls.tearDownResponders()
536
537 @classmethod
538 def tearDownResponders(cls):
539 pass
540
541 @classmethod
542 def tearDownAuth(cls):
543 if 'PDNSRECURSOR_FAST_TESTS' in os.environ:
544 delay = 0.1
545 else:
546 delay = 1.0
547
548 for _, auth in cls._auths.items():
549 try:
550 auth.terminate()
551 if auth.poll() is None:
552 time.sleep(delay)
553 if auth.poll() is None:
554 auth.kill()
555 auth.wait()
556 except OSError as e:
557 if e.errno != errno.ESRCH:
558 raise
559
560 @classmethod
561 def tearDownRecursor(cls):
562 if 'PDNSRECURSOR_FAST_TESTS' in os.environ:
563 delay = 0.1
564 else:
565 delay = 1.0
566 try:
567 if cls._recursor:
568 cls._recursor.terminate()
569 if cls._recursor.poll() is None:
570 time.sleep(delay)
571 if cls._recursor.poll() is None:
572 cls._recursor.kill()
573 cls._recursor.wait()
574 except OSError as e:
575 # There is a race-condition with the poll() and
576 # kill() statements, when the process is dead on the
577 # kill(), this is fine
578 if e.errno != errno.ESRCH:
579 raise
580
581 @classmethod
582 def sendUDPQuery(cls, query, timeout=2.0, decode=True, fwparams=dict()):
583 if timeout:
584 cls._sock.settimeout(timeout)
585
586 try:
587 cls._sock.send(query.to_wire())
588 data = cls._sock.recv(4096)
589 except socket.timeout:
590 data = None
591 finally:
592 if timeout:
593 cls._sock.settimeout(None)
594
595 message = None
596 if data:
597 if not decode:
598 return data
599 message = dns.message.from_wire(data, **fwparams)
600 return message
601
602 @classmethod
603 def sendTCPQuery(cls, query, timeout=2.0):
604 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
605 if timeout:
606 sock.settimeout(timeout)
607
608 sock.connect(("127.0.0.1", cls._recursorPort))
609
610 try:
611 wire = query.to_wire()
612 sock.send(struct.pack("!H", len(wire)))
613 sock.send(wire)
614 data = sock.recv(2)
615 if data:
616 (datalen,) = struct.unpack("!H", data)
617 data = sock.recv(datalen)
618 except socket.timeout as e:
619 print("Timeout: %s" % (str(e)))
620 data = None
621 except socket.error as e:
622 print("Network error: %s" % (str(e)))
623 data = None
624 finally:
625 sock.close()
626
627 message = None
628 if data:
629 message = dns.message.from_wire(data)
630 return message
631
632 def setUp(self):
633 # This function is called before every tests
634 return
635
636 ## Functions for comparisons
637 def assertMessageHasFlags(self, msg, flags, ednsflags=[]):
638 """Asserts that msg has all the flags from flags set
639
640 @param msg: the dns.message.Message to check
641 @param flags: a list of strings with flag mnemonics (like ['RD', 'RA'])
642 @param ednsflags: a list of strings with edns-flag mnemonics (like ['DO'])"""
643
644 if not isinstance(msg, dns.message.Message):
645 raise TypeError("msg is not a dns.message.Message")
646
647 if isinstance(flags, list):
648 for elem in flags:
649 if not isinstance(elem, str):
650 raise TypeError("flags is not a list of strings")
651 else:
652 raise TypeError("flags is not a list of strings")
653
654 if isinstance(ednsflags, list):
655 for elem in ednsflags:
656 if not isinstance(elem, str):
657 raise TypeError("ednsflags is not a list of strings")
658 else:
659 raise TypeError("ednsflags is not a list of strings")
660
661 msgFlags = dns.flags.to_text(msg.flags).split()
662 missingFlags = [flag for flag in flags if flag not in msgFlags]
663
664 msgEdnsFlags = dns.flags.edns_to_text(msg.ednsflags).split()
665 missingEdnsFlags = [ednsflag for ednsflag in ednsflags if ednsflag not in msgEdnsFlags]
666
667 if len(missingFlags) or len(missingEdnsFlags) or len(msgFlags) > len(flags):
668 raise AssertionError("Expected flags '%s' (EDNS: '%s'), found '%s' (EDNS: '%s') in query %s" %
669 (' '.join(flags), ' '.join(ednsflags),
670 ' '.join(msgFlags), ' '.join(msgEdnsFlags),
671 msg.question[0]))
672
673 def assertMessageIsAuthenticated(self, msg):
674 """Asserts that the message has the AD bit set
675
676 @param msg: the dns.message.Message to check"""
677
678 if not isinstance(msg, dns.message.Message):
679 raise TypeError("msg is not a dns.message.Message")
680
681 msgFlags = dns.flags.to_text(msg.flags)
682 self.assertTrue('AD' in msgFlags, "No AD flag found in the message for %s" % msg.question[0].name)
683
684 def assertRRsetInAnswer(self, msg, rrset):
685 """Asserts the rrset (without comparing TTL) exists in the
686 answer section of msg
687
688 @param msg: the dns.message.Message to check
689 @param rrset: a dns.rrset.RRset object"""
690
691 ret = ''
692 if not isinstance(msg, dns.message.Message):
693 raise TypeError("msg is not a dns.message.Message")
694
695 if not isinstance(rrset, dns.rrset.RRset):
696 raise TypeError("rrset is not a dns.rrset.RRset")
697
698 found = False
699 for ans in msg.answer:
700 ret += "%s\n" % ans.to_text()
701 if ans.match(rrset.name, rrset.rdclass, rrset.rdtype, 0, None):
702 self.assertEqual(ans, rrset, "'%s' != '%s'" % (ans.to_text(), rrset.to_text()))
703 found = True
704
705 if not found:
706 raise AssertionError("RRset not found in answer\n\n%s" % ret)
707
708 def assertMatchingRRSIGInAnswer(self, msg, coveredRRset, keys=None):
709 """Looks for coveredRRset in the answer section and if there is an RRSIG RRset
710 that covers that RRset. If keys is not None, this function will also try to
711 validate the RRset against the RRSIG
712
713 @param msg: The dns.message.Message to check
714 @param coveredRRset: The RRSet to check for
715 @param keys: a dictionary keyed by dns.name.Name with node or rdataset values to use for validation"""
716
717 if not isinstance(msg, dns.message.Message):
718 raise TypeError("msg is not a dns.message.Message")
719
720 if not isinstance(coveredRRset, dns.rrset.RRset):
721 raise TypeError("coveredRRset is not a dns.rrset.RRset")
722
723 msgRRsigRRSet = None
724 msgRRSet = None
725
726 ret = ''
727 for ans in msg.answer:
728 ret += ans.to_text() + "\n"
729
730 if ans.match(coveredRRset.name, coveredRRset.rdclass, coveredRRset.rdtype, 0, None):
731 msgRRSet = ans
732 if ans.match(coveredRRset.name, dns.rdataclass.IN, dns.rdatatype.RRSIG, coveredRRset.rdtype, None):
733 msgRRsigRRSet = ans
734 if msgRRSet and msgRRsigRRSet:
735 break
736
737 if not msgRRSet:
738 raise AssertionError("RRset for '%s' not found in answer" % msg.question[0].to_text())
739
740 if not msgRRsigRRSet:
741 raise AssertionError("No RRSIGs found in answer for %s:\nFull answer:\n%s" % (msg.question[0].to_text(), ret))
742
743 if keys:
744 try:
745 dns.dnssec.validate(msgRRSet, msgRRsigRRSet.to_rdataset(), keys)
746 except dns.dnssec.ValidationFailure as e:
747 raise AssertionError("Signature validation failed for %s:\n%s" % (msg.question[0].to_text(), e))
748
749 def assertNoRRSIGsInAnswer(self, msg):
750 """Checks if there are _no_ RRSIGs in the answer section of msg"""
751
752 if not isinstance(msg, dns.message.Message):
753 raise TypeError("msg is not a dns.message.Message")
754
755 ret = ""
756 for ans in msg.answer:
757 if ans.rdtype == dns.rdatatype.RRSIG:
758 ret += ans.name.to_text() + "\n"
759
760 if len(ret):
761 raise AssertionError("RRSIG found in answers for:\n%s" % ret)
762
763 def assertAnswerEmpty(self, msg):
764 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])))
765
766 def assertRcodeEqual(self, msg, rcode):
767 if not isinstance(msg, dns.message.Message):
768 raise TypeError("msg is not a dns.message.Message but a %s" % type(msg))
769
770 if not isinstance(rcode, int):
771 if isinstance(rcode, str):
772 rcode = dns.rcode.from_text(rcode)
773 else:
774 raise TypeError("rcode is neither a str nor int")
775
776 if msg.rcode() != rcode:
777 msgRcode = dns.rcode._by_value[msg.rcode()]
778 wantedRcode = dns.rcode._by_value[rcode]
779
780 raise AssertionError("Rcode for %s is %s, expected %s." % (msg.question[0].to_text(), msgRcode, wantedRcode))
781
782 def assertAuthorityHasSOA(self, msg):
783 if not isinstance(msg, dns.message.Message):
784 raise TypeError("msg is not a dns.message.Message but a %s" % type(msg))
785
786 found = False
787 for rrset in msg.authority:
788 if rrset.rdtype == dns.rdatatype.SOA:
789 found = True
790 break
791
792 if not found:
793 raise AssertionError("No SOA record found in the authority section:\n%s" % msg.to_text())
794
795 def assertResponseMatches(self, query, expectedRRs, response):
796 expectedResponse = dns.message.make_response(query)
797
798 if query.flags & dns.flags.RD:
799 expectedResponse.flags |= dns.flags.RA
800 if query.flags & dns.flags.CD:
801 expectedResponse.flags |= dns.flags.CD
802
803 expectedResponse.answer = expectedRRs
804 print(expectedResponse)
805 print(response)
806 self.assertEquals(response, expectedResponse)
807
808 @classmethod
809 def sendQuery(cls, name, rdtype, useTCP=False):
810 """Helper function that creates the query"""
811 msg = dns.message.make_query(name, rdtype, want_dnssec=True)
812 msg.flags |= dns.flags.AD
813
814 if useTCP:
815 return cls.sendTCPQuery(msg)
816 return cls.sendUDPQuery(msg)
817
818 def createQuery(self, name, rdtype, flags, ednsflags):
819 """Helper function that creates the query with the specified flags.
820 The flags need to be strings (no checking is performed atm)"""
821 msg = dns.message.make_query(name, rdtype)
822 msg.flags = dns.flags.from_text(flags)
823 msg.flags += dns.flags.from_text('RD')
824 msg.use_edns(edns=0, ednsflags=dns.flags.edns_from_text(ednsflags))
825 return msg