]> git.ipfire.org Git - thirdparty/pdns.git/blob - regression-tests.recursor-dnssec/recursortests.py
Merge pull request #10984 from danel1/master
[thirdparty/pdns.git] / regression-tests.recursor-dnssec / recursortests.py
1 #!/usr/bin/env python2
2
3 from __future__ import print_function
4 import errno
5 import shutil
6 import os
7 import socket
8 import struct
9 import subprocess
10 import sys
11 import time
12 import unittest
13 import dns
14 import dns.message
15
16 from proxyprotocol import ProxyProtocol
17
18 from eqdnsmessage import AssertEqualDNSMessageMixin
19
20
21 def 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
35 class RecursorTest(AssertEqualDNSMessageMixin, unittest.TestCase):
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 = """
50 daemon=no
51 trace=yes
52 dont-query=
53 local-address=127.0.0.1
54 packetcache-ttl=0
55 packetcache-servfail-ttl=0
56 max-cache-ttl=15
57 threads=1
58 loglevel=9
59 disable-syslog=yes
60 log-common-errors=yes
61 """
62 _config_template = """
63 """
64 _config_params = []
65 _lua_config_file = None
66 _lua_dns_script_file = None
67 _roothints = """
68 . 3600 IN NS ns.root.
69 ns.root. 3600 IN A %s.8
70 ns.root. 3600 IN AAAA ::1
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.
85 ns.root. 3600 IN A {prefix}.8
86
87 example. 3600 IN NS ns1.example.
88 example. 3600 IN NS ns2.example.
89 example. 3600 IN DS 53174 13 1 50c9e913818767c236c06c2d8272723cb78cbf26
90
91 ns1.example. 3600 IN A {prefix}.10
92 ns2.example. 3600 IN A {prefix}.18
93 """,
94 'example': """
95 example. 3600 IN SOA {soa}
96 example. 3600 IN NS ns1.example.
97 example. 3600 IN NS ns2.example.
98 ns1.example. 3600 IN A {prefix}.10
99 ns2.example. 3600 IN A {prefix}.18
100
101 secure.example. 3600 IN NS ns.secure.example.
102 secure.example. 3600 IN DS 64723 13 1 53eb985040d3a89bacf29dbddb55a65834706f33
103 ns.secure.example. 3600 IN A {prefix}.9
104
105 cname-secure.example. 3600 IN NS ns.cname-secure.example.
106 cname-secure.example. 3600 IN DS 49148 13 1 a10314452d5ec4d97fcc6d7e275d217261fe790f
107 ns.cname-secure.example. 3600 IN A {prefix}.15
108
109 dname-secure.example. 3600 IN NS ns.dname-secure.example.
110 dname-secure.example. 3600 IN DS 42043 13 2 11c67f46b7c4d5968bc5f6cc944d58377b762bda53ddb4f3a6dbe6faf7a9940f
111 ns.dname-secure.example. 3600 IN A {prefix}.13
112
113 bogus.example. 3600 IN NS ns.bogus.example.
114 bogus.example. 3600 IN DS 65034 13 1 6df3bb50ea538e90eacdd7ae5419730783abb0ee
115 ns.bogus.example. 3600 IN A {prefix}.12
116
117 insecure.example. 3600 IN NS ns.insecure.example.
118 ns.insecure.example. 3600 IN A {prefix}.13
119
120 optout.example. 3600 IN NS ns1.optout.example.
121 optout.example. 3600 IN DS 59332 13 1 e664f886ae1b5df03d918bc1217d22afc29925b9
122 ns1.optout.example. 3600 IN A {prefix}.14
123
124 insecure-formerr.example. 3600 IN NS ns1.insecure-formerr.example.
125 ns1.insecure-formerr.example. 3600 IN A {prefix}.2
126
127 ecs-echo.example. 3600 IN NS ns1.ecs-echo.example.
128 ns1.ecs-echo.example. 3600 IN A {prefix}.21
129
130 islandofsecurity.example. 3600 IN NS ns1.islandofsecurity.example.
131 ns1.islandofsecurity.example. 3600 IN A {prefix}.9
132
133 sortcname.example. 3600 IN CNAME sort
134 sort.example. 3600 IN A 17.38.42.80
135 sort.example. 3600 IN A 192.168.0.1
136 sort.example. 3600 IN A 17.238.240.5
137 sort.example. 3600 IN MX 25 mx
138
139 delay1.example. 3600 IN NS ns1.delay1.example.
140 ns1.delay1.example. 3600 IN A {prefix}.16
141 delay1.example. 3600 IN DS 42043 13 2 7319fa605cf117f36e3de070157577ebb9a05a1d1f963d80eda55b5d6e793eb2
142
143 delay2.example. 3600 IN NS ns1.delay2.example.
144 ns1.delay2.example. 3600 IN A {prefix}.17
145 delay2.example. 3600 IN DS 42043 13 2 60a047b87740c8564c21d5fd34626c10a77a6c41e3b34564230119c2f13937b8
146
147 cname-nxd.example. 3600 IN CNAME cname-nxd-target.example.
148 cname-nxd-target.example. 3600 IN A 192.0.2.100
149 cname-nodata.example. 3600 IN CNAME cname-nodata-target.example.
150 cname-nodata-target.example. 3600 IN A 192.0.2.101
151 cname-custom-a.example. 3600 IN CNAME cname-custom-a-target.example.
152 cname-custom-a-target.example. 3600 IN A 192.0.2.102
153 """,
154 'secure.example': """
155 secure.example. 3600 IN SOA {soa}
156 secure.example. 3600 IN NS ns.secure.example.
157 ns.secure.example. 3600 IN A {prefix}.9
158
159 secure.example. 3600 IN A 192.0.2.17
160
161 host1.secure.example. 3600 IN A 192.0.2.2
162 cname.secure.example. 3600 IN CNAME host1.secure.example.
163 cname-to-insecure.secure.example. 3600 IN CNAME node1.insecure.example.
164 cname-to-bogus.secure.example. 3600 IN CNAME ted.bogus.example.
165 cname-to-islandofsecurity.secure.example. 3600 IN CNAME node1.islandofsecurity.example.
166
167 host1.sub.secure.example. 3600 IN A 192.0.2.11
168
169 ;; See #4158
170 sub2.secure.example. 3600 IN CNAME doesnotmatter.insecure.example.
171 insecure.sub2.secure.example. 3600 IN NS ns1.insecure.example.
172
173 *.wildcard.secure.example. 3600 IN A 192.0.2.10
174
175 *.cnamewildcard.secure.example. 3600 IN CNAME host1.secure.example.
176
177 *.cnamewildcardnxdomain.secure.example. 3600 IN CNAME doesnotexist.secure.example.
178
179 cname-to-formerr.secure.example. 3600 IN CNAME host1.insecure-formerr.example.
180
181 dname-secure.secure.example. 3600 IN DNAME dname-secure.example.
182 dname-insecure.secure.example. 3600 IN DNAME insecure.example.
183 dname-bogus.secure.example. 3600 IN DNAME bogus.example.
184
185 non-apex-dnskey.secure.example. 3600 IN DNSKEY 257 3 13 CT6AJ4MEOtNDgj0+xLtTLGHf1WbLsKWZI8ONHOt/6q7hTjeWSnY/SGig1dIKZrHg+pJFUSPaxeShv48SYVRKEg==
186 non-apex-dnskey2.secure.example. 3600 IN DNSKEY 256 3 13 CT6AJ4MEOtNDgj0+xLtTLGHf1WbLsKWZI8ONHOt/6q7hTjeWSnY/SGig1dIKZrHg+pJFUSPaxeShv48SYVRKEg==
187 non-apex-dnskey3.secure.example. 3600 IN DNSKEY 256 3 13 DT6AJ4MEOtNDgj0+xLtTLGHf1WbLsKWZI8ONHOt/6q7hTjeWSnY/SGig1dIKZrHg+pJFUSPaxeShv48SYVRKEg==
188 """,
189 'dname-secure.example': """
190 dname-secure.example. 3600 IN SOA {soa}
191 dname-secure.example. 3600 IN NS ns.dname-secure.example.
192 ns.dname-secure.example. 3600 IN A {prefix}.13
193
194 host1.dname-secure.example. IN A 192.0.2.21
195
196 cname-to-secure.dname-secure.example. 3600 IN CNAME host1.secure.example.
197 cname-to-insecure.dname-secure.example. 3600 IN CNAME node1.insecure.example.
198 cname-to-bogus.dname-secure.example. 3600 IN CNAME ted.bogus.example.
199 """,
200 'cname-secure.example': """
201 cname-secure.example. 3600 IN SOA {soa}
202 cname-secure.example. 3600 IN NS ns.cname-secure.example.
203 ns.cname-secure.example. 3600 IN A {prefix}.15
204 cname-secure.example. 3600 IN CNAME secure.example.
205 """,
206 'bogus.example': """
207 bogus.example. 3600 IN SOA {soa}
208 bogus.example. 3600 IN NS ns1.bogus.example.
209 ns1.bogus.example. 3600 IN A {prefix}.12
210 ted.bogus.example. 3600 IN A 192.0.2.1
211 bill.bogus.example. 3600 IN AAAA 2001:db8:12::3
212 """,
213 'insecure.sub2.secure.example': """
214 insecure.sub2.secure.example. 3600 IN SOA {soa}
215 insecure.sub2.secure.example. 3600 IN NS ns1.insecure.example.
216
217 node1.insecure.sub2.secure.example. 3600 IN A 192.0.2.18
218 """,
219 'insecure.example': """
220 insecure.example. 3600 IN SOA {soa}
221 insecure.example. 3600 IN NS ns1.insecure.example.
222 ns1.insecure.example. 3600 IN A {prefix}.13
223
224 node1.insecure.example. 3600 IN A 192.0.2.6
225
226 cname-to-secure.insecure.example. 3600 IN CNAME host1.secure.example.
227
228 dname-to-secure.insecure.example. 3600 IN DNAME dname-secure.example.
229 """,
230 'optout.example': """
231 optout.example. 3600 IN SOA {soa}
232 optout.example. 3600 IN NS ns1.optout.example.
233 ns1.optout.example. 3600 IN A {prefix}.14
234
235 insecure.optout.example. 3600 IN NS ns1.insecure.optout.example.
236 ns1.insecure.optout.example. 3600 IN A {prefix}.15
237
238 secure.optout.example. 3600 IN NS ns1.secure.optout.example.
239 secure.optout.example. 3600 IN DS 64215 13 1 b88284d7a8d8605c398e8942262f97b9a5a31787
240 ns1.secure.optout.example. 3600 IN A {prefix}.15
241 """,
242 'insecure.optout.example': """
243 insecure.optout.example. 3600 IN SOA {soa}
244 insecure.optout.example. 3600 IN NS ns1.insecure.optout.example.
245 ns1.insecure.optout.example. 3600 IN A {prefix}.15
246
247 node1.insecure.optout.example. 3600 IN A 192.0.2.7
248 """,
249 'secure.optout.example': """
250 secure.optout.example. 3600 IN SOA {soa}
251 secure.optout.example. 3600 IN NS ns1.secure.optout.example.
252 ns1.secure.optout.example. 3600 IN A {prefix}.15
253
254 node1.secure.optout.example. 3600 IN A 192.0.2.8
255 """,
256 'islandofsecurity.example': """
257 islandofsecurity.example. 3600 IN SOA {soa}
258 islandofsecurity.example. 3600 IN NS ns1.islandofsecurity.example.
259 ns1.islandofsecurity.example. 3600 IN A {prefix}.9
260
261 node1.islandofsecurity.example. 3600 IN A 192.0.2.20
262 """,
263 'undelegated.secure.example': """
264 undelegated.secure.example. 3600 IN SOA {soa}
265 undelegated.secure.example. 3600 IN NS ns1.undelegated.secure.example.
266
267 node1.undelegated.secure.example. 3600 IN A 192.0.2.21
268 """,
269 'undelegated.insecure.example': """
270 undelegated.insecure.example. 3600 IN SOA {soa}
271 undelegated.insecure.example. 3600 IN NS ns1.undelegated.insecure.example.
272
273 node1.undelegated.insecure.example. 3600 IN A 192.0.2.22
274 """,
275
276 'delay1.example': """
277 delay1.example. 3600 IN SOA {soa}
278 delay1.example. 3600 IN NS n1.delay1.example.
279 ns1.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': """
284 delay2.example. 3600 IN SOA {soa}
285 delay2.example. 3600 IN NS n1.delay2.example.
286 ns1.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'"
288 """
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': """
295 Private-key-format: v1.2
296 Algorithm: 13 (ECDSAP256SHA256)
297 PrivateKey: rhWuEydDz3QaIspSVj683B8Xq5q/ozzA38XUgzD4Fbo=
298 """,
299
300 'example': """
301 Private-key-format: v1.2
302 Algorithm: 13 (ECDSAP256SHA256)
303 PrivateKey: Lt0v0Gol3pRUFM7fDdcy0IWN0O/MnEmVPA+VylL8Y4U=
304 """,
305
306 'secure.example': """
307 Private-key-format: v1.2
308 Algorithm: 13 (ECDSAP256SHA256)
309 PrivateKey: 1G4WRoOFJJXk+fotDCHVORtJmIG2OUhKi8AO2jDPGZA=
310 """,
311
312 'bogus.example': """
313 Private-key-format: v1.2
314 Algorithm: 13 (ECDSAP256SHA256)
315 PrivateKey: f5jV7Q8kd5hDpMWObsuQ6SQda0ftf+JrO3uZwEg6nVw=
316 """,
317
318 'optout.example': """
319 Private-key-format: v1.2
320 Algorithm: 13 (ECDSAP256SHA256)
321 PrivateKey: efmq9G+J4Y2iPnIBRwJiy6Z/nIHSzpsCy/7XHhlS19A=
322 """,
323
324 'secure.optout.example': """
325 Private-key-format: v1.2
326 Algorithm: 13 (ECDSAP256SHA256)
327 PrivateKey: xcNUxt1Knj14A00lKQFDboluiJyM2f7FxpgsQaQ3AQ4=
328 """,
329
330 'islandofsecurity.example': """
331 Private-key-format: v1.2
332 Algorithm: 13 (ECDSAP256SHA256)
333 PrivateKey: o9F5iix8V68tnMcuOaM2Lt8XXhIIY//SgHIHEePk6cM=
334 """,
335
336 'cname-secure.example': """
337 Private-key-format: v1.2
338 Algorithm: 13 (ECDSAP256SHA256)
339 PrivateKey: kvoV/g4IO/tefSro+FLJ5UC7H3BUf0IUtZQSUOfQGyA=
340 """,
341
342 'dname-secure.example': """
343 Private-key-format: v1.2
344 Algorithm: 13 (ECDSAP256SHA256)
345 PrivateKey: Ep9uo6+wwjb4MaOmqq7LHav2FLrjotVOeZg8JT1Qk04=
346 """,
347
348 'delay1.example': """
349 Private-key-format: v1.2
350 Algorithm: 13 (ECDSAP256SHA256)
351 PrivateKey: Ep9uo6+wwjb4MaOmqq7LHav2FLrjotVOeZg8JT1Qk04=
352 """,
353
354 'delay2.example': """
355 Private-key-format: v1.2
356 Algorithm: 13 (ECDSAP256SHA256)
357 PrivateKey: Ep9uo6+wwjb4MaOmqq7LHav2FLrjotVOeZg8JT1Qk04=
358 """
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 = {
365 '8': {'threads': 1,
366 'zones': ['ROOT']},
367 '9': {'threads': 1,
368 'zones': ['secure.example', 'islandofsecurity.example']},
369 '10': {'threads': 1,
370 'zones': ['example']},
371
372 # 11 is used by CircleCI provided resolver
373
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']},
382 '16': {'threads': 2,
383 'zones': ['delay1.example']},
384 '17': {'threads': 2,
385 'zones': ['delay2.example']},
386 '18': {'threads': 1,
387 'zones': ['example']}
388 }
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
398
399 _auth_cmd = ['authbind',
400 os.environ['PDNS']]
401 _auth_env = {}
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
411 os.mkdir(confdir, 0o755)
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("""
422 options {
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
435 def generateAuthConfig(cls, confdir, threads):
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("""
440 module-dir=../regression-tests/modules
441 launch=bind
442 daemon=no
443 bind-config={confdir}/named.conf
444 bind-dnssec-db={bind_dnssec_db}
445 socket-dir={confdir}
446 cache-ttl=0
447 negquery-cache-ttl=0
448 query-cache-ttl=0
449 log-dns-queries=yes
450 log-dns-details=yes
451 loglevel=9
452 enable-lua-records
453 dname-processing=yes
454 distributor-threads={threads}""".format(confdir=confdir,
455 bind_dnssec_db=bind_dnssec_db,
456 threads=threads))
457
458 pdnsutilCmd = [os.environ['PDNSUTIL'],
459 '--config-dir=%s' % confdir,
460 'create-bind-db',
461 bind_dnssec_db]
462
463 print(' '.join(pdnsutilCmd))
464 try:
465 subprocess.check_output(pdnsutilCmd, stderr=subprocess.STDOUT)
466 except subprocess.CalledProcessError as e:
467 raise AssertionError('%s failed (%d): %s' % (pdnsutilCmd, e.returncode, e.output))
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
490 print(' '.join(pdnsutilCmd))
491 try:
492 subprocess.check_output(pdnsutilCmd, stderr=subprocess.STDOUT)
493 except subprocess.CalledProcessError as e:
494 raise AssertionError('%s failed (%d): %s' % (pdnsutilCmd, e.returncode, e.output))
495
496 @classmethod
497 def generateAllAuthConfig(cls, confdir):
498 if cls._auth_zones:
499 for auth_suffix, zoneinfo in cls._auth_zones.items():
500 threads = zoneinfo['threads']
501 zones = zoneinfo['zones']
502 authconfdir = os.path.join(confdir, 'auth-%s' % auth_suffix)
503
504 os.mkdir(authconfdir)
505
506 cls.generateAuthConfig(authconfdir, threads)
507 cls.generateAuthNamedConf(authconfdir, zones)
508
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))
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
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)
529 sock.settimeout(1.0)
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
538 @classmethod
539 def startAuth(cls, confdir, ipaddress):
540 print("Launching pdns_server..")
541 authcmd = list(cls._auth_cmd)
542 authcmd.append('--config-dir=%s' % confdir)
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)
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,
553 stdout=fdLog, stderr=fdLog,
554 env=cls._auth_env)
555
556 cls.waitForTCPSocket(ipaddress, 53)
557
558 if cls._auths[ipaddress].poll() is not None:
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))
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:
583 luaconf.write("addTA('.', '%s')\n" % cls._root_DS)
584 if cls._lua_config_file:
585 luaconf.write(cls._lua_config_file)
586 conf.write("lua-config-file=%s\n" % luaconfpath)
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)
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
598 @classmethod
599 def startResponders(cls):
600 pass
601
602 @classmethod
603 def startRecursor(cls, confdir, port):
604 print("Launching pdns_recursor..")
605 recursorcmd = [os.environ['PDNSRECURSOR'],
606 '--config-dir=%s' % confdir,
607 '--local-port=%s' % port,
608 '--security-poll-suffix=']
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
616 cls.waitForTCPSocket("127.0.0.1", port)
617
618 if cls._recursor.poll() is not None:
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))
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:
634 raise AssertionError('%s failed (%d): %s' % (rec_controlCmd, e.returncode, e.output))
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()
646
647 cls.startResponders()
648
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()
663 cls.tearDownResponders()
664
665 @classmethod
666 def tearDownResponders(cls):
667 pass
668
669 @classmethod
670 def killProcess(cls, p):
671 # Don't try to kill it if it's already dead
672 if p.poll() is not None:
673 return
674 try:
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()
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
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
701 @classmethod
702 def sendUDPQuery(cls, query, timeout=2.0, decode=True, fwparams=dict()):
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:
717 if not decode:
718 return data
719 message = dns.message.from_wire(data, **fwparams)
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
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
784 def setUp(self):
785 # This function is called before every tests
786 super(RecursorTest, self).setUp()
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
816 msgEdnsFlags = dns.flags.edns_to_text(msg.ednsflags).split()
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):
854 self.assertEqual(ans, rrset, "'%s' != '%s'" % (ans.to_text(), rrset.to_text()))
855 found = True
856
857 if not found:
858 raise AssertionError("RRset not found in answer\n\n%s" % ret)
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:
929 msgRcode = dns.rcode.to_text(msg.rcode())
930 wantedRcode = dns.rcode.to_text(rcode)
931
932 raise AssertionError("Rcode for %s is %s, expected %s." % (msg.question[0].to_text(), msgRcode, wantedRcode))
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())
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)
958 self.assertEqual(response, expectedResponse)
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
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