]> git.ipfire.org Git - thirdparty/pdns.git/blob - regression-tests.dnsdist/test_OutgoingDOH.py
Merge pull request #13674 from dmachard/dockerfile-dnsdist-add-doqdoh3
[thirdparty/pdns.git] / regression-tests.dnsdist / test_OutgoingDOH.py
1 #!/usr/bin/env python
2 import base64
3 import copy
4 import dns
5 import requests
6 import ssl
7 import threading
8 import time
9
10 from dnsdisttests import DNSDistTest, pickAvailablePort
11
12 class OutgoingDOHTests(object):
13
14 _webTimeout = 2.0
15 _webServerPort = pickAvailablePort()
16 _webServerBasicAuthPassword = 'secret'
17 _webServerAPIKey = 'apisecret'
18 _webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
19 _webServerAPIKeyHashed = '$scrypt$ln=10,p=1,r=8$9v8JxDfzQVyTpBkTbkUqYg==$bDQzAOHeK1G9UvTPypNhrX48w974ZXbFPtRKS34+aso='
20
21 def checkOnlyDOHResponderHit(self, numberOfDOHQueries=1):
22 self.assertNotIn('UDP Responder', self._responsesCounter)
23 self.assertNotIn('TCP Responder', self._responsesCounter)
24 self.assertNotIn('TLS Responder', self._responsesCounter)
25 self.assertEqual(self._responsesCounter['DoH Connection Handler'], numberOfDOHQueries)
26
27 def getServerStat(self, key):
28 headers = {'x-api-key': self._webServerAPIKey}
29 url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost'
30 r = requests.get(url, headers=headers, timeout=self._webTimeout)
31 self.assertTrue(r)
32 self.assertEqual(r.status_code, 200)
33 self.assertTrue(r.json())
34 content = r.json()
35 self.assertTrue(len(content['servers']), 1)
36 server = content['servers'][0]
37 self.assertIn(key, server)
38 return server[key]
39
40 def testUDP(self):
41 """
42 Outgoing DOH: UDP query is sent via DOH
43 """
44 name = 'udp.outgoing-doh.test.powerdns.com.'
45 query = dns.message.make_query(name, 'A', 'IN')
46 expectedResponse = dns.message.make_response(query)
47 rrset = dns.rrset.from_text(name,
48 60,
49 dns.rdataclass.IN,
50 dns.rdatatype.A,
51 '127.0.0.1')
52 expectedResponse.answer.append(rrset)
53
54 connsBefore = self.getServerStat('tcpReusedConnections')
55
56 numberOfUDPQueries = 10
57 for _ in range(numberOfUDPQueries):
58 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, expectedResponse)
59 self.assertEqual(query, receivedQuery)
60 self.assertEqual(receivedResponse, expectedResponse)
61
62 # there was one TCP query in testTCP (below, but before in alphabetical order)
63 numberOfQueries = numberOfUDPQueries + 1
64 self.checkOnlyDOHResponderHit(numberOfUDPQueries)
65
66 self.assertEqual(self.getServerStat('tcpNewConnections'), 1)
67 self.assertEqual(self.getServerStat('tcpReusedConnections'), connsBefore + numberOfQueries - 1)
68 self.assertEqual(self.getServerStat('tlsResumptions'), 0)
69
70 def testTCP(self):
71 """
72 Outgoing DOH: TCP query is sent via DOH
73 """
74 name = 'tcp.outgoing-doh.test.powerdns.com.'
75 query = dns.message.make_query(name, 'A', 'IN')
76 expectedResponse = dns.message.make_response(query)
77 rrset = dns.rrset.from_text(name,
78 60,
79 dns.rdataclass.IN,
80 dns.rdatatype.A,
81 '127.0.0.1')
82 expectedResponse.answer.append(rrset)
83
84 connsBefore = self.getServerStat('tcpReusedConnections')
85
86 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, expectedResponse)
87 self.assertEqual(query, receivedQuery)
88 self.assertEqual(receivedResponse, expectedResponse)
89 self.checkOnlyDOHResponderHit()
90 self.assertEqual(self.getServerStat('tcpNewConnections'), 1)
91 self.assertEqual(self.getServerStat('tcpReusedConnections'), connsBefore)
92 self.assertEqual(self.getServerStat('tlsResumptions'), 0)
93
94 def testUDPCache(self):
95 """
96 Outgoing DOH: UDP query is sent via DOH, should be cached
97 """
98 name = 'udp.cached.outgoing-doh.test.powerdns.com.'
99 query = dns.message.make_query(name, 'A', 'IN')
100 expectedResponse = dns.message.make_response(query)
101 rrset = dns.rrset.from_text(name,
102 60,
103 dns.rdataclass.IN,
104 dns.rdatatype.A,
105 '127.0.0.1')
106 expectedResponse.answer.append(rrset)
107
108 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, expectedResponse)
109 self.assertEqual(query, receivedQuery)
110 self.assertEqual(receivedResponse, expectedResponse)
111
112 numberOfUDPQueries = 10
113 for _ in range(numberOfUDPQueries):
114 (_, receivedResponse) = self.sendUDPQuery(query, useQueue=False, response=None)
115 self.assertEqual(receivedResponse, expectedResponse)
116
117 def testTCPCache(self):
118 """
119 Outgoing DOH: TCP query is sent via DOH, should be cached
120 """
121 name = 'tcp.cached.outgoing-doh.test.powerdns.com.'
122 query = dns.message.make_query(name, 'A', 'IN')
123 expectedResponse = dns.message.make_response(query)
124 rrset = dns.rrset.from_text(name,
125 60,
126 dns.rdataclass.IN,
127 dns.rdatatype.A,
128 '127.0.0.1')
129 expectedResponse.answer.append(rrset)
130
131 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, expectedResponse)
132 self.assertEqual(query, receivedQuery)
133 self.assertEqual(receivedResponse, expectedResponse)
134
135 numberOfTCPQueries = 10
136 for _ in range(numberOfTCPQueries):
137 (_, receivedResponse) = self.sendTCPQuery(query, useQueue=False, response=None)
138 self.assertEqual(receivedResponse, expectedResponse)
139
140 def testZHealthChecks(self):
141 # this test has to run last, as it will mess up the TCP connection counter,
142 # hence the 'Z' in the name
143 self.sendConsoleCommand("getServer(0):setAuto()")
144 time.sleep(2)
145 status = self.sendConsoleCommand("if getServer(0):isUp() then return 'up' else return 'down' end").strip("\n")
146 self.assertEqual(status, 'up')
147
148 class BrokenOutgoingDOHTests(object):
149
150 _webTimeout = 2.0
151 _webServerPort = pickAvailablePort()
152 _webServerBasicAuthPassword = 'secret'
153 _webServerAPIKey = 'apisecret'
154 _webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
155 _webServerAPIKeyHashed = '$scrypt$ln=10,p=1,r=8$9v8JxDfzQVyTpBkTbkUqYg==$bDQzAOHeK1G9UvTPypNhrX48w974ZXbFPtRKS34+aso='
156
157 def checkNoResponderHit(self):
158 self.assertNotIn('UDP Responder', self._responsesCounter)
159 self.assertNotIn('TCP Responder', self._responsesCounter)
160 self.assertNotIn('TLS Responder', self._responsesCounter)
161 self.assertNotIn('DOH Responder', self._responsesCounter)
162
163 def testUDP(self):
164 """
165 Outgoing DOH (broken): UDP query is sent via DOH
166 """
167 name = 'udp.broken-outgoing-doh.test.powerdns.com.'
168 query = dns.message.make_query(name, 'A', 'IN')
169
170 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
171 self.assertEqual(receivedResponse, None)
172 self.checkNoResponderHit()
173
174 def testTCP(self):
175 """
176 Outgoing DOH (broken): TCP query is sent via DOH
177 """
178 name = 'tcp.broken-outgoing-doh.test.powerdns.com.'
179 query = dns.message.make_query(name, 'A', 'IN')
180 expectedResponse = dns.message.make_response(query)
181 rrset = dns.rrset.from_text(name,
182 60,
183 dns.rdataclass.IN,
184 dns.rdatatype.A,
185 '127.0.0.1')
186 expectedResponse.answer.append(rrset)
187
188 (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
189 self.assertEqual(receivedResponse, None)
190 self.checkNoResponderHit()
191
192 class OutgoingDOHBrokenResponsesTests(object):
193
194 _webTimeout = 2.0
195 _webServerPort = pickAvailablePort()
196 _webServerBasicAuthPassword = 'secret'
197 _webServerAPIKey = 'apisecret'
198 _webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
199 _webServerAPIKeyHashed = '$scrypt$ln=10,p=1,r=8$9v8JxDfzQVyTpBkTbkUqYg==$bDQzAOHeK1G9UvTPypNhrX48w974ZXbFPtRKS34+aso='
200
201 def testUDP(self):
202 """
203 Outgoing DOH (broken responses): UDP query is sent via DOH
204 """
205 name = '500-status.broken-responses.outgoing-doh.test.powerdns.com.'
206 query = dns.message.make_query(name, 'A', 'IN')
207
208 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
209 self.assertEqual(receivedResponse, None)
210
211 name = 'invalid-dns-payload.broken-responses.outgoing-doh.test.powerdns.com.'
212 query = dns.message.make_query(name, 'A', 'IN')
213
214 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
215 self.assertEqual(receivedResponse, None)
216
217 name = 'closing-connection-id.broken-responses.outgoing-doh.test.powerdns.com.'
218 query = dns.message.make_query(name, 'A', 'IN')
219
220 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
221 self.assertEqual(receivedResponse, None)
222
223 # but a valid response should be successful
224 name = 'valid.broken-responses.outgoing-doh.test.powerdns.com.'
225 query = dns.message.make_query(name, 'A', 'IN')
226 response = dns.message.make_response(query)
227
228 (_, receivedResponse) = self.sendUDPQuery(query, response)
229 # we can't check the received query because the responder does not populate the queue..
230 # self.assertEqual(query, receivedQuery)
231 self.assertEqual(response, receivedResponse)
232
233 def testTCP(self):
234 """
235 Outgoing DOH (broken responses): TCP query is sent via DOH
236 """
237 name = '500-status.broken-responses.outgoing-doh.test.powerdns.com.'
238 query = dns.message.make_query(name, 'A', 'IN')
239
240 (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
241 self.assertEqual(receivedResponse, None)
242
243 name = 'invalid-dns-payload.broken-responses.outgoing-doh.test.powerdns.com.'
244 query = dns.message.make_query(name, 'A', 'IN')
245
246 (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
247 self.assertEqual(receivedResponse, None)
248
249 name = 'closing-connection-id.broken-responses.outgoing-doh.test.powerdns.com.'
250 query = dns.message.make_query(name, 'A', 'IN')
251
252 (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
253 self.assertEqual(receivedResponse, None)
254
255 # but a valid response should be successful
256 name = 'valid.broken-responses.outgoing-doh.test.powerdns.com.'
257 query = dns.message.make_query(name, 'A', 'IN')
258 response = dns.message.make_response(query)
259
260 (_, receivedResponse) = self.sendTCPQuery(query, response)
261 # we can't check the received query because the responder does not populate the queue..
262 #self.assertEqual(query, receivedQuery)
263 self.assertEqual(response, receivedResponse)
264
265 class TestOutgoingDOHOpenSSL(DNSDistTest, OutgoingDOHTests):
266 _tlsBackendPort = pickAvailablePort()
267 _tlsProvider = 'openssl'
268 _consoleKey = DNSDistTest.generateConsoleKey()
269 _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
270 _config_params = ['_consoleKeyB64', '_consolePort', '_tlsBackendPort', '_tlsProvider', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
271 _config_template = """
272 setKey("%s")
273 controlSocket("127.0.0.1:%d")
274 setMaxTCPClientThreads(1)
275 newServer{address="127.0.0.1:%s", tls='%s', validateCertificates=true, caStore='ca.pem', subjectName='powerdns.com', dohPath='/dns-query', pool={'', 'cache'}}:setUp()
276 webserver("127.0.0.1:%s")
277 setWebserverConfig({password="%s", apiKey="%s"})
278
279 pc = newPacketCache(100)
280 getPool('cache'):setCache(pc)
281 smn = newSuffixMatchNode()
282 smn:add('cached.outgoing-doh.test.powerdns.com.')
283 addAction(SuffixMatchNodeRule(smn), PoolAction('cache'))
284 """
285
286 @staticmethod
287 def sniCallback(sslSocket, sni, sslContext):
288 assert(sni == 'powerdns.com')
289 return None
290
291 @classmethod
292 def startResponders(cls):
293 tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
294 tlsContext.set_alpn_protocols(["h2"])
295 tlsContext.load_cert_chain('server.chain', 'server.key')
296 # requires Python 3.7+
297 if hasattr(tlsContext, 'sni_callback'):
298 tlsContext.sni_callback = cls.sniCallback
299
300 print("Launching DOH responder..")
301 cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
302 cls._DOHResponder.daemon = True
303 cls._DOHResponder.start()
304
305 class TestOutgoingDOHGnuTLS(DNSDistTest, OutgoingDOHTests):
306 _tlsBackendPort = pickAvailablePort()
307 _tlsProvider = 'gnutls'
308 _consoleKey = DNSDistTest.generateConsoleKey()
309 _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
310 _config_params = ['_consoleKeyB64', '_consolePort', '_tlsBackendPort', '_tlsProvider', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
311 _config_template = """
312 setKey("%s")
313 controlSocket("127.0.0.1:%d")
314 setMaxTCPClientThreads(1)
315 newServer{address="127.0.0.1:%s", tls='%s', validateCertificates=true, caStore='ca.pem', subjectName='powerdns.com', dohPath='/dns-query', pool={'', 'cache'}}:setUp()
316 webserver("127.0.0.1:%s")
317 setWebserverConfig({password="%s", apiKey="%s"})
318
319 pc = newPacketCache(100)
320 getPool('cache'):setCache(pc)
321 smn = newSuffixMatchNode()
322 smn:add('cached.outgoing-doh.test.powerdns.com.')
323 addAction(SuffixMatchNodeRule(smn), PoolAction('cache'))
324 """
325
326 @classmethod
327 def startResponders(cls):
328 tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
329 tlsContext.load_cert_chain('server.chain', 'server.key')
330 tlsContext.keylog_filename = "/tmp/keys"
331
332 print("Launching DOH responder..")
333 cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
334 cls._DOHResponder.daemon = True
335 cls._DOHResponder.start()
336
337 class TestOutgoingDOHOpenSSLWrongCertName(DNSDistTest, BrokenOutgoingDOHTests):
338 _tlsBackendPort = pickAvailablePort()
339 _config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
340 _config_template = """
341 setMaxTCPClientThreads(1)
342 newServer{address="127.0.0.1:%s", tls='openssl', validateCertificates=true, caStore='ca.pem', subjectName='not-powerdns.com', dohPath='/dns-query'}:setUp()
343 webserver("127.0.0.1:%s")
344 setWebserverConfig({password="%s", apiKey="%s"})
345 """
346
347 @classmethod
348 def startResponders(cls):
349 tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
350 tlsContext.load_cert_chain('server.chain', 'server.key')
351
352 print("Launching DOH responder..")
353 cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
354 cls._DOHResponder.daemon = True
355 cls._DOHResponder.start()
356
357 class TestOutgoingDOHGnuTLSWrongCertName(DNSDistTest, BrokenOutgoingDOHTests):
358 _tlsBackendPort = pickAvailablePort()
359 _config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
360 _config_template = """
361 setMaxTCPClientThreads(1)
362 newServer{address="127.0.0.1:%s", tls='gnutls', validateCertificates=true, caStore='ca.pem', subjectName='not-powerdns.com', dohPath='/dns-query'}:setUp()
363 webserver("127.0.0.1:%s")
364 setWebserverConfig({password="%s", apiKey="%s"})
365 """
366
367 @classmethod
368 def startResponders(cls):
369 tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
370 tlsContext.load_cert_chain('server.chain', 'server.key')
371
372 print("Launching DOH responder..")
373 cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
374 cls._DOHResponder.daemon = True
375 cls._DOHResponder.start()
376
377 class TestOutgoingDOHOpenSSLWrongCertNameButNoCheck(DNSDistTest, OutgoingDOHTests):
378 _tlsBackendPort = pickAvailablePort()
379 _tlsProvider = 'openssl'
380 _consoleKey = DNSDistTest.generateConsoleKey()
381 _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
382 _config_params = ['_consoleKeyB64', '_consolePort', '_tlsBackendPort', '_tlsProvider', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
383 _config_template = """
384 setKey("%s")
385 controlSocket("127.0.0.1:%d")
386 setMaxTCPClientThreads(1)
387 newServer{address="127.0.0.1:%s", tls='%s', validateCertificates=false, caStore='ca.pem', subjectName='not-powerdns.com', dohPath='/dns-query', pool={'', 'cache'}}:setUp()
388 webserver("127.0.0.1:%s")
389 setWebserverConfig({password="%s", apiKey="%s"})
390
391 pc = newPacketCache(100)
392 getPool('cache'):setCache(pc)
393 smn = newSuffixMatchNode()
394 smn:add('cached.outgoing-doh.test.powerdns.com.')
395 addAction(SuffixMatchNodeRule(smn), PoolAction('cache'))
396 """
397
398 @classmethod
399 def startResponders(cls):
400 tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
401 tlsContext.load_cert_chain('server.chain', 'server.key')
402
403 print("Launching DOH responder..")
404 cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
405 cls._DOHResponder.daemon = True
406 cls._DOHResponder.start()
407
408 class TestOutgoingDOHGnuTLSWrongCertNameButNoCheck(DNSDistTest, OutgoingDOHTests):
409 _tlsBackendPort = pickAvailablePort()
410 _tlsProvider = 'gnutls'
411 _consoleKey = DNSDistTest.generateConsoleKey()
412 _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
413 _config_params = ['_consoleKeyB64', '_consolePort', '_tlsBackendPort', '_tlsProvider', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
414 _config_template = """
415 setKey("%s")
416 controlSocket("127.0.0.1:%d")
417 setMaxTCPClientThreads(1)
418 newServer{address="127.0.0.1:%s", tls='%s', validateCertificates=false, caStore='ca.pem', subjectName='not-powerdns.com', dohPath='/dns-query', pool={'', 'cache'}}:setUp()
419 webserver("127.0.0.1:%s")
420 setWebserverConfig({password="%s", apiKey="%s"})
421
422 pc = newPacketCache(100)
423 getPool('cache'):setCache(pc)
424 smn = newSuffixMatchNode()
425 smn:add('cached.outgoing-doh.test.powerdns.com.')
426 addAction(SuffixMatchNodeRule(smn), PoolAction('cache'))
427 """
428
429 @classmethod
430 def startResponders(cls):
431 tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
432 tlsContext.load_cert_chain('server.chain', 'server.key')
433
434 print("Launching DOH responder..")
435 cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
436 cls._DOHResponder.daemon = True
437 cls._DOHResponder.start()
438
439 class TestOutgoingDOHBrokenResponsesOpenSSL(DNSDistTest, OutgoingDOHBrokenResponsesTests):
440 _tlsBackendPort = pickAvailablePort()
441 _config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
442 _config_template = """
443 setMaxTCPClientThreads(1)
444 newServer{address="127.0.0.1:%s", tls='openssl', validateCertificates=true, caStore='ca.pem', subjectName='powerdns.com', dohPath='/dns-query', pool={'', 'cache'}}:setUp()
445 webserver("127.0.0.1:%s")
446 setWebserverConfig({password="%s", apiKey="%s"})
447
448 pc = newPacketCache(100)
449 getPool('cache'):setCache(pc)
450 smn = newSuffixMatchNode()
451 smn:add('cached.outgoing-doh.test.powerdns.com.')
452 addAction(SuffixMatchNodeRule(smn), PoolAction('cache'))
453 """
454
455 def callback(request, headers, fromQueue, toQueue):
456
457 if str(request.question[0].name) == '500-status.broken-responses.outgoing-doh.test.powerdns.com.':
458 print("returning 500")
459 return 500, b'Server error'
460
461 if str(request.question[0].name) == 'invalid-dns-payload.broken-responses.outgoing-doh.test.powerdns.com.':
462 return 200, b'not DNS'
463
464 if str(request.question[0].name) == 'closing-connection-id.broken-responses.outgoing-doh.test.powerdns.com.':
465 return 200, None
466
467 print("Returning default for %s" % (request.question[0].name))
468 return 200, dns.message.make_response(request).to_wire()
469
470 @classmethod
471 def startResponders(cls):
472 tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
473 tlsContext.set_alpn_protocols(["h2"])
474 tlsContext.load_cert_chain('server.chain', 'server.key')
475
476 print("Launching DOH responder..")
477 cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.callback, tlsContext])
478 cls._DOHResponder.daemon = True
479 cls._DOHResponder.start()
480
481 class TestOutgoingDOHBrokenResponsesGnuTLS(DNSDistTest, OutgoingDOHBrokenResponsesTests):
482 _tlsBackendPort = pickAvailablePort()
483 _config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
484 _config_template = """
485 setMaxTCPClientThreads(1)
486 newServer{address="127.0.0.1:%s", tls='gnutls', validateCertificates=true, caStore='ca.pem', subjectName='powerdns.com', dohPath='/dns-query'}:setUp()
487 webserver("127.0.0.1:%s")
488 setWebserverConfig({password="%s", apiKey="%s"})
489 """
490 _verboseMode = True
491
492 def callback(request, headers, fromQueue, toQueue):
493
494 if str(request.question[0].name) == '500-status.broken-responses.outgoing-doh.test.powerdns.com.':
495 print("returning 500")
496 return 500, b'Server error'
497
498 if str(request.question[0].name) == 'invalid-dns-payload.broken-responses.outgoing-doh.test.powerdns.com.':
499 return 200, b'not DNS'
500
501 if str(request.question[0].name) == 'closing-connection-id.broken-responses.outgoing-doh.test.powerdns.com.':
502 return 200, None
503
504 print("Returning default for %s" % (request.question[0].name))
505 return 200, dns.message.make_response(request).to_wire()
506
507 @classmethod
508 def startResponders(cls):
509 tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
510 tlsContext.set_alpn_protocols(["h2"])
511 tlsContext.load_cert_chain('server.chain', 'server.key')
512
513 print("Launching DOH responder..")
514 cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.callback, tlsContext])
515 cls._DOHResponder.daemon = True
516 cls._DOHResponder.start()
517
518 class TestOutgoingDOHProxyProtocol(DNSDistTest):
519
520 _tlsBackendPort = pickAvailablePort()
521 _config_params = ['_tlsBackendPort']
522 _config_template = """
523 setMaxTCPClientThreads(1)
524 newServer{address="127.0.0.1:%s", tls='gnutls', validateCertificates=true, caStore='ca.pem', subjectName='powerdns.com', dohPath='/dns-query', useProxyProtocol=true}:setUp()
525 """
526 _verboseMode = True
527
528 @classmethod
529 def startResponders(cls):
530 tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
531 tlsContext.set_alpn_protocols(["h2"])
532 tlsContext.load_cert_chain('server.chain', 'server.key')
533
534 print("Launching DOH woth Proxy Protocol responder..")
535 cls._DOHResponder = threading.Thread(name='DOH with Proxy Protocol Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext, True])
536 cls._DOHResponder.daemon = True
537 cls._DOHResponder.start()
538
539 def testPP(self):
540 """
541 Outgoing DOH with Proxy Protocol
542 """
543 name = 'proxy-protocol.outgoing-doh.test.powerdns.com.'
544 query = dns.message.make_query(name, 'A', 'IN')
545 expectedResponse = dns.message.make_response(query)
546 rrset = dns.rrset.from_text(name,
547 60,
548 dns.rdataclass.IN,
549 dns.rdatatype.A,
550 '127.0.0.1')
551 expectedResponse.answer.append(rrset)
552
553 (receivedProxyPayload, receivedResponse) = self.sendUDPQuery(query, expectedResponse)
554 receivedQuery = self._fromResponderQueue.get(True, 1.0)
555 self.assertEqual(query, receivedQuery)
556 self.assertEqual(receivedResponse, expectedResponse)
557 self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', False)
558
559 (receivedProxyPayload, receivedResponse) = self.sendTCPQuery(query, expectedResponse)
560 receivedQuery = self._fromResponderQueue.get(True, 1.0)
561 self.assertEqual(query, receivedQuery)
562 self.assertEqual(receivedResponse, expectedResponse)
563 self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True)
564
565 class TestOutgoingDOHXForwarded(DNSDistTest):
566 _tlsBackendPort = pickAvailablePort()
567 _config_params = ['_tlsBackendPort']
568 _config_template = """
569 setMaxTCPClientThreads(1)
570 newServer{address="127.0.0.1:%s", tls='gnutls', validateCertificates=true, caStore='ca.pem', subjectName='powerdns.com', dohPath='/dns-query', addXForwardedHeaders=true}
571 """
572 _verboseMode = True
573
574 def callback(request, headersList, fromQueue, toQueue):
575
576 if str(request.question[0].name) == 'a.root-servers.net.':
577 # do not check headers on health-check queries
578 return 200, dns.message.make_response(request).to_wire()
579
580 headers = {}
581 if headersList:
582 for k,v in headersList:
583 headers[k] = v
584
585 if not b'x-forwarded-for' in headers:
586 print("missing X-Forwarded-For")
587 return 406, b'Missing X-Forwarded-For header'
588 if not b'x-forwarded-port' in headers:
589 print("missing X-Forwarded-Port")
590 return 406, b'Missing X-Forwarded-Port header'
591 if not b'x-forwarded-proto' in headers:
592 print("missing X-Forwarded-Proto")
593 return 406, b'Missing X-Forwarded-Proto header'
594
595 toQueue.put(request, True, 1.0)
596 response = fromQueue.get(True, 1.0)
597 if response:
598 response = copy.copy(response)
599 response.id = request.id
600
601 return 200, response.to_wire()
602
603 @classmethod
604 def startResponders(cls):
605 tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
606 tlsContext.set_alpn_protocols(["h2"])
607 tlsContext.load_cert_chain('server.chain', 'server.key')
608
609 print("Launching DOH responder..")
610 cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.callback, tlsContext])
611 cls._DOHResponder.daemon = True
612 cls._DOHResponder.start()
613
614 def testXForwarded(self):
615 """
616 Outgoing DOH: X-Forwarded
617 """
618 name = 'x-forwarded-for.outgoing-doh.test.powerdns.com.'
619 query = dns.message.make_query(name, 'A', 'IN')
620 expectedResponse = dns.message.make_response(query)
621 rrset = dns.rrset.from_text(name,
622 60,
623 dns.rdataclass.IN,
624 dns.rdatatype.A,
625 '127.0.0.1')
626 expectedResponse.answer.append(rrset)
627
628 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, expectedResponse)
629 self.assertEqual(query, receivedQuery)
630 self.assertEqual(receivedResponse, expectedResponse)
631
632 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, expectedResponse)
633 self.assertEqual(query, receivedQuery)
634 self.assertEqual(receivedResponse, expectedResponse)