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