8 from dnsdisttests
import DNSDistTest
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'
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
= """
36 controlSocket("127.0.0.1:%d")
38 setMaxTCPClientThreads(1)
41 newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
43 -- SVCB record but no upgrade path available
44 newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
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()
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()
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()
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()
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()
61 -- The SVCB response is not valid
62 newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
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()
68 newServer({address="127.0.0.1:%s", caStore='ca.pem', pool={"", "other-pool"}, autoUpgrade=true, source='127.0.0.1@lo'}):setUp()
71 newServer({address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true}):setUp()
74 newServer({address="127.0.0.1:%s", autoUpgrade=true}):setUp()
77 newServer({address="127.0.0.1:%s", autoUpgrade=true}):setUp()
80 newServer({address="127.0.0.1:%s", autoUpgrade=true}):setUp()
83 newServer({address="127.0.0.1:%s", autoUpgrade=true}):setUp()
86 newServer({address="127.0.0.1:%s", autoUpgrade=true}):setUp()
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()
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()
96 def NoSVCCallback(request
):
97 return dns
.message
.make_response(request
).to_wire()
99 def NoUpgradePathCallback(request
):
100 response
= dns
.message
.make_response(request
)
101 rrset
= dns
.rrset
.from_text(request
.question
[0].name
,
105 '1 no-upgrade. alpn="h3"')
106 response
.answer
.append(rrset
)
107 return response
.to_wire()
109 def UpgradeDoTCallback(request
):
110 response
= dns
.message
.make_response(request
)
111 rrset
= dns
.rrset
.from_text(request
.question
[0].name
,
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
,
123 response
.answer
.append(rrset
)
124 # plus more useless records in authority
125 rrset
= dns
.rrset
.from_text(request
.question
[0].name
,
130 response
.authority
.append(rrset
)
131 # and finally valid, albeit useless, hints
132 rrset
= dns
.rrset
.from_text('tls.tests.dnsdist.org.',
137 response
.additional
.append(rrset
)
138 rrset
= dns
.rrset
.from_text('tls.tests.dnsdist.org.',
143 response
.additional
.append(rrset
)
144 return response
.to_wire()
146 def UpgradeDoHCallback(request
):
147 response
= dns
.message
.make_response(request
)
148 rrset
= dns
.rrset
.from_text(request
.question
[0].name
,
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()
156 def UpgradeDoTDifferentAddr1Callback(request
):
157 response
= dns
.message
.make_response(request
)
158 rrset
= dns
.rrset
.from_text(request
.question
[0].name
,
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()
166 def UpgradeDoTDifferentAddr2Callback(request
):
167 response
= dns
.message
.make_response(request
)
168 rrset
= dns
.rrset
.from_text(request
.question
[0].name
,
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()
176 def UpgradeDoTUnreachableCallback(request
):
177 response
= dns
.message
.make_response(request
)
178 rrset
= dns
.rrset
.from_text(request
.question
[0].name
,
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()
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()
192 def UpgradeDoHMissingPathCallback(request
):
193 response
= dns
.message
.make_response(request
)
194 rrset
= dns
.rrset
.from_text(request
.question
[0].name
,
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()
202 def EOFCallback(request
):
205 def ServFailCallback(request
):
206 response
= dns
.message
.make_response(request
)
207 response
.set_rcode(dns
.rcode
.SERVFAIL
)
208 return response
.to_wire()
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()
216 def WrongIDCallback(request
):
217 response
= dns
.message
.make_response(request
)
218 response
.id = request
.id ^
42
219 return response
.to_wire()
221 def WrongIDCallback(request
):
222 response
= dns
.message
.make_response(request
)
223 response
.id = request
.id ^
42
224 return response
.to_wire()
226 def TooManyQuestionsCallback(request
):
227 response
= dns
.message
.make_response(request
)
228 response
.question
.append(response
.question
[0])
229 return response
.to_wire()
231 def BadQNameCallback(request
):
232 response
= dns
.message
.make_response(request
)
233 wire
= bytearray(response
.to_wire())
234 # mess up the first label length
238 def UpgradeDoTNoPortCallback(request
):
239 response
= dns
.message
.make_response(request
)
240 rrset
= dns
.rrset
.from_text(request
.question
[0].name
,
244 '1 tls.tests.dnsdist.org. alpn="dot" ipv4hint=127.0.0.1')
245 response
.answer
.append(rrset
)
246 return response
.to_wire()
248 def UpgradeDoHNoPortCallback(request
):
249 response
= dns
.message
.make_response(request
)
250 rrset
= dns
.rrset
.from_text(request
.question
[0].name
,
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()
259 def startResponders(cls
):
260 tlsContext
= ssl
.SSLContext(ssl
.PROTOCOL_TLS_SERVER
)
261 tlsContext
.load_cert_chain('server.chain', 'server.key')
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()
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()
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()
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()
287 TCPUpgradeToDoTDifferentAddrResponder
= threading
.Thread(name
='TCP upgrade to DoT different addr 1 Responder', target
=cls
.TCPResponder
, args
=[cls
._svcUpgradeDoTBackendDifferentAddrPort
1, 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()
295 TCPUpgradeToDoTDifferentAddrResponder
= threading
.Thread(name
='TCP upgrade to DoT different addr 2 Responder', target
=cls
.TCPResponder
, args
=[cls
._svcUpgradeDoTBackendDifferentAddrPort
2, 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()
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!
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()
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()
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)
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()
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()
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()
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()
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()
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()
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()
350 def checkBackendsUpgraded(self
):
351 output
= self
.sendConsoleCommand('showServers()')
355 for line
in output
.splitlines(False):
356 if line
.startswith('#') or line
.startswith('All'):
358 tokens
= line
.split()
359 self
.assertTrue(len(tokens
) == 12 or len(tokens
) == 13)
360 self
.assertEquals(tokens
[2], 'UP')
362 if len(tokens
) == 13:
364 backends
[tokens
[1]] = pool
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': ''
391 return backends
== expected
393 def testBackendUpgrade(self
):
395 Backend Discovery: Upgrade
397 # enough time for discovery to happen
398 # 5s is not enough with TSAN
400 if not self
.checkBackendsUpgraded():
401 # let's wait a bit longer
403 self
.assertTrue(self
.checkBackendsUpgraded())