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