8 import clientsubnetoption
10 from dnsdistdohtests
import DNSDistDOHTest
11 from dnsdisttests
import pickAvailablePort
14 from io
import BytesIO
16 class DOHTests(object):
17 _serverKey
= 'server.key'
18 _serverCert
= 'server.chain'
19 _serverName
= 'tls.tests.dnsdist.org'
21 _dohServerPort
= pickAvailablePort()
22 _customResponseHeader1
= 'access-control-allow-origin: *'
23 _customResponseHeader2
= 'user-agent: derp'
24 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
25 _config_template
= """
26 newServer{address="127.0.0.1:%d"}
28 addAction("drop.doh.tests.powerdns.com.", DropAction())
29 addAction("refused.doh.tests.powerdns.com.", RCodeAction(DNSRCode.REFUSED))
30 addAction("spoof.doh.tests.powerdns.com.", SpoofAction("1.2.3.4"))
31 addAction(HTTPHeaderRule("X-PowerDNS", "^[a]{5}$"), SpoofAction("2.3.4.5"))
32 addAction(HTTPPathRule("/PowerDNS"), SpoofAction("3.4.5.6"))
33 addAction(HTTPPathRegexRule("^/PowerDNS-[0-9]"), SpoofAction("6.7.8.9"))
34 addAction("http-status-action.doh.tests.powerdns.com.", HTTPStatusAction(200, "Plaintext answer", "text/plain"))
35 addAction("http-status-action-redirect.doh.tests.powerdns.com.", HTTPStatusAction(307, "https://doh.powerdns.org"))
36 addAction("no-backend.doh.tests.powerdns.com.", PoolAction('this-pool-has-no-backend'))
38 function dohHandler(dq)
39 if dq:getHTTPScheme() == 'https' and dq:getHTTPHost() == '%s:%d' and dq:getHTTPPath() == '/' and dq:getHTTPQueryString() == '' then
41 for key,value in pairs(dq:getHTTPHeaders()) do
42 if key == 'content-type' and value == 'application/dns-message' then
48 dq:setHTTPResponse(200, 'It works!', 'text/plain')
50 return DNSAction.HeaderModify
55 addAction("http-lua.doh.tests.powerdns.com.", LuaAction(dohHandler))
57 addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/", "/coffee", "/PowerDNS", "/PowerDNS2", "/PowerDNS-999" }, {customResponseHeaders={["access-control-allow-origin"]="*",["user-agent"]="derp",["UPPERCASE"]="VaLuE"}, keepIncomingHeaders=true, library='%s'})
58 dohFE = getDOHFrontend(0)
59 dohFE:setResponsesMap({newDOHResponseMapEntry('^/coffee$', 418, 'C0FFEE', {['FoO']='bar'})})
61 _config_params
= ['_testServerPort', '_serverName', '_dohServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
64 def testDOHSimple(self
):
68 name
= 'simple.doh.tests.powerdns.com.'
69 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
71 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
73 response
= dns
.message
.make_response(query
)
74 rrset
= dns
.rrset
.from_text(name
,
79 response
.answer
.append(rrset
)
81 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
82 self
.assertTrue(receivedQuery
)
83 self
.assertTrue(receivedResponse
)
84 receivedQuery
.id = expectedQuery
.id
85 self
.assertEqual(expectedQuery
, receivedQuery
)
86 self
.assertTrue((self
._customResponseHeader
1) in self
._response
_headers
.decode())
87 self
.assertTrue((self
._customResponseHeader
2) in self
._response
_headers
.decode())
88 self
.assertFalse(('UPPERCASE: VaLuE' in self
._response
_headers
.decode()))
89 self
.assertTrue(('uppercase: VaLuE' in self
._response
_headers
.decode()))
90 self
.assertTrue(('cache-control: max-age=3600' in self
._response
_headers
.decode()))
91 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
92 self
.assertEqual(response
, receivedResponse
)
93 self
.checkHasHeader('cache-control', 'max-age=3600')
95 def testDOHTransactionID(self
):
97 DOH: Simple query with ID != 0
99 name
= 'simple-with-non-zero-id.doh.tests.powerdns.com.'
100 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
102 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
104 response
= dns
.message
.make_response(query
)
105 rrset
= dns
.rrset
.from_text(name
,
110 response
.answer
.append(rrset
)
112 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
113 self
.assertTrue(receivedQuery
)
114 self
.assertTrue(receivedResponse
)
115 receivedQuery
.id = expectedQuery
.id
116 self
.assertEqual(expectedQuery
, receivedQuery
)
117 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
118 self
.assertEqual(response
, receivedResponse
)
119 # just to be sure the ID _is_ checked
120 self
.assertEqual(response
.id, receivedResponse
.id)
122 def testDOHSimplePOST(self
):
124 DOH: Simple POST query
126 name
= 'simple-post.doh.tests.powerdns.com.'
127 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
129 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
131 response
= dns
.message
.make_response(query
)
132 rrset
= dns
.rrset
.from_text(name
,
137 response
.answer
.append(rrset
)
139 (receivedQuery
, receivedResponse
) = self
.sendDOHPostQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
140 self
.assertTrue(receivedQuery
)
141 self
.assertTrue(receivedResponse
)
142 receivedQuery
.id = expectedQuery
.id
143 self
.assertEqual(expectedQuery
, receivedQuery
)
144 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
145 self
.assertEqual(response
, receivedResponse
)
147 def testDOHExistingEDNS(self
):
151 name
= 'existing-edns.doh.tests.powerdns.com.'
152 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=8192)
154 response
= dns
.message
.make_response(query
)
155 rrset
= dns
.rrset
.from_text(name
,
160 response
.answer
.append(rrset
)
162 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
163 self
.assertTrue(receivedQuery
)
164 self
.assertTrue(receivedResponse
)
165 receivedQuery
.id = query
.id
166 self
.assertEqual(query
, receivedQuery
)
167 self
.assertEqual(response
, receivedResponse
)
168 self
.checkQueryEDNSWithoutECS(query
, receivedQuery
)
169 self
.checkResponseEDNSWithoutECS(response
, receivedResponse
)
171 def testDOHExistingECS(self
):
173 DOH: Existing EDNS Client Subnet
175 name
= 'existing-ecs.doh.tests.powerdns.com.'
176 ecso
= clientsubnetoption
.ClientSubnetOption('1.2.3.4')
177 rewrittenEcso
= clientsubnetoption
.ClientSubnetOption('127.0.0.1', 24)
178 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=512, options
=[ecso
], want_dnssec
=True)
180 response
= dns
.message
.make_response(query
)
181 response
.use_edns(edns
=True, payload
=4096, options
=[rewrittenEcso
])
182 rrset
= dns
.rrset
.from_text(name
,
187 response
.answer
.append(rrset
)
189 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
190 self
.assertTrue(receivedQuery
)
191 self
.assertTrue(receivedResponse
)
192 receivedQuery
.id = query
.id
193 self
.assertEqual(query
, receivedQuery
)
194 self
.assertEqual(response
, receivedResponse
)
195 self
.checkQueryEDNSWithECS(query
, receivedQuery
)
196 self
.checkResponseEDNSWithECS(response
, receivedResponse
)
198 def testDropped(self
):
202 name
= 'drop.doh.tests.powerdns.com.'
203 query
= dns
.message
.make_query(name
, 'A', 'IN')
204 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False)
205 self
.assertEqual(receivedResponse
, None)
207 def testRefused(self
):
211 name
= 'refused.doh.tests.powerdns.com.'
212 query
= dns
.message
.make_query(name
, 'A', 'IN')
214 query
.flags
&= ~dns
.flags
.RD
215 expectedResponse
= dns
.message
.make_response(query
)
216 expectedResponse
.set_rcode(dns
.rcode
.REFUSED
)
218 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False)
219 self
.assertEqual(receivedResponse
, expectedResponse
)
225 name
= 'spoof.doh.tests.powerdns.com.'
226 query
= dns
.message
.make_query(name
, 'A', 'IN')
228 query
.flags
&= ~dns
.flags
.RD
229 expectedResponse
= dns
.message
.make_response(query
)
230 rrset
= dns
.rrset
.from_text(name
,
235 expectedResponse
.answer
.append(rrset
)
237 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False)
238 self
.assertEqual(receivedResponse
, expectedResponse
)
240 def testDOHWithoutQuery(self
):
244 name
= 'empty-get.doh.tests.powerdns.com.'
245 url
= self
._dohBaseURL
246 conn
= self
.openDOHConnection(self
._dohServerPort
, self
._caCert
, timeout
=2.0)
247 conn
.setopt(pycurl
.URL
, url
)
248 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
249 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
250 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
251 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
252 data
= conn
.perform_rb()
253 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
254 self
.assertEqual(rcode
, 400)
256 def testDOHZeroQDCount(self
):
260 if self
._dohLibrary
== 'h2o':
261 raise unittest
.SkipTest('h2o tries to parse the qname early, so this check will fail')
262 name
= 'zero-qdcount.doh.tests.powerdns.com.'
263 query
= dns
.message
.Message()
265 query
.flags
&= ~dns
.flags
.RD
266 expectedResponse
= dns
.message
.make_response(query
)
267 expectedResponse
.set_rcode(dns
.rcode
.NOTIMP
)
269 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False)
270 self
.assertEqual(receivedResponse
, expectedResponse
)
272 def testDOHShortPath(self
):
274 DOH: Short path in GET query
276 name
= 'short-path-get.doh.tests.powerdns.com.'
277 url
= self
._dohBaseURL
+ '/AA'
278 conn
= self
.openDOHConnection(self
._dohServerPort
, self
._caCert
, timeout
=2.0)
279 conn
.setopt(pycurl
.URL
, url
)
280 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
281 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
282 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
283 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
284 data
= conn
.perform_rb()
285 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
286 self
.assertEqual(rcode
, 404)
288 def testDOHQueryNoParameter(self
):
290 DOH: No parameter GET query
292 name
= 'no-parameter-get.doh.tests.powerdns.com.'
293 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
294 wire
= query
.to_wire()
295 b64
= base64
.urlsafe_b64encode(wire
).decode('UTF8').rstrip('=')
296 url
= self
._dohBaseURL
+ '?not-dns=' + b64
297 conn
= self
.openDOHConnection(self
._dohServerPort
, self
._caCert
, timeout
=2.0)
298 conn
.setopt(pycurl
.URL
, url
)
299 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
300 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
301 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
302 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
303 data
= conn
.perform_rb()
304 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
305 self
.assertEqual(rcode
, 400)
307 def testDOHQueryInvalidBase64(self
):
309 DOH: Invalid Base64 GET query
311 name
= 'invalid-b64-get.doh.tests.powerdns.com.'
312 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
313 wire
= query
.to_wire()
314 url
= self
._dohBaseURL
+ '?dns=' + '_-~~~~-_'
315 conn
= self
.openDOHConnection(self
._dohServerPort
, self
._caCert
, timeout
=2.0)
316 conn
.setopt(pycurl
.URL
, url
)
317 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
318 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
319 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
320 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
321 data
= conn
.perform_rb()
322 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
323 self
.assertEqual(rcode
, 400)
325 def testDOHInvalidDNSHeaders(self
):
327 DOH: Invalid DNS headers
329 name
= 'invalid-dns-headers.doh.tests.powerdns.com.'
330 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
331 query
.flags |
= dns
.flags
.QR
332 wire
= query
.to_wire()
333 b64
= base64
.urlsafe_b64encode(wire
).decode('UTF8').rstrip('=')
334 url
= self
._dohBaseURL
+ '?dns=' + b64
335 conn
= self
.openDOHConnection(self
._dohServerPort
, self
._caCert
, timeout
=2.0)
336 conn
.setopt(pycurl
.URL
, url
)
337 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
338 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
339 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
340 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
341 data
= conn
.perform_rb()
342 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
343 self
.assertEqual(rcode
, 400)
345 def testDOHQueryInvalidMethod(self
):
349 if self
._dohLibrary
== 'h2o':
350 raise unittest
.SkipTest('h2o does not check the HTTP method')
351 name
= 'invalid-method.doh.tests.powerdns.com.'
352 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
353 wire
= query
.to_wire()
354 b64
= base64
.urlsafe_b64encode(wire
).decode('UTF8').rstrip('=')
355 url
= self
._dohBaseURL
+ '?dns=' + b64
356 conn
= self
.openDOHConnection(self
._dohServerPort
, self
._caCert
, timeout
=2)
357 conn
.setopt(pycurl
.URL
, url
)
358 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
359 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
360 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
361 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
362 conn
.setopt(pycurl
.CUSTOMREQUEST
, 'PATCH')
363 data
= conn
.perform_rb()
364 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
365 self
.assertEqual(rcode
, 400)
367 def testDOHQueryInvalidALPN(self
):
371 alpn
= ['bogus-alpn']
372 conn
= self
.openTLSConnection(self
._dohServerPort
, self
._serverName
, self
._caCert
, alpn
=alpn
)
375 response
= conn
.recv(65535)
376 self
.assertFalse(response
)
380 def testDOHInvalid(self
):
382 DOH: Invalid DNS query
384 name
= 'invalid.doh.tests.powerdns.com.'
385 invalidQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
387 # first an invalid query
388 invalidQuery
= invalidQuery
.to_wire()
389 invalidQuery
= invalidQuery
[:-5]
390 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, caFile
=self
._caCert
, query
=invalidQuery
, response
=None, useQueue
=False, rawQuery
=True)
391 self
.assertEqual(receivedResponse
, None)
393 # and now a valid one
394 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
396 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
398 response
= dns
.message
.make_response(query
)
399 rrset
= dns
.rrset
.from_text(name
,
404 response
.answer
.append(rrset
)
405 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
406 self
.assertTrue(receivedQuery
)
407 self
.assertTrue(receivedResponse
)
408 receivedQuery
.id = expectedQuery
.id
409 self
.assertEqual(expectedQuery
, receivedQuery
)
410 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
411 self
.assertEqual(response
, receivedResponse
)
413 def testDOHInvalidHeaderName(self
):
415 DOH: Invalid HTTP header name query
417 name
= 'invalid-header-name.doh.tests.powerdns.com.'
418 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
420 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
422 response
= dns
.message
.make_response(query
)
423 rrset
= dns
.rrset
.from_text(name
,
428 response
.answer
.append(rrset
)
429 # this header is invalid, see rfc9113 section 8.2.1. Field Validity
430 customHeaders
= ['{}: test']
432 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
, customHeaders
=customHeaders
)
433 self
.assertFalse(receivedQuery
)
434 self
.assertFalse(receivedResponse
)
438 def testDOHNoBackend(self
):
442 if self
._dohLibrary
== 'h2o':
443 raise unittest
.SkipTest('h2o does not check the HTTP method')
444 name
= 'no-backend.doh.tests.powerdns.com.'
445 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
446 wire
= query
.to_wire()
447 b64
= base64
.urlsafe_b64encode(wire
).decode('UTF8').rstrip('=')
448 url
= self
._dohBaseURL
+ '?dns=' + b64
449 conn
= self
.openDOHConnection(self
._dohServerPort
, self
._caCert
, timeout
=2)
450 conn
.setopt(pycurl
.URL
, url
)
451 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
452 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
453 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
454 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
455 data
= conn
.perform_rb()
456 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
457 self
.assertEqual(rcode
, 403)
459 def testDOHEmptyPOST(self
):
461 DOH: Empty POST query
463 name
= 'empty-post.doh.tests.powerdns.com.'
465 (_
, receivedResponse
) = self
.sendDOHPostQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
="", rawQuery
=True, response
=None, caFile
=self
._caCert
)
466 self
.assertEqual(receivedResponse
, None)
468 # and now a valid one
469 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
471 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
473 response
= dns
.message
.make_response(query
)
474 rrset
= dns
.rrset
.from_text(name
,
479 response
.answer
.append(rrset
)
480 (receivedQuery
, receivedResponse
) = self
.sendDOHPostQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
481 self
.assertTrue(receivedQuery
)
482 self
.assertTrue(receivedResponse
)
483 receivedQuery
.id = expectedQuery
.id
484 self
.assertEqual(expectedQuery
, receivedQuery
)
485 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
486 self
.assertEqual(response
, receivedResponse
)
488 def testHeaderRule(self
):
492 name
= 'header-rule.doh.tests.powerdns.com.'
493 query
= dns
.message
.make_query(name
, 'A', 'IN')
495 query
.flags
&= ~dns
.flags
.RD
496 expectedResponse
= dns
.message
.make_response(query
)
497 rrset
= dns
.rrset
.from_text(name
,
502 expectedResponse
.answer
.append(rrset
)
504 # this header should match
505 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False, customHeaders
=['x-powerdnS: aaaaa'])
506 self
.assertEqual(receivedResponse
, expectedResponse
)
508 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
509 expectedQuery
.flags
&= ~dns
.flags
.RD
511 response
= dns
.message
.make_response(query
)
512 rrset
= dns
.rrset
.from_text(name
,
517 response
.answer
.append(rrset
)
519 # this content of the header should NOT match
520 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
, customHeaders
=['x-powerdnS: bbbbb'])
521 self
.assertTrue(receivedQuery
)
522 self
.assertTrue(receivedResponse
)
523 receivedQuery
.id = expectedQuery
.id
524 self
.assertEqual(expectedQuery
, receivedQuery
)
525 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
526 self
.assertEqual(response
, receivedResponse
)
528 def testHTTPPath(self
):
532 name
= 'http-path.doh.tests.powerdns.com.'
533 query
= dns
.message
.make_query(name
, 'A', 'IN')
535 query
.flags
&= ~dns
.flags
.RD
536 expectedResponse
= dns
.message
.make_response(query
)
537 rrset
= dns
.rrset
.from_text(name
,
542 expectedResponse
.answer
.append(rrset
)
544 # this path should match
545 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ 'PowerDNS', caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False)
546 self
.assertEqual(receivedResponse
, expectedResponse
)
548 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
550 expectedQuery
.flags
&= ~dns
.flags
.RD
551 response
= dns
.message
.make_response(query
)
552 rrset
= dns
.rrset
.from_text(name
,
557 response
.answer
.append(rrset
)
559 # this path should NOT match
560 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ "PowerDNS2", query
, response
=response
, caFile
=self
._caCert
)
561 self
.assertTrue(receivedQuery
)
562 self
.assertTrue(receivedResponse
)
563 receivedQuery
.id = expectedQuery
.id
564 self
.assertEqual(expectedQuery
, receivedQuery
)
565 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
566 self
.assertEqual(response
, receivedResponse
)
568 # this path is not in the URLs map and should lead to a 404
569 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ "PowerDNS/something", query
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True)
570 self
.assertTrue(receivedResponse
)
571 self
.assertEqual(receivedResponse
, b
'there is no endpoint configured for this path')
572 self
.assertEqual(self
._rcode
, 404)
574 def testHTTPPathRegex(self
):
578 name
= 'http-path-regex.doh.tests.powerdns.com.'
579 query
= dns
.message
.make_query(name
, 'A', 'IN')
581 query
.flags
&= ~dns
.flags
.RD
582 expectedResponse
= dns
.message
.make_response(query
)
583 rrset
= dns
.rrset
.from_text(name
,
588 expectedResponse
.answer
.append(rrset
)
590 # this path should match
591 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ 'PowerDNS-999', caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False)
592 self
.assertEqual(receivedResponse
, expectedResponse
)
594 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
596 expectedQuery
.flags
&= ~dns
.flags
.RD
597 response
= dns
.message
.make_response(query
)
598 rrset
= dns
.rrset
.from_text(name
,
603 response
.answer
.append(rrset
)
605 # this path should NOT match
606 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ "PowerDNS2", query
, response
=response
, caFile
=self
._caCert
)
607 self
.assertTrue(receivedQuery
)
608 self
.assertTrue(receivedResponse
)
609 receivedQuery
.id = expectedQuery
.id
610 self
.assertEqual(expectedQuery
, receivedQuery
)
611 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
612 self
.assertEqual(response
, receivedResponse
)
614 def testHTTPStatusAction200(self
):
616 DOH: HTTPStatusAction 200 OK
618 name
= 'http-status-action.doh.tests.powerdns.com.'
619 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
622 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True)
623 self
.assertTrue(receivedResponse
)
624 self
.assertEqual(receivedResponse
, b
'Plaintext answer')
625 self
.assertEqual(self
._rcode
, 200)
626 self
.assertTrue('content-type: text/plain' in self
._response
_headers
.decode())
628 def testHTTPStatusAction307(self
):
630 DOH: HTTPStatusAction 307
632 name
= 'http-status-action-redirect.doh.tests.powerdns.com.'
633 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
636 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True)
637 self
.assertTrue(receivedResponse
)
638 self
.assertEqual(self
._rcode
, 307)
639 self
.assertTrue('location: https://doh.powerdns.org' in self
._response
_headers
.decode())
641 def testHTTPLuaResponse(self
):
643 DOH: Lua HTTP Response
645 name
= 'http-lua.doh.tests.powerdns.com.'
646 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
649 (_
, receivedResponse
) = self
.sendDOHPostQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True)
650 self
.assertTrue(receivedResponse
)
651 self
.assertEqual(receivedResponse
, b
'It works!')
652 self
.assertEqual(self
._rcode
, 200)
653 self
.assertTrue('content-type: text/plain' in self
._response
_headers
.decode())
655 def testHTTPEarlyResponse(self
):
657 DOH: HTTP Early Response
659 response_headers
= BytesIO()
660 url
= self
._dohBaseURL
+ 'coffee'
661 conn
= self
.openDOHConnection(self
._dohServerPort
, caFile
=self
._caCert
, timeout
=2.0)
662 conn
.setopt(pycurl
.URL
, url
)
663 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
664 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
665 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
666 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
667 conn
.setopt(pycurl
.HEADERFUNCTION
, response_headers
.write
)
668 data
= conn
.perform_rb()
669 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
670 headers
= response_headers
.getvalue().decode()
672 self
.assertEqual(rcode
, 418)
673 self
.assertEqual(data
, b
'C0FFEE')
674 self
.assertIn('foo: bar', headers
)
675 self
.assertNotIn(self
._customResponseHeader
2, headers
)
677 response_headers
= BytesIO()
678 conn
= self
.openDOHConnection(self
._dohServerPort
, caFile
=self
._caCert
, timeout
=2.0)
679 conn
.setopt(pycurl
.URL
, url
)
680 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
681 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
682 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
683 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
684 conn
.setopt(pycurl
.HEADERFUNCTION
, response_headers
.write
)
685 conn
.setopt(pycurl
.POST
, True)
687 conn
.setopt(pycurl
.POSTFIELDS
, data
)
689 data
= conn
.perform_rb()
690 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
691 headers
= response_headers
.getvalue().decode()
692 self
.assertEqual(rcode
, 418)
693 self
.assertEqual(data
, b
'C0FFEE')
694 self
.assertIn('foo: bar', headers
)
695 self
.assertNotIn(self
._customResponseHeader
2, headers
)
697 class TestDoHNGHTTP2(DOHTests
, DNSDistDOHTest
):
698 _dohLibrary
= 'nghttp2'
700 class TestDoHH2O(DOHTests
, DNSDistDOHTest
):
703 class DOHSubPathsTests(object):
704 _serverKey
= 'server.key'
705 _serverCert
= 'server.chain'
706 _serverName
= 'tls.tests.dnsdist.org'
708 _dohServerPort
= pickAvailablePort()
709 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
710 _config_template
= """
711 newServer{address="127.0.0.1:%s"}
713 addAction(AllRule(), SpoofAction("3.4.5.6"))
715 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/PowerDNS" }, {exactPathMatching=false, library='%s'})
717 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
719 def testSubPath(self
):
723 name
= 'sub-path.doh.tests.powerdns.com.'
724 query
= dns
.message
.make_query(name
, 'A', 'IN')
726 query
.flags
&= ~dns
.flags
.RD
727 expectedResponse
= dns
.message
.make_response(query
)
728 rrset
= dns
.rrset
.from_text(name
,
733 expectedResponse
.answer
.append(rrset
)
735 # this path should match
736 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ 'PowerDNS', caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False)
737 self
.assertEqual(receivedResponse
, expectedResponse
)
739 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
741 expectedQuery
.flags
&= ~dns
.flags
.RD
742 response
= dns
.message
.make_response(query
)
743 rrset
= dns
.rrset
.from_text(name
,
748 response
.answer
.append(rrset
)
750 # this path is not in the URLs map and should lead to a 404
751 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ "NotPowerDNS", query
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True)
752 self
.assertTrue(receivedResponse
)
753 self
.assertIn(receivedResponse
, [b
'there is no endpoint configured for this path', b
'not found'])
754 self
.assertEqual(self
._rcode
, 404)
756 # this path is below one in the URLs map and exactPathMatching is false, so we should be good
757 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ 'PowerDNS/something', caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False)
758 self
.assertEqual(receivedResponse
, expectedResponse
)
760 class TestDoHSubPathsNGHTTP2(DOHSubPathsTests
, DNSDistDOHTest
):
761 _dohLibrary
= 'nghttp2'
763 class TestDoHSubPathsH2O(DOHSubPathsTests
, DNSDistDOHTest
):
766 class DOHAddingECSTests(object):
768 _serverKey
= 'server.key'
769 _serverCert
= 'server.chain'
770 _serverName
= 'tls.tests.dnsdist.org'
772 _dohServerPort
= pickAvailablePort()
773 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
774 _config_template
= """
775 newServer{address="127.0.0.1:%s", useClientSubnet=true}
776 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {library='%s'})
779 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
781 def testDOHSimple(self
):
783 DOH with ECS: Simple query
785 name
= 'simple.doh-ecs.tests.powerdns.com.'
786 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
788 rewrittenEcso
= clientsubnetoption
.ClientSubnetOption('127.0.0.0', 24)
789 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096, options
=[rewrittenEcso
])
790 response
= dns
.message
.make_response(query
)
791 rrset
= dns
.rrset
.from_text(name
,
796 response
.answer
.append(rrset
)
798 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
799 self
.assertTrue(receivedQuery
)
800 self
.assertTrue(receivedResponse
)
801 expectedQuery
.id = receivedQuery
.id
802 self
.assertEqual(expectedQuery
, receivedQuery
)
803 self
.checkQueryEDNSWithECS(expectedQuery
, receivedQuery
)
804 self
.assertEqual(response
, receivedResponse
)
805 self
.checkResponseNoEDNS(response
, receivedResponse
)
807 def testDOHExistingEDNS(self
):
809 DOH with ECS: Existing EDNS
811 name
= 'existing-edns.doh-ecs.tests.powerdns.com.'
812 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=8192)
814 rewrittenEcso
= clientsubnetoption
.ClientSubnetOption('127.0.0.0', 24)
815 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=8192, options
=[rewrittenEcso
])
816 response
= dns
.message
.make_response(query
)
817 rrset
= dns
.rrset
.from_text(name
,
822 response
.answer
.append(rrset
)
824 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
825 self
.assertTrue(receivedQuery
)
826 self
.assertTrue(receivedResponse
)
827 receivedQuery
.id = expectedQuery
.id
828 self
.assertEqual(expectedQuery
, receivedQuery
)
829 self
.assertEqual(response
, receivedResponse
)
830 self
.checkQueryEDNSWithECS(expectedQuery
, receivedQuery
)
831 self
.checkResponseEDNSWithoutECS(response
, receivedResponse
)
833 def testDOHExistingECS(self
):
835 DOH with ECS: Existing EDNS Client Subnet
837 name
= 'existing-ecs.doh-ecs.tests.powerdns.com.'
838 ecso
= clientsubnetoption
.ClientSubnetOption('1.2.3.4')
839 rewrittenEcso
= clientsubnetoption
.ClientSubnetOption('127.0.0.0', 24)
840 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=512, options
=[ecso
], want_dnssec
=True)
842 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=512, options
=[rewrittenEcso
])
843 response
= dns
.message
.make_response(query
)
844 response
.use_edns(edns
=True, payload
=4096, options
=[rewrittenEcso
])
845 rrset
= dns
.rrset
.from_text(name
,
850 response
.answer
.append(rrset
)
852 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
853 self
.assertTrue(receivedQuery
)
854 self
.assertTrue(receivedResponse
)
855 receivedQuery
.id = expectedQuery
.id
856 self
.assertEqual(expectedQuery
, receivedQuery
)
857 self
.assertEqual(response
, receivedResponse
)
858 self
.checkQueryEDNSWithECS(expectedQuery
, receivedQuery
)
859 self
.checkResponseEDNSWithECS(response
, receivedResponse
)
861 class TestDoHAddingECSNGHTTP2(DOHAddingECSTests
, DNSDistDOHTest
):
862 _dohLibrary
= 'nghttp2'
864 class TestDoHAddingECSH2O(DOHAddingECSTests
, DNSDistDOHTest
):
867 class DOHOverHTTP(object):
868 _dohServerPort
= pickAvailablePort()
869 _serverName
= 'tls.tests.dnsdist.org'
870 _dohBaseURL
= ("http://%s:%d/dns-query" % (_serverName
, _dohServerPort
))
871 _config_template
= """
872 newServer{address="127.0.0.1:%s"}
873 addDOHLocal("127.0.0.1:%s", nil, nil, '/dns-query', {library='%s'})
875 _config_params
= ['_testServerPort', '_dohServerPort', '_dohLibrary']
877 def testDOHSimple(self
):
879 DOH over HTTP: Simple query
881 name
= 'simple.doh-over-http.tests.powerdns.com.'
882 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
884 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
885 response
= dns
.message
.make_response(query
)
886 rrset
= dns
.rrset
.from_text(name
,
891 response
.answer
.append(rrset
)
893 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, useHTTPS
=False)
894 self
.assertTrue(receivedQuery
)
895 self
.assertTrue(receivedResponse
)
896 expectedQuery
.id = receivedQuery
.id
897 self
.assertEqual(expectedQuery
, receivedQuery
)
898 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
899 self
.assertEqual(response
, receivedResponse
)
900 self
.checkResponseNoEDNS(response
, receivedResponse
)
902 def testDOHSimplePOST(self
):
904 DOH over HTTP: Simple POST query
906 name
= 'simple-post.doh-over-http.tests.powerdns.com.'
907 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
909 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
911 response
= dns
.message
.make_response(query
)
912 rrset
= dns
.rrset
.from_text(name
,
917 response
.answer
.append(rrset
)
919 (receivedQuery
, receivedResponse
) = self
.sendDOHPostQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, useHTTPS
=False)
920 self
.assertTrue(receivedQuery
)
921 self
.assertTrue(receivedResponse
)
922 receivedQuery
.id = expectedQuery
.id
923 self
.assertEqual(expectedQuery
, receivedQuery
)
924 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
925 self
.assertEqual(response
, receivedResponse
)
926 self
.checkResponseNoEDNS(response
, receivedResponse
)
928 class TestDOHOverHTTPNGHTTP2(DOHOverHTTP
, DNSDistDOHTest
):
929 _dohLibrary
= 'nghttp2'
930 _checkConfigExpectedOutput
= b
"""No certificate provided for DoH endpoint 127.0.0.1:%d, running in DNS over HTTP mode instead of DNS over HTTPS
931 Configuration 'configs/dnsdist_TestDOHOverHTTPNGHTTP2.conf' OK!
932 """ % (DOHOverHTTP
._dohServerPort
)
934 class TestDOHOverHTTPH2O(DOHOverHTTP
, DNSDistDOHTest
):
936 _checkConfigExpectedOutput
= b
"""No certificate provided for DoH endpoint 127.0.0.1:%d, running in DNS over HTTP mode instead of DNS over HTTPS
937 Configuration 'configs/dnsdist_TestDOHOverHTTPH2O.conf' OK!
938 """ % (DOHOverHTTP
._dohServerPort
)
940 class DOHWithCache(object):
942 _serverKey
= 'server.key'
943 _serverCert
= 'server.chain'
944 _serverName
= 'tls.tests.dnsdist.org'
946 _dohServerPort
= pickAvailablePort()
947 _dohBaseURL
= ("https://%s:%d/dns-query" % (_serverName
, _dohServerPort
))
948 _config_template
= """
949 newServer{address="127.0.0.1:%s"}
951 addDOHLocal("127.0.0.1:%s", "%s", "%s", '/dns-query', {library='%s'})
953 pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
954 getPool(""):setCache(pc)
956 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
958 def testDOHCacheLargeAnswer(self
):
960 DOH with cache: Check that we can cache (and retrieve) large answers
963 name
= 'large.doh-with-cache.tests.powerdns.com.'
964 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
966 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
968 response
= dns
.message
.make_response(query
)
969 # we prepare a large answer
973 content
= content
+ ', '
974 content
= content
+ (str(i
)*50)
976 content
= content
+ 'A'*40
978 rrset
= dns
.rrset
.from_text(name
,
983 response
.answer
.append(rrset
)
984 self
.assertEqual(len(response
.to_wire()), 4096)
986 # first query to fill the cache
987 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
988 self
.assertTrue(receivedQuery
)
989 self
.assertTrue(receivedResponse
)
990 receivedQuery
.id = expectedQuery
.id
991 self
.assertEqual(expectedQuery
, receivedQuery
)
992 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
993 self
.assertEqual(response
, receivedResponse
)
994 self
.checkHasHeader('cache-control', 'max-age=3600')
996 for _
in range(numberOfQueries
):
997 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False)
998 self
.assertEqual(receivedResponse
, response
)
999 self
.checkHasHeader('cache-control', 'max-age=' + str(receivedResponse
.answer
[0].ttl
))
1003 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False)
1004 self
.assertEqual(receivedResponse
, response
)
1005 self
.checkHasHeader('cache-control', 'max-age=' + str(receivedResponse
.answer
[0].ttl
))
1007 def testDOHGetFromUDPCache(self
):
1009 DOH with cache: Check that we can retrieve an answer received for a UDP query
1011 name
= 'doh-query-insert-udp.doh-with-cache.tests.powerdns.com.'
1012 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1013 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1014 expectedQuery
.id = 0
1015 response
= dns
.message
.make_response(query
)
1016 rrset
= dns
.rrset
.from_text(name
,
1021 response
.answer
.append(rrset
)
1023 # first query to fill the cache
1024 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
1025 self
.assertTrue(receivedQuery
)
1026 self
.assertTrue(receivedResponse
)
1027 receivedQuery
.id = expectedQuery
.id
1028 self
.assertEqual(expectedQuery
, receivedQuery
)
1029 self
.assertEqual(response
, receivedResponse
)
1031 # now we send the exact same query over DoH, we should get a cache hit
1032 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False)
1033 self
.assertTrue(receivedResponse
)
1034 self
.assertEqual(response
, receivedResponse
)
1036 def testDOHInsertIntoUDPCache(self
):
1038 DOH with cache: Check that we can retrieve an answer received for a DoH query from UDP
1040 name
= 'udp-query-get-doh.doh-with-cache.tests.powerdns.com.'
1041 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1042 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1043 expectedQuery
.id = 0
1044 response
= dns
.message
.make_response(query
)
1045 rrset
= dns
.rrset
.from_text(name
,
1050 response
.answer
.append(rrset
)
1052 # first query to fill the cache
1053 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
1054 self
.assertTrue(receivedQuery
)
1055 self
.assertTrue(receivedResponse
)
1056 receivedQuery
.id = expectedQuery
.id
1057 self
.assertEqual(expectedQuery
, receivedQuery
)
1058 self
.assertEqual(response
, receivedResponse
)
1060 # now we send the exact same query over DoH, we should get a cache hit
1061 (_
, receivedResponse
) = self
.sendUDPQuery(query
, response
=None, useQueue
=False)
1062 self
.assertTrue(receivedResponse
)
1063 self
.assertEqual(response
, receivedResponse
)
1065 def testTruncation(self
):
1067 DOH: Truncation over UDP (with cache)
1069 # the query is first forwarded over UDP, leading to a TC=1 answer from the
1070 # backend, then over TCP
1071 name
= 'truncated-udp.doh-with-cache.tests.powerdns.com.'
1072 query
= dns
.message
.make_query(name
, 'A', 'IN')
1074 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1075 expectedQuery
.id = 42
1076 response
= dns
.message
.make_response(query
)
1077 rrset
= dns
.rrset
.from_text(name
,
1082 response
.answer
.append(rrset
)
1084 # first response is a TC=1
1085 tcResponse
= dns
.message
.make_response(query
)
1086 tcResponse
.flags |
= dns
.flags
.TC
1087 self
._toResponderQueue
.put(tcResponse
, True, 2.0)
1089 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, response
=response
)
1090 # first query, received by the responder over UDP
1091 self
.assertTrue(receivedQuery
)
1092 receivedQuery
.id = expectedQuery
.id
1093 self
.assertEqual(expectedQuery
, receivedQuery
)
1094 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
1096 # check the response
1097 self
.assertTrue(receivedResponse
)
1098 self
.assertEqual(response
, receivedResponse
)
1100 # check the second query, received by the responder over TCP
1101 receivedQuery
= self
._fromResponderQueue
.get(True, 2.0)
1102 self
.assertTrue(receivedQuery
)
1103 receivedQuery
.id = expectedQuery
.id
1104 self
.assertEqual(expectedQuery
, receivedQuery
)
1105 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
1107 # now check the cache for a DoH query
1108 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False)
1109 self
.assertEqual(response
, receivedResponse
)
1111 # The TC=1 answer received over UDP will not be cached, because we currently do not cache answers with no records (no TTL)
1112 # The TCP one should, however
1113 (_
, receivedResponse
) = self
.sendTCPQuery(expectedQuery
, response
=None, useQueue
=False)
1114 self
.assertEqual(response
, receivedResponse
)
1116 def testResponsesReceivedOverUDP(self
):
1118 DOH: Check that responses received over UDP are cached (with cache)
1120 name
= 'cached-udp.doh-with-cache.tests.powerdns.com.'
1121 query
= dns
.message
.make_query(name
, 'A', 'IN')
1123 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1124 expectedQuery
.id = 0
1125 response
= dns
.message
.make_response(query
)
1126 rrset
= dns
.rrset
.from_text(name
,
1131 response
.answer
.append(rrset
)
1133 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, response
=response
)
1134 self
.assertTrue(receivedQuery
)
1135 receivedQuery
.id = expectedQuery
.id
1136 self
.assertEqual(expectedQuery
, receivedQuery
)
1137 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
1138 self
.assertTrue(receivedResponse
)
1139 self
.assertEqual(response
, receivedResponse
)
1141 # now check the cache for a DoH query
1142 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False)
1143 self
.assertEqual(response
, receivedResponse
)
1145 # Check that the answer is usable for UDP queries as well
1146 (_
, receivedResponse
) = self
.sendUDPQuery(expectedQuery
, response
=None, useQueue
=False)
1147 self
.assertEqual(response
, receivedResponse
)
1149 class TestDOHWithCacheNGHTTP2(DOHWithCache
, DNSDistDOHTest
):
1150 _dohLibrary
= 'nghttp2'
1153 class TestDOHWithCacheH2O(DOHWithCache
, DNSDistDOHTest
):
1156 class DOHWithoutCacheControl(object):
1158 _serverKey
= 'server.key'
1159 _serverCert
= 'server.chain'
1160 _serverName
= 'tls.tests.dnsdist.org'
1162 _dohServerPort
= pickAvailablePort()
1163 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1164 _config_template
= """
1165 newServer{address="127.0.0.1:%s"}
1167 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {sendCacheControlHeaders=false, library='%s'})
1169 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
1171 def testDOHSimple(self
):
1173 DOH without cache-control
1175 name
= 'simple.doh.tests.powerdns.com.'
1176 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
1178 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1179 expectedQuery
.id = 0
1180 response
= dns
.message
.make_response(query
)
1181 rrset
= dns
.rrset
.from_text(name
,
1186 response
.answer
.append(rrset
)
1188 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
1189 self
.assertTrue(receivedQuery
)
1190 self
.assertTrue(receivedResponse
)
1191 receivedQuery
.id = expectedQuery
.id
1192 self
.assertEqual(expectedQuery
, receivedQuery
)
1193 self
.checkNoHeader('cache-control')
1194 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
1195 self
.assertEqual(response
, receivedResponse
)
1197 class TestDOHWithoutCacheControlNGHTTP2(DOHWithoutCacheControl
, DNSDistDOHTest
):
1198 _dohLibrary
= 'nghttp2'
1200 class TestDOHWithoutCacheControlH2O(DOHWithoutCacheControl
, DNSDistDOHTest
):
1203 class DOHFFI(object):
1204 _serverKey
= 'server.key'
1205 _serverCert
= 'server.chain'
1206 _serverName
= 'tls.tests.dnsdist.org'
1208 _dohServerPort
= pickAvailablePort()
1209 _customResponseHeader1
= 'access-control-allow-origin: *'
1210 _customResponseHeader2
= 'user-agent: derp'
1211 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1212 _config_template
= """
1213 newServer{address="127.0.0.1:%s"}
1215 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {customResponseHeaders={["access-control-allow-origin"]="*",["user-agent"]="derp",["UPPERCASE"]="VaLuE"}, keepIncomingHeaders=true, library='%s'})
1217 local ffi = require("ffi")
1219 function dohHandler(dq)
1220 local scheme = ffi.string(ffi.C.dnsdist_ffi_dnsquestion_get_http_scheme(dq))
1221 local host = ffi.string(ffi.C.dnsdist_ffi_dnsquestion_get_http_host(dq))
1222 local path = ffi.string(ffi.C.dnsdist_ffi_dnsquestion_get_http_path(dq))
1223 local query_string = ffi.string(ffi.C.dnsdist_ffi_dnsquestion_get_http_query_string(dq))
1224 if scheme == 'https' and host == '%s:%d' and path == '/' and query_string == '' then
1225 local foundct = false
1226 local headers_ptr = ffi.new("const dnsdist_ffi_http_header_t *[1]")
1227 local headers_ptr_param = ffi.cast("const dnsdist_ffi_http_header_t **", headers_ptr)
1229 local headers_count = tonumber(ffi.C.dnsdist_ffi_dnsquestion_get_http_headers(dq, headers_ptr_param))
1230 if headers_count > 0 then
1231 for idx = 0, headers_count-1 do
1232 if ffi.string(headers_ptr[0][idx].name) == 'content-type' and ffi.string(headers_ptr[0][idx].value) == 'application/dns-message' then
1239 local response = 'It works!'
1240 ffi.C.dnsdist_ffi_dnsquestion_set_http_response(dq, 200, response, #response, 'text/plain')
1241 return DNSAction.HeaderModify
1244 return DNSAction.None
1246 addAction("http-lua-ffi.doh.tests.powerdns.com.", LuaFFIAction(dohHandler))
1248 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary', '_serverName', '_dohServerPort']
1250 def testHTTPLuaFFIResponse(self
):
1252 DOH: Lua FFI HTTP Response
1254 name
= 'http-lua-ffi.doh.tests.powerdns.com.'
1255 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
1258 (_
, receivedResponse
) = self
.sendDOHPostQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True)
1259 self
.assertTrue(receivedResponse
)
1260 self
.assertEqual(receivedResponse
, b
'It works!')
1261 self
.assertEqual(self
._rcode
, 200)
1262 self
.assertTrue('content-type: text/plain' in self
._response
_headers
.decode())
1264 class TestDOHFFINGHTTP2(DOHFFI
, DNSDistDOHTest
):
1265 _dohLibrary
= 'nghttp2'
1267 class TestDOHFFIH2O(DOHFFI
, DNSDistDOHTest
):
1270 class DOHForwardedFor(object):
1271 _serverKey
= 'server.key'
1272 _serverCert
= 'server.chain'
1273 _serverName
= 'tls.tests.dnsdist.org'
1275 _dohServerPort
= pickAvailablePort()
1276 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1277 _config_template
= """
1278 newServer{address="127.0.0.1:%s"}
1280 setACL('192.0.2.1/32')
1281 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {trustForwardedForHeader=true, library='%s'})
1282 -- Set a maximum number of TCP connections per client, to exercise
1283 -- that code along with X-Forwarded-For support
1284 setMaxTCPConnectionsPerClient(2)
1286 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
1288 def testDOHAllowedForwarded(self
):
1290 DOH with X-Forwarded-For allowed
1292 name
= 'allowed.forwarded.doh.tests.powerdns.com.'
1293 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
1295 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1296 expectedQuery
.id = 0
1297 response
= dns
.message
.make_response(query
)
1298 rrset
= dns
.rrset
.from_text(name
,
1303 response
.answer
.append(rrset
)
1305 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
, customHeaders
=['x-forwarded-for: 127.0.0.1:42, 127.0.0.1, 192.0.2.1:4200'])
1306 self
.assertTrue(receivedQuery
)
1307 self
.assertTrue(receivedResponse
)
1308 receivedQuery
.id = expectedQuery
.id
1309 self
.assertEqual(expectedQuery
, receivedQuery
)
1310 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
1311 self
.assertEqual(response
, receivedResponse
)
1313 def testDOHDeniedForwarded(self
):
1315 DOH with X-Forwarded-For not allowed
1317 name
= 'not-allowed.forwarded.doh.tests.powerdns.com.'
1318 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
1320 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1321 expectedQuery
.id = 0
1322 response
= dns
.message
.make_response(query
)
1323 rrset
= dns
.rrset
.from_text(name
,
1328 response
.answer
.append(rrset
)
1330 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True, customHeaders
=['x-forwarded-for: 127.0.0.1:42, 127.0.0.1'])
1332 self
.assertEqual(self
._rcode
, 403)
1333 self
.assertEqual(receivedResponse
, b
'DoH query not allowed because of ACL')
1335 class TestDOHForwardedForNGHTTP2(DOHForwardedFor
, DNSDistDOHTest
):
1336 _dohLibrary
= 'nghttp2'
1338 class TestDOHForwardedForH2O(DOHForwardedFor
, DNSDistDOHTest
):
1341 class DOHForwardedForNoTrusted(object):
1343 _serverKey
= 'server.key'
1344 _serverCert
= 'server.chain'
1345 _serverName
= 'tls.tests.dnsdist.org'
1347 _dohServerPort
= pickAvailablePort()
1348 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1349 _config_template
= """
1350 newServer{address="127.0.0.1:%s"}
1352 setACL('192.0.2.1/32')
1353 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {earlyACLDrop=true, library='%s'})
1355 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
1357 def testDOHForwardedUntrusted(self
):
1359 DOH with X-Forwarded-For not trusted
1361 name
= 'not-trusted.forwarded.doh.tests.powerdns.com.'
1362 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
1364 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1365 expectedQuery
.id = 0
1366 response
= dns
.message
.make_response(query
)
1367 rrset
= dns
.rrset
.from_text(name
,
1372 response
.answer
.append(rrset
)
1376 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True, customHeaders
=['x-forwarded-for: 192.0.2.1:4200'])
1377 self
.assertEqual(self
._rcode
, 403)
1378 self
.assertEqual(receivedResponse
, b
'DoH query not allowed because of ACL')
1379 except pycurl
.error
as e
:
1382 self
.assertTrue(dropped
)
1384 class TestDOHForwardedForNoTrustedNGHTTP2(DOHForwardedForNoTrusted
, DNSDistDOHTest
):
1385 _dohLibrary
= 'nghttp2'
1387 class TestDOHForwardedForNoTrustedH2O(DOHForwardedForNoTrusted
, DNSDistDOHTest
):
1390 class DOHFrontendLimits(object):
1392 # this test suite uses a different responder port
1393 # because it uses a different health check configuration
1394 _testServerPort
= pickAvailablePort()
1395 _answerUnexpected
= True
1397 _serverKey
= 'server.key'
1398 _serverCert
= 'server.chain'
1399 _serverName
= 'tls.tests.dnsdist.org'
1401 _dohServerPort
= pickAvailablePort()
1402 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1403 _skipListeningOnCL
= True
1404 _maxTCPConnsPerDOHFrontend
= 5
1405 _config_template
= """
1406 newServer{address="127.0.0.1:%s"}
1407 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, { maxConcurrentTCPConnections=%d, library='%s' })
1409 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_maxTCPConnsPerDOHFrontend', '_dohLibrary']
1410 _alternateListeningAddr
= '127.0.0.1'
1411 _alternateListeningPort
= _dohServerPort
1413 def testTCPConnsPerDOHFrontend(self
):
1415 DoH Frontend Limits: Maximum number of conns per DoH frontend
1417 name
= 'maxconnsperfrontend.doh.tests.powerdns.com.'
1418 query
= b
"GET / HTTP/1.0\r\n\r\n"
1421 for idx
in range(self
._maxTCPConnsPerDOHFrontend
+ 1):
1424 if self
._dohLibrary
!= 'h2o':
1426 conns
.append(self
.openTLSConnection(self
._dohServerPort
, self
._serverName
, self
._caCert
, alpn
=alpn
))
1439 response
= conn
.recv(65535)
1451 # wait a bit to be sure that dnsdist closed the connections
1452 # and decremented the counters on its side, otherwise subsequent
1453 # connections will be dropped
1456 self
.assertEqual(count
, self
._maxTCPConnsPerDOHFrontend
)
1457 self
.assertEqual(failed
, 1)
1459 class TestDOHFrontendLimitsNGHTTP2(DOHFrontendLimits
, DNSDistDOHTest
):
1460 _dohLibrary
= 'nghttp2'
1462 class TestDOHFrontendLimitsH2O(DOHFrontendLimits
, DNSDistDOHTest
):
1465 class Protocols(object):
1466 _serverKey
= 'server.key'
1467 _serverCert
= 'server.chain'
1468 _serverName
= 'tls.tests.dnsdist.org'
1470 _dohServerPort
= pickAvailablePort()
1471 _customResponseHeader1
= 'access-control-allow-origin: *'
1472 _customResponseHeader2
= 'user-agent: derp'
1473 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1474 _config_template
= """
1475 function checkDOH(dq)
1476 if dq:getProtocol() ~= "DNS over HTTPS" then
1477 return DNSAction.Spoof, '1.2.3.4'
1479 return DNSAction.None
1482 addAction("protocols.doh.tests.powerdns.com.", LuaAction(checkDOH))
1483 newServer{address="127.0.0.1:%s"}
1484 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {library='%s'})
1486 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
1488 def testProtocolDOH(self
):
1490 DoH: Test DNSQuestion.Protocol
1492 name
= 'protocols.doh.tests.powerdns.com.'
1493 query
= dns
.message
.make_query(name
, 'A', 'IN')
1494 response
= dns
.message
.make_response(query
)
1495 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1496 expectedQuery
.id = 0
1498 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
1499 self
.assertTrue(receivedQuery
)
1500 self
.assertTrue(receivedResponse
)
1501 receivedQuery
.id = expectedQuery
.id
1502 self
.assertEqual(expectedQuery
, receivedQuery
)
1503 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
1504 self
.assertEqual(response
, receivedResponse
)
1506 class TestProtocolsNGHTTP2(Protocols
, DNSDistDOHTest
):
1507 _dohLibrary
= 'nghttp2'
1509 class TestProtocolsH2O(Protocols
, DNSDistDOHTest
):
1512 class DOHWithPKCS12Cert(object):
1513 _serverCert
= 'server.p12'
1514 _pkcs12Password
= 'passw0rd'
1515 _serverName
= 'tls.tests.dnsdist.org'
1517 _dohServerPort
= pickAvailablePort()
1518 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1519 _config_template
= """
1520 newServer{address="127.0.0.1:%s"}
1521 cert=newTLSCertificate("%s", {password="%s"})
1522 addDOHLocal("127.0.0.1:%s", cert, "", { "/" }, {library='%s'})
1524 _config_params
= ['_testServerPort', '_serverCert', '_pkcs12Password', '_dohServerPort', '_dohLibrary']
1526 def testPKCS12DOH(self
):
1528 DoH: Test Simple DOH Query with a password protected PKCS12 file configured
1530 name
= 'simple.doh.tests.powerdns.com.'
1531 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
1533 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1534 expectedQuery
.id = 0
1535 response
= dns
.message
.make_response(query
)
1536 rrset
= dns
.rrset
.from_text(name
,
1541 response
.answer
.append(rrset
)
1543 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
1544 self
.assertTrue(receivedQuery
)
1545 self
.assertTrue(receivedResponse
)
1546 receivedQuery
.id = expectedQuery
.id
1547 self
.assertEqual(expectedQuery
, receivedQuery
)
1549 class TestDOHWithPKCS12CertNGHTTP2(DOHWithPKCS12Cert
, DNSDistDOHTest
):
1550 _dohLibrary
= 'nghttp2'
1552 class TestDOHWithPKCS12CertH2O(DOHWithPKCS12Cert
, DNSDistDOHTest
):
1555 class DOHForwardedToTCPOnly(object):
1556 _serverKey
= 'server.key'
1557 _serverCert
= 'server.chain'
1558 _serverName
= 'tls.tests.dnsdist.org'
1560 _dohServerPort
= pickAvailablePort()
1561 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1562 _config_template
= """
1563 newServer{address="127.0.0.1:%s", tcpOnly=true}
1564 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {library='%s'})
1566 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
1568 def testDOHTCPOnly(self
):
1570 DoH: Test a DoH query forwarded to a TCP-only server
1572 name
= 'tcponly.doh.tests.powerdns.com.'
1573 query
= dns
.message
.make_query(name
, 'A', 'IN')
1575 response
= dns
.message
.make_response(query
)
1576 rrset
= dns
.rrset
.from_text(name
,
1581 response
.answer
.append(rrset
)
1583 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
1584 self
.assertTrue(receivedQuery
)
1585 self
.assertTrue(receivedResponse
)
1586 receivedQuery
.id = query
.id
1587 self
.assertEqual(receivedQuery
, query
)
1588 self
.assertEqual(receivedResponse
, response
)
1590 class TestDOHForwardedToTCPOnlyNGHTTP2(DOHForwardedToTCPOnly
, DNSDistDOHTest
):
1591 _dohLibrary
= 'nghttp2'
1593 class TestDOHForwardedToTCPOnlyH2O(DOHForwardedToTCPOnly
, DNSDistDOHTest
):
1596 class DOHLimits(object):
1597 _serverName
= 'tls.tests.dnsdist.org'
1599 _dohServerPort
= pickAvailablePort()
1600 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1601 _serverKey
= 'server.key'
1602 _serverCert
= 'server.chain'
1603 _maxTCPConnsPerClient
= 3
1604 _config_template
= """
1605 newServer{address="127.0.0.1:%d"}
1606 addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, {library='%s'})
1607 setMaxTCPConnectionsPerClient(%d)
1609 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary', '_maxTCPConnsPerClient']
1611 def testConnsPerClient(self
):
1613 DoH Limits: Maximum number of conns per client
1615 name
= 'maxconnsperclient.doh.tests.powerdns.com.'
1616 query
= dns
.message
.make_query(name
, 'A', 'IN')
1617 url
= self
.getDOHGetURL(self
._dohBaseURL
, query
)
1620 for idx
in range(self
._maxTCPConnsPerClient
+ 1):
1621 conn
= self
.openDOHConnection(self
._dohServerPort
, self
._caCert
, timeout
=2.0)
1622 conn
.setopt(pycurl
.URL
, url
)
1623 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
1624 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
1625 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
1626 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
1633 data
= conn
.perform_rb()
1634 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
1642 # wait a bit to be sure that dnsdist closed the connections
1643 # and decremented the counters on its side, otherwise subsequent
1644 # connections will be dropped
1647 self
.assertEqual(count
, self
._maxTCPConnsPerClient
)
1648 self
.assertEqual(failed
, 1)
1650 class TestDOHLimitsNGHTTP2(DOHLimits
, DNSDistDOHTest
):
1651 _dohLibrary
= 'nghttp2'
1653 class TestDOHLimitsH2O(DOHLimits
, DNSDistDOHTest
):