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