]> git.ipfire.org Git - thirdparty/pdns.git/blob - regression-tests.dnsdist/test_BackendDiscovery.py
dnsdist: Better coverage of the backend's discovery code
[thirdparty/pdns.git] / regression-tests.dnsdist / test_BackendDiscovery.py
1 #!/usr/bin/env python
2 import base64
3 import dns
4 import threading
5 import time
6 import ssl
7
8 from dnsdisttests import DNSDistTest
9
10 class TestBackendDiscovery(DNSDistTest):
11 _noSVCBackendPort = 10600
12 _svcNoUpgradeBackendPort = 10601
13 _svcUpgradeDoTBackendPort = 10602
14 _svcUpgradeDoHBackendPort = 10603
15 _svcUpgradeDoTBackendDifferentAddrPort1 = 10604
16 _svcUpgradeDoTBackendDifferentAddrPort2 = 10605
17 _svcUpgradeDoTUnreachableBackendPort = 10606
18 _svcBrokenDNSResponseBackendPort = 10607
19 _svcUpgradeDoHBackendWithoutPathPort = 10608
20 _connectionRefusedBackendPort = 10609
21 _eofBackendPort = 10610
22 _servfailBackendPort = 10611
23 _wrongNameBackendPort = 10612
24 _wrongIDBackendPort = 10613
25 _tooManyQuestionsBackendPort = 10614
26 _badQNameBackendPort = 10615
27 _svcUpgradeDoTNoPortBackendPort = 10616
28 _svcUpgradeDoHNoPortBackendPort = 10617
29 _upgradedBackendsPool = 'upgraded'
30
31 _consoleKey = DNSDistTest.generateConsoleKey()
32 _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
33 _config_params = ['_consoleKeyB64', '_consolePort', '_noSVCBackendPort', '_svcNoUpgradeBackendPort', '_svcUpgradeDoTBackendPort', '_upgradedBackendsPool', '_svcUpgradeDoHBackendPort', '_svcUpgradeDoTBackendDifferentAddrPort1', '_svcUpgradeDoTBackendDifferentAddrPort2', '_svcUpgradeDoTUnreachableBackendPort', '_svcBrokenDNSResponseBackendPort', '_svcUpgradeDoHBackendWithoutPathPort', '_connectionRefusedBackendPort', '_eofBackendPort', '_servfailBackendPort', '_wrongNameBackendPort', '_wrongIDBackendPort', '_tooManyQuestionsBackendPort', '_badQNameBackendPort', '_svcUpgradeDoTNoPortBackendPort', '_svcUpgradeDoHNoPortBackendPort']
34 _config_template = """
35 setKey("%s")
36 controlSocket("127.0.0.1:%d")
37
38 setMaxTCPClientThreads(1)
39
40 -- no SVCB
41 newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
42
43 -- SVCB record but no upgrade path available
44 newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
45
46 -- SVCB upgrade to DoT, same address, keep the backend, different pool
47 newServer{address="127.0.0.1:%s", caStore='ca.pem', pool={'', 'another-pool'}, autoUpgrade=true, autoUpgradePool='%s', autoUpgradeKeep=true, source='127.0.0.1@lo'}:setUp()
48
49 -- SVCB upgrade to DoH, same address, do not keep the backend, same pool
50 newServer{address="127.0.0.1:%s", caStore='ca.pem', pool={'another-pool'}, autoUpgrade=true, autoUpgradeKeep=false}:setUp()
51
52 -- SVCB upgrade to DoT, different address, certificate is valid for the initial address
53 newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
54
55 -- SVCB upgrade to DoT, different address, certificate is NOT valid for the initial address
56 newServer{address="127.0.0.2:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
57
58 -- SVCB upgrade to DoT but upgraded port is not reachable
59 newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
60
61 -- The SVCB response is not valid
62 newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
63
64 -- SVCB upgrade to DoH except the path is not specified
65 newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
66
67 -- Connection refused
68 newServer({address="127.0.0.1:%s", caStore='ca.pem', pool={"", "other-pool"}, autoUpgrade=true, source='127.0.0.1@lo'}):setUp()
69
70 -- EOF
71 newServer({address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true}):setUp()
72
73 -- ServFail
74 newServer({address="127.0.0.1:%s", autoUpgrade=true}):setUp()
75
76 -- Wrong name
77 newServer({address="127.0.0.1:%s", autoUpgrade=true}):setUp()
78
79 -- Wrong ID
80 newServer({address="127.0.0.1:%s", autoUpgrade=true}):setUp()
81
82 -- Too many questions
83 newServer({address="127.0.0.1:%s", autoUpgrade=true}):setUp()
84
85 -- Bad QName
86 newServer({address="127.0.0.1:%s", autoUpgrade=true}):setUp()
87
88 -- SVCB upgrade to DoT, same address, no port specified via SVCB
89 newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
90
91 -- SVCB upgrade to DoH, same address, no port specified via SVCB
92 newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
93 """
94 _verboseMode = True
95
96 def NoSVCCallback(request):
97 return dns.message.make_response(request).to_wire()
98
99 def NoUpgradePathCallback(request):
100 response = dns.message.make_response(request)
101 rrset = dns.rrset.from_text(request.question[0].name,
102 60,
103 dns.rdataclass.IN,
104 dns.rdatatype.SVCB,
105 '1 no-upgrade. alpn="h3"')
106 response.answer.append(rrset)
107 return response.to_wire()
108
109 def UpgradeDoTCallback(request):
110 response = dns.message.make_response(request)
111 rrset = dns.rrset.from_text(request.question[0].name,
112 60,
113 dns.rdataclass.IN,
114 dns.rdatatype.SVCB,
115 '1 tls.tests.dnsdist.org. alpn="dot" port=10652 ipv4hint=127.0.0.1')
116 response.answer.append(rrset)
117 # add a useless A record for good measure
118 rrset = dns.rrset.from_text(request.question[0].name,
119 60,
120 dns.rdataclass.IN,
121 dns.rdatatype.A,
122 '192.0.2.1')
123 response.answer.append(rrset)
124 # plus more useless records in authority
125 rrset = dns.rrset.from_text(request.question[0].name,
126 60,
127 dns.rdataclass.IN,
128 dns.rdatatype.A,
129 '192.0.2.1')
130 response.authority.append(rrset)
131 # and finally valid, albeit useless, hints
132 rrset = dns.rrset.from_text('tls.tests.dnsdist.org.',
133 60,
134 dns.rdataclass.IN,
135 dns.rdatatype.A,
136 '127.0.0.1')
137 response.additional.append(rrset)
138 rrset = dns.rrset.from_text('tls.tests.dnsdist.org.',
139 60,
140 dns.rdataclass.IN,
141 dns.rdatatype.AAAA,
142 '::1')
143 response.additional.append(rrset)
144 return response.to_wire()
145
146 def UpgradeDoHCallback(request):
147 response = dns.message.make_response(request)
148 rrset = dns.rrset.from_text(request.question[0].name,
149 60,
150 dns.rdataclass.IN,
151 dns.rdatatype.SVCB,
152 '1 tls.tests.dnsdist.org. alpn="h2" port=10653 ipv4hint=127.0.0.1 key7="/dns-query{?dns}"')
153 response.answer.append(rrset)
154 return response.to_wire()
155
156 def UpgradeDoTDifferentAddr1Callback(request):
157 response = dns.message.make_response(request)
158 rrset = dns.rrset.from_text(request.question[0].name,
159 60,
160 dns.rdataclass.IN,
161 dns.rdatatype.SVCB,
162 '1 tls.tests.dnsdist.org. alpn="dot" port=10654 ipv4hint=127.0.0.2')
163 response.answer.append(rrset)
164 return response.to_wire()
165
166 def UpgradeDoTDifferentAddr2Callback(request):
167 response = dns.message.make_response(request)
168 rrset = dns.rrset.from_text(request.question[0].name,
169 60,
170 dns.rdataclass.IN,
171 dns.rdatatype.SVCB,
172 '1 tls.tests.dnsdist.org. alpn="dot" port=10655 ipv4hint=127.0.0.1')
173 response.answer.append(rrset)
174 return response.to_wire()
175
176 def UpgradeDoTUnreachableCallback(request):
177 response = dns.message.make_response(request)
178 rrset = dns.rrset.from_text(request.question[0].name,
179 60,
180 dns.rdataclass.IN,
181 dns.rdatatype.SVCB,
182 '1 tls.tests.dnsdist.org. alpn="dot" port=10656 ipv4hint=127.0.0.1')
183 response.answer.append(rrset)
184 return response.to_wire()
185
186 def BrokenResponseCallback(request):
187 response = dns.message.make_response(request)
188 response.use_edns(edns=False)
189 response.question = []
190 return response.to_wire()
191
192 def UpgradeDoHMissingPathCallback(request):
193 response = dns.message.make_response(request)
194 rrset = dns.rrset.from_text(request.question[0].name,
195 60,
196 dns.rdataclass.IN,
197 dns.rdatatype.SVCB,
198 '1 tls.tests.dnsdist.org. alpn="h2" port=10653 ipv4hint=127.0.0.1')
199 response.answer.append(rrset)
200 return response.to_wire()
201
202 def EOFCallback(request):
203 return None
204
205 def ServFailCallback(request):
206 response = dns.message.make_response(request)
207 response.set_rcode(dns.rcode.SERVFAIL)
208 return response.to_wire()
209
210 def WrongNameCallback(request):
211 query = dns.message.make_query('not-the-right-one.', dns.rdatatype.SVCB)
212 response = dns.message.make_response(query)
213 response.id = request.id
214 return response.to_wire()
215
216 def WrongIDCallback(request):
217 response = dns.message.make_response(request)
218 response.id = request.id ^ 42
219 return response.to_wire()
220
221 def WrongIDCallback(request):
222 response = dns.message.make_response(request)
223 response.id = request.id ^ 42
224 return response.to_wire()
225
226 def TooManyQuestionsCallback(request):
227 response = dns.message.make_response(request)
228 response.question.append(response.question[0])
229 return response.to_wire()
230
231 def BadQNameCallback(request):
232 response = dns.message.make_response(request)
233 wire = bytearray(response.to_wire())
234 # mess up the first label length
235 wire[12] = 0xFF
236 return wire
237
238 def UpgradeDoTNoPortCallback(request):
239 response = dns.message.make_response(request)
240 rrset = dns.rrset.from_text(request.question[0].name,
241 60,
242 dns.rdataclass.IN,
243 dns.rdatatype.SVCB,
244 '1 tls.tests.dnsdist.org. alpn="dot" ipv4hint=127.0.0.1')
245 response.answer.append(rrset)
246 return response.to_wire()
247
248 def UpgradeDoHNoPortCallback(request):
249 response = dns.message.make_response(request)
250 rrset = dns.rrset.from_text(request.question[0].name,
251 60,
252 dns.rdataclass.IN,
253 dns.rdatatype.SVCB,
254 '1 tls.tests.dnsdist.org. alpn="h2" ipv4hint=127.0.0.1 key7="/dns-query{?dns}"')
255 response.answer.append(rrset)
256 return response.to_wire()
257
258 @classmethod
259 def startResponders(cls):
260 tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
261 tlsContext.load_cert_chain('server.chain', 'server.key')
262
263 TCPNoSVCResponder = threading.Thread(name='TCP no SVC Responder', target=cls.TCPResponder, args=[cls._noSVCBackendPort, cls._toResponderQueue, cls._fromResponderQueue, True, False, cls.NoSVCCallback])
264 TCPNoSVCResponder.setDaemon(True)
265 TCPNoSVCResponder.start()
266
267 TCPNoUpgradeResponder = threading.Thread(name='TCP no upgrade Responder', target=cls.TCPResponder, args=[cls._svcNoUpgradeBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.NoUpgradePathCallback])
268 TCPNoUpgradeResponder.setDaemon(True)
269 TCPNoUpgradeResponder.start()
270
271 TCPUpgradeToDoTResponder = threading.Thread(name='TCP upgrade to DoT Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoTBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoTCallback])
272 TCPUpgradeToDoTResponder.setDaemon(True)
273 TCPUpgradeToDoTResponder.start()
274 # and the corresponding DoT responder
275 UpgradedDoTResponder = threading.Thread(name='DoT upgraded Responder', target=cls.TCPResponder, args=[10652, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
276 UpgradedDoTResponder.setDaemon(True)
277 UpgradedDoTResponder.start()
278
279 TCPUpgradeToDoHResponder = threading.Thread(name='TCP upgrade to DoH Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoHBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoHCallback])
280 TCPUpgradeToDoHResponder.setDaemon(True)
281 TCPUpgradeToDoHResponder.start()
282 # and the corresponding DoH responder
283 UpgradedDOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[10653, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
284 UpgradedDOHResponder.setDaemon(True)
285 UpgradedDOHResponder.start()
286
287 TCPUpgradeToDoTDifferentAddrResponder = threading.Thread(name='TCP upgrade to DoT different addr 1 Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoTBackendDifferentAddrPort1, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoTDifferentAddr1Callback])
288 TCPUpgradeToDoTDifferentAddrResponder.setDaemon(True)
289 TCPUpgradeToDoTDifferentAddrResponder.start()
290 # and the corresponding DoT responder
291 UpgradedDoTResponder = threading.Thread(name='DoT upgraded different addr 1 Responder', target=cls.TCPResponder, args=[10654, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext, False, '127.0.0.2'])
292 UpgradedDoTResponder.setDaemon(True)
293 UpgradedDoTResponder.start()
294
295 TCPUpgradeToDoTDifferentAddrResponder = threading.Thread(name='TCP upgrade to DoT different addr 2 Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoTBackendDifferentAddrPort2, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoTDifferentAddr2Callback, None, False, '127.0.0.2'])
296 TCPUpgradeToDoTDifferentAddrResponder.setDaemon(True)
297 TCPUpgradeToDoTDifferentAddrResponder.start()
298 # and the corresponding DoT responder
299 UpgradedDoTResponder = threading.Thread(name='DoT upgraded different addr 2 Responder', target=cls.TCPResponder, args=[10655, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext, False])
300 UpgradedDoTResponder.setDaemon(True)
301 UpgradedDoTResponder.start()
302
303 TCPUpgradeToUnreachableDoTResponder = threading.Thread(name='TCP upgrade to unreachable DoT Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoTUnreachableBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoTUnreachableCallback])
304 TCPUpgradeToUnreachableDoTResponder.setDaemon(True)
305 TCPUpgradeToUnreachableDoTResponder.start()
306 # and NO corresponding DoT responder
307 # this is not a mistake!
308
309 BrokenResponseResponder = threading.Thread(name='Broken response Responder', target=cls.TCPResponder, args=[cls._svcBrokenDNSResponseBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.BrokenResponseCallback])
310 BrokenResponseResponder.setDaemon(True)
311 BrokenResponseResponder.start()
312
313 DOHMissingPathResponder = threading.Thread(name='DoH missing path Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoHBackendWithoutPathPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoHMissingPathCallback])
314 DOHMissingPathResponder.setDaemon(True)
315 DOHMissingPathResponder.start()
316
317 EOFResponder = threading.Thread(name='EOF Responder', target=cls.TCPResponder, args=[cls._eofBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.EOFCallback])
318 EOFResponder.setDaemon(True)
319 EOFResponder.start()
320
321 ServFailResponder = threading.Thread(name='ServFail Responder', target=cls.TCPResponder, args=[cls._servfailBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.ServFailCallback])
322 ServFailResponder.setDaemon(True)
323 ServFailResponder.start()
324
325 WrongNameResponder = threading.Thread(name='Wrong Name Responder', target=cls.TCPResponder, args=[cls._wrongNameBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.WrongNameCallback])
326 WrongNameResponder.setDaemon(True)
327 WrongNameResponder.start()
328
329 WrongIDResponder = threading.Thread(name='Wrong ID Responder', target=cls.TCPResponder, args=[cls._wrongIDBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.WrongIDCallback])
330 WrongIDResponder.setDaemon(True)
331 WrongIDResponder.start()
332
333 TooManyQuestionsResponder = threading.Thread(name='Too many questions Responder', target=cls.TCPResponder, args=[cls._tooManyQuestionsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.TooManyQuestionsCallback])
334 TooManyQuestionsResponder.setDaemon(True)
335 TooManyQuestionsResponder.start()
336
337 badQNameResponder = threading.Thread(name='Bad QName Responder', target=cls.TCPResponder, args=[cls._badQNameBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.BadQNameCallback])
338 badQNameResponder.setDaemon(True)
339 badQNameResponder.start()
340
341 TCPUpgradeToDoTNoPortResponder = threading.Thread(name='TCP upgrade to DoT (no port) Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoTNoPortBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoTNoPortCallback])
342 TCPUpgradeToDoTNoPortResponder.setDaemon(True)
343 TCPUpgradeToDoTNoPortResponder.start()
344
345 TCPUpgradeToDoHNoPortResponder = threading.Thread(name='TCP upgrade to DoH (no port) Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoHNoPortBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoHNoPortCallback])
346 TCPUpgradeToDoHNoPortResponder.setDaemon(True)
347 TCPUpgradeToDoHNoPortResponder.start()
348
349
350 def checkBackendsUpgraded(self):
351 output = self.sendConsoleCommand('showServers()')
352 print(output)
353
354 backends = {}
355 for line in output.splitlines(False):
356 if line.startswith('#') or line.startswith('All'):
357 continue
358 tokens = line.split()
359 self.assertTrue(len(tokens) == 12 or len(tokens) == 13)
360 self.assertEquals(tokens[2], 'UP')
361 pool = ''
362 if len(tokens) == 13:
363 pool = tokens[12]
364 backends[tokens[1]] = pool
365
366 expected = {
367 '127.0.0.1:10600': '',
368 '127.0.0.1:10601': '',
369 '127.0.0.1:10602': 'another-pool',
370 # 10603 has been upgraded to 10653 and removed
371 # 10604 has been upgraded to 10654 and removed
372 '127.0.0.2:10605': '',
373 '127.0.0.1:10606': '',
374 '127.0.0.1:10607': '',
375 '127.0.0.1:10608': '',
376 '127.0.0.1:10609': 'other-pool',
377 '127.0.0.1:10610': '',
378 '127.0.0.1:10611': '',
379 '127.0.0.1:10612': '',
380 '127.0.0.1:10613': '',
381 '127.0.0.1:10614': '',
382 '127.0.0.1:10615': '',
383 # these two are not upgraded because there is no backend listening on the default ports (443 and 853)
384 '127.0.0.1:10616': '',
385 '127.0.0.1:10617': '',
386 '127.0.0.1:10652': 'upgraded',
387 '127.0.0.1:10653': 'another-pool',
388 '127.0.0.2:10654': ''
389 }
390 print(backends)
391 return backends == expected
392
393 def testBackendUpgrade(self):
394 """
395 Backend Discovery: Upgrade
396 """
397 # enough time for discovery to happen
398 # 5s is not enough with TSAN
399 time.sleep(10)
400 if not self.checkBackendsUpgraded():
401 # let's wait a bit longer
402 time.sleep(5)
403 self.assertTrue(self.checkBackendsUpgraded())