]> git.ipfire.org Git - thirdparty/pdns.git/blame - regression-tests.recursor-dnssec/recursortests.py
rec: Support multiple values for the same EDNS option in gettag
[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}
f3be2401
RG
73example. 3600 IN NS ns1.example.
74example. 3600 IN NS ns2.example.
7568b07d
PL
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
d5b8ccd4
PL
82cname-secure.example. 3600 IN NS ns.cname-secure.example.
83cname-secure.example. 3600 IN DS 49148 13 1 a10314452d5ec4d97fcc6d7e275d217261fe790f
84ns.cname-secure.example. 3600 IN A {prefix}.15
85
7568b07d
PL
86bogus.example. 3600 IN NS ns.bogus.example.
87bogus.example. 3600 IN DS 65034 13 1 6df3bb50ea538e90eacdd7ae5419730783abb0ee
88ns.bogus.example. 3600 IN A {prefix}.12
89
90insecure.example. 3600 IN NS ns.insecure.example.
91ns.insecure.example. 3600 IN A {prefix}.13
11886ab9
PL
92
93optout.example. 3600 IN NS ns1.optout.example.
94optout.example. 3600 IN DS 59332 13 1 e664f886ae1b5df03d918bc1217d22afc29925b9
95ns1.optout.example. 3600 IN A {prefix}.14
962a980d
PL
96
97insecure-formerr.example. 3600 IN NS ns1.insecure-formerr.example.
98ns1.insecure-formerr.example. 3600 IN A {prefix}.2
3bebf5f0 99
9a0b88e8
RG
100ecs-echo.example. 3600 IN NS ns1.ecs-echo.example.
101ns1.ecs-echo.example. 3600 IN A {prefix}.21
102
3bebf5f0
PL
103islandofsecurity.example. 3600 IN NS ns1.islandofsecurity.example.
104ns1.islandofsecurity.example. 3600 IN A {prefix}.9
2fed5be3
PD
105
106sortcname.example. 3600 IN CNAME sort
107sort.example. 3600 IN A 17.38.42.80
108sort.example. 3600 IN A 192.168.0.1
109sort.example. 3600 IN A 17.238.240.5
110sort.example. 3600 IN MX 25 mx
7568b07d
PL
111 """,
112 'secure.example': """
113secure.example. 3600 IN SOA {soa}
114secure.example. 3600 IN NS ns.secure.example.
115ns.secure.example. 3600 IN A {prefix}.9
116
d5b8ccd4
PL
117secure.example. 3600 IN A 192.0.2.17
118
7568b07d 119host1.secure.example. 3600 IN A 192.0.2.2
05537f80 120cname.secure.example. 3600 IN CNAME host1.secure.example.
6552b37b 121cname-to-insecure.secure.example. 3600 IN CNAME node1.insecure.example.
faa3b298 122cname-to-bogus.secure.example. 3600 IN CNAME ted.bogus.example.
3bebf5f0 123cname-to-islandofsecurity.secure.example. 3600 IN CNAME node1.islandofsecurity.example.
46419ee3
PL
124
125host1.sub.secure.example. 3600 IN A 192.0.2.11
fdb27cb2 126
694ef440
PL
127;; See #4158
128sub2.secure.example. 3600 IN CNAME doesnotmatter.insecure.example.
129insecure.sub2.secure.example. 3600 IN NS ns1.insecure.example.
130
fdb27cb2 131*.wildcard.secure.example. 3600 IN A 192.0.2.10
52033c6f
PL
132
133*.cnamewildcard.secure.example. 3600 IN CNAME host1.secure.example.
134
135*.cnamewildcardnxdomain.secure.example. 3600 IN CNAME doesntexist.secure.example.
962a980d
PL
136
137cname-to-formerr.secure.example. 3600 IN CNAME host1.insecure-formerr.example.
d5b8ccd4
PL
138 """,
139 'cname-secure.example': """
140cname-secure.example. 3600 IN SOA {soa}
141cname-secure.example. 3600 IN NS ns.cname-secure.example.
142ns.cname-secure.example. 3600 IN A {prefix}.15
143cname-secure.example. 3600 IN CNAME secure.example.
7568b07d
PL
144 """,
145 'bogus.example': """
146bogus.example. 3600 IN SOA {soa}
147bogus.example. 3600 IN NS ns1.bogus.example.
148ns1.bogus.example. 3600 IN A {prefix}.12
149ted.bogus.example. 3600 IN A 192.0.2.1
150bill.bogus.example. 3600 IN AAAA 2001:db8:12::3
694ef440
PL
151 """,
152 'insecure.sub2.secure.example': """
153insecure.sub2.secure.example. 3600 IN SOA {soa}
154insecure.sub2.secure.example. 3600 IN NS ns1.insecure.example.
155
156node1.insecure.sub2.secure.example. 3600 IN A 192.0.2.18
7568b07d
PL
157 """,
158 'insecure.example': """
159insecure.example. 3600 IN SOA {soa}
160insecure.example. 3600 IN NS ns1.insecure.example.
161ns1.insecure.example. 3600 IN A {prefix}.13
162
163node1.insecure.example. 3600 IN A 192.0.2.6
6552b37b
PL
164
165cname-to-secure.insecure.example. 3600 IN CNAME host1.secure.example.
11886ab9
PL
166 """,
167 'optout.example': """
168optout.example. 3600 IN SOA {soa}
169optout.example. 3600 IN NS ns1.optout.example.
170ns1.optout.example. 3600 IN A {prefix}.14
171
172insecure.optout.example. 3600 IN NS ns1.insecure.optout.example.
173ns1.insecure.optout.example. 3600 IN A {prefix}.15
174
175secure.optout.example. 3600 IN NS ns1.secure.optout.example.
176secure.optout.example. 3600 IN DS 64215 13 1 b88284d7a8d8605c398e8942262f97b9a5a31787
177ns1.secure.optout.example. 3600 IN A {prefix}.15
178 """,
179 'insecure.optout.example': """
180insecure.optout.example. 3600 IN SOA {soa}
181insecure.optout.example. 3600 IN NS ns1.insecure.optout.example.
182ns1.insecure.optout.example. 3600 IN A {prefix}.15
183
184node1.insecure.optout.example. 3600 IN A 192.0.2.7
185 """,
186 'secure.optout.example': """
187secure.optout.example. 3600 IN SOA {soa}
188secure.optout.example. 3600 IN NS ns1.secure.optout.example.
189ns1.secure.optout.example. 3600 IN A {prefix}.15
190
191node1.secure.optout.example. 3600 IN A 192.0.2.8
3bebf5f0
PL
192 """,
193 'islandofsecurity.example': """
194islandofsecurity.example. 3600 IN SOA {soa}
195islandofsecurity.example. 3600 IN NS ns1.islandofsecurity.example.
196ns1.islandofsecurity.example. 3600 IN A {prefix}.9
197
198node1.islandofsecurity.example. 3600 IN A 192.0.2.20
3a7ddb0b
PL
199 """,
200 'undelegated.secure.example': """
201undelegated.secure.example. 3600 IN SOA {soa}
202undelegated.secure.example. 3600 IN NS ns1.undelegated.secure.example.
203
204node1.undelegated.secure.example. 3600 IN A 192.0.2.21
205 """,
206 'undelegated.insecure.example': """
207undelegated.insecure.example. 3600 IN SOA {soa}
208undelegated.insecure.example. 3600 IN NS ns1.undelegated.insecure.example.
209
210node1.undelegated.insecure.example. 3600 IN A 192.0.2.22
211 """,
7568b07d
PL
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': """
218Private-key-format: v1.2
219Algorithm: 13 (ECDSAP256SHA256)
220PrivateKey: rhWuEydDz3QaIspSVj683B8Xq5q/ozzA38XUgzD4Fbo=
221 """,
222
223 'example': """
224Private-key-format: v1.2
225Algorithm: 13 (ECDSAP256SHA256)
226PrivateKey: Lt0v0Gol3pRUFM7fDdcy0IWN0O/MnEmVPA+VylL8Y4U=
227 """,
228
229 'secure.example': """
230Private-key-format: v1.2
231Algorithm: 13 (ECDSAP256SHA256)
232PrivateKey: 1G4WRoOFJJXk+fotDCHVORtJmIG2OUhKi8AO2jDPGZA=
233 """,
234
235 'bogus.example': """
236Private-key-format: v1.2
237Algorithm: 13 (ECDSAP256SHA256)
238PrivateKey: f5jV7Q8kd5hDpMWObsuQ6SQda0ftf+JrO3uZwEg6nVw=
239 """,
11886ab9
PL
240
241 'optout.example': """
242Private-key-format: v1.2
243Algorithm: 13 (ECDSAP256SHA256)
244PrivateKey: efmq9G+J4Y2iPnIBRwJiy6Z/nIHSzpsCy/7XHhlS19A=
245 """,
246
247 'secure.optout.example': """
248Private-key-format: v1.2
249Algorithm: 13 (ECDSAP256SHA256)
250PrivateKey: xcNUxt1Knj14A00lKQFDboluiJyM2f7FxpgsQaQ3AQ4=
3bebf5f0
PL
251 """,
252
253 'islandofsecurity.example': """
254Private-key-format: v1.2
255Algorithm: 13 (ECDSAP256SHA256)
256PrivateKey: o9F5iix8V68tnMcuOaM2Lt8XXhIIY//SgHIHEePk6cM=
d5b8ccd4
PL
257 """,
258
259 'cname-secure.example': """
260Private-key-format: v1.2
261Algorithm: 13 (ECDSAP256SHA256)
262PrivateKey: kvoV/g4IO/tefSro+FLJ5UC7H3BUf0IUtZQSUOfQGyA=
263"""
7568b07d
PL
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'],
3bebf5f0 271 '9': ['secure.example', 'islandofsecurity.example'],
7568b07d
PL
272 '10': ['example'],
273 '11': ['example'],
3a7ddb0b 274 '12': ['bogus.example', 'undelegated.secure.example', 'undelegated.insecure.example'],
694ef440 275 '13': ['insecure.example', 'insecure.sub2.secure.example'],
11886ab9 276 '14': ['optout.example'],
d5b8ccd4 277 '15': ['insecure.optout.example', 'secure.optout.example', 'cname-secure.example']
7568b07d
PL
278 }
279
cb54e9b5
PL
280 _auth_cmd = ['authbind',
281 os.environ['PDNS']]
282 _auth_env = {}
7568b07d
PL
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("""
303options {
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("""
321module-dir=../regression-tests/modules
322launch=bind
323daemon=no
324local-ipv6=
325bind-config={confdir}/named.conf
326bind-dnssec-db={bind_dnssec_db}
327socket-dir={confdir}
328cache-ttl=0
329negquery-cache-ttl=0
330query-cache-ttl=0
331log-dns-queries=yes
332log-dns-details=yes
333loglevel=9
334distributor-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
11886ab9
PL
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))
7568b07d
PL
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..")
11886ab9 406 authcmd = list(cls._auth_cmd)
cb54e9b5
PL
407 authcmd.append('--config-dir=%s' % confdir)
408 authcmd.append('--local-address=%s' % ipaddress)
7568b07d
PL
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,
cb54e9b5
PL
414 stdout=fdLog, stderr=fdLog,
415 env=cls._auth_env)
7568b07d
PL
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
962a980d
PL
457 @classmethod
458 def startResponders(cls):
459 pass
460
7568b07d
PL
461 @classmethod
462 def startRecursor(cls, confdir, port):
463 print("Launching pdns_recursor..")
464 recursorcmd = [os.environ['PDNSRECURSOR'],
465 '--config-dir=%s' % confdir,
962a980d
PL
466 '--local-port=%s' % port,
467 '--security-poll-suffix=']
7568b07d
PL
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()
962a980d
PL
514
515 cls.startResponders()
516
7568b07d
PL
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()
962a980d
PL
531 cls.tearDownResponders()
532
533 @classmethod
534 def tearDownResponders(cls):
535 pass
7568b07d
PL
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
7568b07d 577 @classmethod
fb611f07 578 def sendUDPQuery(cls, query, timeout=2.0, decode=True, fwparams=dict()):
7568b07d
PL
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:
fb611f07
RG
593 if not decode:
594 return data
554c69f9 595 message = dns.message.from_wire(data, **fwparams)
7568b07d
PL
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
8b16b9c2 660 msgEdnsFlags = dns.flags.edns_to_text(msg.ednsflags).split()
7568b07d
PL
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):
de9650d1 698 self.assertEqual(ans, rrset, "'%s' != '%s'" % (ans.to_text(), rrset.to_text()))
7568b07d
PL
699 found = True
700
701 if not found:
6552b37b 702 raise AssertionError("RRset not found in answer\n\n%s" % ret)
7568b07d
PL
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))
05537f80
PL
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())