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