7 import clientsubnetoption
9 from dnsdistdohtests
import DNSDistDOHTest
12 from io
import BytesIO
14 class TestDOH(DNSDistDOHTest
):
16 _serverKey
= 'server.key'
17 _serverCert
= 'server.chain'
18 _serverName
= 'tls.tests.dnsdist.org'
21 _customResponseHeader1
= 'access-control-allow-origin: *'
22 _customResponseHeader2
= 'user-agent: derp'
23 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
24 _config_template
= """
25 newServer{address="127.0.0.1:%s"}
27 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/", "/coffee", "/PowerDNS", "/PowerDNS2", "/PowerDNS-999" }, {customResponseHeaders={["access-control-allow-origin"]="*",["user-agent"]="derp",["UPPERCASE"]="VaLuE"}, keepIncomingHeaders=true})
28 dohFE = getDOHFrontend(0)
29 dohFE:setResponsesMap({newDOHResponseMapEntry('^/coffee$', 418, 'C0FFEE', {['FoO']='bar'})})
31 addAction("drop.doh.tests.powerdns.com.", DropAction())
32 addAction("refused.doh.tests.powerdns.com.", RCodeAction(DNSRCode.REFUSED))
33 addAction("spoof.doh.tests.powerdns.com.", SpoofAction("1.2.3.4"))
34 addAction(HTTPHeaderRule("X-PowerDNS", "^[a]{5}$"), SpoofAction("2.3.4.5"))
35 addAction(HTTPPathRule("/PowerDNS"), SpoofAction("3.4.5.6"))
36 addAction(HTTPPathRegexRule("^/PowerDNS-[0-9]"), SpoofAction("6.7.8.9"))
37 addAction("http-status-action.doh.tests.powerdns.com.", HTTPStatusAction(200, "Plaintext answer", "text/plain"))
38 addAction("http-status-action-redirect.doh.tests.powerdns.com.", HTTPStatusAction(307, "https://doh.powerdns.org"))
40 function dohHandler(dq)
41 if dq:getHTTPScheme() == 'https' and dq:getHTTPHost() == '%s:%d' and dq:getHTTPPath() == '/' and dq:getHTTPQueryString() == '' then
43 for key,value in pairs(dq:getHTTPHeaders()) do
44 if key == 'content-type' and value == 'application/dns-message' then
50 dq:setHTTPResponse(200, 'It works!', 'text/plain')
52 return DNSAction.HeaderModify
57 addAction("http-lua.doh.tests.powerdns.com.", LuaAction(dohHandler))
59 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_serverName', '_dohServerPort']
61 def testDOHSimple(self
):
65 name
= 'simple.doh.tests.powerdns.com.'
66 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
68 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
70 response
= dns
.message
.make_response(query
)
71 rrset
= dns
.rrset
.from_text(name
,
76 response
.answer
.append(rrset
)
78 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
79 self
.assertTrue(receivedQuery
)
80 self
.assertTrue(receivedResponse
)
81 receivedQuery
.id = expectedQuery
.id
82 self
.assertEqual(expectedQuery
, receivedQuery
)
83 self
.assertTrue((self
._customResponseHeader
1) in self
._response
_headers
.decode())
84 self
.assertTrue((self
._customResponseHeader
2) in self
._response
_headers
.decode())
85 self
.assertFalse(('UPPERCASE: VaLuE' in self
._response
_headers
.decode()))
86 self
.assertTrue(('uppercase: VaLuE' in self
._response
_headers
.decode()))
87 self
.assertTrue(('cache-control: max-age=3600' in self
._response
_headers
.decode()))
88 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
89 self
.assertEqual(response
, receivedResponse
)
90 self
.checkHasHeader('cache-control', 'max-age=3600')
92 def testDOHTransactionID(self
):
94 DOH: Simple query with ID != 0
96 name
= 'simple-with-non-zero-id.doh.tests.powerdns.com.'
97 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
99 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
101 response
= dns
.message
.make_response(query
)
102 rrset
= dns
.rrset
.from_text(name
,
107 response
.answer
.append(rrset
)
109 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
110 self
.assertTrue(receivedQuery
)
111 self
.assertTrue(receivedResponse
)
112 receivedQuery
.id = expectedQuery
.id
113 self
.assertEqual(expectedQuery
, receivedQuery
)
114 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
115 self
.assertEqual(response
, receivedResponse
)
116 # just to be sure the ID _is_ checked
117 self
.assertEqual(response
.id, receivedResponse
.id)
119 def testDOHSimplePOST(self
):
121 DOH: Simple POST query
123 name
= 'simple-post.doh.tests.powerdns.com.'
124 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
126 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
128 response
= dns
.message
.make_response(query
)
129 rrset
= dns
.rrset
.from_text(name
,
134 response
.answer
.append(rrset
)
136 (receivedQuery
, receivedResponse
) = self
.sendDOHPostQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
137 self
.assertTrue(receivedQuery
)
138 self
.assertTrue(receivedResponse
)
139 receivedQuery
.id = expectedQuery
.id
140 self
.assertEqual(expectedQuery
, receivedQuery
)
141 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
142 self
.assertEqual(response
, receivedResponse
)
144 def testDOHExistingEDNS(self
):
148 name
= 'existing-edns.doh.tests.powerdns.com.'
149 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=8192)
151 response
= dns
.message
.make_response(query
)
152 rrset
= dns
.rrset
.from_text(name
,
157 response
.answer
.append(rrset
)
159 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
160 self
.assertTrue(receivedQuery
)
161 self
.assertTrue(receivedResponse
)
162 receivedQuery
.id = query
.id
163 self
.assertEqual(query
, receivedQuery
)
164 self
.assertEqual(response
, receivedResponse
)
165 self
.checkQueryEDNSWithoutECS(query
, receivedQuery
)
166 self
.checkResponseEDNSWithoutECS(response
, receivedResponse
)
168 def testDOHExistingECS(self
):
170 DOH: Existing EDNS Client Subnet
172 name
= 'existing-ecs.doh.tests.powerdns.com.'
173 ecso
= clientsubnetoption
.ClientSubnetOption('1.2.3.4')
174 rewrittenEcso
= clientsubnetoption
.ClientSubnetOption('127.0.0.1', 24)
175 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=512, options
=[ecso
], want_dnssec
=True)
177 response
= dns
.message
.make_response(query
)
178 response
.use_edns(edns
=True, payload
=4096, options
=[rewrittenEcso
])
179 rrset
= dns
.rrset
.from_text(name
,
184 response
.answer
.append(rrset
)
186 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
187 self
.assertTrue(receivedQuery
)
188 self
.assertTrue(receivedResponse
)
189 receivedQuery
.id = query
.id
190 self
.assertEqual(query
, receivedQuery
)
191 self
.assertEqual(response
, receivedResponse
)
192 self
.checkQueryEDNSWithECS(query
, receivedQuery
)
193 self
.checkResponseEDNSWithECS(response
, receivedResponse
)
195 def testDropped(self
):
199 name
= 'drop.doh.tests.powerdns.com.'
200 query
= dns
.message
.make_query(name
, 'A', 'IN')
201 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False)
202 self
.assertEqual(receivedResponse
, None)
204 def testRefused(self
):
208 name
= 'refused.doh.tests.powerdns.com.'
209 query
= dns
.message
.make_query(name
, 'A', 'IN')
211 query
.flags
&= ~dns
.flags
.RD
212 expectedResponse
= dns
.message
.make_response(query
)
213 expectedResponse
.set_rcode(dns
.rcode
.REFUSED
)
215 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False)
216 self
.assertEqual(receivedResponse
, expectedResponse
)
222 name
= 'spoof.doh.tests.powerdns.com.'
223 query
= dns
.message
.make_query(name
, 'A', 'IN')
225 query
.flags
&= ~dns
.flags
.RD
226 expectedResponse
= dns
.message
.make_response(query
)
227 rrset
= dns
.rrset
.from_text(name
,
232 expectedResponse
.answer
.append(rrset
)
234 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False)
235 self
.assertEqual(receivedResponse
, expectedResponse
)
237 def testDOHInvalid(self
):
241 name
= 'invalid.doh.tests.powerdns.com.'
242 invalidQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
244 # first an invalid query
245 invalidQuery
= invalidQuery
.to_wire()
246 invalidQuery
= invalidQuery
[:-5]
247 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, caFile
=self
._caCert
, query
=invalidQuery
, response
=None, useQueue
=False, rawQuery
=True)
248 self
.assertEqual(receivedResponse
, None)
250 # and now a valid one
251 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
253 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
255 response
= dns
.message
.make_response(query
)
256 rrset
= dns
.rrset
.from_text(name
,
261 response
.answer
.append(rrset
)
262 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
263 self
.assertTrue(receivedQuery
)
264 self
.assertTrue(receivedResponse
)
265 receivedQuery
.id = expectedQuery
.id
266 self
.assertEqual(expectedQuery
, receivedQuery
)
267 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
268 self
.assertEqual(response
, receivedResponse
)
270 def testDOHWithoutQuery(self
):
274 name
= 'empty-get.doh.tests.powerdns.com.'
275 url
= self
._dohBaseURL
276 conn
= self
.openDOHConnection(self
._dohServerPort
, self
._caCert
, timeout
=2.0)
277 conn
.setopt(pycurl
.URL
, url
)
278 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
279 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
280 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
281 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
282 data
= conn
.perform_rb()
283 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
284 self
.assertEqual(rcode
, 400)
286 def testDOHEmptyPOST(self
):
288 DOH: Empty POST query
290 name
= 'empty-post.doh.tests.powerdns.com.'
292 (_
, receivedResponse
) = self
.sendDOHPostQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
="", rawQuery
=True, response
=None, caFile
=self
._caCert
)
293 self
.assertEqual(receivedResponse
, None)
295 # and now a valid one
296 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
298 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
300 response
= dns
.message
.make_response(query
)
301 rrset
= dns
.rrset
.from_text(name
,
306 response
.answer
.append(rrset
)
307 (receivedQuery
, receivedResponse
) = self
.sendDOHPostQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
308 self
.assertTrue(receivedQuery
)
309 self
.assertTrue(receivedResponse
)
310 receivedQuery
.id = expectedQuery
.id
311 self
.assertEqual(expectedQuery
, receivedQuery
)
312 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
313 self
.assertEqual(response
, receivedResponse
)
315 def testHeaderRule(self
):
319 name
= 'header-rule.doh.tests.powerdns.com.'
320 query
= dns
.message
.make_query(name
, 'A', 'IN')
322 query
.flags
&= ~dns
.flags
.RD
323 expectedResponse
= dns
.message
.make_response(query
)
324 rrset
= dns
.rrset
.from_text(name
,
329 expectedResponse
.answer
.append(rrset
)
331 # this header should match
332 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False, customHeaders
=['x-powerdnS: aaaaa'])
333 self
.assertEqual(receivedResponse
, expectedResponse
)
335 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
336 expectedQuery
.flags
&= ~dns
.flags
.RD
338 response
= dns
.message
.make_response(query
)
339 rrset
= dns
.rrset
.from_text(name
,
344 response
.answer
.append(rrset
)
346 # this content of the header should NOT match
347 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
, customHeaders
=['x-powerdnS: bbbbb'])
348 self
.assertTrue(receivedQuery
)
349 self
.assertTrue(receivedResponse
)
350 receivedQuery
.id = expectedQuery
.id
351 self
.assertEqual(expectedQuery
, receivedQuery
)
352 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
353 self
.assertEqual(response
, receivedResponse
)
355 def testHTTPPath(self
):
359 name
= 'http-path.doh.tests.powerdns.com.'
360 query
= dns
.message
.make_query(name
, 'A', 'IN')
362 query
.flags
&= ~dns
.flags
.RD
363 expectedResponse
= dns
.message
.make_response(query
)
364 rrset
= dns
.rrset
.from_text(name
,
369 expectedResponse
.answer
.append(rrset
)
371 # this path should match
372 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ 'PowerDNS', caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False)
373 self
.assertEqual(receivedResponse
, expectedResponse
)
375 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
377 expectedQuery
.flags
&= ~dns
.flags
.RD
378 response
= dns
.message
.make_response(query
)
379 rrset
= dns
.rrset
.from_text(name
,
384 response
.answer
.append(rrset
)
386 # this path should NOT match
387 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ "PowerDNS2", query
, response
=response
, caFile
=self
._caCert
)
388 self
.assertTrue(receivedQuery
)
389 self
.assertTrue(receivedResponse
)
390 receivedQuery
.id = expectedQuery
.id
391 self
.assertEqual(expectedQuery
, receivedQuery
)
392 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
393 self
.assertEqual(response
, receivedResponse
)
395 # this path is not in the URLs map and should lead to a 404
396 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ "PowerDNS/something", query
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True)
397 self
.assertTrue(receivedResponse
)
398 self
.assertEqual(receivedResponse
, b
'there is no endpoint configured for this path')
399 self
.assertEqual(self
._rcode
, 404)
401 def testHTTPPathRegex(self
):
405 name
= 'http-path-regex.doh.tests.powerdns.com.'
406 query
= dns
.message
.make_query(name
, 'A', 'IN')
408 query
.flags
&= ~dns
.flags
.RD
409 expectedResponse
= dns
.message
.make_response(query
)
410 rrset
= dns
.rrset
.from_text(name
,
415 expectedResponse
.answer
.append(rrset
)
417 # this path should match
418 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ 'PowerDNS-999', caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False)
419 self
.assertEqual(receivedResponse
, expectedResponse
)
421 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
423 expectedQuery
.flags
&= ~dns
.flags
.RD
424 response
= dns
.message
.make_response(query
)
425 rrset
= dns
.rrset
.from_text(name
,
430 response
.answer
.append(rrset
)
432 # this path should NOT match
433 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ "PowerDNS2", query
, response
=response
, caFile
=self
._caCert
)
434 self
.assertTrue(receivedQuery
)
435 self
.assertTrue(receivedResponse
)
436 receivedQuery
.id = expectedQuery
.id
437 self
.assertEqual(expectedQuery
, receivedQuery
)
438 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
439 self
.assertEqual(response
, receivedResponse
)
441 def testHTTPStatusAction200(self
):
443 DOH: HTTPStatusAction 200 OK
445 name
= 'http-status-action.doh.tests.powerdns.com.'
446 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
449 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True)
450 self
.assertTrue(receivedResponse
)
451 self
.assertEqual(receivedResponse
, b
'Plaintext answer')
452 self
.assertEqual(self
._rcode
, 200)
453 self
.assertTrue('content-type: text/plain' in self
._response
_headers
.decode())
455 def testHTTPStatusAction307(self
):
457 DOH: HTTPStatusAction 307
459 name
= 'http-status-action-redirect.doh.tests.powerdns.com.'
460 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
463 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True)
464 self
.assertTrue(receivedResponse
)
465 self
.assertEqual(self
._rcode
, 307)
466 self
.assertTrue('location: https://doh.powerdns.org' in self
._response
_headers
.decode())
468 def testHTTPLuaResponse(self
):
470 DOH: Lua HTTP Response
472 name
= 'http-lua.doh.tests.powerdns.com.'
473 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
476 (_
, receivedResponse
) = self
.sendDOHPostQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True)
477 self
.assertTrue(receivedResponse
)
478 self
.assertEqual(receivedResponse
, b
'It works!')
479 self
.assertEqual(self
._rcode
, 200)
480 self
.assertTrue('content-type: text/plain' in self
._response
_headers
.decode())
482 def testHTTPEarlyResponse(self
):
484 DOH: HTTP Early Response
486 response_headers
= BytesIO()
487 url
= self
._dohBaseURL
+ 'coffee'
488 conn
= self
.openDOHConnection(self
._dohServerPort
, caFile
=self
._caCert
, timeout
=2.0)
489 conn
.setopt(pycurl
.URL
, url
)
490 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
491 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
492 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
493 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
494 conn
.setopt(pycurl
.HEADERFUNCTION
, response_headers
.write
)
495 data
= conn
.perform_rb()
496 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
497 headers
= response_headers
.getvalue().decode()
499 self
.assertEqual(rcode
, 418)
500 self
.assertEqual(data
, b
'C0FFEE')
501 self
.assertIn('foo: bar', headers
)
502 self
.assertNotIn(self
._customResponseHeader
2, headers
)
504 response_headers
= BytesIO()
505 conn
= self
.openDOHConnection(self
._dohServerPort
, caFile
=self
._caCert
, timeout
=2.0)
506 conn
.setopt(pycurl
.URL
, url
)
507 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
508 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
509 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
510 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
511 conn
.setopt(pycurl
.HEADERFUNCTION
, response_headers
.write
)
512 conn
.setopt(pycurl
.POST
, True)
514 conn
.setopt(pycurl
.POSTFIELDS
, data
)
516 data
= conn
.perform_rb()
517 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
518 headers
= response_headers
.getvalue().decode()
519 self
.assertEqual(rcode
, 418)
520 self
.assertEqual(data
, b
'C0FFEE')
521 self
.assertIn('foo: bar', headers
)
522 self
.assertNotIn(self
._customResponseHeader
2, headers
)
524 class TestDOHSubPaths(DNSDistDOHTest
):
526 _serverKey
= 'server.key'
527 _serverCert
= 'server.chain'
528 _serverName
= 'tls.tests.dnsdist.org'
530 _dohServerPort
= 8443
531 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
532 _config_template
= """
533 newServer{address="127.0.0.1:%s"}
535 addAction(AllRule(), SpoofAction("3.4.5.6"))
537 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/PowerDNS" }, {exactPathMatching=false})
539 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
541 def testSubPath(self
):
545 name
= 'sub-path.doh.tests.powerdns.com.'
546 query
= dns
.message
.make_query(name
, 'A', 'IN')
548 query
.flags
&= ~dns
.flags
.RD
549 expectedResponse
= dns
.message
.make_response(query
)
550 rrset
= dns
.rrset
.from_text(name
,
555 expectedResponse
.answer
.append(rrset
)
557 # this path should match
558 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ 'PowerDNS', caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False)
559 self
.assertEqual(receivedResponse
, expectedResponse
)
561 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
563 expectedQuery
.flags
&= ~dns
.flags
.RD
564 response
= dns
.message
.make_response(query
)
565 rrset
= dns
.rrset
.from_text(name
,
570 response
.answer
.append(rrset
)
572 # this path is not in the URLs map and should lead to a 404
573 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ "NotPowerDNS", query
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True)
574 self
.assertTrue(receivedResponse
)
575 self
.assertEqual(receivedResponse
, b
'not found')
576 self
.assertEqual(self
._rcode
, 404)
578 # this path is below one in the URLs map and exactPathMatching is false, so we should be good
579 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
+ 'PowerDNS/something', caFile
=self
._caCert
, query
=query
, response
=None, useQueue
=False)
580 self
.assertEqual(receivedResponse
, expectedResponse
)
582 class TestDOHAddingECS(DNSDistDOHTest
):
584 _serverKey
= 'server.key'
585 _serverCert
= 'server.chain'
586 _serverName
= 'tls.tests.dnsdist.org'
588 _dohServerPort
= 8443
589 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
590 _config_template
= """
591 newServer{address="127.0.0.1:%s", useClientSubnet=true}
592 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" })
595 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
597 def testDOHSimple(self
):
599 DOH with ECS: Simple query
601 name
= 'simple.doh-ecs.tests.powerdns.com.'
602 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
604 rewrittenEcso
= clientsubnetoption
.ClientSubnetOption('127.0.0.0', 24)
605 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096, options
=[rewrittenEcso
])
606 response
= dns
.message
.make_response(query
)
607 rrset
= dns
.rrset
.from_text(name
,
612 response
.answer
.append(rrset
)
614 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
615 self
.assertTrue(receivedQuery
)
616 self
.assertTrue(receivedResponse
)
617 expectedQuery
.id = receivedQuery
.id
618 self
.assertEqual(expectedQuery
, receivedQuery
)
619 self
.checkQueryEDNSWithECS(expectedQuery
, receivedQuery
)
620 self
.assertEqual(response
, receivedResponse
)
621 self
.checkResponseNoEDNS(response
, receivedResponse
)
623 def testDOHExistingEDNS(self
):
625 DOH with ECS: Existing EDNS
627 name
= 'existing-edns.doh-ecs.tests.powerdns.com.'
628 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=8192)
630 rewrittenEcso
= clientsubnetoption
.ClientSubnetOption('127.0.0.0', 24)
631 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=8192, options
=[rewrittenEcso
])
632 response
= dns
.message
.make_response(query
)
633 rrset
= dns
.rrset
.from_text(name
,
638 response
.answer
.append(rrset
)
640 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
641 self
.assertTrue(receivedQuery
)
642 self
.assertTrue(receivedResponse
)
643 receivedQuery
.id = expectedQuery
.id
644 self
.assertEqual(expectedQuery
, receivedQuery
)
645 self
.assertEqual(response
, receivedResponse
)
646 self
.checkQueryEDNSWithECS(expectedQuery
, receivedQuery
)
647 self
.checkResponseEDNSWithoutECS(response
, receivedResponse
)
649 def testDOHExistingECS(self
):
651 DOH with ECS: Existing EDNS Client Subnet
653 name
= 'existing-ecs.doh-ecs.tests.powerdns.com.'
654 ecso
= clientsubnetoption
.ClientSubnetOption('1.2.3.4')
655 rewrittenEcso
= clientsubnetoption
.ClientSubnetOption('127.0.0.0', 24)
656 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=512, options
=[ecso
], want_dnssec
=True)
658 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=512, options
=[rewrittenEcso
])
659 response
= dns
.message
.make_response(query
)
660 response
.use_edns(edns
=True, payload
=4096, options
=[rewrittenEcso
])
661 rrset
= dns
.rrset
.from_text(name
,
666 response
.answer
.append(rrset
)
668 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
669 self
.assertTrue(receivedQuery
)
670 self
.assertTrue(receivedResponse
)
671 receivedQuery
.id = expectedQuery
.id
672 self
.assertEqual(expectedQuery
, receivedQuery
)
673 self
.assertEqual(response
, receivedResponse
)
674 self
.checkQueryEDNSWithECS(expectedQuery
, receivedQuery
)
675 self
.checkResponseEDNSWithECS(response
, receivedResponse
)
677 class TestDOHOverHTTP(DNSDistDOHTest
):
679 _dohServerPort
= 8480
680 _serverName
= 'tls.tests.dnsdist.org'
681 _dohBaseURL
= ("http://%s:%d/dns-query" % (_serverName
, _dohServerPort
))
682 _config_template
= """
683 newServer{address="127.0.0.1:%s"}
684 addDOHLocal("127.0.0.1:%s")
686 _config_params
= ['_testServerPort', '_dohServerPort']
687 _checkConfigExpectedOutput
= b
"""No certificate provided for DoH endpoint 127.0.0.1:8480, running in DNS over HTTP mode instead of DNS over HTTPS
688 Configuration 'configs/dnsdist_TestDOHOverHTTP.conf' OK!
691 def testDOHSimple(self
):
693 DOH over HTTP: Simple query
695 name
= 'simple.doh-over-http.tests.powerdns.com.'
696 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
698 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
699 response
= dns
.message
.make_response(query
)
700 rrset
= dns
.rrset
.from_text(name
,
705 response
.answer
.append(rrset
)
707 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, useHTTPS
=False)
708 self
.assertTrue(receivedQuery
)
709 self
.assertTrue(receivedResponse
)
710 expectedQuery
.id = receivedQuery
.id
711 self
.assertEqual(expectedQuery
, receivedQuery
)
712 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
713 self
.assertEqual(response
, receivedResponse
)
714 self
.checkResponseNoEDNS(response
, receivedResponse
)
716 def testDOHSimplePOST(self
):
718 DOH over HTTP: Simple POST query
720 name
= 'simple-post.doh-over-http.tests.powerdns.com.'
721 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
723 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
725 response
= dns
.message
.make_response(query
)
726 rrset
= dns
.rrset
.from_text(name
,
731 response
.answer
.append(rrset
)
733 (receivedQuery
, receivedResponse
) = self
.sendDOHPostQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, useHTTPS
=False)
734 self
.assertTrue(receivedQuery
)
735 self
.assertTrue(receivedResponse
)
736 receivedQuery
.id = expectedQuery
.id
737 self
.assertEqual(expectedQuery
, receivedQuery
)
738 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
739 self
.assertEqual(response
, receivedResponse
)
740 self
.checkResponseNoEDNS(response
, receivedResponse
)
742 class TestDOHWithCache(DNSDistDOHTest
):
744 _serverKey
= 'server.key'
745 _serverCert
= 'server.chain'
746 _serverName
= 'tls.tests.dnsdist.org'
748 _dohServerPort
= 8443
749 _dohBaseURL
= ("https://%s:%d/dns-query" % (_serverName
, _dohServerPort
))
750 _config_template
= """
751 newServer{address="127.0.0.1:%s"}
753 addDOHLocal("127.0.0.1:%s", "%s", "%s")
755 pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
756 getPool(""):setCache(pc)
758 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
760 def testDOHCacheLargeAnswer(self
):
762 DOH with cache: Check that we can cache (and retrieve) large answers
765 name
= 'large.doh-with-cache.tests.powerdns.com.'
766 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
768 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
770 response
= dns
.message
.make_response(query
)
771 # we prepare a large answer
775 content
= content
+ ', '
776 content
= content
+ (str(i
)*50)
778 content
= content
+ 'A'*40
780 rrset
= dns
.rrset
.from_text(name
,
785 response
.answer
.append(rrset
)
786 self
.assertEqual(len(response
.to_wire()), 4096)
788 # first query to fill the cache
789 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
790 self
.assertTrue(receivedQuery
)
791 self
.assertTrue(receivedResponse
)
792 receivedQuery
.id = expectedQuery
.id
793 self
.assertEqual(expectedQuery
, receivedQuery
)
794 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
795 self
.assertEqual(response
, receivedResponse
)
796 self
.checkHasHeader('cache-control', 'max-age=3600')
798 for _
in range(numberOfQueries
):
799 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False)
800 self
.assertEqual(receivedResponse
, response
)
801 self
.checkHasHeader('cache-control', 'max-age=' + str(receivedResponse
.answer
[0].ttl
))
805 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False)
806 self
.assertEqual(receivedResponse
, response
)
807 self
.checkHasHeader('cache-control', 'max-age=' + str(receivedResponse
.answer
[0].ttl
))
809 def testDOHGetFromUDPCache(self
):
811 DOH with cache: Check that we can retrieve an answer received for a UDP query
813 name
= 'doh-query-insert-udp.doh-with-cache.tests.powerdns.com.'
814 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
815 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
817 response
= dns
.message
.make_response(query
)
818 rrset
= dns
.rrset
.from_text(name
,
823 response
.answer
.append(rrset
)
825 # first query to fill the cache
826 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
827 self
.assertTrue(receivedQuery
)
828 self
.assertTrue(receivedResponse
)
829 receivedQuery
.id = expectedQuery
.id
830 self
.assertEqual(expectedQuery
, receivedQuery
)
831 self
.assertEqual(response
, receivedResponse
)
833 # now we send the exact same query over DoH, we should get a cache hit
834 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False)
835 self
.assertTrue(receivedResponse
)
836 self
.assertEqual(response
, receivedResponse
)
838 def testDOHInsertIntoUDPCache(self
):
840 DOH with cache: Check that we can retrieve an answer received for a DoH query from UDP
842 name
= 'udp-query-get-doh.doh-with-cache.tests.powerdns.com.'
843 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
844 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
846 response
= dns
.message
.make_response(query
)
847 rrset
= dns
.rrset
.from_text(name
,
852 response
.answer
.append(rrset
)
854 # first query to fill the cache
855 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
856 self
.assertTrue(receivedQuery
)
857 self
.assertTrue(receivedResponse
)
858 receivedQuery
.id = expectedQuery
.id
859 self
.assertEqual(expectedQuery
, receivedQuery
)
860 self
.assertEqual(response
, receivedResponse
)
862 # now we send the exact same query over DoH, we should get a cache hit
863 (_
, receivedResponse
) = self
.sendUDPQuery(query
, response
=None, useQueue
=False)
864 self
.assertTrue(receivedResponse
)
865 self
.assertEqual(response
, receivedResponse
)
867 def testTruncation(self
):
869 DOH: Truncation over UDP (with cache)
871 # the query is first forwarded over UDP, leading to a TC=1 answer from the
872 # backend, then over TCP
873 name
= 'truncated-udp.doh-with-cache.tests.powerdns.com.'
874 query
= dns
.message
.make_query(name
, 'A', 'IN')
876 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
877 expectedQuery
.id = 42
878 response
= dns
.message
.make_response(query
)
879 rrset
= dns
.rrset
.from_text(name
,
884 response
.answer
.append(rrset
)
886 # first response is a TC=1
887 tcResponse
= dns
.message
.make_response(query
)
888 tcResponse
.flags |
= dns
.flags
.TC
889 self
._toResponderQueue
.put(tcResponse
, True, 2.0)
891 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, response
=response
)
892 # first query, received by the responder over UDP
893 self
.assertTrue(receivedQuery
)
894 receivedQuery
.id = expectedQuery
.id
895 self
.assertEqual(expectedQuery
, receivedQuery
)
896 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
899 self
.assertTrue(receivedResponse
)
900 self
.assertEqual(response
, receivedResponse
)
902 # check the second query, received by the responder over TCP
903 receivedQuery
= self
._fromResponderQueue
.get(True, 2.0)
904 self
.assertTrue(receivedQuery
)
905 receivedQuery
.id = expectedQuery
.id
906 self
.assertEqual(expectedQuery
, receivedQuery
)
907 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
909 # now check the cache for a DoH query
910 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False)
911 self
.assertEqual(response
, receivedResponse
)
913 # The TC=1 answer received over UDP will not be cached, because we currently do not cache answers with no records (no TTL)
914 # The TCP one should, however
915 (_
, receivedResponse
) = self
.sendTCPQuery(expectedQuery
, response
=None, useQueue
=False)
916 self
.assertEqual(response
, receivedResponse
)
918 def testResponsesReceivedOverUDP(self
):
920 DOH: Check that responses received over UDP are cached (with cache)
922 name
= 'cached-udp.doh-with-cache.tests.powerdns.com.'
923 query
= dns
.message
.make_query(name
, 'A', 'IN')
925 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
927 response
= dns
.message
.make_response(query
)
928 rrset
= dns
.rrset
.from_text(name
,
933 response
.answer
.append(rrset
)
935 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, response
=response
)
936 self
.assertTrue(receivedQuery
)
937 receivedQuery
.id = expectedQuery
.id
938 self
.assertEqual(expectedQuery
, receivedQuery
)
939 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
940 self
.assertTrue(receivedResponse
)
941 self
.assertEqual(response
, receivedResponse
)
943 # now check the cache for a DoH query
944 (_
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False)
945 self
.assertEqual(response
, receivedResponse
)
947 # Check that the answer is usable for UDP queries as well
948 (_
, receivedResponse
) = self
.sendUDPQuery(expectedQuery
, response
=None, useQueue
=False)
949 self
.assertEqual(response
, receivedResponse
)
951 class TestDOHWithoutCacheControl(DNSDistDOHTest
):
953 _serverKey
= 'server.key'
954 _serverCert
= 'server.chain'
955 _serverName
= 'tls.tests.dnsdist.org'
957 _dohServerPort
= 8443
958 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
959 _config_template
= """
960 newServer{address="127.0.0.1:%s"}
962 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {sendCacheControlHeaders=false})
964 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
966 def testDOHSimple(self
):
968 DOH without cache-control
970 name
= 'simple.doh.tests.powerdns.com.'
971 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
973 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
975 response
= dns
.message
.make_response(query
)
976 rrset
= dns
.rrset
.from_text(name
,
981 response
.answer
.append(rrset
)
983 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
984 self
.assertTrue(receivedQuery
)
985 self
.assertTrue(receivedResponse
)
986 receivedQuery
.id = expectedQuery
.id
987 self
.assertEqual(expectedQuery
, receivedQuery
)
988 self
.checkNoHeader('cache-control')
989 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
990 self
.assertEqual(response
, receivedResponse
)
992 class TestDOHFFI(DNSDistDOHTest
):
994 _serverKey
= 'server.key'
995 _serverCert
= 'server.chain'
996 _serverName
= 'tls.tests.dnsdist.org'
998 _dohServerPort
= 8443
999 _customResponseHeader1
= 'access-control-allow-origin: *'
1000 _customResponseHeader2
= 'user-agent: derp'
1001 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1002 _config_template
= """
1003 newServer{address="127.0.0.1:%s"}
1005 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {customResponseHeaders={["access-control-allow-origin"]="*",["user-agent"]="derp",["UPPERCASE"]="VaLuE"}, keepIncomingHeaders=true})
1007 local ffi = require("ffi")
1009 function dohHandler(dq)
1010 local scheme = ffi.string(ffi.C.dnsdist_ffi_dnsquestion_get_http_scheme(dq))
1011 local host = ffi.string(ffi.C.dnsdist_ffi_dnsquestion_get_http_host(dq))
1012 local path = ffi.string(ffi.C.dnsdist_ffi_dnsquestion_get_http_path(dq))
1013 local query_string = ffi.string(ffi.C.dnsdist_ffi_dnsquestion_get_http_query_string(dq))
1014 if scheme == 'https' and host == '%s:%d' and path == '/' and query_string == '' then
1015 local foundct = false
1016 local headers_ptr = ffi.new("const dnsdist_ffi_http_header_t *[1]")
1017 local headers_ptr_param = ffi.cast("const dnsdist_ffi_http_header_t **", headers_ptr)
1019 local headers_count = tonumber(ffi.C.dnsdist_ffi_dnsquestion_get_http_headers(dq, headers_ptr_param))
1020 if headers_count > 0 then
1021 for idx = 0, headers_count-1 do
1022 if ffi.string(headers_ptr[0][idx].name) == 'content-type' and ffi.string(headers_ptr[0][idx].value) == 'application/dns-message' then
1029 local response = 'It works!'
1030 ffi.C.dnsdist_ffi_dnsquestion_set_http_response(dq, 200, response, #response, 'text/plain')
1031 return DNSAction.HeaderModify
1034 return DNSAction.None
1036 addAction("http-lua-ffi.doh.tests.powerdns.com.", LuaFFIAction(dohHandler))
1038 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_serverName', '_dohServerPort']
1040 def testHTTPLuaFFIResponse(self
):
1042 DOH: Lua FFI HTTP Response
1044 name
= 'http-lua-ffi.doh.tests.powerdns.com.'
1045 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
1048 (_
, receivedResponse
) = self
.sendDOHPostQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, useQueue
=False, rawResponse
=True)
1049 self
.assertTrue(receivedResponse
)
1050 self
.assertEqual(receivedResponse
, b
'It works!')
1051 self
.assertEqual(self
._rcode
, 200)
1052 self
.assertTrue('content-type: text/plain' in self
._response
_headers
.decode())
1054 class TestDOHForwardedFor(DNSDistDOHTest
):
1056 _serverKey
= 'server.key'
1057 _serverCert
= 'server.chain'
1058 _serverName
= 'tls.tests.dnsdist.org'
1060 _dohServerPort
= 8443
1061 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1062 _config_template
= """
1063 newServer{address="127.0.0.1:%s"}
1065 setACL('192.0.2.1/32')
1066 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {trustForwardedForHeader=true})
1068 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
1070 def testDOHAllowedForwarded(self
):
1072 DOH with X-Forwarded-For allowed
1074 name
= 'allowed.forwarded.doh.tests.powerdns.com.'
1075 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
1077 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1078 expectedQuery
.id = 0
1079 response
= dns
.message
.make_response(query
)
1080 rrset
= dns
.rrset
.from_text(name
,
1085 response
.answer
.append(rrset
)
1087 (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'])
1088 self
.assertTrue(receivedQuery
)
1089 self
.assertTrue(receivedResponse
)
1090 receivedQuery
.id = expectedQuery
.id
1091 self
.assertEqual(expectedQuery
, receivedQuery
)
1092 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
1093 self
.assertEqual(response
, receivedResponse
)
1095 def testDOHDeniedForwarded(self
):
1097 DOH with X-Forwarded-For not allowed
1099 name
= 'not-allowed.forwarded.doh.tests.powerdns.com.'
1100 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
1102 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1103 expectedQuery
.id = 0
1104 response
= dns
.message
.make_response(query
)
1105 rrset
= dns
.rrset
.from_text(name
,
1110 response
.answer
.append(rrset
)
1112 (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'])
1114 self
.assertEqual(self
._rcode
, 403)
1115 self
.assertEqual(receivedResponse
, b
'dns query not allowed because of ACL')
1117 class TestDOHForwardedForNoTrusted(DNSDistDOHTest
):
1119 _serverKey
= 'server.key'
1120 _serverCert
= 'server.chain'
1121 _serverName
= 'tls.tests.dnsdist.org'
1123 _dohServerPort
= 8443
1124 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1125 _config_template
= """
1126 newServer{address="127.0.0.1:%s"}
1128 setACL('192.0.2.1/32')
1129 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" })
1131 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
1133 def testDOHForwardedUntrusted(self
):
1135 DOH with X-Forwarded-For not trusted
1137 name
= 'not-trusted.forwarded.doh.tests.powerdns.com.'
1138 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
1140 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1141 expectedQuery
.id = 0
1142 response
= dns
.message
.make_response(query
)
1143 rrset
= dns
.rrset
.from_text(name
,
1148 response
.answer
.append(rrset
)
1150 (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'])
1152 self
.assertEqual(self
._rcode
, 403)
1153 self
.assertEqual(receivedResponse
, b
'dns query not allowed because of ACL')
1155 class TestDOHFrontendLimits(DNSDistDOHTest
):
1157 # this test suite uses a different responder port
1158 # because it uses a different health check configuration
1159 _testServerPort
= 5395
1160 _answerUnexpected
= True
1162 _serverKey
= 'server.key'
1163 _serverCert
= 'server.chain'
1164 _serverName
= 'tls.tests.dnsdist.org'
1166 _dohServerPort
= 8443
1167 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1169 _skipListeningOnCL
= True
1170 _maxTCPConnsPerDOHFrontend
= 5
1171 _config_template
= """
1172 newServer{address="127.0.0.1:%s"}
1173 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, { maxConcurrentTCPConnections=%d })
1175 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_maxTCPConnsPerDOHFrontend']
1177 def testTCPConnsPerDOHFrontend(self
):
1179 DoH Frontend Limits: Maximum number of conns per DoH frontend
1181 name
= 'maxconnsperfrontend.doh.tests.powerdns.com.'
1182 query
= b
"GET / HTTP/1.0\r\n\r\n"
1185 for idx
in range(self
._maxTCPConnsPerDOHFrontend
+ 1):
1187 conns
.append(self
.openTLSConnection(self
._dohServerPort
, self
._serverName
, self
._caCert
))
1200 response
= conn
.recv(65535)
1212 # wait a bit to be sure that dnsdist closed the connections
1213 # and decremented the counters on its side, otherwise subsequent
1214 # connections will be dropped
1217 self
.assertEqual(count
, self
._maxTCPConnsPerDOHFrontend
)
1218 self
.assertEqual(failed
, 1)
1220 class TestProtocols(DNSDistDOHTest
):
1221 _serverKey
= 'server.key'
1222 _serverCert
= 'server.chain'
1223 _serverName
= 'tls.tests.dnsdist.org'
1225 _dohServerPort
= 8443
1226 _customResponseHeader1
= 'access-control-allow-origin: *'
1227 _customResponseHeader2
= 'user-agent: derp'
1228 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1229 _config_template
= """
1230 function checkDOH(dq)
1231 if dq:getProtocol() ~= "DNS over HTTPS" then
1232 return DNSAction.Spoof, '1.2.3.4'
1234 return DNSAction.None
1237 addAction("protocols.doh.tests.powerdns.com.", LuaAction(checkDOH))
1238 newServer{address="127.0.0.1:%s"}
1239 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" })
1241 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
1243 def testProtocolDOH(self
):
1245 DoH: Test DNSQuestion.Protocol
1247 name
= 'protocols.doh.tests.powerdns.com.'
1248 query
= dns
.message
.make_query(name
, 'A', 'IN')
1249 response
= dns
.message
.make_response(query
)
1250 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1251 expectedQuery
.id = 0
1253 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
1254 self
.assertTrue(receivedQuery
)
1255 self
.assertTrue(receivedResponse
)
1256 receivedQuery
.id = expectedQuery
.id
1257 self
.assertEqual(expectedQuery
, receivedQuery
)
1258 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
1259 self
.assertEqual(response
, receivedResponse
)
1261 class TestDOHWithPCKS12Cert(DNSDistDOHTest
):
1262 _serverCert
= 'server.p12'
1263 _pkcs12Password
= 'passw0rd'
1264 _serverName
= 'tls.tests.dnsdist.org'
1266 _dohServerPort
= 8443
1267 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1268 _config_template
= """
1269 newServer{address="127.0.0.1:%s"}
1270 cert=newTLSCertificate("%s", {password="%s"})
1271 addDOHLocal("127.0.0.1:%s", cert, "", { "/" })
1273 _config_params
= ['_testServerPort', '_serverCert', '_pkcs12Password', '_dohServerPort']
1275 def testProtocolDOH(self
):
1277 DoH: Test Simple DOH Query with a password protected PCKS12 file configured
1279 name
= 'simple.doh.tests.powerdns.com.'
1280 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
1282 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
1283 expectedQuery
.id = 0
1284 response
= dns
.message
.make_response(query
)
1285 rrset
= dns
.rrset
.from_text(name
,
1290 response
.answer
.append(rrset
)
1292 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
1293 self
.assertTrue(receivedQuery
)
1294 self
.assertTrue(receivedResponse
)
1295 receivedQuery
.id = expectedQuery
.id
1296 self
.assertEqual(expectedQuery
, receivedQuery
)
1298 class TestDOHForwardedToTCPOnly(DNSDistDOHTest
):
1299 _serverKey
= 'server.key'
1300 _serverCert
= 'server.chain'
1301 _serverName
= 'tls.tests.dnsdist.org'
1303 _dohServerPort
= 8443
1304 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1305 _config_template
= """
1306 newServer{address="127.0.0.1:%s", tcpOnly=true}
1307 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" })
1309 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
1311 def testDOHTCPOnly(self
):
1313 DoH: Test a DoH query forwarded to a TCP-only server
1315 name
= 'tcponly.doh.tests.powerdns.com.'
1316 query
= dns
.message
.make_query(name
, 'A', 'IN')
1318 response
= dns
.message
.make_response(query
)
1319 rrset
= dns
.rrset
.from_text(name
,
1324 response
.answer
.append(rrset
)
1326 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, response
=response
, caFile
=self
._caCert
)
1327 self
.assertTrue(receivedQuery
)
1328 self
.assertTrue(receivedResponse
)
1329 receivedQuery
.id = query
.id
1330 self
.assertEqual(receivedQuery
, query
)
1331 self
.assertEqual(receivedResponse
, response
)
1333 class TestDOHLimits(DNSDistDOHTest
):
1334 _serverName
= 'tls.tests.dnsdist.org'
1336 _dohServerPort
= 8443
1337 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
1338 _serverKey
= 'server.key'
1339 _serverCert
= 'server.chain'
1340 _maxTCPConnsPerClient
= 3
1341 _config_template
= """
1342 newServer{address="127.0.0.1:%s"}
1343 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" })
1344 setMaxTCPConnectionsPerClient(%s)
1346 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_maxTCPConnsPerClient']
1348 def testConnsPerClient(self
):
1350 DoH Limits: Maximum number of conns per client
1352 name
= 'maxconnsperclient.doh.tests.powerdns.com.'
1353 query
= dns
.message
.make_query(name
, 'A', 'IN')
1354 url
= self
.getDOHGetURL(self
._dohBaseURL
, query
)
1357 for idx
in range(self
._maxTCPConnsPerClient
+ 1):
1358 conn
= self
.openDOHConnection(self
._dohServerPort
, self
._caCert
, timeout
=2.0)
1359 conn
.setopt(pycurl
.URL
, url
)
1360 conn
.setopt(pycurl
.RESOLVE
, ["%s:%d:127.0.0.1" % (self
._serverName
, self
._dohServerPort
)])
1361 conn
.setopt(pycurl
.SSL_VERIFYPEER
, 1)
1362 conn
.setopt(pycurl
.SSL_VERIFYHOST
, 2)
1363 conn
.setopt(pycurl
.CAINFO
, self
._caCert
)
1370 data
= conn
.perform_rb()
1371 rcode
= conn
.getinfo(pycurl
.RESPONSE_CODE
)
1379 # wait a bit to be sure that dnsdist closed the connections
1380 # and decremented the counters on its side, otherwise subsequent
1381 # connections will be dropped
1384 self
.assertEqual(count
, self
._maxTCPConnsPerClient
)
1385 self
.assertEqual(failed
, 1)