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 testDOHHTTP1(self
):
384 if self
._dohLibrary
== 'h2o':
385 raise unittest
.SkipTest('h2o supports HTTP/1.1, this test is only relevant for nghttp2')
386 name
= 'http11.doh.tests.powerdns.com.'
387 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
388 wire
= query
.to_wire()
389 b64
= base64
.urlsafe_b64encode(wire
).decode('UTF8').rstrip('=')
390 url
= self
._dohBaseURL
+ '?dns=' + b64
392 conn
.setopt(pycurl
.HTTP_VERSION
, pycurl
.CURL_HTTP_VERSION_1_1
)
393 conn
.setopt(pycurl
.HTTPHEADER
, ["Content-type: application/dns-message",
394 "Accept: application/dns-message"])
395 conn
.setopt(pycurl
.URL
, url
)
396 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
397 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
398 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
399 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
400 data
= conn
.perform_rb()
401 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
402 self
.assertEqual(rcode
, 400)
403 self
.assertEqual(data
, b
'<html><body>This server implements RFC 8484 - DNS Queries over HTTP, and requires HTTP/2 in accordance with section 5.2 of the RFC.</body></html>\r\n')
405 def testDOHHTTP1NotSelectedOverH2(self
):
407 DOH: Check that HTTP/1.1 is not selected over H2 when offered in the wrong order by the client
409 if self
._dohLibrary
== 'h2o':
410 raise unittest
.SkipTest('h2o supports HTTP/1.1, this test is only relevant for nghttp2')
411 alpn
= ['http/1.1', 'h2']
412 conn
= self
.openTLSConnection(self
._dohServerPort
, self
._serverName
, self
._caCert
, alpn
=alpn
)
413 if not hasattr(conn
, 'selected_alpn_protocol'):
414 raise unittest
.SkipTest('Unable to check the selected ALPN, Python version is too old to support selected_alpn_protocol')
415 self
.assertEqual(conn
.selected_alpn_protocol(), 'h2')
417 def testDOHInvalid(self
):
419 DOH: Invalid DNS query
421 name
= 'invalid.doh.tests.powerdns.com.'
422 invalidQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
424 # first an invalid query
425 invalidQuery
= invalidQuery
.to_wire()
426 invalidQuery
= invalidQuery
[:-5]
427 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, caFile
=self
._caCert
, query
=invalidQuery
, response
=None, useQueue
=False, rawQuery
=True)
428 self
.assertEqual(receivedResponse
, None)
430 # and now a valid one
431 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
433 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
435 response
= dns
.message
.make_response(query
)
436 rrset
= dns
.rrset
.from_text(name
,
441 response
.answer
.append(rrset
)
442 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
443 self
.assertTrue(receivedQuery
)
444 self
.assertTrue(receivedResponse
)
445 receivedQuery
.id = expectedQuery
.id
446 self
.assertEqual(expectedQuery
, receivedQuery
)
447 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
448 self
.assertEqual(response
, receivedResponse
)
450 def testDOHInvalidHeaderName(self
):
452 DOH: Invalid HTTP header name query
454 name
= 'invalid-header-name.doh.tests.powerdns.com.'
455 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
457 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
459 response
= dns
.message
.make_response(query
)
460 rrset
= dns
.rrset
.from_text(name
,
465 response
.answer
.append(rrset
)
466 # this header is invalid, see rfc9113 section 8.2.1. Field Validity
467 customHeaders
= ['{}: test']
469 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
, customHeaders
=customHeaders
)
470 self
.assertFalse(receivedQuery
)
471 self
.assertFalse(receivedResponse
)
475 def testDOHNoBackend(self
):
479 if self
._dohLibrary
== 'h2o':
480 raise unittest
.SkipTest('h2o does not check the HTTP method')
481 name
= 'no-backend.doh.tests.powerdns.com.'
482 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
483 wire
= query
.to_wire()
484 b64
= base64
.urlsafe_b64encode(wire
).decode('UTF8').rstrip('=')
485 url
= self
._dohBaseURL
+ '?dns=' + b64
486 conn
= self
.openDOHConnection(self
._dohServerPort
, self
._caCert
, timeout
=2)
487 conn
.setopt(pycurl
.URL
, url
)
488 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
489 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
490 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
491 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
492 data
= conn
.perform_rb()
493 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
494 self
.assertEqual(rcode
, 403)
496 def testDOHEmptyPOST(self
):
498 DOH: Empty POST query
500 name
= 'empty-post.doh.tests.powerdns.com.'
502 (_
, receivedResponse
) = self
.sendDOHPostQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
="", rawQuery
=True, response
=None, caFile
=self
._caCert
)
503 self
.assertEqual(receivedResponse
, None)
505 # and now a valid one
506 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
508 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
510 response
= dns
.message
.make_response(query
)
511 rrset
= dns
.rrset
.from_text(name
,
516 response
.answer
.append(rrset
)
517 (receivedQuery
, receivedResponse
) = self
.sendDOHPostQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
518 self
.assertTrue(receivedQuery
)
519 self
.assertTrue(receivedResponse
)
520 receivedQuery
.id = expectedQuery
.id
521 self
.assertEqual(expectedQuery
, receivedQuery
)
522 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
523 self
.assertEqual(response
, receivedResponse
)
525 def testHeaderRule(self
):
529 name
= 'header-rule.doh.tests.powerdns.com.'
530 query
= dns
.message
.make_query(name
, 'A', 'IN')
532 query
.flags
&= ~dns
.flags
.RD
533 expectedResponse
= dns
.message
.make_response(query
)
534 rrset
= dns
.rrset
.from_text(name
,
539 expectedResponse
.answer
.append(rrset
)
541 # this header should match
542 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False, customHeaders
=['x-powerdnS: aaaaa'])
543 self
.assertEqual(receivedResponse
, expectedResponse
)
545 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
546 expectedQuery
.flags
&= ~dns
.flags
.RD
548 response
= dns
.message
.make_response(query
)
549 rrset
= dns
.rrset
.from_text(name
,
554 response
.answer
.append(rrset
)
556 # this content of the header should NOT match
557 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
, customHeaders
=['x-powerdnS: bbbbb'])
558 self
.assertTrue(receivedQuery
)
559 self
.assertTrue(receivedResponse
)
560 receivedQuery
.id = expectedQuery
.id
561 self
.assertEqual(expectedQuery
, receivedQuery
)
562 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
563 self
.assertEqual(response
, receivedResponse
)
565 def testHTTPPath(self
):
569 name
= 'http-path.doh.tests.powerdns.com.'
570 query
= dns
.message
.make_query(name
, 'A', 'IN')
572 query
.flags
&= ~dns
.flags
.RD
573 expectedResponse
= dns
.message
.make_response(query
)
574 rrset
= dns
.rrset
.from_text(name
,
579 expectedResponse
.answer
.append(rrset
)
581 # this path should match
582 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ 'PowerDNS', caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False)
583 self
.assertEqual(receivedResponse
, expectedResponse
)
585 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
587 expectedQuery
.flags
&= ~dns
.flags
.RD
588 response
= dns
.message
.make_response(query
)
589 rrset
= dns
.rrset
.from_text(name
,
594 response
.answer
.append(rrset
)
596 # this path should NOT match
597 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ "PowerDNS2", query
, response
=response
, caFile
=self
._caCert
)
598 self
.assertTrue(receivedQuery
)
599 self
.assertTrue(receivedResponse
)
600 receivedQuery
.id = expectedQuery
.id
601 self
.assertEqual(expectedQuery
, receivedQuery
)
602 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
603 self
.assertEqual(response
, receivedResponse
)
605 # this path is not in the URLs map and should lead to a 404
606 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ "PowerDNS/something", query
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True)
607 self
.assertTrue(receivedResponse
)
608 self
.assertEqual(receivedResponse
, b
'there is no endpoint configured for this path')
609 self
.assertEqual(self
._rcode
, 404)
611 def testHTTPPathRegex(self
):
615 name
= 'http-path-regex.doh.tests.powerdns.com.'
616 query
= dns
.message
.make_query(name
, 'A', 'IN')
618 query
.flags
&= ~dns
.flags
.RD
619 expectedResponse
= dns
.message
.make_response(query
)
620 rrset
= dns
.rrset
.from_text(name
,
625 expectedResponse
.answer
.append(rrset
)
627 # this path should match
628 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ 'PowerDNS-999', caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False)
629 self
.assertEqual(receivedResponse
, expectedResponse
)
631 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
633 expectedQuery
.flags
&= ~dns
.flags
.RD
634 response
= dns
.message
.make_response(query
)
635 rrset
= dns
.rrset
.from_text(name
,
640 response
.answer
.append(rrset
)
642 # this path should NOT match
643 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ "PowerDNS2", query
, response
=response
, caFile
=self
._caCert
)
644 self
.assertTrue(receivedQuery
)
645 self
.assertTrue(receivedResponse
)
646 receivedQuery
.id = expectedQuery
.id
647 self
.assertEqual(expectedQuery
, receivedQuery
)
648 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
649 self
.assertEqual(response
, receivedResponse
)
651 def testHTTPStatusAction200(self
):
653 DOH: HTTPStatusAction 200 OK
655 name
= 'http-status-action.doh.tests.powerdns.com.'
656 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
659 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True)
660 self
.assertTrue(receivedResponse
)
661 self
.assertEqual(receivedResponse
, b
'Plaintext answer')
662 self
.assertEqual(self
._rcode
, 200)
663 self
.assertTrue('content-type: text/plain' in self
._response
_headers
.decode())
665 def testHTTPStatusAction307(self
):
667 DOH: HTTPStatusAction 307
669 name
= 'http-status-action-redirect.doh.tests.powerdns.com.'
670 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
673 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True)
674 self
.assertTrue(receivedResponse
)
675 self
.assertEqual(self
._rcode
, 307)
676 self
.assertTrue('location: https://doh.powerdns.org' in self
._response
_headers
.decode())
678 def testHTTPLuaResponse(self
):
680 DOH: Lua HTTP Response
682 name
= 'http-lua.doh.tests.powerdns.com.'
683 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
686 (_
, receivedResponse
) = self
.sendDOHPostQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True)
687 self
.assertTrue(receivedResponse
)
688 self
.assertEqual(receivedResponse
, b
'It works!')
689 self
.assertEqual(self
._rcode
, 200)
690 self
.assertTrue('content-type: text/plain' in self
._response
_headers
.decode())
692 def testHTTPEarlyResponse(self
):
694 DOH: HTTP Early Response
696 response_headers
= BytesIO()
697 url
= self
._dohBaseURL
+ 'coffee'
698 conn
= self
.openDOHConnection(self
._dohServerPort
, caFile
=self
._caCert
, timeout
=2.0)
699 conn
.setopt(pycurl
.URL
, url
)
700 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
701 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
702 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
703 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
704 conn
.setopt(pycurl
.HEADERFUNCTION
, response_headers
.write
)
705 data
= conn
.perform_rb()
706 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
707 headers
= response_headers
.getvalue().decode()
709 self
.assertEqual(rcode
, 418)
710 self
.assertEqual(data
, b
'C0FFEE')
711 self
.assertIn('foo: bar', headers
)
712 self
.assertNotIn(self
._customResponseHeader
2, headers
)
714 response_headers
= BytesIO()
715 conn
= self
.openDOHConnection(self
._dohServerPort
, caFile
=self
._caCert
, timeout
=2.0)
716 conn
.setopt(pycurl
.URL
, url
)
717 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
718 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
719 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
720 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
721 conn
.setopt(pycurl
.HEADERFUNCTION
, response_headers
.write
)
722 conn
.setopt(pycurl
.POST
, True)
724 conn
.setopt(pycurl
.POSTFIELDS
, data
)
726 data
= conn
.perform_rb()
727 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
728 headers
= response_headers
.getvalue().decode()
729 self
.assertEqual(rcode
, 418)
730 self
.assertEqual(data
, b
'C0FFEE')
731 self
.assertIn('foo: bar', headers
)
732 self
.assertNotIn(self
._customResponseHeader
2, headers
)
734 class TestDoHNGHTTP2(DOHTests
, DNSDistDOHTest
):
735 _dohLibrary
= 'nghttp2'
737 class TestDoHH2O(DOHTests
, DNSDistDOHTest
):
740 class DOHSubPathsTests(object):
741 _serverKey
= 'server.key'
742 _serverCert
= 'server.chain'
743 _serverName
= 'tls.tests.dnsdist.org'
745 _dohServerPort
= pickAvailablePort()
746 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
747 _config_template
= """
748 newServer{address="127.0.0.1:%s"}
750 addAction(AllRule(), SpoofAction("3.4.5.6"))
752 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/PowerDNS" }, {exactPathMatching=false, library='%s'})
754 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
756 def testSubPath(self
):
760 name
= 'sub-path.doh.tests.powerdns.com.'
761 query
= dns
.message
.make_query(name
, 'A', 'IN')
763 query
.flags
&= ~dns
.flags
.RD
764 expectedResponse
= dns
.message
.make_response(query
)
765 rrset
= dns
.rrset
.from_text(name
,
770 expectedResponse
.answer
.append(rrset
)
772 # this path should match
773 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ 'PowerDNS', caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False)
774 self
.assertEqual(receivedResponse
, expectedResponse
)
776 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
778 expectedQuery
.flags
&= ~dns
.flags
.RD
779 response
= dns
.message
.make_response(query
)
780 rrset
= dns
.rrset
.from_text(name
,
785 response
.answer
.append(rrset
)
787 # this path is not in the URLs map and should lead to a 404
788 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ "NotPowerDNS", query
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True)
789 self
.assertTrue(receivedResponse
)
790 self
.assertIn(receivedResponse
, [b
'there is no endpoint configured for this path', b
'not found'])
791 self
.assertEqual(self
._rcode
, 404)
793 # this path is below one in the URLs map and exactPathMatching is false, so we should be good
794 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ 'PowerDNS/something', caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False)
795 self
.assertEqual(receivedResponse
, expectedResponse
)
797 class TestDoHSubPathsNGHTTP2(DOHSubPathsTests
, DNSDistDOHTest
):
798 _dohLibrary
= 'nghttp2'
800 class TestDoHSubPathsH2O(DOHSubPathsTests
, DNSDistDOHTest
):
803 class DOHAddingECSTests(object):
805 _serverKey
= 'server.key'
806 _serverCert
= 'server.chain'
807 _serverName
= 'tls.tests.dnsdist.org'
809 _dohServerPort
= pickAvailablePort()
810 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
811 _config_template
= """
812 newServer{address="127.0.0.1:%s", useClientSubnet=true}
813 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {library='%s'})
816 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
818 def testDOHSimple(self
):
820 DOH with ECS: Simple query
822 name
= 'simple.doh-ecs.tests.powerdns.com.'
823 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
825 rewrittenEcso
= clientsubnetoption
.ClientSubnetOption('127.0.0.0', 24)
826 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096, options
=[rewrittenEcso
])
827 response
= dns
.message
.make_response(query
)
828 rrset
= dns
.rrset
.from_text(name
,
833 response
.answer
.append(rrset
)
835 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
836 self
.assertTrue(receivedQuery
)
837 self
.assertTrue(receivedResponse
)
838 expectedQuery
.id = receivedQuery
.id
839 self
.assertEqual(expectedQuery
, receivedQuery
)
840 self
.checkQueryEDNSWithECS(expectedQuery
, receivedQuery
)
841 self
.assertEqual(response
, receivedResponse
)
842 self
.checkResponseNoEDNS(response
, receivedResponse
)
844 def testDOHExistingEDNS(self
):
846 DOH with ECS: Existing EDNS
848 name
= 'existing-edns.doh-ecs.tests.powerdns.com.'
849 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=8192)
851 rewrittenEcso
= clientsubnetoption
.ClientSubnetOption('127.0.0.0', 24)
852 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=8192, options
=[rewrittenEcso
])
853 response
= dns
.message
.make_response(query
)
854 rrset
= dns
.rrset
.from_text(name
,
859 response
.answer
.append(rrset
)
861 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
862 self
.assertTrue(receivedQuery
)
863 self
.assertTrue(receivedResponse
)
864 receivedQuery
.id = expectedQuery
.id
865 self
.assertEqual(expectedQuery
, receivedQuery
)
866 self
.assertEqual(response
, receivedResponse
)
867 self
.checkQueryEDNSWithECS(expectedQuery
, receivedQuery
)
868 self
.checkResponseEDNSWithoutECS(response
, receivedResponse
)
870 def testDOHExistingECS(self
):
872 DOH with ECS: Existing EDNS Client Subnet
874 name
= 'existing-ecs.doh-ecs.tests.powerdns.com.'
875 ecso
= clientsubnetoption
.ClientSubnetOption('1.2.3.4')
876 rewrittenEcso
= clientsubnetoption
.ClientSubnetOption('127.0.0.0', 24)
877 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=512, options
=[ecso
], want_dnssec
=True)
879 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=512, options
=[rewrittenEcso
])
880 response
= dns
.message
.make_response(query
)
881 response
.use_edns(edns
=True, payload
=4096, options
=[rewrittenEcso
])
882 rrset
= dns
.rrset
.from_text(name
,
887 response
.answer
.append(rrset
)
889 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
890 self
.assertTrue(receivedQuery
)
891 self
.assertTrue(receivedResponse
)
892 receivedQuery
.id = expectedQuery
.id
893 self
.assertEqual(expectedQuery
, receivedQuery
)
894 self
.assertEqual(response
, receivedResponse
)
895 self
.checkQueryEDNSWithECS(expectedQuery
, receivedQuery
)
896 self
.checkResponseEDNSWithECS(response
, receivedResponse
)
898 class TestDoHAddingECSNGHTTP2(DOHAddingECSTests
, DNSDistDOHTest
):
899 _dohLibrary
= 'nghttp2'
901 class TestDoHAddingECSH2O(DOHAddingECSTests
, DNSDistDOHTest
):
904 class DOHOverHTTP(object):
905 _dohServerPort
= pickAvailablePort()
906 _serverName
= 'tls.tests.dnsdist.org'
907 _dohBaseURL
= ("http://%s:%d/dns-query" % (_serverName
, _dohServerPort
))
908 _config_template
= """
909 newServer{address="127.0.0.1:%s"}
910 addDOHLocal("127.0.0.1:%s", nil, nil, '/dns-query', {library='%s'})
912 _config_params
= ['_testServerPort', '_dohServerPort', '_dohLibrary']
914 def testDOHSimple(self
):
916 DOH over HTTP: Simple query
918 name
= 'simple.doh-over-http.tests.powerdns.com.'
919 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
921 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
922 response
= dns
.message
.make_response(query
)
923 rrset
= dns
.rrset
.from_text(name
,
928 response
.answer
.append(rrset
)
930 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, useHTTPS
=False)
931 self
.assertTrue(receivedQuery
)
932 self
.assertTrue(receivedResponse
)
933 expectedQuery
.id = receivedQuery
.id
934 self
.assertEqual(expectedQuery
, receivedQuery
)
935 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
936 self
.assertEqual(response
, receivedResponse
)
937 self
.checkResponseNoEDNS(response
, receivedResponse
)
939 def testDOHSimplePOST(self
):
941 DOH over HTTP: Simple POST query
943 name
= 'simple-post.doh-over-http.tests.powerdns.com.'
944 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
946 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
948 response
= dns
.message
.make_response(query
)
949 rrset
= dns
.rrset
.from_text(name
,
954 response
.answer
.append(rrset
)
956 (receivedQuery
, receivedResponse
) = self
.sendDOHPostQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, useHTTPS
=False)
957 self
.assertTrue(receivedQuery
)
958 self
.assertTrue(receivedResponse
)
959 receivedQuery
.id = expectedQuery
.id
960 self
.assertEqual(expectedQuery
, receivedQuery
)
961 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
962 self
.assertEqual(response
, receivedResponse
)
963 self
.checkResponseNoEDNS(response
, receivedResponse
)
965 class TestDOHOverHTTPNGHTTP2(DOHOverHTTP
, DNSDistDOHTest
):
966 _dohLibrary
= 'nghttp2'
967 _checkConfigExpectedOutput
= b
"""No certificate provided for DoH endpoint 127.0.0.1:%d, running in DNS over HTTP mode instead of DNS over HTTPS
968 Configuration 'configs/dnsdist_TestDOHOverHTTPNGHTTP2.conf' OK!
969 """ % (DOHOverHTTP
._dohServerPort
)
971 class TestDOHOverHTTPH2O(DOHOverHTTP
, DNSDistDOHTest
):
973 _checkConfigExpectedOutput
= b
"""No certificate provided for DoH endpoint 127.0.0.1:%d, running in DNS over HTTP mode instead of DNS over HTTPS
974 Configuration 'configs/dnsdist_TestDOHOverHTTPH2O.conf' OK!
975 """ % (DOHOverHTTP
._dohServerPort
)
977 class DOHWithCache(object):
979 _serverKey
= 'server.key'
980 _serverCert
= 'server.chain'
981 _serverName
= 'tls.tests.dnsdist.org'
983 _dohServerPort
= pickAvailablePort()
984 _dohBaseURL
= ("https://%s:%d/dns-query" % (_serverName
, _dohServerPort
))
985 _config_template
= """
986 newServer{address="127.0.0.1:%s"}
988 addDOHLocal("127.0.0.1:%s", "%s", "%s", '/dns-query', {library='%s'})
990 pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
991 getPool(""):setCache(pc)
993 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
995 def testDOHCacheLargeAnswer(self
):
997 DOH with cache: Check that we can cache (and retrieve) large answers
1000 name
= 'large.doh-with-cache.tests.powerdns.com.'
1001 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
1003 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1004 expectedQuery
.id = 0
1005 response
= dns
.message
.make_response(query
)
1006 # we prepare a large answer
1009 if len(content
) > 0:
1010 content
= content
+ ', '
1011 content
= content
+ (str(i
)*50)
1013 content
= content
+ 'A'*40
1015 rrset
= dns
.rrset
.from_text(name
,
1020 response
.answer
.append(rrset
)
1021 self
.assertEqual(len(response
.to_wire()), 4096)
1023 # first query to fill the cache
1024 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
1025 self
.assertTrue(receivedQuery
)
1026 self
.assertTrue(receivedResponse
)
1027 receivedQuery
.id = expectedQuery
.id
1028 self
.assertEqual(expectedQuery
, receivedQuery
)
1029 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
1030 self
.assertEqual(response
, receivedResponse
)
1031 self
.checkHasHeader('cache-control', 'max-age=3600')
1033 for _
in range(numberOfQueries
):
1034 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False)
1035 self
.assertEqual(receivedResponse
, response
)
1036 self
.checkHasHeader('cache-control', 'max-age=' + str(receivedResponse
.answer
[0].ttl
))
1040 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False)
1041 self
.assertEqual(receivedResponse
, response
)
1042 self
.checkHasHeader('cache-control', 'max-age=' + str(receivedResponse
.answer
[0].ttl
))
1044 def testDOHGetFromUDPCache(self
):
1046 DOH with cache: Check that we can retrieve an answer received for a UDP query
1048 name
= 'doh-query-insert-udp.doh-with-cache.tests.powerdns.com.'
1049 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1050 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1051 expectedQuery
.id = 0
1052 response
= dns
.message
.make_response(query
)
1053 rrset
= dns
.rrset
.from_text(name
,
1058 response
.answer
.append(rrset
)
1060 # first query to fill the cache
1061 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
1062 self
.assertTrue(receivedQuery
)
1063 self
.assertTrue(receivedResponse
)
1064 receivedQuery
.id = expectedQuery
.id
1065 self
.assertEqual(expectedQuery
, receivedQuery
)
1066 self
.assertEqual(response
, receivedResponse
)
1068 # now we send the exact same query over DoH, we should get a cache hit
1069 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False)
1070 self
.assertTrue(receivedResponse
)
1071 self
.assertEqual(response
, receivedResponse
)
1073 def testDOHInsertIntoUDPCache(self
):
1075 DOH with cache: Check that we can retrieve an answer received for a DoH query from UDP
1077 name
= 'udp-query-get-doh.doh-with-cache.tests.powerdns.com.'
1078 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1079 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1080 expectedQuery
.id = 0
1081 response
= dns
.message
.make_response(query
)
1082 rrset
= dns
.rrset
.from_text(name
,
1087 response
.answer
.append(rrset
)
1089 # first query to fill the cache
1090 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
1091 self
.assertTrue(receivedQuery
)
1092 self
.assertTrue(receivedResponse
)
1093 receivedQuery
.id = expectedQuery
.id
1094 self
.assertEqual(expectedQuery
, receivedQuery
)
1095 self
.assertEqual(response
, receivedResponse
)
1097 # now we send the exact same query over DoH, we should get a cache hit
1098 (_
, receivedResponse
) = self
.sendUDPQuery(query
, response
=None, useQueue
=False)
1099 self
.assertTrue(receivedResponse
)
1100 self
.assertEqual(response
, receivedResponse
)
1102 def testTruncation(self
):
1104 DOH: Truncation over UDP (with cache)
1106 # the query is first forwarded over UDP, leading to a TC=1 answer from the
1107 # backend, then over TCP
1108 name
= 'truncated-udp.doh-with-cache.tests.powerdns.com.'
1109 query
= dns
.message
.make_query(name
, 'A', 'IN')
1111 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1112 expectedQuery
.id = 42
1113 response
= dns
.message
.make_response(query
)
1114 rrset
= dns
.rrset
.from_text(name
,
1119 response
.answer
.append(rrset
)
1121 # first response is a TC=1
1122 tcResponse
= dns
.message
.make_response(query
)
1123 tcResponse
.flags |
= dns
.flags
.TC
1124 self
._toResponderQueue
.put(tcResponse
, True, 2.0)
1126 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, response
=response
)
1127 # first query, received by the responder over UDP
1128 self
.assertTrue(receivedQuery
)
1129 receivedQuery
.id = expectedQuery
.id
1130 self
.assertEqual(expectedQuery
, receivedQuery
)
1131 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
1133 # check the response
1134 self
.assertTrue(receivedResponse
)
1135 self
.assertEqual(response
, receivedResponse
)
1137 # check the second query, received by the responder over TCP
1138 receivedQuery
= self
._fromResponderQueue
.get(True, 2.0)
1139 self
.assertTrue(receivedQuery
)
1140 receivedQuery
.id = expectedQuery
.id
1141 self
.assertEqual(expectedQuery
, receivedQuery
)
1142 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
1144 # now check the cache for a DoH query
1145 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False)
1146 self
.assertEqual(response
, receivedResponse
)
1148 # The TC=1 answer received over UDP will not be cached, because we currently do not cache answers with no records (no TTL)
1149 # The TCP one should, however
1150 (_
, receivedResponse
) = self
.sendTCPQuery(expectedQuery
, response
=None, useQueue
=False)
1151 self
.assertEqual(response
, receivedResponse
)
1153 def testResponsesReceivedOverUDP(self
):
1155 DOH: Check that responses received over UDP are cached (with cache)
1157 name
= 'cached-udp.doh-with-cache.tests.powerdns.com.'
1158 query
= dns
.message
.make_query(name
, 'A', 'IN')
1160 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1161 expectedQuery
.id = 0
1162 response
= dns
.message
.make_response(query
)
1163 rrset
= dns
.rrset
.from_text(name
,
1168 response
.answer
.append(rrset
)
1170 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, response
=response
)
1171 self
.assertTrue(receivedQuery
)
1172 receivedQuery
.id = expectedQuery
.id
1173 self
.assertEqual(expectedQuery
, receivedQuery
)
1174 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
1175 self
.assertTrue(receivedResponse
)
1176 self
.assertEqual(response
, receivedResponse
)
1178 # now check the cache for a DoH query
1179 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False)
1180 self
.assertEqual(response
, receivedResponse
)
1182 # Check that the answer is usable for UDP queries as well
1183 (_
, receivedResponse
) = self
.sendUDPQuery(expectedQuery
, response
=None, useQueue
=False)
1184 self
.assertEqual(response
, receivedResponse
)
1186 class TestDOHWithCacheNGHTTP2(DOHWithCache
, DNSDistDOHTest
):
1187 _dohLibrary
= 'nghttp2'
1190 class TestDOHWithCacheH2O(DOHWithCache
, DNSDistDOHTest
):
1193 class DOHWithoutCacheControl(object):
1195 _serverKey
= 'server.key'
1196 _serverCert
= 'server.chain'
1197 _serverName
= 'tls.tests.dnsdist.org'
1199 _dohServerPort
= pickAvailablePort()
1200 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1201 _config_template
= """
1202 newServer{address="127.0.0.1:%s"}
1204 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {sendCacheControlHeaders=false, library='%s'})
1206 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
1208 def testDOHSimple(self
):
1210 DOH without cache-control
1212 name
= 'simple.doh.tests.powerdns.com.'
1213 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
1215 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1216 expectedQuery
.id = 0
1217 response
= dns
.message
.make_response(query
)
1218 rrset
= dns
.rrset
.from_text(name
,
1223 response
.answer
.append(rrset
)
1225 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
1226 self
.assertTrue(receivedQuery
)
1227 self
.assertTrue(receivedResponse
)
1228 receivedQuery
.id = expectedQuery
.id
1229 self
.assertEqual(expectedQuery
, receivedQuery
)
1230 self
.checkNoHeader('cache-control')
1231 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
1232 self
.assertEqual(response
, receivedResponse
)
1234 class TestDOHWithoutCacheControlNGHTTP2(DOHWithoutCacheControl
, DNSDistDOHTest
):
1235 _dohLibrary
= 'nghttp2'
1237 class TestDOHWithoutCacheControlH2O(DOHWithoutCacheControl
, DNSDistDOHTest
):
1240 class DOHFFI(object):
1241 _serverKey
= 'server.key'
1242 _serverCert
= 'server.chain'
1243 _serverName
= 'tls.tests.dnsdist.org'
1245 _dohServerPort
= pickAvailablePort()
1246 _customResponseHeader1
= 'access-control-allow-origin: *'
1247 _customResponseHeader2
= 'user-agent: derp'
1248 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1249 _config_template
= """
1250 newServer{address="127.0.0.1:%s"}
1252 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {customResponseHeaders={["access-control-allow-origin"]="*",["user-agent"]="derp",["UPPERCASE"]="VaLuE"}, keepIncomingHeaders=true, library='%s'})
1254 local ffi = require("ffi")
1256 function dohHandler(dq)
1257 local scheme = ffi.string(ffi.C.dnsdist_ffi_dnsquestion_get_http_scheme(dq))
1258 local host = ffi.string(ffi.C.dnsdist_ffi_dnsquestion_get_http_host(dq))
1259 local path = ffi.string(ffi.C.dnsdist_ffi_dnsquestion_get_http_path(dq))
1260 local query_string = ffi.string(ffi.C.dnsdist_ffi_dnsquestion_get_http_query_string(dq))
1261 if scheme == 'https' and host == '%s:%d' and path == '/' and query_string == '' then
1262 local foundct = false
1263 local headers_ptr = ffi.new("const dnsdist_ffi_http_header_t *[1]")
1264 local headers_ptr_param = ffi.cast("const dnsdist_ffi_http_header_t **", headers_ptr)
1266 local headers_count = tonumber(ffi.C.dnsdist_ffi_dnsquestion_get_http_headers(dq, headers_ptr_param))
1267 if headers_count > 0 then
1268 for idx = 0, headers_count-1 do
1269 if ffi.string(headers_ptr[0][idx].name) == 'content-type' and ffi.string(headers_ptr[0][idx].value) == 'application/dns-message' then
1276 local response = 'It works!'
1277 ffi.C.dnsdist_ffi_dnsquestion_set_http_response(dq, 200, response, #response, 'text/plain')
1278 return DNSAction.HeaderModify
1281 return DNSAction.None
1283 addAction("http-lua-ffi.doh.tests.powerdns.com.", LuaFFIAction(dohHandler))
1285 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary', '_serverName', '_dohServerPort']
1287 def testHTTPLuaFFIResponse(self
):
1289 DOH: Lua FFI HTTP Response
1291 name
= 'http-lua-ffi.doh.tests.powerdns.com.'
1292 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
1295 (_
, receivedResponse
) = self
.sendDOHPostQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True)
1296 self
.assertTrue(receivedResponse
)
1297 self
.assertEqual(receivedResponse
, b
'It works!')
1298 self
.assertEqual(self
._rcode
, 200)
1299 self
.assertTrue('content-type: text/plain' in self
._response
_headers
.decode())
1301 class TestDOHFFINGHTTP2(DOHFFI
, DNSDistDOHTest
):
1302 _dohLibrary
= 'nghttp2'
1304 class TestDOHFFIH2O(DOHFFI
, DNSDistDOHTest
):
1307 class DOHForwardedFor(object):
1308 _serverKey
= 'server.key'
1309 _serverCert
= 'server.chain'
1310 _serverName
= 'tls.tests.dnsdist.org'
1312 _dohServerPort
= pickAvailablePort()
1313 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1314 _config_template
= """
1315 newServer{address="127.0.0.1:%s"}
1317 setACL('192.0.2.1/32')
1318 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {trustForwardedForHeader=true, library='%s'})
1319 -- Set a maximum number of TCP connections per client, to exercise
1320 -- that code along with X-Forwarded-For support
1321 setMaxTCPConnectionsPerClient(2)
1323 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
1325 def testDOHAllowedForwarded(self
):
1327 DOH with X-Forwarded-For allowed
1329 name
= 'allowed.forwarded.doh.tests.powerdns.com.'
1330 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
1332 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1333 expectedQuery
.id = 0
1334 response
= dns
.message
.make_response(query
)
1335 rrset
= dns
.rrset
.from_text(name
,
1340 response
.answer
.append(rrset
)
1342 (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'])
1343 self
.assertTrue(receivedQuery
)
1344 self
.assertTrue(receivedResponse
)
1345 receivedQuery
.id = expectedQuery
.id
1346 self
.assertEqual(expectedQuery
, receivedQuery
)
1347 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
1348 self
.assertEqual(response
, receivedResponse
)
1350 def testDOHDeniedForwarded(self
):
1352 DOH with X-Forwarded-For not allowed
1354 name
= 'not-allowed.forwarded.doh.tests.powerdns.com.'
1355 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
1357 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1358 expectedQuery
.id = 0
1359 response
= dns
.message
.make_response(query
)
1360 rrset
= dns
.rrset
.from_text(name
,
1365 response
.answer
.append(rrset
)
1367 (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'])
1369 self
.assertEqual(self
._rcode
, 403)
1370 self
.assertEqual(receivedResponse
, b
'DoH query not allowed because of ACL')
1372 class TestDOHForwardedForNGHTTP2(DOHForwardedFor
, DNSDistDOHTest
):
1373 _dohLibrary
= 'nghttp2'
1375 class TestDOHForwardedForH2O(DOHForwardedFor
, DNSDistDOHTest
):
1378 class DOHForwardedForNoTrusted(object):
1380 _serverKey
= 'server.key'
1381 _serverCert
= 'server.chain'
1382 _serverName
= 'tls.tests.dnsdist.org'
1384 _dohServerPort
= pickAvailablePort()
1385 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1386 _config_template
= """
1387 newServer{address="127.0.0.1:%s"}
1389 setACL('192.0.2.1/32')
1390 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {earlyACLDrop=true, library='%s'})
1392 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
1394 def testDOHForwardedUntrusted(self
):
1396 DOH with X-Forwarded-For not trusted
1398 name
= 'not-trusted.forwarded.doh.tests.powerdns.com.'
1399 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
1401 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1402 expectedQuery
.id = 0
1403 response
= dns
.message
.make_response(query
)
1404 rrset
= dns
.rrset
.from_text(name
,
1409 response
.answer
.append(rrset
)
1413 (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'])
1414 self
.assertEqual(self
._rcode
, 403)
1415 self
.assertEqual(receivedResponse
, b
'DoH query not allowed because of ACL')
1416 except pycurl
.error
as e
:
1419 self
.assertTrue(dropped
)
1421 class TestDOHForwardedForNoTrustedNGHTTP2(DOHForwardedForNoTrusted
, DNSDistDOHTest
):
1422 _dohLibrary
= 'nghttp2'
1424 class TestDOHForwardedForNoTrustedH2O(DOHForwardedForNoTrusted
, DNSDistDOHTest
):
1427 class DOHFrontendLimits(object):
1429 # this test suite uses a different responder port
1430 # because it uses a different health check configuration
1431 _testServerPort
= pickAvailablePort()
1432 _answerUnexpected
= True
1434 _serverKey
= 'server.key'
1435 _serverCert
= 'server.chain'
1436 _serverName
= 'tls.tests.dnsdist.org'
1438 _dohServerPort
= pickAvailablePort()
1439 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1440 _skipListeningOnCL
= True
1441 _maxTCPConnsPerDOHFrontend
= 5
1442 _config_template
= """
1443 newServer{address="127.0.0.1:%s"}
1444 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, { maxConcurrentTCPConnections=%d, library='%s' })
1446 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_maxTCPConnsPerDOHFrontend', '_dohLibrary']
1447 _alternateListeningAddr
= '127.0.0.1'
1448 _alternateListeningPort
= _dohServerPort
1450 def testTCPConnsPerDOHFrontend(self
):
1452 DoH Frontend Limits: Maximum number of conns per DoH frontend
1454 name
= 'maxconnsperfrontend.doh.tests.powerdns.com.'
1455 query
= b
"GET / HTTP/1.0\r\n\r\n"
1458 for idx
in range(self
._maxTCPConnsPerDOHFrontend
+ 1):
1461 if self
._dohLibrary
!= 'h2o':
1463 conns
.append(self
.openTLSConnection(self
._dohServerPort
, self
._serverName
, self
._caCert
, alpn
=alpn
))
1476 response
= conn
.recv(65535)
1488 # wait a bit to be sure that dnsdist closed the connections
1489 # and decremented the counters on its side, otherwise subsequent
1490 # connections will be dropped
1493 self
.assertEqual(count
, self
._maxTCPConnsPerDOHFrontend
)
1494 self
.assertEqual(failed
, 1)
1496 class TestDOHFrontendLimitsNGHTTP2(DOHFrontendLimits
, DNSDistDOHTest
):
1497 _dohLibrary
= 'nghttp2'
1499 class TestDOHFrontendLimitsH2O(DOHFrontendLimits
, DNSDistDOHTest
):
1502 class Protocols(object):
1503 _serverKey
= 'server.key'
1504 _serverCert
= 'server.chain'
1505 _serverName
= 'tls.tests.dnsdist.org'
1507 _dohServerPort
= pickAvailablePort()
1508 _customResponseHeader1
= 'access-control-allow-origin: *'
1509 _customResponseHeader2
= 'user-agent: derp'
1510 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1511 _config_template
= """
1512 function checkDOH(dq)
1513 if dq:getProtocol() ~= "DNS over HTTPS" then
1514 return DNSAction.Spoof, '1.2.3.4'
1516 return DNSAction.None
1519 addAction("protocols.doh.tests.powerdns.com.", LuaAction(checkDOH))
1520 newServer{address="127.0.0.1:%s"}
1521 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {library='%s'})
1523 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
1525 def testProtocolDOH(self
):
1527 DoH: Test DNSQuestion.Protocol
1529 name
= 'protocols.doh.tests.powerdns.com.'
1530 query
= dns
.message
.make_query(name
, 'A', 'IN')
1531 response
= dns
.message
.make_response(query
)
1532 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1533 expectedQuery
.id = 0
1535 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
1536 self
.assertTrue(receivedQuery
)
1537 self
.assertTrue(receivedResponse
)
1538 receivedQuery
.id = expectedQuery
.id
1539 self
.assertEqual(expectedQuery
, receivedQuery
)
1540 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
1541 self
.assertEqual(response
, receivedResponse
)
1543 class TestProtocolsNGHTTP2(Protocols
, DNSDistDOHTest
):
1544 _dohLibrary
= 'nghttp2'
1546 class TestProtocolsH2O(Protocols
, DNSDistDOHTest
):
1549 class DOHWithPKCS12Cert(object):
1550 _serverCert
= 'server.p12'
1551 _pkcs12Password
= 'passw0rd'
1552 _serverName
= 'tls.tests.dnsdist.org'
1554 _dohServerPort
= pickAvailablePort()
1555 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1556 _config_template
= """
1557 newServer{address="127.0.0.1:%s"}
1558 cert=newTLSCertificate("%s", {password="%s"})
1559 addDOHLocal("127.0.0.1:%s", cert, "", { "/" }, {library='%s'})
1561 _config_params
= ['_testServerPort', '_serverCert', '_pkcs12Password', '_dohServerPort', '_dohLibrary']
1563 def testPKCS12DOH(self
):
1565 DoH: Test Simple DOH Query with a password protected PKCS12 file configured
1567 name
= 'simple.doh.tests.powerdns.com.'
1568 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
1570 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1571 expectedQuery
.id = 0
1572 response
= dns
.message
.make_response(query
)
1573 rrset
= dns
.rrset
.from_text(name
,
1578 response
.answer
.append(rrset
)
1580 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
1581 self
.assertTrue(receivedQuery
)
1582 self
.assertTrue(receivedResponse
)
1583 receivedQuery
.id = expectedQuery
.id
1584 self
.assertEqual(expectedQuery
, receivedQuery
)
1586 class TestDOHWithPKCS12CertNGHTTP2(DOHWithPKCS12Cert
, DNSDistDOHTest
):
1587 _dohLibrary
= 'nghttp2'
1589 class TestDOHWithPKCS12CertH2O(DOHWithPKCS12Cert
, DNSDistDOHTest
):
1592 class DOHForwardedToTCPOnly(object):
1593 _serverKey
= 'server.key'
1594 _serverCert
= 'server.chain'
1595 _serverName
= 'tls.tests.dnsdist.org'
1597 _dohServerPort
= pickAvailablePort()
1598 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1599 _config_template
= """
1600 newServer{address="127.0.0.1:%s", tcpOnly=true}
1601 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {library='%s'})
1603 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
1605 def testDOHTCPOnly(self
):
1607 DoH: Test a DoH query forwarded to a TCP-only server
1609 name
= 'tcponly.doh.tests.powerdns.com.'
1610 query
= dns
.message
.make_query(name
, 'A', 'IN')
1612 response
= dns
.message
.make_response(query
)
1613 rrset
= dns
.rrset
.from_text(name
,
1618 response
.answer
.append(rrset
)
1620 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
1621 self
.assertTrue(receivedQuery
)
1622 self
.assertTrue(receivedResponse
)
1623 receivedQuery
.id = query
.id
1624 self
.assertEqual(receivedQuery
, query
)
1625 self
.assertEqual(receivedResponse
, response
)
1627 class TestDOHForwardedToTCPOnlyNGHTTP2(DOHForwardedToTCPOnly
, DNSDistDOHTest
):
1628 _dohLibrary
= 'nghttp2'
1630 class TestDOHForwardedToTCPOnlyH2O(DOHForwardedToTCPOnly
, DNSDistDOHTest
):
1633 class DOHLimits(object):
1634 _serverName
= 'tls.tests.dnsdist.org'
1636 _dohServerPort
= pickAvailablePort()
1637 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1638 _serverKey
= 'server.key'
1639 _serverCert
= 'server.chain'
1640 _maxTCPConnsPerClient
= 3
1641 _config_template
= """
1642 newServer{address="127.0.0.1:%d"}
1643 addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, {library='%s'})
1644 setMaxTCPConnectionsPerClient(%d)
1646 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary', '_maxTCPConnsPerClient']
1648 def testConnsPerClient(self
):
1650 DoH Limits: Maximum number of conns per client
1652 name
= 'maxconnsperclient.doh.tests.powerdns.com.'
1653 query
= dns
.message
.make_query(name
, 'A', 'IN')
1654 url
= self
.getDOHGetURL(self
._dohBaseURL
, query
)
1657 for idx
in range(self
._maxTCPConnsPerClient
+ 1):
1658 conn
= self
.openDOHConnection(self
._dohServerPort
, self
._caCert
, timeout
=2.0)
1659 conn
.setopt(pycurl
.URL
, url
)
1660 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
1661 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
1662 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
1663 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
1670 data
= conn
.perform_rb()
1671 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
1679 # wait a bit to be sure that dnsdist closed the connections
1680 # and decremented the counters on its side, otherwise subsequent
1681 # connections will be dropped
1684 self
.assertEqual(count
, self
._maxTCPConnsPerClient
)
1685 self
.assertEqual(failed
, 1)
1687 class TestDOHLimitsNGHTTP2(DOHLimits
, DNSDistDOHTest
):
1688 _dohLibrary
= 'nghttp2'
1690 class TestDOHLimitsH2O(DOHLimits
, DNSDistDOHTest
):