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 testDOHShortPath(self
):
258 DOH: Short path in GET query
260 name
= 'short-path-get.doh.tests.powerdns.com.'
261 url
= self
._dohBaseURL
+ '/AA'
262 conn
= self
.openDOHConnection(self
._dohServerPort
, self
._caCert
, timeout
=2.0)
263 conn
.setopt(pycurl
.URL
, url
)
264 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
265 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
266 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
267 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
268 data
= conn
.perform_rb()
269 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
270 self
.assertEqual(rcode
, 404)
272 def testDOHQueryNoParameter(self
):
274 DOH: No parameter GET query
276 name
= 'no-parameter-get.doh.tests.powerdns.com.'
277 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
278 wire
= query
.to_wire()
279 b64
= base64
.urlsafe_b64encode(wire
).decode('UTF8').rstrip('=')
280 url
= self
._dohBaseURL
+ '?not-dns=' + b64
281 conn
= self
.openDOHConnection(self
._dohServerPort
, self
._caCert
, timeout
=2.0)
282 conn
.setopt(pycurl
.URL
, url
)
283 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
284 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
285 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
286 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
287 data
= conn
.perform_rb()
288 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
289 self
.assertEqual(rcode
, 400)
291 def testDOHQueryInvalidBase64(self
):
293 DOH: Invalid Base64 GET query
295 name
= 'invalid-b64-get.doh.tests.powerdns.com.'
296 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
297 wire
= query
.to_wire()
298 url
= self
._dohBaseURL
+ '?dns=' + '_-~~~~-_'
299 conn
= self
.openDOHConnection(self
._dohServerPort
, self
._caCert
, timeout
=2.0)
300 conn
.setopt(pycurl
.URL
, url
)
301 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
302 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
303 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
304 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
305 data
= conn
.perform_rb()
306 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
307 self
.assertEqual(rcode
, 400)
309 def testDOHInvalidDNSHeaders(self
):
311 DOH: Invalid DNS headers
313 name
= 'invalid-dns-headers.doh.tests.powerdns.com.'
314 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
315 query
.flags |
= dns
.flags
.QR
316 wire
= query
.to_wire()
317 b64
= base64
.urlsafe_b64encode(wire
).decode('UTF8').rstrip('=')
318 url
= self
._dohBaseURL
+ '?dns=' + b64
319 conn
= self
.openDOHConnection(self
._dohServerPort
, self
._caCert
, timeout
=2.0)
320 conn
.setopt(pycurl
.URL
, url
)
321 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
322 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
323 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
324 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
325 data
= conn
.perform_rb()
326 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
327 self
.assertEqual(rcode
, 400)
329 def testDOHQueryInvalidMethod(self
):
333 if self
._dohLibrary
== 'h2o':
334 raise unittest
.SkipTest('h2o does not check the HTTP method')
335 name
= 'invalid-method.doh.tests.powerdns.com.'
336 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
337 wire
= query
.to_wire()
338 b64
= base64
.urlsafe_b64encode(wire
).decode('UTF8').rstrip('=')
339 url
= self
._dohBaseURL
+ '?dns=' + b64
340 conn
= self
.openDOHConnection(self
._dohServerPort
, self
._caCert
, timeout
=2)
341 conn
.setopt(pycurl
.URL
, url
)
342 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
343 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
344 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
345 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
346 conn
.setopt(pycurl
.CUSTOMREQUEST
, 'PATCH')
347 data
= conn
.perform_rb()
348 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
349 self
.assertEqual(rcode
, 400)
351 def testDOHQueryInvalidALPN(self
):
355 alpn
= ['bogus-alpn']
356 conn
= self
.openTLSConnection(self
._dohServerPort
, self
._serverName
, self
._caCert
, alpn
=alpn
)
359 response
= conn
.recv(65535)
360 self
.assertFalse(response
)
364 def testDOHInvalid(self
):
366 DOH: Invalid DNS query
368 name
= 'invalid.doh.tests.powerdns.com.'
369 invalidQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
371 # first an invalid query
372 invalidQuery
= invalidQuery
.to_wire()
373 invalidQuery
= invalidQuery
[:-5]
374 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, caFile
=self
._caCert
, query
=invalidQuery
, response
=None, useQueue
=False, rawQuery
=True)
375 self
.assertEqual(receivedResponse
, None)
377 # and now a valid one
378 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
380 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
382 response
= dns
.message
.make_response(query
)
383 rrset
= dns
.rrset
.from_text(name
,
388 response
.answer
.append(rrset
)
389 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
390 self
.assertTrue(receivedQuery
)
391 self
.assertTrue(receivedResponse
)
392 receivedQuery
.id = expectedQuery
.id
393 self
.assertEqual(expectedQuery
, receivedQuery
)
394 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
395 self
.assertEqual(response
, receivedResponse
)
397 def testDOHInvalidHeaderName(self
):
399 DOH: Invalid HTTP header name query
401 name
= 'invalid-header-name.doh.tests.powerdns.com.'
402 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
404 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
406 response
= dns
.message
.make_response(query
)
407 rrset
= dns
.rrset
.from_text(name
,
412 response
.answer
.append(rrset
)
413 # this header is invalid, see rfc9113 section 8.2.1. Field Validity
414 customHeaders
= ['{}: test']
416 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
, customHeaders
=customHeaders
)
417 self
.assertFalse(receivedQuery
)
418 self
.assertFalse(receivedResponse
)
422 def testDOHNoBackend(self
):
426 if self
._dohLibrary
== 'h2o':
427 raise unittest
.SkipTest('h2o does not check the HTTP method')
428 name
= 'no-backend.doh.tests.powerdns.com.'
429 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
430 wire
= query
.to_wire()
431 b64
= base64
.urlsafe_b64encode(wire
).decode('UTF8').rstrip('=')
432 url
= self
._dohBaseURL
+ '?dns=' + b64
433 conn
= self
.openDOHConnection(self
._dohServerPort
, self
._caCert
, timeout
=2)
434 conn
.setopt(pycurl
.URL
, url
)
435 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
436 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
437 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
438 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
439 data
= conn
.perform_rb()
440 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
441 self
.assertEqual(rcode
, 403)
443 def testDOHEmptyPOST(self
):
445 DOH: Empty POST query
447 name
= 'empty-post.doh.tests.powerdns.com.'
449 (_
, receivedResponse
) = self
.sendDOHPostQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
="", rawQuery
=True, response
=None, caFile
=self
._caCert
)
450 self
.assertEqual(receivedResponse
, None)
452 # and now a valid one
453 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
455 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
457 response
= dns
.message
.make_response(query
)
458 rrset
= dns
.rrset
.from_text(name
,
463 response
.answer
.append(rrset
)
464 (receivedQuery
, receivedResponse
) = self
.sendDOHPostQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
465 self
.assertTrue(receivedQuery
)
466 self
.assertTrue(receivedResponse
)
467 receivedQuery
.id = expectedQuery
.id
468 self
.assertEqual(expectedQuery
, receivedQuery
)
469 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
470 self
.assertEqual(response
, receivedResponse
)
472 def testHeaderRule(self
):
476 name
= 'header-rule.doh.tests.powerdns.com.'
477 query
= dns
.message
.make_query(name
, 'A', 'IN')
479 query
.flags
&= ~dns
.flags
.RD
480 expectedResponse
= dns
.message
.make_response(query
)
481 rrset
= dns
.rrset
.from_text(name
,
486 expectedResponse
.answer
.append(rrset
)
488 # this header should match
489 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False, customHeaders
=['x-powerdnS: aaaaa'])
490 self
.assertEqual(receivedResponse
, expectedResponse
)
492 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
493 expectedQuery
.flags
&= ~dns
.flags
.RD
495 response
= dns
.message
.make_response(query
)
496 rrset
= dns
.rrset
.from_text(name
,
501 response
.answer
.append(rrset
)
503 # this content of the header should NOT match
504 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
, customHeaders
=['x-powerdnS: bbbbb'])
505 self
.assertTrue(receivedQuery
)
506 self
.assertTrue(receivedResponse
)
507 receivedQuery
.id = expectedQuery
.id
508 self
.assertEqual(expectedQuery
, receivedQuery
)
509 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
510 self
.assertEqual(response
, receivedResponse
)
512 def testHTTPPath(self
):
516 name
= 'http-path.doh.tests.powerdns.com.'
517 query
= dns
.message
.make_query(name
, 'A', 'IN')
519 query
.flags
&= ~dns
.flags
.RD
520 expectedResponse
= dns
.message
.make_response(query
)
521 rrset
= dns
.rrset
.from_text(name
,
526 expectedResponse
.answer
.append(rrset
)
528 # this path should match
529 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ 'PowerDNS', caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False)
530 self
.assertEqual(receivedResponse
, expectedResponse
)
532 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
534 expectedQuery
.flags
&= ~dns
.flags
.RD
535 response
= dns
.message
.make_response(query
)
536 rrset
= dns
.rrset
.from_text(name
,
541 response
.answer
.append(rrset
)
543 # this path should NOT match
544 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ "PowerDNS2", query
, response
=response
, caFile
=self
._caCert
)
545 self
.assertTrue(receivedQuery
)
546 self
.assertTrue(receivedResponse
)
547 receivedQuery
.id = expectedQuery
.id
548 self
.assertEqual(expectedQuery
, receivedQuery
)
549 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
550 self
.assertEqual(response
, receivedResponse
)
552 # this path is not in the URLs map and should lead to a 404
553 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ "PowerDNS/something", query
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True)
554 self
.assertTrue(receivedResponse
)
555 self
.assertEqual(receivedResponse
, b
'there is no endpoint configured for this path')
556 self
.assertEqual(self
._rcode
, 404)
558 def testHTTPPathRegex(self
):
562 name
= 'http-path-regex.doh.tests.powerdns.com.'
563 query
= dns
.message
.make_query(name
, 'A', 'IN')
565 query
.flags
&= ~dns
.flags
.RD
566 expectedResponse
= dns
.message
.make_response(query
)
567 rrset
= dns
.rrset
.from_text(name
,
572 expectedResponse
.answer
.append(rrset
)
574 # this path should match
575 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ 'PowerDNS-999', caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False)
576 self
.assertEqual(receivedResponse
, expectedResponse
)
578 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
580 expectedQuery
.flags
&= ~dns
.flags
.RD
581 response
= dns
.message
.make_response(query
)
582 rrset
= dns
.rrset
.from_text(name
,
587 response
.answer
.append(rrset
)
589 # this path should NOT match
590 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ "PowerDNS2", query
, response
=response
, caFile
=self
._caCert
)
591 self
.assertTrue(receivedQuery
)
592 self
.assertTrue(receivedResponse
)
593 receivedQuery
.id = expectedQuery
.id
594 self
.assertEqual(expectedQuery
, receivedQuery
)
595 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
596 self
.assertEqual(response
, receivedResponse
)
598 def testHTTPStatusAction200(self
):
600 DOH: HTTPStatusAction 200 OK
602 name
= 'http-status-action.doh.tests.powerdns.com.'
603 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
606 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True)
607 self
.assertTrue(receivedResponse
)
608 self
.assertEqual(receivedResponse
, b
'Plaintext answer')
609 self
.assertEqual(self
._rcode
, 200)
610 self
.assertTrue('content-type: text/plain' in self
._response
_headers
.decode())
612 def testHTTPStatusAction307(self
):
614 DOH: HTTPStatusAction 307
616 name
= 'http-status-action-redirect.doh.tests.powerdns.com.'
617 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
620 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True)
621 self
.assertTrue(receivedResponse
)
622 self
.assertEqual(self
._rcode
, 307)
623 self
.assertTrue('location: https://doh.powerdns.org' in self
._response
_headers
.decode())
625 def testHTTPLuaResponse(self
):
627 DOH: Lua HTTP Response
629 name
= 'http-lua.doh.tests.powerdns.com.'
630 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
633 (_
, receivedResponse
) = self
.sendDOHPostQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True)
634 self
.assertTrue(receivedResponse
)
635 self
.assertEqual(receivedResponse
, b
'It works!')
636 self
.assertEqual(self
._rcode
, 200)
637 self
.assertTrue('content-type: text/plain' in self
._response
_headers
.decode())
639 def testHTTPEarlyResponse(self
):
641 DOH: HTTP Early Response
643 response_headers
= BytesIO()
644 url
= self
._dohBaseURL
+ 'coffee'
645 conn
= self
.openDOHConnection(self
._dohServerPort
, caFile
=self
._caCert
, timeout
=2.0)
646 conn
.setopt(pycurl
.URL
, url
)
647 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
648 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
649 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
650 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
651 conn
.setopt(pycurl
.HEADERFUNCTION
, response_headers
.write
)
652 data
= conn
.perform_rb()
653 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
654 headers
= response_headers
.getvalue().decode()
656 self
.assertEqual(rcode
, 418)
657 self
.assertEqual(data
, b
'C0FFEE')
658 self
.assertIn('foo: bar', headers
)
659 self
.assertNotIn(self
._customResponseHeader
2, headers
)
661 response_headers
= BytesIO()
662 conn
= self
.openDOHConnection(self
._dohServerPort
, caFile
=self
._caCert
, timeout
=2.0)
663 conn
.setopt(pycurl
.URL
, url
)
664 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
665 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
666 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
667 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
668 conn
.setopt(pycurl
.HEADERFUNCTION
, response_headers
.write
)
669 conn
.setopt(pycurl
.POST
, True)
671 conn
.setopt(pycurl
.POSTFIELDS
, data
)
673 data
= conn
.perform_rb()
674 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
675 headers
= response_headers
.getvalue().decode()
676 self
.assertEqual(rcode
, 418)
677 self
.assertEqual(data
, b
'C0FFEE')
678 self
.assertIn('foo: bar', headers
)
679 self
.assertNotIn(self
._customResponseHeader
2, headers
)
681 class TestDoHNGHTTP2(DOHTests
, DNSDistDOHTest
):
682 _dohLibrary
= 'nghttp2'
684 class TestDoHH2O(DOHTests
, DNSDistDOHTest
):
687 class DOHSubPathsTests(object):
688 _serverKey
= 'server.key'
689 _serverCert
= 'server.chain'
690 _serverName
= 'tls.tests.dnsdist.org'
692 _dohServerPort
= pickAvailablePort()
693 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
694 _config_template
= """
695 newServer{address="127.0.0.1:%s"}
697 addAction(AllRule(), SpoofAction("3.4.5.6"))
699 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/PowerDNS" }, {exactPathMatching=false, library='%s'})
701 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
703 def testSubPath(self
):
707 name
= 'sub-path.doh.tests.powerdns.com.'
708 query
= dns
.message
.make_query(name
, 'A', 'IN')
710 query
.flags
&= ~dns
.flags
.RD
711 expectedResponse
= dns
.message
.make_response(query
)
712 rrset
= dns
.rrset
.from_text(name
,
717 expectedResponse
.answer
.append(rrset
)
719 # this path should match
720 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ 'PowerDNS', caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False)
721 self
.assertEqual(receivedResponse
, expectedResponse
)
723 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
725 expectedQuery
.flags
&= ~dns
.flags
.RD
726 response
= dns
.message
.make_response(query
)
727 rrset
= dns
.rrset
.from_text(name
,
732 response
.answer
.append(rrset
)
734 # this path is not in the URLs map and should lead to a 404
735 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ "NotPowerDNS", query
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True)
736 self
.assertTrue(receivedResponse
)
737 self
.assertIn(receivedResponse
, [b
'there is no endpoint configured for this path', b
'not found'])
738 self
.assertEqual(self
._rcode
, 404)
740 # this path is below one in the URLs map and exactPathMatching is false, so we should be good
741 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ 'PowerDNS/something', caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False)
742 self
.assertEqual(receivedResponse
, expectedResponse
)
744 class TestDoHSubPathsNGHTTP2(DOHSubPathsTests
, DNSDistDOHTest
):
745 _dohLibrary
= 'nghttp2'
747 class TestDoHSubPathsH2O(DOHSubPathsTests
, DNSDistDOHTest
):
750 class DOHAddingECSTests(object):
752 _serverKey
= 'server.key'
753 _serverCert
= 'server.chain'
754 _serverName
= 'tls.tests.dnsdist.org'
756 _dohServerPort
= pickAvailablePort()
757 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
758 _config_template
= """
759 newServer{address="127.0.0.1:%s", useClientSubnet=true}
760 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {library='%s'})
763 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
765 def testDOHSimple(self
):
767 DOH with ECS: Simple query
769 name
= 'simple.doh-ecs.tests.powerdns.com.'
770 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
772 rewrittenEcso
= clientsubnetoption
.ClientSubnetOption('127.0.0.0', 24)
773 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096, options
=[rewrittenEcso
])
774 response
= dns
.message
.make_response(query
)
775 rrset
= dns
.rrset
.from_text(name
,
780 response
.answer
.append(rrset
)
782 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
783 self
.assertTrue(receivedQuery
)
784 self
.assertTrue(receivedResponse
)
785 expectedQuery
.id = receivedQuery
.id
786 self
.assertEqual(expectedQuery
, receivedQuery
)
787 self
.checkQueryEDNSWithECS(expectedQuery
, receivedQuery
)
788 self
.assertEqual(response
, receivedResponse
)
789 self
.checkResponseNoEDNS(response
, receivedResponse
)
791 def testDOHExistingEDNS(self
):
793 DOH with ECS: Existing EDNS
795 name
= 'existing-edns.doh-ecs.tests.powerdns.com.'
796 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=8192)
798 rewrittenEcso
= clientsubnetoption
.ClientSubnetOption('127.0.0.0', 24)
799 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=8192, options
=[rewrittenEcso
])
800 response
= dns
.message
.make_response(query
)
801 rrset
= dns
.rrset
.from_text(name
,
806 response
.answer
.append(rrset
)
808 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
809 self
.assertTrue(receivedQuery
)
810 self
.assertTrue(receivedResponse
)
811 receivedQuery
.id = expectedQuery
.id
812 self
.assertEqual(expectedQuery
, receivedQuery
)
813 self
.assertEqual(response
, receivedResponse
)
814 self
.checkQueryEDNSWithECS(expectedQuery
, receivedQuery
)
815 self
.checkResponseEDNSWithoutECS(response
, receivedResponse
)
817 def testDOHExistingECS(self
):
819 DOH with ECS: Existing EDNS Client Subnet
821 name
= 'existing-ecs.doh-ecs.tests.powerdns.com.'
822 ecso
= clientsubnetoption
.ClientSubnetOption('1.2.3.4')
823 rewrittenEcso
= clientsubnetoption
.ClientSubnetOption('127.0.0.0', 24)
824 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=512, options
=[ecso
], want_dnssec
=True)
826 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=512, options
=[rewrittenEcso
])
827 response
= dns
.message
.make_response(query
)
828 response
.use_edns(edns
=True, payload
=4096, options
=[rewrittenEcso
])
829 rrset
= dns
.rrset
.from_text(name
,
834 response
.answer
.append(rrset
)
836 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
837 self
.assertTrue(receivedQuery
)
838 self
.assertTrue(receivedResponse
)
839 receivedQuery
.id = expectedQuery
.id
840 self
.assertEqual(expectedQuery
, receivedQuery
)
841 self
.assertEqual(response
, receivedResponse
)
842 self
.checkQueryEDNSWithECS(expectedQuery
, receivedQuery
)
843 self
.checkResponseEDNSWithECS(response
, receivedResponse
)
845 class TestDoHAddingECSNGHTTP2(DOHAddingECSTests
, DNSDistDOHTest
):
846 _dohLibrary
= 'nghttp2'
848 class TestDoHAddingECSH2O(DOHAddingECSTests
, DNSDistDOHTest
):
851 class DOHOverHTTP(object):
852 _dohServerPort
= pickAvailablePort()
853 _serverName
= 'tls.tests.dnsdist.org'
854 _dohBaseURL
= ("http://%s:%d/dns-query" % (_serverName
, _dohServerPort
))
855 _config_template
= """
856 newServer{address="127.0.0.1:%s"}
857 addDOHLocal("127.0.0.1:%s", nil, nil, '/dns-query', {library='%s'})
859 _config_params
= ['_testServerPort', '_dohServerPort', '_dohLibrary']
861 def testDOHSimple(self
):
863 DOH over HTTP: Simple query
865 name
= 'simple.doh-over-http.tests.powerdns.com.'
866 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
868 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
869 response
= dns
.message
.make_response(query
)
870 rrset
= dns
.rrset
.from_text(name
,
875 response
.answer
.append(rrset
)
877 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, useHTTPS
=False)
878 self
.assertTrue(receivedQuery
)
879 self
.assertTrue(receivedResponse
)
880 expectedQuery
.id = receivedQuery
.id
881 self
.assertEqual(expectedQuery
, receivedQuery
)
882 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
883 self
.assertEqual(response
, receivedResponse
)
884 self
.checkResponseNoEDNS(response
, receivedResponse
)
886 def testDOHSimplePOST(self
):
888 DOH over HTTP: Simple POST query
890 name
= 'simple-post.doh-over-http.tests.powerdns.com.'
891 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
893 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
895 response
= dns
.message
.make_response(query
)
896 rrset
= dns
.rrset
.from_text(name
,
901 response
.answer
.append(rrset
)
903 (receivedQuery
, receivedResponse
) = self
.sendDOHPostQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, useHTTPS
=False)
904 self
.assertTrue(receivedQuery
)
905 self
.assertTrue(receivedResponse
)
906 receivedQuery
.id = expectedQuery
.id
907 self
.assertEqual(expectedQuery
, receivedQuery
)
908 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
909 self
.assertEqual(response
, receivedResponse
)
910 self
.checkResponseNoEDNS(response
, receivedResponse
)
912 class TestDOHOverHTTPNGHTTP2(DOHOverHTTP
, DNSDistDOHTest
):
913 _dohLibrary
= 'nghttp2'
914 _checkConfigExpectedOutput
= b
"""No certificate provided for DoH endpoint 127.0.0.1:%d, running in DNS over HTTP mode instead of DNS over HTTPS
915 Configuration 'configs/dnsdist_TestDOHOverHTTPNGHTTP2.conf' OK!
916 """ % (DOHOverHTTP
._dohServerPort
)
918 class TestDOHOverHTTPH2O(DOHOverHTTP
, DNSDistDOHTest
):
920 _checkConfigExpectedOutput
= b
"""No certificate provided for DoH endpoint 127.0.0.1:%d, running in DNS over HTTP mode instead of DNS over HTTPS
921 Configuration 'configs/dnsdist_TestDOHOverHTTPH2O.conf' OK!
922 """ % (DOHOverHTTP
._dohServerPort
)
924 class DOHWithCache(object):
926 _serverKey
= 'server.key'
927 _serverCert
= 'server.chain'
928 _serverName
= 'tls.tests.dnsdist.org'
930 _dohServerPort
= pickAvailablePort()
931 _dohBaseURL
= ("https://%s:%d/dns-query" % (_serverName
, _dohServerPort
))
932 _config_template
= """
933 newServer{address="127.0.0.1:%s"}
935 addDOHLocal("127.0.0.1:%s", "%s", "%s", '/dns-query', {library='%s'})
937 pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
938 getPool(""):setCache(pc)
940 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
942 def testDOHCacheLargeAnswer(self
):
944 DOH with cache: Check that we can cache (and retrieve) large answers
947 name
= 'large.doh-with-cache.tests.powerdns.com.'
948 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
950 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
952 response
= dns
.message
.make_response(query
)
953 # we prepare a large answer
957 content
= content
+ ', '
958 content
= content
+ (str(i
)*50)
960 content
= content
+ 'A'*40
962 rrset
= dns
.rrset
.from_text(name
,
967 response
.answer
.append(rrset
)
968 self
.assertEqual(len(response
.to_wire()), 4096)
970 # first query to fill the cache
971 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
972 self
.assertTrue(receivedQuery
)
973 self
.assertTrue(receivedResponse
)
974 receivedQuery
.id = expectedQuery
.id
975 self
.assertEqual(expectedQuery
, receivedQuery
)
976 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
977 self
.assertEqual(response
, receivedResponse
)
978 self
.checkHasHeader('cache-control', 'max-age=3600')
980 for _
in range(numberOfQueries
):
981 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False)
982 self
.assertEqual(receivedResponse
, response
)
983 self
.checkHasHeader('cache-control', 'max-age=' + str(receivedResponse
.answer
[0].ttl
))
987 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False)
988 self
.assertEqual(receivedResponse
, response
)
989 self
.checkHasHeader('cache-control', 'max-age=' + str(receivedResponse
.answer
[0].ttl
))
991 def testDOHGetFromUDPCache(self
):
993 DOH with cache: Check that we can retrieve an answer received for a UDP query
995 name
= 'doh-query-insert-udp.doh-with-cache.tests.powerdns.com.'
996 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
997 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
999 response
= dns
.message
.make_response(query
)
1000 rrset
= dns
.rrset
.from_text(name
,
1005 response
.answer
.append(rrset
)
1007 # first query to fill the cache
1008 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
1009 self
.assertTrue(receivedQuery
)
1010 self
.assertTrue(receivedResponse
)
1011 receivedQuery
.id = expectedQuery
.id
1012 self
.assertEqual(expectedQuery
, receivedQuery
)
1013 self
.assertEqual(response
, receivedResponse
)
1015 # now we send the exact same query over DoH, we should get a cache hit
1016 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False)
1017 self
.assertTrue(receivedResponse
)
1018 self
.assertEqual(response
, receivedResponse
)
1020 def testDOHInsertIntoUDPCache(self
):
1022 DOH with cache: Check that we can retrieve an answer received for a DoH query from UDP
1024 name
= 'udp-query-get-doh.doh-with-cache.tests.powerdns.com.'
1025 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1026 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1027 expectedQuery
.id = 0
1028 response
= dns
.message
.make_response(query
)
1029 rrset
= dns
.rrset
.from_text(name
,
1034 response
.answer
.append(rrset
)
1036 # first query to fill the cache
1037 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
1038 self
.assertTrue(receivedQuery
)
1039 self
.assertTrue(receivedResponse
)
1040 receivedQuery
.id = expectedQuery
.id
1041 self
.assertEqual(expectedQuery
, receivedQuery
)
1042 self
.assertEqual(response
, receivedResponse
)
1044 # now we send the exact same query over DoH, we should get a cache hit
1045 (_
, receivedResponse
) = self
.sendUDPQuery(query
, response
=None, useQueue
=False)
1046 self
.assertTrue(receivedResponse
)
1047 self
.assertEqual(response
, receivedResponse
)
1049 def testTruncation(self
):
1051 DOH: Truncation over UDP (with cache)
1053 # the query is first forwarded over UDP, leading to a TC=1 answer from the
1054 # backend, then over TCP
1055 name
= 'truncated-udp.doh-with-cache.tests.powerdns.com.'
1056 query
= dns
.message
.make_query(name
, 'A', 'IN')
1058 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1059 expectedQuery
.id = 42
1060 response
= dns
.message
.make_response(query
)
1061 rrset
= dns
.rrset
.from_text(name
,
1066 response
.answer
.append(rrset
)
1068 # first response is a TC=1
1069 tcResponse
= dns
.message
.make_response(query
)
1070 tcResponse
.flags |
= dns
.flags
.TC
1071 self
._toResponderQueue
.put(tcResponse
, True, 2.0)
1073 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, response
=response
)
1074 # first query, received by the responder over UDP
1075 self
.assertTrue(receivedQuery
)
1076 receivedQuery
.id = expectedQuery
.id
1077 self
.assertEqual(expectedQuery
, receivedQuery
)
1078 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
1080 # check the response
1081 self
.assertTrue(receivedResponse
)
1082 self
.assertEqual(response
, receivedResponse
)
1084 # check the second query, received by the responder over TCP
1085 receivedQuery
= self
._fromResponderQueue
.get(True, 2.0)
1086 self
.assertTrue(receivedQuery
)
1087 receivedQuery
.id = expectedQuery
.id
1088 self
.assertEqual(expectedQuery
, receivedQuery
)
1089 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
1091 # now check the cache for a DoH query
1092 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False)
1093 self
.assertEqual(response
, receivedResponse
)
1095 # The TC=1 answer received over UDP will not be cached, because we currently do not cache answers with no records (no TTL)
1096 # The TCP one should, however
1097 (_
, receivedResponse
) = self
.sendTCPQuery(expectedQuery
, response
=None, useQueue
=False)
1098 self
.assertEqual(response
, receivedResponse
)
1100 def testResponsesReceivedOverUDP(self
):
1102 DOH: Check that responses received over UDP are cached (with cache)
1104 name
= 'cached-udp.doh-with-cache.tests.powerdns.com.'
1105 query
= dns
.message
.make_query(name
, 'A', 'IN')
1107 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1108 expectedQuery
.id = 0
1109 response
= dns
.message
.make_response(query
)
1110 rrset
= dns
.rrset
.from_text(name
,
1115 response
.answer
.append(rrset
)
1117 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, response
=response
)
1118 self
.assertTrue(receivedQuery
)
1119 receivedQuery
.id = expectedQuery
.id
1120 self
.assertEqual(expectedQuery
, receivedQuery
)
1121 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
1122 self
.assertTrue(receivedResponse
)
1123 self
.assertEqual(response
, receivedResponse
)
1125 # now check the cache for a DoH query
1126 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False)
1127 self
.assertEqual(response
, receivedResponse
)
1129 # Check that the answer is usable for UDP queries as well
1130 (_
, receivedResponse
) = self
.sendUDPQuery(expectedQuery
, response
=None, useQueue
=False)
1131 self
.assertEqual(response
, receivedResponse
)
1133 class TestDOHWithCacheNGHTTP2(DOHWithCache
, DNSDistDOHTest
):
1134 _dohLibrary
= 'nghttp2'
1137 class TestDOHWithCacheH2O(DOHWithCache
, DNSDistDOHTest
):
1140 class DOHWithoutCacheControl(object):
1142 _serverKey
= 'server.key'
1143 _serverCert
= 'server.chain'
1144 _serverName
= 'tls.tests.dnsdist.org'
1146 _dohServerPort
= pickAvailablePort()
1147 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1148 _config_template
= """
1149 newServer{address="127.0.0.1:%s"}
1151 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {sendCacheControlHeaders=false, library='%s'})
1153 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
1155 def testDOHSimple(self
):
1157 DOH without cache-control
1159 name
= 'simple.doh.tests.powerdns.com.'
1160 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
1162 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1163 expectedQuery
.id = 0
1164 response
= dns
.message
.make_response(query
)
1165 rrset
= dns
.rrset
.from_text(name
,
1170 response
.answer
.append(rrset
)
1172 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
1173 self
.assertTrue(receivedQuery
)
1174 self
.assertTrue(receivedResponse
)
1175 receivedQuery
.id = expectedQuery
.id
1176 self
.assertEqual(expectedQuery
, receivedQuery
)
1177 self
.checkNoHeader('cache-control')
1178 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
1179 self
.assertEqual(response
, receivedResponse
)
1181 class TestDOHWithoutCacheControlNGHTTP2(DOHWithoutCacheControl
, DNSDistDOHTest
):
1182 _dohLibrary
= 'nghttp2'
1184 class TestDOHWithoutCacheControlH2O(DOHWithoutCacheControl
, DNSDistDOHTest
):
1187 class DOHFFI(object):
1188 _serverKey
= 'server.key'
1189 _serverCert
= 'server.chain'
1190 _serverName
= 'tls.tests.dnsdist.org'
1192 _dohServerPort
= pickAvailablePort()
1193 _customResponseHeader1
= 'access-control-allow-origin: *'
1194 _customResponseHeader2
= 'user-agent: derp'
1195 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1196 _config_template
= """
1197 newServer{address="127.0.0.1:%s"}
1199 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {customResponseHeaders={["access-control-allow-origin"]="*",["user-agent"]="derp",["UPPERCASE"]="VaLuE"}, keepIncomingHeaders=true, library='%s'})
1201 local ffi = require("ffi")
1203 function dohHandler(dq)
1204 local scheme = ffi.string(ffi.C.dnsdist_ffi_dnsquestion_get_http_scheme(dq))
1205 local host = ffi.string(ffi.C.dnsdist_ffi_dnsquestion_get_http_host(dq))
1206 local path = ffi.string(ffi.C.dnsdist_ffi_dnsquestion_get_http_path(dq))
1207 local query_string = ffi.string(ffi.C.dnsdist_ffi_dnsquestion_get_http_query_string(dq))
1208 if scheme == 'https' and host == '%s:%d' and path == '/' and query_string == '' then
1209 local foundct = false
1210 local headers_ptr = ffi.new("const dnsdist_ffi_http_header_t *[1]")
1211 local headers_ptr_param = ffi.cast("const dnsdist_ffi_http_header_t **", headers_ptr)
1213 local headers_count = tonumber(ffi.C.dnsdist_ffi_dnsquestion_get_http_headers(dq, headers_ptr_param))
1214 if headers_count > 0 then
1215 for idx = 0, headers_count-1 do
1216 if ffi.string(headers_ptr[0][idx].name) == 'content-type' and ffi.string(headers_ptr[0][idx].value) == 'application/dns-message' then
1223 local response = 'It works!'
1224 ffi.C.dnsdist_ffi_dnsquestion_set_http_response(dq, 200, response, #response, 'text/plain')
1225 return DNSAction.HeaderModify
1228 return DNSAction.None
1230 addAction("http-lua-ffi.doh.tests.powerdns.com.", LuaFFIAction(dohHandler))
1232 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary', '_serverName', '_dohServerPort']
1234 def testHTTPLuaFFIResponse(self
):
1236 DOH: Lua FFI HTTP Response
1238 name
= 'http-lua-ffi.doh.tests.powerdns.com.'
1239 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
1242 (_
, receivedResponse
) = self
.sendDOHPostQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True)
1243 self
.assertTrue(receivedResponse
)
1244 self
.assertEqual(receivedResponse
, b
'It works!')
1245 self
.assertEqual(self
._rcode
, 200)
1246 self
.assertTrue('content-type: text/plain' in self
._response
_headers
.decode())
1248 class TestDOHFFINGHTTP2(DOHFFI
, DNSDistDOHTest
):
1249 _dohLibrary
= 'nghttp2'
1251 class TestDOHFFIH2O(DOHFFI
, DNSDistDOHTest
):
1254 class DOHForwardedFor(object):
1255 _serverKey
= 'server.key'
1256 _serverCert
= 'server.chain'
1257 _serverName
= 'tls.tests.dnsdist.org'
1259 _dohServerPort
= pickAvailablePort()
1260 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1261 _config_template
= """
1262 newServer{address="127.0.0.1:%s"}
1264 setACL('192.0.2.1/32')
1265 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {trustForwardedForHeader=true, library='%s'})
1266 -- Set a maximum number of TCP connections per client, to exercise
1267 -- that code along with X-Forwarded-For support
1268 setMaxTCPConnectionsPerClient(2)
1270 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
1272 def testDOHAllowedForwarded(self
):
1274 DOH with X-Forwarded-For allowed
1276 name
= 'allowed.forwarded.doh.tests.powerdns.com.'
1277 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
1279 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1280 expectedQuery
.id = 0
1281 response
= dns
.message
.make_response(query
)
1282 rrset
= dns
.rrset
.from_text(name
,
1287 response
.answer
.append(rrset
)
1289 (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'])
1290 self
.assertTrue(receivedQuery
)
1291 self
.assertTrue(receivedResponse
)
1292 receivedQuery
.id = expectedQuery
.id
1293 self
.assertEqual(expectedQuery
, receivedQuery
)
1294 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
1295 self
.assertEqual(response
, receivedResponse
)
1297 def testDOHDeniedForwarded(self
):
1299 DOH with X-Forwarded-For not allowed
1301 name
= 'not-allowed.forwarded.doh.tests.powerdns.com.'
1302 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
1304 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1305 expectedQuery
.id = 0
1306 response
= dns
.message
.make_response(query
)
1307 rrset
= dns
.rrset
.from_text(name
,
1312 response
.answer
.append(rrset
)
1314 (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'])
1316 self
.assertEqual(self
._rcode
, 403)
1317 self
.assertEqual(receivedResponse
, b
'DoH query not allowed because of ACL')
1319 class TestDOHForwardedForNGHTTP2(DOHForwardedFor
, DNSDistDOHTest
):
1320 _dohLibrary
= 'nghttp2'
1322 class TestDOHForwardedForH2O(DOHForwardedFor
, DNSDistDOHTest
):
1325 class DOHForwardedForNoTrusted(object):
1327 _serverKey
= 'server.key'
1328 _serverCert
= 'server.chain'
1329 _serverName
= 'tls.tests.dnsdist.org'
1331 _dohServerPort
= pickAvailablePort()
1332 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1333 _config_template
= """
1334 newServer{address="127.0.0.1:%s"}
1336 setACL('192.0.2.1/32')
1337 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {earlyACLDrop=true, library='%s'})
1339 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
1341 def testDOHForwardedUntrusted(self
):
1343 DOH with X-Forwarded-For not trusted
1345 name
= 'not-trusted.forwarded.doh.tests.powerdns.com.'
1346 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
1348 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1349 expectedQuery
.id = 0
1350 response
= dns
.message
.make_response(query
)
1351 rrset
= dns
.rrset
.from_text(name
,
1356 response
.answer
.append(rrset
)
1360 (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'])
1361 self
.assertEqual(self
._rcode
, 403)
1362 self
.assertEqual(receivedResponse
, b
'DoH query not allowed because of ACL')
1363 except pycurl
.error
as e
:
1366 self
.assertTrue(dropped
)
1368 class TestDOHForwardedForNoTrustedNGHTTP2(DOHForwardedForNoTrusted
, DNSDistDOHTest
):
1369 _dohLibrary
= 'nghttp2'
1371 class TestDOHForwardedForNoTrustedH2O(DOHForwardedForNoTrusted
, DNSDistDOHTest
):
1374 class DOHFrontendLimits(object):
1376 # this test suite uses a different responder port
1377 # because it uses a different health check configuration
1378 _testServerPort
= pickAvailablePort()
1379 _answerUnexpected
= True
1381 _serverKey
= 'server.key'
1382 _serverCert
= 'server.chain'
1383 _serverName
= 'tls.tests.dnsdist.org'
1385 _dohServerPort
= pickAvailablePort()
1386 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1387 _skipListeningOnCL
= True
1388 _maxTCPConnsPerDOHFrontend
= 5
1389 _config_template
= """
1390 newServer{address="127.0.0.1:%s"}
1391 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, { maxConcurrentTCPConnections=%d, library='%s' })
1393 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_maxTCPConnsPerDOHFrontend', '_dohLibrary']
1394 _alternateListeningAddr
= '127.0.0.1'
1395 _alternateListeningPort
= _dohServerPort
1397 def testTCPConnsPerDOHFrontend(self
):
1399 DoH Frontend Limits: Maximum number of conns per DoH frontend
1401 name
= 'maxconnsperfrontend.doh.tests.powerdns.com.'
1402 query
= b
"GET / HTTP/1.0\r\n\r\n"
1405 for idx
in range(self
._maxTCPConnsPerDOHFrontend
+ 1):
1408 if self
._dohLibrary
!= 'h2o':
1410 conns
.append(self
.openTLSConnection(self
._dohServerPort
, self
._serverName
, self
._caCert
, alpn
=alpn
))
1423 response
= conn
.recv(65535)
1435 # wait a bit to be sure that dnsdist closed the connections
1436 # and decremented the counters on its side, otherwise subsequent
1437 # connections will be dropped
1440 self
.assertEqual(count
, self
._maxTCPConnsPerDOHFrontend
)
1441 self
.assertEqual(failed
, 1)
1443 class TestDOHFrontendLimitsNGHTTP2(DOHFrontendLimits
, DNSDistDOHTest
):
1444 _dohLibrary
= 'nghttp2'
1446 class TestDOHFrontendLimitsH2O(DOHFrontendLimits
, DNSDistDOHTest
):
1449 class Protocols(object):
1450 _serverKey
= 'server.key'
1451 _serverCert
= 'server.chain'
1452 _serverName
= 'tls.tests.dnsdist.org'
1454 _dohServerPort
= pickAvailablePort()
1455 _customResponseHeader1
= 'access-control-allow-origin: *'
1456 _customResponseHeader2
= 'user-agent: derp'
1457 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1458 _config_template
= """
1459 function checkDOH(dq)
1460 if dq:getProtocol() ~= "DNS over HTTPS" then
1461 return DNSAction.Spoof, '1.2.3.4'
1463 return DNSAction.None
1466 addAction("protocols.doh.tests.powerdns.com.", LuaAction(checkDOH))
1467 newServer{address="127.0.0.1:%s"}
1468 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {library='%s'})
1470 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
1472 def testProtocolDOH(self
):
1474 DoH: Test DNSQuestion.Protocol
1476 name
= 'protocols.doh.tests.powerdns.com.'
1477 query
= dns
.message
.make_query(name
, 'A', 'IN')
1478 response
= dns
.message
.make_response(query
)
1479 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1480 expectedQuery
.id = 0
1482 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
1483 self
.assertTrue(receivedQuery
)
1484 self
.assertTrue(receivedResponse
)
1485 receivedQuery
.id = expectedQuery
.id
1486 self
.assertEqual(expectedQuery
, receivedQuery
)
1487 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
1488 self
.assertEqual(response
, receivedResponse
)
1490 class TestProtocolsNGHTTP2(Protocols
, DNSDistDOHTest
):
1491 _dohLibrary
= 'nghttp2'
1493 class TestProtocolsH2O(Protocols
, DNSDistDOHTest
):
1496 class DOHWithPKCS12Cert(object):
1497 _serverCert
= 'server.p12'
1498 _pkcs12Password
= 'passw0rd'
1499 _serverName
= 'tls.tests.dnsdist.org'
1501 _dohServerPort
= pickAvailablePort()
1502 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1503 _config_template
= """
1504 newServer{address="127.0.0.1:%s"}
1505 cert=newTLSCertificate("%s", {password="%s"})
1506 addDOHLocal("127.0.0.1:%s", cert, "", { "/" }, {library='%s'})
1508 _config_params
= ['_testServerPort', '_serverCert', '_pkcs12Password', '_dohServerPort', '_dohLibrary']
1510 def testPKCS12DOH(self
):
1512 DoH: Test Simple DOH Query with a password protected PKCS12 file configured
1514 name
= 'simple.doh.tests.powerdns.com.'
1515 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
1517 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1518 expectedQuery
.id = 0
1519 response
= dns
.message
.make_response(query
)
1520 rrset
= dns
.rrset
.from_text(name
,
1525 response
.answer
.append(rrset
)
1527 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
1528 self
.assertTrue(receivedQuery
)
1529 self
.assertTrue(receivedResponse
)
1530 receivedQuery
.id = expectedQuery
.id
1531 self
.assertEqual(expectedQuery
, receivedQuery
)
1533 class TestDOHWithPKCS12CertNGHTTP2(DOHWithPKCS12Cert
, DNSDistDOHTest
):
1534 _dohLibrary
= 'nghttp2'
1536 class TestDOHWithPKCS12CertH2O(DOHWithPKCS12Cert
, DNSDistDOHTest
):
1539 class DOHForwardedToTCPOnly(object):
1540 _serverKey
= 'server.key'
1541 _serverCert
= 'server.chain'
1542 _serverName
= 'tls.tests.dnsdist.org'
1544 _dohServerPort
= pickAvailablePort()
1545 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1546 _config_template
= """
1547 newServer{address="127.0.0.1:%s", tcpOnly=true}
1548 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {library='%s'})
1550 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
1552 def testDOHTCPOnly(self
):
1554 DoH: Test a DoH query forwarded to a TCP-only server
1556 name
= 'tcponly.doh.tests.powerdns.com.'
1557 query
= dns
.message
.make_query(name
, 'A', 'IN')
1559 response
= dns
.message
.make_response(query
)
1560 rrset
= dns
.rrset
.from_text(name
,
1565 response
.answer
.append(rrset
)
1567 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
1568 self
.assertTrue(receivedQuery
)
1569 self
.assertTrue(receivedResponse
)
1570 receivedQuery
.id = query
.id
1571 self
.assertEqual(receivedQuery
, query
)
1572 self
.assertEqual(receivedResponse
, response
)
1574 class TestDOHForwardedToTCPOnlyNGHTTP2(DOHForwardedToTCPOnly
, DNSDistDOHTest
):
1575 _dohLibrary
= 'nghttp2'
1577 class TestDOHForwardedToTCPOnlyH2O(DOHForwardedToTCPOnly
, DNSDistDOHTest
):
1580 class DOHLimits(object):
1581 _serverName
= 'tls.tests.dnsdist.org'
1583 _dohServerPort
= pickAvailablePort()
1584 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1585 _serverKey
= 'server.key'
1586 _serverCert
= 'server.chain'
1587 _maxTCPConnsPerClient
= 3
1588 _config_template
= """
1589 newServer{address="127.0.0.1:%d"}
1590 addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, {library='%s'})
1591 setMaxTCPConnectionsPerClient(%d)
1593 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary', '_maxTCPConnsPerClient']
1595 def testConnsPerClient(self
):
1597 DoH Limits: Maximum number of conns per client
1599 name
= 'maxconnsperclient.doh.tests.powerdns.com.'
1600 query
= dns
.message
.make_query(name
, 'A', 'IN')
1601 url
= self
.getDOHGetURL(self
._dohBaseURL
, query
)
1604 for idx
in range(self
._maxTCPConnsPerClient
+ 1):
1605 conn
= self
.openDOHConnection(self
._dohServerPort
, self
._caCert
, timeout
=2.0)
1606 conn
.setopt(pycurl
.URL
, url
)
1607 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
1608 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
1609 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
1610 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
1617 data
= conn
.perform_rb()
1618 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
1626 # wait a bit to be sure that dnsdist closed the connections
1627 # and decremented the counters on its side, otherwise subsequent
1628 # connections will be dropped
1631 self
.assertEqual(count
, self
._maxTCPConnsPerClient
)
1632 self
.assertEqual(failed
, 1)
1634 class TestDOHLimitsNGHTTP2(DOHLimits
, DNSDistDOHTest
):
1635 _dohLibrary
= 'nghttp2'
1637 class TestDOHLimitsH2O(DOHLimits
, DNSDistDOHTest
):