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